{"record":{"id":"d7f95e5c640c650b","repo":"astrid-runtime/astrid","slug":"capsule-identity-mismatch-expected-manifest","errorCode":null,"errorMessage":"capsule identity mismatch: expected '{}', manifest declares '{id}'","messagePattern":"capsule identity mismatch: expected '(.+?)', manifest declares '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/local.rs","lineNumber":576,"sourceCode":"        Some(\n            workspace\n                .layout\n                .resolve(root)\n                .context(\"selected workspace state path is unsafe\")?,\n        )\n    } else {\n        None\n    };\n    let manifest_path = source_dir.join(\"Capsule.toml\");\n    if !manifest_path.exists() {\n        bail!(\"No Capsule.toml found in {}\", source_dir.display());\n    }\n    let manifest = load_manifest(&manifest_path).context(\"failed to load Capsule manifest\")?;\n    let id = CapsuleId::new(manifest.package.name.clone())?;\n    if let Some(expected) = expected\n        && id != *expected.id\n    {\n        bail!(\n            \"capsule identity mismatch: expected '{}', manifest declares '{id}'\",\n            expected.id\n        );\n    }\n    let installed_version = manifest.package.version.clone();\n    if let Some(expected_version) = expected.and_then(|expected| expected.version)\n        && installed_version != expected_version\n    {\n        bail!(\n            \"capsule version mismatch for '{id}': expected '{expected_version}', manifest declares '{installed_version}'\"\n        );\n    }\n\n    // Re-verify the exact source immediately before any target mutation. This\n    // closes the gap between pre-install approval and the transactional copy,\n    // including provenance-envelope swaps that leave content bytes unchanged.\n    let installed_authority =\n        authority_for_install_source(source_dir, &manifest, installed_authority)?;","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/local.rs#L558-L594","documentation":"Inside install_from_local_path_internal, after loading the manifest, the parsed CapsuleId is compared against the optional ExpectedCapsuleIdentity passed by the caller. A mismatch means the directory being installed declares a different capsule name than the one the caller intended/approved, so the install aborts before any copy or store mutation. This duplicates the pre-check at the checked_authorized layer for callers that skip it.","triggerScenarios":"Passing expected = Some(ExpectedCapsuleIdentity { id, .. }) to install_from_local_path_internal (via any wrapper) while the Capsule.toml in source_dir declares a different package.name — e.g. expected 'app-core', manifest says 'app-core-utils'.","commonSituations":"Renamed capsule without updating callers; wrong source directory after a refactor; case-sensitive name mismatch; reusing an install routine parameterized by id but pointing at a template directory.","solutions":["Read package.name from the source Capsule.toml and make the expected id match exactly","Correct package.name in the manifest if the directory is the source of truth","Point source_dir at the directory of the intended capsule","Drop the expected identity only if you truly want an unchecked install (not recommended for authorized flows)"],"exampleFix":"// before\nlet expected = ExpectedCapsuleIdentity { id: &CapsuleId::new(\"app-core\")?, version: None };\n// after\nlet manifest: Manifest = toml::from_str(&std::fs::read_to_string(src.join(\"Capsule.toml\"))?)?;\nlet expected = ExpectedCapsuleIdentity { id: &CapsuleId::new(manifest.package.name.clone())?, version: None };","handlingStrategy":"validation","validationCode":"fn check_identity(src: &Path, expected_id: &CapsuleId) -> anyhow::Result<()> {\n    let manifest: Manifest = toml::from_str(&std::fs::read_to_string(src.join(\"Capsule.toml\"))?)?;\n    anyhow::ensure!(CapsuleId::new(manifest.package.name.clone())? == *expected_id, \"id drift\");\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = install_from_local_path_internal(...) {\n    if e.to_string().contains(\"capsule identity mismatch\") {\n        // reload manifest, resync expected identity, retry once\n    } else { return Err(e); }\n}","preventionTips":["Single source of truth for capsule ids shared by approval and install code paths","Re-derive expected identity from the manifest immediately before install","Handle renames of package.name across the whole pipeline","Avoid passing expected identities captured long before the install call"],"tags":["capsule-install","identity-mismatch","manifest"],"backgroundTag":"invalid-argument-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"}