astrid-runtime/astrid · critical

durable capsule {id} authority claims provenance absent from

Error message

durable capsule {id} authority claims provenance absent from archive

What it means

The archive is unsigned (ArtifactVerification::Unsigned) but the authority receipt records a signer and/or signature. The library throws this because the receipt claims provenance that the artifact itself does not carry; installing it would let an unsigned artifact pass under a signed identity. Verification fails for the durable read.

Source

Thrown at crates/astrid-capsule-install/src/storage.rs:344

    if !effective_capabilities
        .expansions_from(&authority.approved_capabilities)
        .is_empty()
    {
        bail!("durable capsule {id} manifest exceeds its authority receipt");
    }
    match verification {
        ArtifactVerification::Signed(provenance) => {
            let signer = provenance.signer.to_string();
            let signature = provenance.signature.to_string();
            if authority.signer.as_deref() != Some(signer.as_str())
                || authority.signature.as_deref() != Some(signature.as_str())
            {
                bail!("durable capsule {id} provenance differs from authority receipt");
            }
        },
        ArtifactVerification::Unsigned { .. } => {
            if authority.signer.is_some() || authority.signature.is_some() {
                bail!("durable capsule {id} authority claims provenance absent from archive");
            }
        },
    }
    Ok(())
}

struct ArchiveInventory {
    files: std::collections::BTreeMap<String, Vec<u8>>,
    directories: std::collections::BTreeSet<String>,
}

fn read_archive_files(archive_bytes: &[u8]) -> anyhow::Result<ArchiveInventory> {
    let decoder = flate2::read::GzDecoder::new(Cursor::new(archive_bytes));
    let mut archive = tar::Archive::new(decoder);
    let mut files = std::collections::BTreeMap::new();
    let mut directories = std::collections::BTreeSet::new();
    for entry in archive.entries().context("read durable capsule archive")? {
        let mut entry = entry.context("read durable capsule archive entry")?;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Sign the archive with the expected key so the artifact matches the receipt's provenance.
  2. Clear authority.signer/authority.signature if the capsule is intentionally unsigned and re-approve it.
  3. Republish the capsule so receipt and artifact are generated in one consistent step.
  4. Check the build pipeline for a signing step that was skipped or failed silently.

Example fix

// before: unsigned artifact with receipt provenance
ArtifactVerification::Unsigned { .. } + authority.signer = Some("pub")
// after: sign the artifact before installing
let provenance = sign_artifact(&archive_bytes, &signing_key)?;
ArtifactVerification::Signed(provenance)
Defensive patterns

Strategy: validation

Validate before calling

if let ArtifactVerification::Unsigned { .. } = verification {
    if authority.signer.is_some() || authority.signature.is_some() {
        return Err("receipt claims provenance but artifact is unsigned");
    }
}

Type guard

fn receipt_provenance_consistent(v: &ArtifactVerification, authority: &InstalledAuthority) -> bool {
    match v {
        ArtifactVerification::Signed(_) => true,
        ArtifactVerification::Unsigned { .. } => authority.signer.is_none() && authority.signature.is_none(),
    }
}

Try / catch

match read_verified_durable_package_for_owner(&store, owner, id).await {
    Ok(pkg) => pkg,
    Err(e) if e.to_string().contains("provenance absent from archive") => {
        // sign the artifact or clear receipt provenance, then republish
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: read_verified_durable_package_for_owner where verification is Unsigned { .. } and authority.signer.is_some() || authority.signature.is_some().

Common situations: Signing step dropped during republish (e.g. CI built an unsigned artifact) while the receipt was preserved; receipt copied from a signed capsule; artifact re-exported without its signature.

Related errors


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