{"record":{"id":"1b2a0b99eaa3b698","repo":"astrid-runtime/astrid","slug":"signed-distro-lock-entry-has-no-capsule-hash","errorCode":null,"errorMessage":"signed Distro.lock entry '{}' has no capsule hash","messagePattern":"signed Distro\\.lock entry '(.+?)' has no capsule hash","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/init_signed_source.rs","lineNumber":366,"sourceCode":"    );\n    for capsule in &lock.capsules {\n        let declared_capsule = declared\n            .get(capsule.name.as_str())\n            .copied()\n            .ok_or_else(|| {\n                anyhow::anyhow!(\n                    \"signed Distro.lock contains undeclared capsule '{}'\",\n                    capsule.name\n                )\n            })?;\n        if capsule.source != declared_capsule.source || capsule.version != declared_capsule.version\n        {\n            bail!(\n                \"signed Distro.lock entry '{}' does not match Distro.toml\",\n                capsule.name\n            );\n        }\n        anyhow::ensure!(\n            !capsule.hash.is_empty(),\n            \"signed Distro.lock entry '{}' has no capsule hash\",\n            capsule.name\n        );\n    }\n    Ok(())\n}\n","sourceCodeStart":348,"sourceCodeEnd":374,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/init_signed_source.rs#L348-L374","documentation":"Every capsule entry in the signed Distro.lock must carry a content hash; the hash is what the trust layer pins and verifies after signature checking. An empty `hash` means the lock entry is unusable for integrity verification, so validate_signed_member_sets rejects it.","triggerScenarios":"fetch_signed_manifest -> verify_signed_manifest -> validate_signed_member_sets when a Distro.lock capsule entry has `hash = \"\"` or the hash field omitted — typically a hand-written or partially generated lock.","commonSituations":"Lock authored manually without running the hash step; a tool bug that emitted empty hashes; lock file truncated/edited during publish.","solutions":["Regenerate Distro.lock with the official lock/seal command so each capsule gets a real content hash","Fill in the correct hash for the entry (computed over the capsule artifact) and re-sign the lock","Verify the lock file was not truncated or edited in transit (compare against the publisher's copy)"],"exampleFix":"# before\n[[capsules]]\nname = \"alpha\"\nhash = \"\"\n# after\n[[capsules]]\nname = \"alpha\"\nhash = \"sha256:<hex-of-capsule>\"","handlingStrategy":"validation","validationCode":"for c in &lock.capsules {\n    anyhow::ensure!(!c.hash.is_empty(), \"capsule '{}' in Distro.lock has no hash; regenerate the lock\", c.name);\n}","typeGuard":"fn has_hash(c: &DistroLockCapsule) -> bool { !c.hash.is_empty() }","tryCatchPattern":"match validate_signed_member_sets(manifest, lock) {\n    Err(e) if e.to_string().contains(\"has no capsule hash\") => {\n        eprintln!(\"Lock entry missing hash — regenerate Distro.lock with the official seal step\");\n    }\n    r => r?,\n}","preventionTips":["Never author Distro.lock by hand; use the lock/seal command so hashes are computed","Validate every lock entry has a non-empty hash before publishing","Compare published lock against a freshly generated one in CI"],"tags":["signing","integrity","hash"],"backgroundTag":"checksum-mismatch","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}