astrid-runtime/astrid · error

durable capsule {id} exports differ between metadata and arc

Error message

durable capsule {id} exports differ between metadata and archive

What it means

During durable package verification, verify_package_identity recomputes the export list from the manifest's WIT exports (name + version) and compares it with the exports recorded in the capsule metadata. This bail fires when the two differ, meaning the metadata file was hand-edited, regenerated from a different manifest, or paired with the wrong archive. The library throws it to prevent installing a capsule whose advertised API surface does not match what was actually approved and archived.

Source

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

    }
    let manifest_digest = crate::authority::digest_manifest(manifest_bytes);
    if authority.manifest_digest != manifest_digest {
        bail!("durable capsule {id} manifest digest differs from authority receipt");
    }
    if authority.content_digest != verification.content_digest() {
        bail!("durable capsule {id} content digest differs from authority receipt");
    }
    let expected_imports = crate::wit::version_map_to_strings(&manifest.imports, |definition| {
        definition.version.to_string()
    });
    if metadata.imports != expected_imports {
        bail!("durable capsule {id} imports differ between metadata and archive");
    }
    let expected_exports = crate::wit::version_map_to_strings(&manifest.exports, |definition| {
        definition.version.to_string()
    });
    if metadata.exports != expected_exports {
        bail!("durable capsule {id} exports differ between metadata and archive");
    }
    if authority.wasm_hash_pinned && metadata.wasm_hash != authority.approved_wasm_hash {
        bail!("durable capsule {id} metadata executable hash differs from authority receipt");
    }
    if let Some(component) = manifest.components.first() {
        let Some(relative) = component.path.to_str() else {
            bail!("durable capsule {id} component path is not UTF-8");
        };
        let Some(bytes) = archive_files.get(relative) else {
            bail!("durable capsule {id} component is missing from its archive");
        };
        if Path::new(relative)
            .extension()
            .is_some_and(|extension| extension.eq_ignore_ascii_case("wasm"))
        {
            let archive_hash = blake3::hash(bytes).to_hex().to_string();
            if authority.wasm_hash_pinned
                && authority.approved_wasm_hash.as_deref() != Some(archive_hash.as_str())

View on GitHub (pinned to affd8760f4)

Solutions

  1. Regenerate the capsule metadata (CapsuleMeta) from the current manifest/archive so the exports field matches manifest.exports versions.
  2. Reinstall the durable capsule from a freshly built, internally consistent package so metadata, manifest, and authority receipt are all produced together.
  3. If the old exports are intended, restore the original manifest/archive that matches the existing metadata instead of changing metadata.
  4. Compare manifest.exports vs metadata.exports (names and version strings) to identify which side is stale before regenerating.

Example fix

// before: metadata written once, manifest later gained an export
let metadata = read_metadata(); // exports: ["astrid:store@1.0.0"]
let manifest = read_manifest(); // exports: ["astrid:store@1.1.0"]
// after: regenerate metadata from the manifest before installing
let mut metadata = read_metadata();
metadata.exports = crate::wit::version_map_to_strings(&manifest.exports, |d| d.version.to_string());
write_metadata(&metadata);
Defensive patterns

Strategy: validation

Validate before calling

let expected_exports = crate::wit::version_map_to_strings(&manifest.exports, |d| d.version.to_string());
if metadata.exports != expected_exports {
    return Err(anyhow!("exports mismatch: metadata {:?} vs manifest {:?}", metadata.exports, expected_exports));
}

Prevention

When it happens

Trigger: Calling read_verified_durable_package_for_owner (or the test-path durable_metadata_cross_binding_rejects_manifest_and_archive_mismatches) on a durable capsule where metadata.exports != version_map_to_strings(manifest.exports), i.e. the manifest in the archive declares exports (names or versions) that don't exactly equal the exports list persisted in CapsuleMeta.

Common situations: Rebuilding a capsule with added/removed/renamed WIT exports but reusing the old metadata file; bumping an export's version in the manifest without regenerating metadata; mixing archives and metadata from different capsule versions; editing metadata by hand or with a tool that rewrote the exports field.

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