astrid-runtime/astrid · error

absent migration source has a digest

Error message

absent migration source has a digest: {}

What it means

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.

Solutions

  1. Set source.digest to the literal string "absent" whenever source.present is false
  2. If the source actually exists, set source.present = true and keep the real digest
  3. Regenerate the ledger entry with the library's own writer instead of constructing it by hand

Example fix

// before
MigrationSource { present: false, digest: "blake3:abc123", .. }
// after
MigrationSource { present: false, digest: "absent", .. }
Defensive patterns

Strategy: validation

Validate before calling

fn source_absent_digest_ok(c: &MigrationComponent) -> bool {
    c.source.present || c.source.digest == "absent"
}
// validate all components before calling write_ledger
let ok = ledger.components.iter().all(source_absent_digest_ok);

Type guard

fn has_consistent_source(c: &MigrationComponent) -> bool {
    if c.source.present { c.source.digest != "absent" } else { c.source.digest == "absent" }
}

Try / catch

match write_ledger(&ledger) {
    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains("absent migration source has a digest") => {
        // fix source.present/digest pairing and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: 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".

Common situations: 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.

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/2abb5ca174e3b9a3. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs:642

            .is_some_and(|previous: &String| previous >= &component.name)
        {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "migration ledger components are not canonically sorted",
            ));
        }
        previous = Some(component.name.clone());
        if component.source.present && component.source.digest == "absent" {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "present migration source has absent digest: {}",
                    component.name
                ),
            ));
        }
        if !component.source.present && component.source.digest != "absent" {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("absent migration source has a digest: {}", component.name),
            ));
        }
        if component.destination_proof.is_absent() && component.source.present {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("invalid destination proof for component {}", component.name),
            ));
        }
        if component.name == "system:cow" {
            let expected = format!("source-digest={}", component.source.digest);
            if !component
                .destination_proof
                .starts_with("verified-discard-v1:")
                || !component.destination_proof.contains(&expected)
                || !component
                    .destination_proof

View on GitHub (pinned to affd8760f4)