{"record":{"id":"f82a895bf24fcba4","repo":"astrid-runtime/astrid","slug":"capsule-is-missing-from-the-shuttle-mirror","errorCode":null,"errorMessage":"capsule '{}' is missing from the shuttle mirror","messagePattern":"capsule '(.+?)' is missing from the shuttle mirror","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/shuttle_install.rs","lineNumber":302,"sourceCode":"        },\n        None if signed => bail!(\n            \"signed shuttle for '{distro_id}' is missing its manifest_hash binding — refusing. \\\n             The signature covers the lock, not Distro.toml; without manifest_hash the manifest \\\n             (env/selection) is unauthenticated and could be swapped.\"\n        ),\n        None => Ok(()),\n    }\n}\n\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","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/shuttle_install.rs#L284-L320","documentation":"verify_capsule_hashes iterates the capsules recorded in DistroLock and resolves each to its mirror file. If a locked capsule's archive is absent from the shuttle mirror, the integrity gate bails before any install side effects (the function is deliberately pure). Unlike error 290 this checks the full lock, not just the user's selection.","triggerScenarios":"install_from_shuttle where the lock lists a capsule whose archive file is missing from the mirror directory — shuttle built incompletely, file deleted, or lock from a newer build than the mirror.","commonSituations":"Shuttle media partially copied; capsules dropped during packing due to disk-full; lock regenerated while the mirror directory wasn't repopulated.","solutions":["Rebuild the shuttle so the mirror contains every capsule named in DistroLock","Verify the mirror directory contents against the lock's capsule list before transport","Re-copy the shuttle if media loss is suspected"],"exampleFix":"// before\n// ls /media/shuttle/capsules -> distro-core.capsule missing\n// after\n// astrid distro pack --output /media/shuttle  # repopulate mirror to match lock","handlingStrategy":"validation","validationCode":"for entry in &lock.capsules {\n    let file = shuttle::capsule_mirror_path(mirror, &entry.name);\n    if !file.is_file() {\n        return Err(anyhow!(\"capsule '{}' missing from mirror\", entry.name));\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run a mirror-vs-lock completeness check before shipping the shuttle","Copy media with verification (checksum-aware copy tools)","Monitor for disk-full during shuttle packing"],"tags":["offline-install","missing-file","integrity","mirror"],"backgroundTag":"file-not-found","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"}