{"record":{"id":"3032d3a08d5f9366","repo":"astrid-runtime/astrid","slug":"present-migration-source-has-absent-digest-3032d3","errorCode":null,"errorMessage":"present migration source has absent digest","messagePattern":"present migration source has absent digest","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/source.rs","lineNumber":209,"sourceCode":"        }\n    }\n\n    pub(super) fn from_snapshot_fields(\n        digest: &str,\n        entries: u64,\n        bytes: u64,\n        present: bool,\n    ) -> io::Result<Self> {\n        if !present {\n            return Ok(Self::absent());\n        }\n        Self::present(\n            SourceDigest::parse(digest.to_owned())\n                .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?,\n            SourceCount::new(entries),\n            SourceCount::new(bytes),\n        )\n        .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))\n    }\n}\n\nimpl<'de> Deserialize<'de> for SourceIdentity {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        #[derive(Deserialize)]\n        #[serde(deny_unknown_fields)]\n        struct Raw {\n            digest: SourceDigest,\n            entries: SourceCount,\n            bytes: SourceCount,\n            present: bool,\n        }\n        let raw = Raw::deserialize(deserializer)?;\n        SourceIdentity {","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/source.rs#L191-L227","documentation":"`SourceIdentity::from_snapshot_fields` was called with `present=true`, but building the present identity failed because the digest parsed as the absent sentinel — i.e. the digest string does not represent a real blake3 hex value that this constructor accepts as present. A source marked present must have a real digest; present-with-absent-digest is an internal invariant of the migration snapshot.","triggerScenarios":"Passing an empty or sentinel/absent digest string together with `present=true` to `from_snapshot_fields` (e.g. snapshot JSON where `digest` is `\"\"` or a placeholder while `present` is `true`).","commonSituations":"Snapshot writers that defaulted `present=true` while leaving the digest unset; partial serialization bugs; hand-assembled snapshot records during migration tooling development.","solutions":["Either supply the real 64-hex digest or set `present=false` for an absent source (digest then must be absent).","Fix the snapshot producer so `present` is derived from whether a digest was actually computed.","Re-generate the migration snapshot rather than patching the digest field by hand.","Assert in caller code that a digest exists before marking a source present."],"exampleFix":"// before\nSourceIdentity::from_snapshot_fields(\"\", entries, bytes, /*present=*/true)?;\n// after\nSourceIdentity::from_snapshot_fields(\"\", entries, bytes, /*present=*/false)?; // or pass real digest","handlingStrategy":"validation","validationCode":"fn present_identity(digest: &str, entries: u64, bytes: u64) -> std::io::Result<SourceIdentity> {\n    if digest.is_empty() {\n        return SourceIdentity::from_snapshot_fields(digest, entries, bytes, false); // absent\n    }\n    SourceIdentity::from_snapshot_fields(digest, entries, bytes, true)\n}","typeGuard":"fn digest_is_present(digest: &str) -> bool { !digest.is_empty() }","tryCatchPattern":"match from_snapshot_fields(digest, entries, bytes, present) {\n    Err(e) if e.to_string().contains(\"absent digest\") => eprintln!(\"snapshot inconsistent: present=true but no digest\"),\n    other => other?,\n}","preventionTips":["Derive `present` from whether a digest was actually computed, never hardcode true.","Generate snapshots only via the provided constructors/serde impls.","Round-trip validate snapshot JSON before persisting."],"tags":["validation","migration","invariant","digest"],"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-17T15:17:12.973Z"}