astrid-runtime/astrid · error

durable capsule {id} has an unpinned WIT file {key}

Error message

durable capsule {id} has an unpinned WIT file {key}

What it means

After verifying all pinned WIT files, verify_wit_files scans the archive for any `wit/*.wit` file that was NOT declared in metadata.wit_files. Durable capsules must pin every WIT file they ship; an unpinned .wit file in the archive means the archive contains interface definitions whose integrity was never attested, so the package is rejected.

Source

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

        {
            bail!("durable capsule {id} has unsafe WIT metadata path {relative}");
        }
        let key = format!("wit/{relative}");
        let Some(bytes) = files.get(&key) else {
            bail!("durable capsule {id} is missing WIT file {relative}");
        };
        if !is_hex_digest(pin) || blake3::hash(bytes).to_hex().as_str() != pin {
            bail!("durable capsule {id} WIT digest mismatch for {relative}");
        }
        expected.insert(key);
    }
    for key in files.keys().filter(|key| key.starts_with("wit/")) {
        if Path::new(key)
            .extension()
            .is_some_and(|extension| extension.eq_ignore_ascii_case("wit"))
            && !expected.contains(key)
        {
            bail!("durable capsule {id} has an unpinned WIT file {key}");
        }
    }
    Ok(())
}

fn is_hex_digest(value: &str) -> bool {
    value.len() == 64
        && value
            .bytes()
            .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
}

fn verify_package_identity(
    id: &str,
    manifest: &CapsuleManifest,
    metadata: &CapsuleMeta,
    authority: &InstalledAuthority,
    manifest_bytes: &[u8],

View on GitHub (pinned to affd8760f4)

Solutions

  1. Regenerate the capsule metadata so wit_files pins every .wit file in the archive, then republish.
  2. Delete or move stray .wit files out of the archive source tree and rebuild.
  3. Ensure the packaging step and metadata generation use the same file list (same glob/manifest).
  4. List untracked files by diffing archive contents (`tar -tzf`) against metadata.wit_files keys.

Example fix

// before: new file added but metadata not regenerated
// archive: wit/api.wit, wit/new.wit; metadata pins only api.wit
// after: regenerate metadata from the archive
metadata.wit_files = pin_all(glob("wit/*.wit"));
Defensive patterns

Strategy: validation

Validate before calling

fn no_unpinned_wit_files(archive_files: &[String], pinned: &std::collections::BTreeSet<String>) -> bool {
    archive_files.iter().all(|f| {
        !f.starts_with("wit/")
            || std::path::Path::new(f).extension().map(|e| e.eq_ignore_ascii_case("wit")) != Some(true)
            || pinned.contains(f)
    })
}

Type guard

fn is_wit_key(key: &str) -> bool {
    key.starts_with("wit/")
        && std::path::Path::new(key).extension().is_some_and(|e| e.eq_ignore_ascii_case("wit"))
}

Try / catch

match read_verified_durable_package_for_owner(id) {
    Err(e) if e.to_string().contains("unpinned WIT file") => {
        // stray .wit in archive: clean the build tree and republish
        clean_wit_tree_and_republish(id)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: read_verified_durable_package_for_owner on a capsule whose archive contains extra .wit files under wit/ that metadata.wit_files does not list — e.g. a .wit file added to the archive after metadata generation, or metadata regenerated against a subset of files.

Common situations: Adding a new .wit file and rebuilding the archive but not the metadata; leftover stale .wit files in the build directory that get swept into the archive; packaging scripts that glob wit/*.wit while metadata generation uses a manifest subset.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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