{"record":{"id":"ec7b3e5478508b97","repo":"astrid-runtime/astrid","slug":"invaliddata-ec7b3e","errorCode":"InvalidData","errorMessage":"absent migration source has a digest: {}","messagePattern":"absent migration source has a digest: (.+?)","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs","lineNumber":648,"sourceCode":"        }\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\n                    .contains(\"layout-receipt=layout-v1-to-v2.complete\")\n            {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    \"CoW ledger component is not a source-bound verified discard\",\n                ));","sourceCodeStart":630,"sourceCodeEnd":666,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs#L630-L666","documentation":"validate_ledger_shape enforces the mirror invariant of the present/absent digest check: a component whose source IS present must not carry the sentinel digest \"absent\". A present source with an \"absent\" digest cannot be source-bound to any destination proof, so the ledger is rejected as InvalidData before it is written.","triggerScenarios":"Calling write_ledger (or any caller that validates ledger shape: reject_incomplete_layout_v2, retire_post_barrier_sources, resume_existing_layout) with a component entry where source.present == true but source.digest == \"absent\".","commonSituations":"Marking a component as migrated/present after the fact without recomputing the source digest; templated ledger generation that fills in present=true while leaving placeholder digests; a snapshot tool that copied the sentinel value into the wrong field.","solutions":["Compute and set the real content digest (e.g. blake3:...) for the present source","If the source is genuinely gone, set source.present = false instead","Re-run the migration step that produces the digest before writing the ledger"],"exampleFix":"// before\nMigrationSource { present: true, digest: \"absent\", .. }\n// after\nMigrationSource { present: true, digest: \"blake3:<computed>\", .. }","handlingStrategy":"validation","validationCode":"fn present_source_digest_ok(c: &MigrationComponent) -> bool {\n    !c.source.present || c.source.digest != \"absent\"\n}\nlet ok = ledger.components.iter().all(present_source_digest_ok);","typeGuard":"fn has_real_digest(c: &MigrationComponent) -> bool {\n    !c.source.present || (c.source.digest != \"absent\" && c.source.digest.starts_with(\"blake3:\"))\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        // recompute digests for present sources, then retry\n    }\n    other => other?,\n}","preventionTips":["Compute the source digest at the moment the source is marked present","Reject placeholder digests early in any ledger-building code","Keep digest computation and source presence in one code path"],"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-14T11:17:12.474Z"}