JuliusBrussee/caveman · error

bundle completeness cannot be attested by unsigned export…

Error message

bundle completeness cannot be attested by unsigned export metadata

What it means

Thrown by embeddedReceiptKeys when an unsigned bundle has completeness_attested set to true. Because the bundle itself is unsigned, nothing cryptographically backs a claim that the export is complete (contains every receipt); the verifier therefore refuses to honor such an attestation, preventing an unverifiable completeness claim from being trusted.

Solutions

  1. Set completeness_attested to false (or remove it) in the unsigned bundle and re-run verification.
  2. Use the signed export path if you genuinely need an attested-complete bundle.
  3. Re-export with the official unsigned export command instead of editing the JSON.

Example fix

// before
{"completeness_attested": true, ...} // unsigned bundle
// after
{"completeness_attested": false, ...} // or use a signed bundle for attestation
Defensive patterns

Strategy: validation

Validate before calling

if (!isSignedBundle && bundle.completeness_attested === true) throw new Error('completeness_attested is not allowed on unsigned bundles');

Type guard

function isAttestableBundle(b: { schema: string; completeness_attested?: boolean }): boolean {
  return b.schema.includes('signed') || b.completeness_attested !== true;
}

Try / catch

try {
  verifyBundle(bundle);
} catch (e) {
  if ((e as Error).message.includes('completeness')) console.error('unsigned bundles cannot attest completeness; use a signed export');
  throw e;
}

Prevention

When it happens

Trigger: Verifying an unsigned bundle where completeness_attested is true — set by hand-editing the JSON, misusing a signed-export field in an unsigned bundle, or an exporter bug.

Common situations: Copy-pasting fields from a signed bundle template into an unsigned one; toggling the flag hoping to silence a completeness warning; tooling that copies signed export metadata onto unsigned exports.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/433b018d6181f6ad. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:17869

  const raw = Buffer.from(info.key, "base64");
  if (raw.length !== 32 || raw.toString("base64") !== info.key) throw new Error(`${label} must be a canonical base64 Ed25519 public key`);
  return { info, raw, key: ed25519PublicKey(raw) };
}

function decodeUniqueKeyring(infos: ReceiptPublicKey[], label: string): Map<string, DecodedReceiptKey> {
  const keys = new Map<string, DecodedReceiptKey>();
  for (const [index, info] of infos.entries()) {
    const decoded = decodeReceiptKey(info, `${label}[${index}]`);
    if (keys.has(decoded.info.key_id)) throw new Error(`${label} contains duplicate key_id ${decoded.info.key_id}`);
    keys.set(decoded.info.key_id, decoded);
  }
  return keys;
}

function embeddedReceiptKeys(bundle: ReceiptBundle): { current: DecodedReceiptKey; keys: Map<string, DecodedReceiptKey> } {
  if (bundle.schema !== RECEIPT_BUNDLE_V1 && bundle.schema !== RECEIPT_BUNDLE_V2) throw new Error(`unsupported bundle schema ${String(bundle.schema)}`);
  if (bundle.verification_coverage !== undefined && bundle.verification_coverage !== INCLUDED_RECEIPTS_ONLY) throw new Error(`unsupported unsigned verification coverage ${String(bundle.verification_coverage)}`);
  if (bundle.completeness_attested === true) throw new Error("bundle completeness cannot be attested by unsigned export metadata");
  const current = decodeReceiptKey(bundle.public_key, "public_key");
  if (bundle.public_keys !== undefined && !Array.isArray(bundle.public_keys)) throw new Error("public_keys must be an array");
  if (bundle.schema === RECEIPT_BUNDLE_V2 && (!Array.isArray(bundle.public_keys) || bundle.public_keys.length === 0)) throw new Error("v2 bundle requires public_keys");
  const keys = decodeUniqueKeyring(bundle.public_keys ?? [], "public_keys");
  const currentInRing = keys.get(current.info.key_id);
  if (currentInRing && !currentInRing.raw.equals(current.raw)) throw new Error(`public_key conflicts with public_keys entry ${current.info.key_id}`);
  if (bundle.schema === RECEIPT_BUNDLE_V2 && !currentInRing) throw new Error("v2 public_keys must include public_key");
  if (!currentInRing) keys.set(current.info.key_id, current);
  return { current, keys };
}

async function pinnedReceiptKeys(file: string, current: DecodedReceiptKey): Promise<{ keys: Map<string, DecodedReceiptKey>; trust: string }> {
  const source = (await readFile(file, "utf8")).trim();
  if (!source.startsWith("{")) {
    const pinned = decodeReceiptKey({ ...current.info, key: source }, "--pubkey");
    if (!pinned.raw.equals(current.raw)) throw new Error("bundle public key does not match the published --pubkey");
    return { keys: new Map([[current.info.key_id, pinned]]), trust: "pinned_public_key" };
  }

View on GitHub (pinned to 3ee70a1026)