astrid-runtime/astrid · error

distro provenance receipt read-back differs

Error message

distro provenance receipt read-back differs

What it means

After writing a distro provenance receipt during migration, verify_destination reads the value back from the destination store, hashes it with blake3, and compares to the expected digest. On mismatch it returns AlreadyExists with 'distro provenance receipt read-back differs', indicating the persisted receipt does not match what was intended to be written.

Solutions

  1. Re-run the migration for this entry after quiescing other writers to the store
  2. Verify backend/store integrity; check for stale caches or replication lag in the destination store
  3. Delete the divergent receipt key and re-migrate so it is rewritten atomically
  4. Enable single-writer semantics or locking around migration to prevent concurrent overwrites

Example fix

// before: concurrent job rewriting the same key
0 * * * * reconcile-store --all   # races with migration
// after: pause conflicting jobs during migration
systemctl stop reconcile.timer && migrate && systemctl start reconcile.timer
Defensive patterns

Strategy: try-catch

Validate before calling

let bytes = dest.get(KEY).await?;
if let Some(b) = bytes {
    if format!("blake3:{}", blake3::hash(&b).to_hex()) != expected_digest {
        eprintln!("receipt diverges before migration completes");
    }
}

Try / catch

match migrate_one(entry).await {
    Err(e) if e.to_string().contains("read-back differs") => {
        // quiesce writers, delete key, retry migration
    }
    other => other?,
}

Prevention

When it happens

Trigger: Running migrate_one → verify_destination when the read-back bytes' blake3 hash differs from the expected digest recorded during the write phase.

Common situations: Concurrent writers overwrote the receipt mid-migration; storage corruption or a lying/network store returning stale or different bytes; non-atomic store writes; retried migration overwriting the key with different content.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-kernel/src/principal_distro_migration.rs:306

    retire_source(&source, &receipt.source_digest, receipt.source_bytes)
}

async fn verify_destination(
    store: &RuntimePrincipalStore,
    uid: PrincipalUid,
    expected_digest: &str,
) -> io::Result<()> {
    let scoped = store
        .principal_control_kv(uid, "distro")
        .map_err(io::Error::other)?;
    let bytes = scoped
        .get(KEY)
        .await
        .map_err(io::Error::other)?
        .ok_or_else(|| io::Error::other("distro provenance is missing after receipt"))?;
    let actual = format!("blake3:{}", blake3::hash(&bytes).to_hex());
    if actual != expected_digest {
        return Err(io::Error::new(
            io::ErrorKind::AlreadyExists,
            "distro provenance receipt read-back differs",
        ));
    }
    Ok(())
}

fn convert_lock(lock: LegacyLock) -> io::Result<DistroProvenance> {
    let provenance = DistroProvenance {
        schema_version: lock.schema_version,
        distro_id: lock.distro.id,
        distro_version: lock.distro.version,
        resolved_at: lock.distro.resolved_at,
        capsules: lock
            .capsules
            .into_iter()
            .map(|capsule| DistroCapsuleProvenance {
                name: capsule.name,

View on GitHub (pinned to affd8760f4)