{"record":{"id":"90337d2aa9be018d","repo":"JuliusBrussee/caveman","slug":"usage-invokedcommand-receipts-verify-bundl","errorCode":null,"errorMessage":"usage: ${invokedCommand(\"receipts\")} verify <bundle.json> [--pubkey <file>]","messagePattern":"usage: (.+?) verify <bundle\\.json> \\[--pubkey <file>\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":17451,"sourceCode":"  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}`); }\n  if (!Array.isArray(bundle.receipts)) return fail(\"bundle receipts must be an array\");\n\n  try {\n    const embedded = embeddedReceiptKeys(bundle);\n    const pubkeyFile = flagFrom(argv, \"--pubkey\", \"\");\n    const pinned = pubkeyFile ? await pinnedReceiptKeys(pubkeyFile, embedded.current) : null;\n    const keys = pinned?.keys ?? embedded.keys;\n    for (const receipt of bundle.receipts) {\n      const embeddedKey = embedded.keys.get(receipt.signature?.key_id);\n      if (!embeddedKey) return fail(`seq ${receipt.seq}: no embedded public key for key_id ${String(receipt.signature?.key_id)}`);\n      if (pinned) {\n        const trusted = keys.get(receipt.signature?.key_id);\n        if (!trusted) return fail(`seq ${receipt.seq}: key_id ${String(receipt.signature?.key_id)} is not present in trusted --pubkey material`);\n        if (!trusted.raw.equals(embeddedKey.raw)) return fail(`seq ${receipt.seq}: embedded key ${receipt.signature.key_id} does not match trusted --pubkey material`);\n      }\n    }","sourceCodeStart":17433,"sourceCodeEnd":17469,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L17433-L17469","documentation":"receiptsVerify needs a positional bundle path; positionalAfterOptions scans argv after the subcommand, skipping only --pubkey and its value. If no positional remains, the CLI prints the usage line and exits non-zero.","triggerScenarios":"Running `caveman receipts verify` with no file, or `caveman receipts verify --pubkey keys.json` where the bundle path was forgotten (or was consumed as the --pubkey value).","commonSituations":"Shell scripts that pass empty variables (\"$BUNDLE\" unset expands to nothing); flag-order mistakes; copy-pasting a command minus the path.","solutions":["Add the bundle path: caveman receipts verify <bundle.json> [--pubkey <file>]","In scripts, guard with [ -n \"$BUNDLE\" ] or set -u so unset variables fail loudly","Put the bundle path before the flags to avoid it being swallowed as a flag value"],"exampleFix":"# before\ncaveman receipts verify --pubkey keys.json\n\n# after\ncaveman receipts verify bundle.json --pubkey keys.json","handlingStrategy":"validation","validationCode":"const args = [\"receipts\", \"verify\", bundlePath];\nif (pubkeyPath) args.push(\"--pubkey\", pubkeyPath);\nif (!bundlePath || !existsSync(bundlePath)) {\n  throw new Error(`bundle path missing or not found: ${bundlePath}`);\n}","typeGuard":"function hasBundlePositional(argv: string[]): boolean {\n  const rest = argv.slice(1);\n  for (let i = 0; i < rest.length; i++) {\n    if (rest[i] === \"--pubkey\") { i++; continue; }\n    return true;\n  }\n  return false;\n}","tryCatchPattern":"try { execFileSync(\"caveman\", args); }\ncatch (e) {\n  if (/^usage: caveman receipts verify/.test(String((e as Error).message))) fail(\"missing bundle path argument\");\n  throw e;\n}","preventionTips":["Quote and check script variables: [ -n \"$BUNDLE\" ] || exit 1, and use set -u","Put the bundle path before flags in the command line","Wrap CLI calls in scripts that validate arguments first"],"tags":["cli","usage","receipts","arguments"],"backgroundTag":"missing-cli-argument","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"}