{"record":{"id":"aa521a1d869bf282","repo":"astrid-runtime/astrid","slug":"capsule-hash-mismatch-lock-has-archive-h","errorCode":null,"errorMessage":"capsule '{}' hash mismatch: lock has {}, archive has {actual}","messagePattern":"capsule '(.+?)' hash mismatch: lock has (.+?), archive has (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/astrid-cli/src/commands/distro/shuttle_install.rs","lineNumber":311,"sourceCode":"\n/// Verify the per-capsule blake3 of every lock entry against the bytes\n/// actually present in the mirror. Returns an error on the first\n/// mismatch or missing file. Pure (no install side effects) so the\n/// integrity gate is unit-testable.\nfn verify_capsule_hashes(mirror: &Path, lock: &DistroLock) -> anyhow::Result<()> {\n    for entry in &lock.capsules {\n        let file = shuttle::capsule_mirror_path(mirror, &entry.name);\n        if !file.is_file() {\n            bail!(\n                \"capsule '{}' is missing from the shuttle mirror\",\n                entry.name\n            );\n        }\n        let bytes = std::fs::read(&file)\n            .with_context(|| format!(\"failed to read mirrored capsule {}\", entry.name))?;\n        let actual = format!(\"blake3:{}\", blake3::hash(&bytes).to_hex());\n        if entry.hash != actual {\n            bail!(\n                \"capsule '{}' hash mismatch: lock has {}, archive has {actual}\",\n                entry.name,\n                entry.hash\n            );\n        }\n    }\n    Ok(())\n}\n\nfn source_digest(path: &Path) -> anyhow::Result<String> {\n    let mut file = std::fs::File::open(path)\n        .with_context(|| format!(\"open capsule source {}\", path.display()))?;\n    let mut hasher = blake3::Hasher::new();\n    let mut buffer = vec![0_u8; 1024 * 1024].into_boxed_slice();\n    loop {\n        let count = file.read(&mut buffer)?;\n        if count == 0 {\n            break;","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/shuttle_install.rs#L293-L329","documentation":"For each locked capsule, verify_capsule_hashes reads the mirrored archive and computes its blake3 hash, comparing it to the hash recorded in the lock. A mismatch means the archive on the shuttle differs from what was sealed — corruption or substitution. The install aborts before any side effects.","triggerScenarios":"install_from_shuttle where a mirrored capsule file's blake3 hash differs from lock.capsules[].hash — bit rot on removable media, truncated copy, or a replaced archive file.","commonSituations":"USB media corruption or incomplete copy; someone swapped an archive on the shuttle; shuttle contents edited after sealing.","solutions":["Re-copy or rebuild the shuttle so each capsule archive matches its locked blake3 hash","Check the source media/storage for corruption and re-transfer","Never replace archive files on a sealed shuttle; repack instead"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let bytes = std::fs::read(&file)?;\nlet actual = format!(\"blake3:{}\", blake3::hash(&bytes).to_hex());\nif entry.hash != actual {\n    return Err(anyhow!(\"capsule '{}' hash mismatch\", entry.name));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never modify archives on a sealed shuttle; repack instead","Use verified transfers for removable media","Re-check hashes after any re-copy of the shuttle"],"tags":["integrity","hash-mismatch","blake3","tamper-detection"],"backgroundTag":"checksum-mismatch","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}