{"record":{"id":"ee2efb18bf105f94","repo":"astrid-runtime/astrid","slug":"durable-capsule-id-wit-digest-mismatch-for-rela","errorCode":null,"errorMessage":"durable capsule {id} WIT digest mismatch for {relative}","messagePattern":"durable capsule (.+?) WIT digest mismatch for (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/storage.rs","lineNumber":228,"sourceCode":"        if relative_path.is_absolute()\n            || relative_path.components().any(|component| {\n                matches!(\n                    component,\n                    std::path::Component::ParentDir | std::path::Component::RootDir\n                )\n            })\n            || relative_path\n                .components()\n                .any(|component| !matches!(component, std::path::Component::Normal(_)))\n        {\n            bail!(\"durable capsule {id} has unsafe WIT metadata path {relative}\");\n        }\n        let key = format!(\"wit/{relative}\");\n        let Some(bytes) = files.get(&key) else {\n            bail!(\"durable capsule {id} is missing WIT file {relative}\");\n        };\n        if !is_hex_digest(pin) || blake3::hash(bytes).to_hex().as_str() != pin {\n            bail!(\"durable capsule {id} WIT digest mismatch for {relative}\");\n        }\n        expected.insert(key);\n    }\n    for key in files.keys().filter(|key| key.starts_with(\"wit/\")) {\n        if Path::new(key)\n            .extension()\n            .is_some_and(|extension| extension.eq_ignore_ascii_case(\"wit\"))\n            && !expected.contains(key)\n        {\n            bail!(\"durable capsule {id} has an unpinned WIT file {key}\");\n        }\n    }\n    Ok(())\n}\n\nfn is_hex_digest(value: &str) -> bool {\n    value.len() == 64\n        && value","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/storage.rs#L210-L246","documentation":"Each WIT file pinned in capsule metadata carries a blake3 hex digest ('pin'). verify_wit_files hashes the actual bytes of `wit/<relative>` from the archive and compares, also requiring the pin to be a 64-char lowercase hex string. A mismatch means the archive's WIT content differs from what the metadata pins — tampering, corruption, or stale metadata — so the durable package is rejected.","triggerScenarios":"read_verified_durable_package_for_owner where metadata.wit_files contains an invalid pin (not 64 hex chars) or the blake3 hash of the archive's wit file bytes differs from the pin — e.g. the .wit file was modified after metadata generation, or the wrong pin was recorded.","commonSituations":"Editing a .wit file and re-archiving without regenerating metadata pins; copy-paste of pins between files; corrupted archive transfer; mixing files from two capsule versions.","solutions":["Regenerate the capsule package so metadata pins are recomputed from the current .wit files, then republish/reinstall.","Check the pin value is a 64-character lowercase hex blake3 digest and correct typos.","If files were hand-edited inside the durable store, rebuild the capsule from source instead.","Compare `blake3 hash wit/<file>` output against the metadata pin to identify which file drifted."],"exampleFix":"// before: stale pin after editing the wit file\nwit_files: {\"api.wit\": \"<old-hash>\"}\n// after: regenerate pins from current content\nwit_files: {\"api.wit\": blake3_hex(read(\"wit/api.wit\"))}","handlingStrategy":"validation","validationCode":"fn wit_pins_match(wit_files: &[(String, String)], files: &std::collections::BTreeMap<String, Vec<u8>>) -> bool {\n    wit_files.iter().all(|(rel, pin)| {\n        pin.len() == 64 && pin.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))\n            && files.get(&format!(\"wit/{rel}\")).map(|bytes| blake3::hash(bytes).to_hex().as_str() == pin.as_str()).unwrap_or(false)\n    })\n}","typeGuard":"fn is_hex_digest(value: &str) -> bool {\n    value.len() == 64 && value.bytes().all(|b| b.is_ascii_digit() || matches!(b, b'a'..=b'f'))\n}","tryCatchPattern":"match read_verified_durable_package_for_owner(id) {\n    Err(e) if e.to_string().contains(\"WIT digest mismatch\") => {\n        // content drifted from pinned metadata: rebuild and republish the capsule\n        rebuild_and_republish(id)?;\n    }\n    other => other?,\n}","preventionTips":["Always regenerate metadata pins after modifying any .wit file","Never copy pins between files or hand-write digest values","Compare `blake3 hash` output to metadata when debugging drift","Rebuild capsules from one source of truth rather than patching installed files"],"tags":["integrity","checksum","wit","metadata"],"backgroundTag":"checksum-mismatch","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"}