{"record":{"id":"5ca05bc8c9b0a604","repo":"astrid-runtime/astrid","slug":"capsule-manifest-changed-after-authority-decision","errorCode":null,"errorMessage":"capsule manifest changed after authority decision","messagePattern":"capsule manifest changed after authority decision","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/authority.rs","lineNumber":937,"sourceCode":"\n    if let Some(approved) = approved {\n        if approved.content_digest != content_digest {\n            bail!(\n                \"capsule content changed after authority decision (approved {}, found {})\",\n                approved.content_digest,\n                content_digest\n            );\n        }\n        if approved.signer != signer || approved.signature != signature {\n            bail!(\"capsule provenance changed after authority decision\");\n        }\n        if approved.capsule_id != manifest.package.name\n            || approved.version != manifest.package.version\n        {\n            bail!(\"capsule identity or version changed after authority decision\");\n        }\n        if approved.manifest_digest != manifest_digest {\n            bail!(\"capsule manifest changed after authority decision\");\n        }\n        if approved.approved_capabilities != manifest.capabilities {\n            bail!(\"capsule capabilities changed after authority decision\");\n        }\n        return Ok(approved);\n    }\n\n    // Calling the legacy library install API is itself an operator-authority\n    // action. User-facing CLI and daemon entry points use explicit decisions;\n    // this path preserves the existing trusted embedding API while recording\n    // the same exact content and capability ceiling.\n    Ok(InstalledAuthority {\n        schema_version: 1,\n        source: AuthoritySource::OperatorDistribution,\n        capsule_id: manifest.package.name.clone(),\n        version: manifest.package.version.clone(),\n        content_digest,\n        manifest_digest,","sourceCodeStart":919,"sourceCodeEnd":955,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/authority.rs#L919-L955","documentation":"`authority_for_install_source` recomputes the digest of `Capsule.toml` (`digest_manifest`, a domain-separated blake3 hash) and compares it to `approved.manifest_digest`. This error is thrown when the manifest file's bytes changed after the authority decision — even if name/version look the same, any manifest edit (comments, fields, formatting) invalidates the approval.","triggerScenarios":"Calling `install_from_local_path_internal` with an approved `InstalledAuthority` whose `manifest_digest` differs from the digest of the current `source_dir/Capsule.toml` — any byte-level modification of the manifest between approval and install.","commonSituations":"A formatter or codegen tool rewriting Capsule.toml after review; adding/removing a capability or metadata field post-approval; manually editing the manifest (even whitespace) before installing.","solutions":["Re-run the authority approval on the current manifest so a receipt with the new manifest_digest is issued","Restore Capsule.toml to its exact approved bytes (git checkout the reviewed version) and retry the install","Check for tools (formatters, build scripts) that touch Capsule.toml and exclude it from automatic rewriting"],"exampleFix":"// before\nbail!(\"capsule manifest changed after authority decision\");\n// after\n// git checkout -- Capsule.toml   # restore approved bytes\n// install_from_local_path_internal(source_dir, Some(approved))","handlingStrategy":"validation","validationCode":"// Compare manifest digest against the approved decision before install\nlet bytes = std::fs::read(source_dir.join(\"Capsule.toml\"))?;\nlet digest = digest_manifest(&bytes);\nassert_eq!(approved.manifest_digest, digest, \"Capsule.toml changed since approval\");","typeGuard":"fn manifest_unchanged(approved: &InstalledAuthority, manifest_bytes: &[u8]) -> bool {\n    approved.manifest_digest == digest_manifest(manifest_bytes)\n}","tryCatchPattern":"match authority_for_install_source(source_dir, &manifest, Some(approved)) {\n    Err(e) if e.to_string().contains(\"manifest changed\") => restore_manifest_and_retry(),\n    other => other.map(install),\n}","preventionTips":["Exclude Capsule.toml from formatters/codegen that rewrite files automatically","Commit the manifest before review and don't touch it until install completes","Diff manifest bytes (not just semantics) against the approved state","Re-approve on any manifest edit, even whitespace"],"tags":["security","manifest","digest-mismatch","install"],"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"}