astrid-runtime/astrid · error

present migration source has absent digest

Error message

present migration source has absent digest

What it means

`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.

Solutions

  1. Either supply the real 64-hex digest or set `present=false` for an absent source (digest then must be absent).
  2. Fix the snapshot producer so `present` is derived from whether a digest was actually computed.
  3. Re-generate the migration snapshot rather than patching the digest field by hand.
  4. Assert in caller code that a digest exists before marking a source present.

Example fix

// before
SourceIdentity::from_snapshot_fields("", entries, bytes, /*present=*/true)?;
// after
SourceIdentity::from_snapshot_fields("", entries, bytes, /*present=*/false)?; // or pass real digest
Defensive patterns

Strategy: validation

Validate before calling

fn present_identity(digest: &str, entries: u64, bytes: u64) -> std::io::Result<SourceIdentity> {
    if digest.is_empty() {
        return SourceIdentity::from_snapshot_fields(digest, entries, bytes, false); // absent
    }
    SourceIdentity::from_snapshot_fields(digest, entries, bytes, true)
}

Type guard

fn digest_is_present(digest: &str) -> bool { !digest.is_empty() }

Try / catch

match from_snapshot_fields(digest, entries, bytes, present) {
    Err(e) if e.to_string().contains("absent digest") => eprintln!("snapshot inconsistent: present=true but no digest"),
    other => other?,
}

Prevention

When it happens

Trigger: 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`).

Common situations: Snapshot writers that defaulted `present=true` while leaving the digest unset; partial serialization bugs; hand-assembled snapshot records during migration tooling development.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/3032d3a08d5f9366. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/source.rs:209

        }
    }

    pub(super) fn from_snapshot_fields(
        digest: &str,
        entries: u64,
        bytes: u64,
        present: bool,
    ) -> io::Result<Self> {
        if !present {
            return Ok(Self::absent());
        }
        Self::present(
            SourceDigest::parse(digest.to_owned())
                .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?,
            SourceCount::new(entries),
            SourceCount::new(bytes),
        )
        .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
    }
}

impl<'de> Deserialize<'de> for SourceIdentity {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(deny_unknown_fields)]
        struct Raw {
            digest: SourceDigest,
            entries: SourceCount,
            bytes: SourceCount,
            present: bool,
        }
        let raw = Raw::deserialize(deserializer)?;
        SourceIdentity {

View on GitHub (pinned to affd8760f4)