{"record":{"id":"2abb5ca174e3b9a3","repo":"astrid-runtime/astrid","slug":"absent-migration-source-has-a-digest","errorCode":null,"errorMessage":"absent migration source has a digest: {}","messagePattern":"absent migration source has a digest: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs","lineNumber":642,"sourceCode":"            .is_some_and(|previous: &String| previous >= &component.name)\n        {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"migration ledger components are not canonically sorted\",\n            ));\n        }\n        previous = Some(component.name.clone());\n        if component.source.present && component.source.digest == \"absent\" {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"present migration source has absent digest: {}\",\n                    component.name\n                ),\n            ));\n        }\n        if !component.source.present && component.source.digest != \"absent\" {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\"absent migration source has a digest: {}\", component.name),\n            ));\n        }\n        if component.destination_proof.is_absent() && component.source.present {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\"invalid destination proof for component {}\", component.name),\n            ));\n        }\n        if component.name == \"system:cow\" {\n            let expected = format!(\"source-digest={}\", component.source.digest);\n            if !component\n                .destination_proof\n                .starts_with(\"verified-discard-v1:\")\n                || !component.destination_proof.contains(&expected)\n                || !component\n                    .destination_proof","sourceCodeStart":624,"sourceCodeEnd":660,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs#L624-L660","documentation":"validate_ledger_shape enforces that every migration ledger component's source presence flag is consistent with its digest. A component whose source is not present must carry the literal digest \"absent\"; any other digest is a self-contradictory record. This is an InvalidData io::Error raised during ledger admission before any migration state is written.","triggerScenarios":"Calling write_ledger (directly or via reject_incomplete_layout_v2, retire_post_barrier_sources, or resume_existing_layout, or the ledger-shape tests) with a MigrationLedger containing a component where source.present == false but source.digest is set to a real digest value instead of \"absent\".","commonSituations":"Hand-edited or programmatically generated ledger JSON where an author zeroes out the source but forgets to reset the digest; migrating ledgers between schema versions that changed the sentinel convention; copying a component entry and clearing present without clearing digest.","solutions":["Set source.digest to the literal string \"absent\" whenever source.present is false","If the source actually exists, set source.present = true and keep the real digest","Regenerate the ledger entry with the library's own writer instead of constructing it by hand"],"exampleFix":"// before\nMigrationSource { present: false, digest: \"blake3:abc123\", .. }\n// after\nMigrationSource { present: false, digest: \"absent\", .. }","handlingStrategy":"validation","validationCode":"fn source_absent_digest_ok(c: &MigrationComponent) -> bool {\n    c.source.present || c.source.digest == \"absent\"\n}\n// validate all components before calling write_ledger\nlet ok = ledger.components.iter().all(source_absent_digest_ok);","typeGuard":"fn has_consistent_source(c: &MigrationComponent) -> bool {\n    if c.source.present { c.source.digest != \"absent\" } else { c.source.digest == \"absent\" }\n}","tryCatchPattern":"match write_ledger(&ledger) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"absent migration source has a digest\") => {\n        // fix source.present/digest pairing and retry\n    }\n    other => other?,\n}","preventionTips":["Always construct MigrationSource through a helper that keeps present and digest in sync","Never hand-edit ledger JSON; regenerate it via the library's writer","Add a unit test asserting absent sources use the \"absent\" sentinel"],"tags":["rust","validation","migration","ledger"],"backgroundTag":"internal-invariant-violation","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"}