{"record":{"id":"a2bbfb1856bfa431","repo":"JuliusBrussee/caveman","slug":"pubkey-json-must-contain-public-key-or-public-ke","errorCode":null,"errorMessage":"--pubkey JSON must contain public_key or public_keys","messagePattern":"--pubkey JSON must contain public_key or public_keys","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":17435,"sourceCode":"  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\");\n    return { keys: new Map([[current.info.key_id, pinned]]), trust: \"pinned_public_key\" };\n  }\n  let parsed: { public_key?: ReceiptPublicKey; public_keys?: ReceiptPublicKey[] };\n  try { parsed = JSON.parse(source); } catch { throw new Error(\"--pubkey JSON is malformed\"); }\n  const infos = Array.isArray(parsed.public_keys) ? parsed.public_keys : parsed.public_key ? [parsed.public_key] : [];\n  if (infos.length === 0) throw new Error(\"--pubkey JSON must contain public_key or public_keys\");\n  const keys = decodeUniqueKeyring(infos, \"--pubkey public_keys\");\n  const pinnedCurrent = keys.get(current.info.key_id);\n  if (!pinnedCurrent || !pinnedCurrent.raw.equals(current.raw)) throw new Error(\"trusted --pubkey keyring does not contain the bundle public key\");\n  return { keys, trust: \"pinned_keyring\" };\n}\n\n// receiptsVerify validates a signed receipt bundle offline (no network). A raw\n// --pubkey pins the current key; JSON may independently pin a full rotation\n// keyring. Without either, embedded keys prove self-consistency, not publisher\n// authenticity. Exits non-zero on any included content, signature, or\n// scope-chain break. Tail/scope omission needs separately trusted head manifest;\n// bundle output states completeness is not attested.\n//   caveman receipts verify <bundle.json> [--pubkey <file>]\nasync function receiptsVerify(argv: string[]) {\n  const file = positionalAfterOptions(argv.slice(1), new Set([\"--pubkey\"]));\n  if (!file) throw new Error(`usage: ${invokedCommand(\"receipts\")} verify <bundle.json> [--pubkey <file>]`);\n  let bundle: ReceiptBundle;\n  try { bundle = JSON.parse(await readFile(file, \"utf8\")) as ReceiptBundle; } catch (e) { return fail(`invalid bundle JSON: ${(e as Error).message}`); }","sourceCodeStart":17417,"sourceCodeEnd":17453,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L17417-L17453","documentation":"A --pubkey JSON file must contain either public_key (an object) or public_keys (an array). If public_keys is not an array and public_key is falsy, no key candidates exist and the CLI throws \"--pubkey JSON must contain public_key or public_keys\".","triggerScenarios":"Passing the wrong JSON file to --pubkey — e.g. the receipt bundle itself, a credentials/config file, or a key document using different field names (e.g. \"keys\" or \"jwk\").","commonSituations":"Reusing the bundle path for both arguments; a key file produced by another tool whose schema names fields differently; typos in field names when hand-writing the file.","solutions":["Pass a document that has public_key or public_keys at the top level","Check field names exactly: public_key (object) or public_keys (array of key objects)","Do not point --pubkey at the bundle; it needs the separately published key material"],"exampleFix":"# before\ncaveman receipts verify bundle.json --pubkey bundle.json\n\n# after\ncaveman receipts verify bundle.json --pubkey published-keyring.json\n# published-keyring.json: { \"public_keys\": [ { \"key_id\": \"k1\", \"alg\": \"Ed25519\", \"key\": \"AAA...\" } ] }","handlingStrategy":"validation","validationCode":"const parsed = JSON.parse(await readFile(pubkeyFile, \"utf8\"));\nif (!Array.isArray(parsed.public_keys) && !parsed.public_key) {\n  throw new Error(\"--pubkey document has neither public_key nor public_keys\");\n}","typeGuard":"function isPubkeyDocument(v: unknown): v is { public_key?: unknown; public_keys?: unknown[] } {\n  return !!v && typeof v === \"object\" &&\n    (Array.isArray((v as any).public_keys) || !!(v as any).public_key);\n}","tryCatchPattern":"try { execSync(`caveman receipts verify ${bundle} --pubkey ${pubkey}`); }\ncatch (e) {\n  if (/must contain public_key or public_keys/.test(String((e as Error).message))) fail(\"wrong file passed to --pubkey\");\n  throw e;\n}","preventionTips":["Use distinct, descriptive filenames for bundles vs key documents","Validate the --pubkey document shape in pipeline preflight","Keep key documents in one canonical schema ({public_keys: [...]})"],"tags":["receipts","pubkey","json","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}