{"record":{"id":"134b09af7428f7c0","repo":"JuliusBrussee/caveman","slug":"unsupported-bundle-schema-string-bundle-schema","errorCode":null,"errorMessage":"unsupported bundle schema ${String(bundle.schema)}","messagePattern":"unsupported bundle schema (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":17411,"sourceCode":"  if (info.alg !== \"Ed25519\") throw new Error(`${label} has unsupported algorithm ${String(info.alg)}`);\n  if (typeof info.key !== \"string\" || !info.key.trim()) throw new Error(`${label} key is required`);\n  const raw = Buffer.from(info.key, \"base64\");\n  if (raw.length !== 32 || raw.toString(\"base64\") !== info.key) throw new Error(`${label} must be a canonical base64 Ed25519 public key`);\n  return { info, raw, key: ed25519PublicKey(raw) };\n}\n\nfunction decodeUniqueKeyring(infos: ReceiptPublicKey[], label: string): Map<string, DecodedReceiptKey> {\n  const keys = new Map<string, DecodedReceiptKey>();\n  for (const [index, info] of infos.entries()) {\n    const decoded = decodeReceiptKey(info, `${label}[${index}]`);\n    if (keys.has(decoded.info.key_id)) throw new Error(`${label} contains duplicate key_id ${decoded.info.key_id}`);\n    keys.set(decoded.info.key_id, decoded);\n  }\n  return keys;\n}\n\nfunction embeddedReceiptKeys(bundle: ReceiptBundle): { current: DecodedReceiptKey; keys: Map<string, DecodedReceiptKey> } {\n  if (bundle.schema !== RECEIPT_BUNDLE_V1 && bundle.schema !== RECEIPT_BUNDLE_V2) throw new Error(`unsupported bundle schema ${String(bundle.schema)}`);\n  if (bundle.verification_coverage !== undefined && bundle.verification_coverage !== INCLUDED_RECEIPTS_ONLY) throw new Error(`unsupported unsigned verification coverage ${String(bundle.verification_coverage)}`);\n  if (bundle.completeness_attested === true) throw new Error(\"bundle completeness cannot be attested by unsigned export metadata\");\n  const current = decodeReceiptKey(bundle.public_key, \"public_key\");\n  if (bundle.public_keys !== undefined && !Array.isArray(bundle.public_keys)) throw new Error(\"public_keys must be an array\");\n  if (bundle.schema === RECEIPT_BUNDLE_V2 && (!Array.isArray(bundle.public_keys) || bundle.public_keys.length === 0)) throw new Error(\"v2 bundle requires public_keys\");\n  const keys = decodeUniqueKeyring(bundle.public_keys ?? [], \"public_keys\");\n  const currentInRing = keys.get(current.info.key_id);\n  if (currentInRing && !currentInRing.raw.equals(current.raw)) throw new Error(`public_key conflicts with public_keys entry ${current.info.key_id}`);\n  if (bundle.schema === RECEIPT_BUNDLE_V2 && !currentInRing) throw new Error(\"v2 public_keys must include public_key\");\n  if (!currentInRing) keys.set(current.info.key_id, current);\n  return { current, keys };\n}\n\nasync function pinnedReceiptKeys(file: string, current: DecodedReceiptKey): Promise<{ keys: Map<string, DecodedReceiptKey>; trust: string }> {\n  const source = (await readFile(file, \"utf8\")).trim();\n  if (!source.startsWith(\"{\")) {\n    const pinned = decodeReceiptKey({ ...current.info, key: source }, \"--pubkey\");\n    if (!pinned.raw.equals(current.raw)) throw new Error(\"bundle public key does not match the published --pubkey\");","sourceCodeStart":17393,"sourceCodeEnd":17429,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L17393-L17429","documentation":"embeddedReceiptKeys accepts only bundles whose schema is \"caveman.receipt-bundle.v1\" or \"caveman.receipt-bundle.v2\". Any other value (null, a typo, or a newer version) is rejected before key decoding, because the verifier has no parsing rules for unknown schema versions.","triggerScenarios":"Running `caveman receipts verify` on a bundle whose schema field is anything other than the two supported literals, e.g. a future \"caveman.receipt-bundle.v3\" produced by a newer release, or a hand-built bundle with a misspelled schema.","commonSituations":"Version skew: bundle produced by a newer caveman CLI than the one verifying; hand-authored bundles; CI pinned to an old CLI version while producers upgraded.","solutions":["Upgrade the verifying CLI to a release that knows the bundle's schema version","Regenerate the bundle with schema \"caveman.receipt-bundle.v1\" or \"caveman.receipt-bundle.v2\"","Inspect the field first: jq .schema bundle.json and compare against the supported literals"],"exampleFix":"// before\n{ \"schema\": \"receipts-v2\", ... }\n\n// after\n{ \"schema\": \"caveman.receipt-bundle.v2\", ... }","handlingStrategy":"type-guard","validationCode":"const SUPPORTED = new Set([\"caveman.receipt-bundle.v1\", \"caveman.receipt-bundle.v2\"]);\nif (!SUPPORTED.has(String(bundle.schema))) {\n  throw new Error(`bundle schema ${String(bundle.schema)} not supported by this CLI version`);\n}","typeGuard":"function isSupportedBundleSchema(v: unknown): v is \"caveman.receipt-bundle.v1\" | \"caveman.receipt-bundle.v2\" {\n  return v === \"caveman.receipt-bundle.v1\" || v === \"caveman.receipt-bundle.v2\";\n}","tryCatchPattern":"try { await verifyBundle(bundle); }\ncatch (e) {\n  if (/unsupported bundle schema/.test(String((e as Error).message))) {\n    log(`bundle schema too new/unknown — upgrade the verifying CLI`);\n    return;\n  }\n  throw e;\n}","preventionTips":["Pin producer and verifier CLI versions together in CI so schemas stay compatible","Check bundle.schema with jq before passing bundles to the verifier","Surface the CLI's supported schema versions in your pipeline's preflight"],"tags":["receipts","schema-version","version-skew","validation"],"backgroundTag":"unsupported-schema-version","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}