{"record":{"id":"358c2ccd128780f1","repo":"astrid-runtime/astrid","slug":"durable-capsule-id-metadata-names-a-hash-for-a-n","errorCode":null,"errorMessage":"durable capsule {id} metadata names a hash for a non-WASM component","messagePattern":"durable capsule (.+?) metadata names a hash for a non-WASM component","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/storage.rs","lineNumber":315,"sourceCode":"        };\n        let Some(bytes) = archive_files.get(relative) else {\n            bail!(\"durable capsule {id} component is missing from its archive\");\n        };\n        if Path::new(relative)\n            .extension()\n            .is_some_and(|extension| extension.eq_ignore_ascii_case(\"wasm\"))\n        {\n            let archive_hash = blake3::hash(bytes).to_hex().to_string();\n            if authority.wasm_hash_pinned\n                && authority.approved_wasm_hash.as_deref() != Some(archive_hash.as_str())\n            {\n                bail!(\"durable capsule {id} WASM hash differs between authority and archive\");\n            }\n            if metadata.wasm_hash.as_deref() != Some(archive_hash.as_str()) {\n                bail!(\"durable capsule {id} WASM hash differs between metadata and archive\");\n            }\n        } else if metadata.wasm_hash.is_some() {\n            bail!(\"durable capsule {id} metadata names a hash for a non-WASM component\");\n        }\n    } else if metadata.wasm_hash.is_some() {\n        bail!(\"durable capsule {id} metadata names a component absent from its archive\");\n    }\n    let mut effective_capabilities = manifest.capabilities.clone();\n    for component in &manifest.components {\n        if let Some(capabilities) = &component.capabilities {\n            effective_capabilities.merge_from(capabilities);\n        }\n    }\n    if !effective_capabilities\n        .expansions_from(&authority.approved_capabilities)\n        .is_empty()\n    {\n        bail!(\"durable capsule {id} manifest exceeds its authority receipt\");\n    }\n    match verification {\n        ArtifactVerification::Signed(provenance) => {","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/storage.rs#L297-L333","documentation":"verify_package_identity found metadata.wasm_hash set while the capsule's component file does not have a .wasm extension, so there is no WASM artifact the recorded hash could describe. The library rejects this because wasm_hash is only meaningful for a WASM component; a populated value next to a non-WASM component indicates inconsistent metadata. Read of the verified durable package is refused.","triggerScenarios":"read_verified_durable_package_for_owner on a capsule whose manifest component path lacks a .wasm extension (or component.path isn't UTF-8-safe with .wasm) while CapsuleMeta.wasm_hash is Some(_).","commonSituations":"Metadata written for an earlier WASM build that was later replaced by a non-WASM component (e.g. a WIT-only or data component); copy-pasting metadata between capsules; a tool that unconditionally stamps wasm_hash.","solutions":["Clear metadata.wasm_hash (set to None) for capsules whose component is not a .wasm file.","Restore the .wasm component if the capsule was actually meant to ship WASM.","Fix the publishing tool so it only sets wasm_hash when the component path ends in .wasm.","Republish the capsule with consistent manifest + metadata."],"exampleFix":"// before\nmeta.wasm_hash = Some(hash_of_nonwasm_component);\n// after\nif component.path.extension().is_some_and(|e| e.eq_ignore_ascii_case(\"wasm\")) {\n    meta.wasm_hash = Some(hash);\n} else {\n    meta.wasm_hash = None;\n}","handlingStrategy":"validation","validationCode":"if meta.wasm_hash.is_some()\n    && !component.path.extension().is_some_and(|e| e.eq_ignore_ascii_case(\"wasm\"))\n{\n    return Err(\"wasm_hash set but component is not a .wasm file\");\n}","typeGuard":"fn is_wasm_component(p: &std::path::Path) -> bool {\n    p.extension().is_some_and(|e| e.eq_ignore_ascii_case(\"wasm\"))\n}","tryCatchPattern":"match read_verified_durable_package_for_owner(&store, owner, id).await {\n    Ok(pkg) => pkg,\n    Err(e) if e.to_string().contains(\"hash for a non-WASM component\") => {\n        // clear metadata.wasm_hash or restore the wasm component\n    },\n    Err(e) => return Err(e),\n}","preventionTips":["Set wasm_hash only when the component path ends in .wasm.","Regenerate metadata whenever the component type changes.","Add a pre-publish check that mirrors verify_package_identity's rules."],"tags":["metadata","wasm","validation","storage"],"backgroundTag":"invalid-config-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}