{"record":{"id":"c42dcda668cd574b","repo":"astrid-runtime/astrid","slug":"durable-capsule-id-has-an-unpinned-wit-file-key","errorCode":null,"errorMessage":"durable capsule {id} has an unpinned WIT file {key}","messagePattern":"durable capsule (.+?) has an unpinned WIT file (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/storage.rs","lineNumber":238,"sourceCode":"        {\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\n            .bytes()\n            .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))\n}\n\nfn verify_package_identity(\n    id: &str,\n    manifest: &CapsuleManifest,\n    metadata: &CapsuleMeta,\n    authority: &InstalledAuthority,\n    manifest_bytes: &[u8],","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/storage.rs#L220-L256","documentation":"After verifying all pinned WIT files, verify_wit_files scans the archive for any `wit/*.wit` file that was NOT declared in metadata.wit_files. Durable capsules must pin every WIT file they ship; an unpinned .wit file in the archive means the archive contains interface definitions whose integrity was never attested, so the package is rejected.","triggerScenarios":"read_verified_durable_package_for_owner on a capsule whose archive contains extra .wit files under wit/ that metadata.wit_files does not list — e.g. a .wit file added to the archive after metadata generation, or metadata regenerated against a subset of files.","commonSituations":"Adding a new .wit file and rebuilding the archive but not the metadata; leftover stale .wit files in the build directory that get swept into the archive; packaging scripts that glob wit/*.wit while metadata generation uses a manifest subset.","solutions":["Regenerate the capsule metadata so wit_files pins every .wit file in the archive, then republish.","Delete or move stray .wit files out of the archive source tree and rebuild.","Ensure the packaging step and metadata generation use the same file list (same glob/manifest).","List untracked files by diffing archive contents (`tar -tzf`) against metadata.wit_files keys."],"exampleFix":"// before: new file added but metadata not regenerated\n// archive: wit/api.wit, wit/new.wit; metadata pins only api.wit\n// after: regenerate metadata from the archive\nmetadata.wit_files = pin_all(glob(\"wit/*.wit\"));","handlingStrategy":"validation","validationCode":"fn no_unpinned_wit_files(archive_files: &[String], pinned: &std::collections::BTreeSet<String>) -> bool {\n    archive_files.iter().all(|f| {\n        !f.starts_with(\"wit/\")\n            || std::path::Path::new(f).extension().map(|e| e.eq_ignore_ascii_case(\"wit\")) != Some(true)\n            || pinned.contains(f)\n    })\n}","typeGuard":"fn is_wit_key(key: &str) -> bool {\n    key.starts_with(\"wit/\")\n        && std::path::Path::new(key).extension().is_some_and(|e| e.eq_ignore_ascii_case(\"wit\"))\n}","tryCatchPattern":"match read_verified_durable_package_for_owner(id) {\n    Err(e) if e.to_string().contains(\"unpinned WIT file\") => {\n        // stray .wit in archive: clean the build tree and republish\n        clean_wit_tree_and_republish(id)?;\n    }\n    other => other?,\n}","preventionTips":["Keep the packaging glob and metadata generation in sync (same file list)","Remove stale .wit files from build trees before archiving","Regenerate metadata on every archive rebuild, not just on manifest changes","Diff `tar -tzf` output against metadata.wit_files before publishing"],"tags":["integrity","metadata","wit","package"],"backgroundTag":"schema-validation-failed","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"}