{"record":{"id":"cd87c280854bc1a1","repo":"astrid-runtime/astrid","slug":"decode-migration-ledger-error","errorCode":null,"errorMessage":"decode migration ledger {}: {error}","messagePattern":"decode migration ledger (.+?): (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs","lineNumber":584,"sourceCode":"            format!(\"unknown migration component name: {name}\"),\n        ));\n    }\n    Ok(())\n}\n\npub(super) fn destination_file_proof(path: &Path) -> io::Result<DestinationProof> {\n    Ok(match read_bounded_file(path, MAX_BYTES)? {\n        Some(bytes) => DestinationProof::from_hashed_bytes(&bytes),\n        None => DestinationProof::absent(),\n    })\n}\n\npub(super) fn decode_canonical<T: for<'de> Deserialize<'de> + Serialize>(\n    bytes: &[u8],\n    path: &Path,\n) -> io::Result<T> {\n    let value = serde_json::from_slice(bytes).map_err(|error| {\n        io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"decode migration ledger {}: {error}\", path.display()),\n        )\n    })?;\n    if canonical_json(&value)? != bytes {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"migration ledger is not canonical: {}\", path.display()),\n        ));\n    }\n    Ok(value)\n}\n\npub(super) fn canonical_json<T: Serialize>(value: &T) -> io::Result<Vec<u8>> {\n    let mut bytes = serde_json::to_vec(value).map_err(io::Error::other)?;\n    bytes.push(b'\\n');\n    Ok(bytes)\n}","sourceCodeStart":566,"sourceCodeEnd":602,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs#L566-L602","documentation":"`decode_canonical` deserializes a migration ledger file from bytes and requires both valid JSON and byte-level canonical form. This error is thrown when `serde_json::from_slice` fails, i.e. the file at the given path is not parseable JSON (syntax error, truncation, binary content, or a type not matching the expected ledger schema).","triggerScenarios":"Any read path that calls `decode_canonical` — `reject_incomplete_layout_v2`, `retire_post_barrier_sources`, `resume_existing_layout`, `legacy_secret_source_must_be_absent`, `record_absent_legacy_secret_for_test` — against a ledger/receipt file that is empty, truncated mid-write, corrupted, or not JSON at all.","commonSituations":"A crash or power loss during ledger write left a partial file; an editor saved the ledger as JSON5/with comments; the file was overwritten by logs or binary data; a Git merge produced conflict markers inside the ledger.","solutions":["Inspect the file at the path in the message and fix or restore it to valid JSON matching the ledger schema (the serde error text names the exact parse failure).","Restore the ledger from backup if the content is corrupted beyond repair.","If a migration was interrupted, re-run the migration from the pre-migration state so a fresh, complete ledger is written.","Never hand-edit ledgers with non-JSON tools; use the library's `write_ledger` path."],"exampleFix":"// before: truncated ledger\n{\"components\":[{\"name\":\"system:state-db\"   <- cut off\n// after: restore complete file\n{\"components\":[{\"name\":\"system:state-db\",\"source\":{...},\"destination_proof\":\"...\"}]}\n","handlingStrategy":"try-catch","validationCode":"// Pre-flight: file exists, non-empty, and parses as JSON\nfn ledger_parses(path: &Path) -> io::Result<()> {\n    let bytes = std::fs::read(path)?;\n    if bytes.is_empty() { return Err(io::Error::new(io::ErrorKind::InvalidData, \"ledger empty\")); }\n    serde_json::from_slice::<serde_json::Value>(&bytes)\n        .map(|_| ())\n        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))\n}","typeGuard":null,"tryCatchPattern":"match resume_existing_layout(&home) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().starts_with(\"decode migration ledger\") => {\n        // ledger file unreadable/corrupt: restore from backup or re-run migration\n    }\n    other => other?,\n}","preventionTips":["Write ledger files atomically (temp file + rename) to survive crashes","Never let editors/formatters save migration ledger files","Resolve Git merge conflicts in ledgers before running any migration command","Monitor disk-full conditions during migration runs"],"tags":["migration","json","parse-error","rust"],"backgroundTag":"json-decode-failed","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"}