{"record":{"id":"cc2f6eba94772067","repo":"astrid-runtime/astrid","slug":"distro-lock-capsule-capsule-requires-a-canonic-cc2f6e","errorCode":null,"errorMessage":"Distro.lock capsule '{capsule}' requires a canonical lowercase BLAKE3 hash","messagePattern":"Distro\\.lock capsule '(.+?)' requires a canonical lowercase BLAKE3 hash","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/init_grant.rs","lineNumber":432,"sourceCode":"            )\n        })?\n    };\n    let actual = blake3::hash(&bytes);\n    if actual != locked {\n        bail!(\"Distro.lock capsule '{capsule}' content blob bytes do not match hash {locked_hash}\");\n    }\n    Ok(())\n}\n\nfn parse_locked_blake3(capsule: &CapsuleId, value: &str) -> anyhow::Result<blake3::Hash> {\n    let Some(hex) = value.strip_prefix(\"blake3:\") else {\n        bail!(\"Distro.lock capsule '{capsule}' requires a canonical blake3:<hex> WASM hash\");\n    };\n    let hash = blake3::Hash::from_hex(hex).map_err(|_| {\n        anyhow::anyhow!(\"Distro.lock capsule '{capsule}' has an invalid BLAKE3 hash\")\n    })?;\n    if hex.len() != 64 || hash.to_hex().as_str() != hex {\n        bail!(\"Distro.lock capsule '{capsule}' requires a canonical lowercase BLAKE3 hash\");\n    }\n    Ok(hash)\n}\n\nfn manifest_declares_wasm(manifest: &CapsuleManifest) -> bool {\n    manifest\n        .components\n        .iter()\n        .any(|component| component.path.extension().and_then(|ext| ext.to_str()) == Some(\"wasm\"))\n}\n\n/// Apply capsule-access grants for the installed set (opt-in), or print\n/// the discoverability hint when the flag was omitted.\n///\n/// On the grant path the capsules are already installed and the lock is\n/// written; a failure here (daemon unreachable, caller lacks `agent:modify`)\n/// returns `Err` so `init` exits non-zero, but always prints the exact\n/// manual command to finish. The kernel applies the whole `add_capsules`","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/init_grant.rs#L414-L450","documentation":"Even a parseable blake3 hash must be in canonical form: exactly 64 hex characters whose lowercase hex round-trips through blake3::Hash::to_hex. Uppercase letters or extra characters are rejected so lockfiles stay byte-comparable and diff-stable.","triggerScenarios":"parse_locked_blake3 parses the hex successfully but hex.len() != 64 or hash.to_hex().as_str() != hex — e.g. uppercase hex, or a hex string with a stray character that still parses.","commonSituations":"Uppercase hex from another tool's output, padded or trimmed hex from manual editing, script-generated lockfile emitting non-canonical casing.","solutions":["Lowercase the hex portion of the hash in Distro.lock and confirm it is exactly 64 characters.","Regenerate Distro.lock with the CLI so it writes the canonical lowercase hash.","Normalize hashes in any tooling that writes the lockfile (lowercase, no padding)."],"exampleFix":"// before (Distro.lock)\nhash = \"blake3:9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08\"\n// after\nhash = \"blake3:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\"","handlingStrategy":"validation","validationCode":"let hex = value.strip_prefix(\"blake3:\")?;\nlet h = blake3::Hash::from_hex(hex).ok()?;\nif hex.len() != 64 || h.to_hex().as_str() != hex { return Err(anyhow!(\"non-canonical hash\")); }","typeGuard":"fn is_canonical_blake3(value: &str) -> bool {\n    match value.strip_prefix(\"blake3:\") {\n        Some(h) => h.len() == 64\n            && h.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))\n            && blake3::Hash::from_hex(h).map(|x| x.to_hex().as_str() == h).unwrap_or(false),\n        None => false,\n    }\n}","tryCatchPattern":"match parse_locked_blake3(capsule, value) {\n    Err(e) if e.to_string().contains(\"canonical lowercase\") => normalize_hash_case(capsule)?,\n    other => other?,\n}","preventionTips":["Lowercase all hex digests before writing lockfiles.","Add a canonicalization step to any script that emits hashes.","Run lockfile format checks in CI."],"tags":["hash","format","canonicalization","lockfile"],"backgroundTag":"invalid-argument-format","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"}