{"record":{"id":"679d6c85ae14d141","repo":"JuliusBrussee/caveman","slug":"label-key-id-is-required","errorCode":null,"errorMessage":"${label} key_id is required","messagePattern":"(.+?) key_id is required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":17392,"sourceCode":"  const sorted = [...receipts].sort((a, b) => a.seq - b.seq);\n  let prev: Receipt | undefined;\n  for (const r of sorted) {\n    const decoded = keys.get(r.signature?.key_id);\n    if (!decoded) return `seq ${r.seq}: no trusted public key for key_id ${String(r.signature?.key_id)}`;\n    const err = verifyReceipt(r, decoded.key, decoded.info.key_id);\n    if (err) return err;\n    if (prev) {\n      if (r.seq !== prev.seq + 1) return `seq ${r.seq}: not strictly after ${prev.seq}`;\n      if (r.prev_receipt_hash !== prev.receipt_hash) return `seq ${r.seq}: prev_receipt_hash does not link to seq ${prev.seq}`;\n      if (r.day <= prev.day) return `seq ${r.seq}: day ${r.day} does not follow ${prev.day}`;\n    }\n    prev = r;\n  }\n  return null;\n}\n\nfunction decodeReceiptKey(info: ReceiptPublicKey, label: string): DecodedReceiptKey {\n  if (!info || typeof info.key_id !== \"string\" || !info.key_id.trim()) throw new Error(`${label} key_id is required`);\n  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> } {","sourceCodeStart":17374,"sourceCodeEnd":17410,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L17374-L17410","documentation":"decodeReceiptKey validates every keyring entry used to verify signed usage receipts. key_id must be a non-empty (after trim) string because each receipt's signature must be attributable to a named key; a missing, empty, or whitespace-only key_id aborts verification. The label in the message identifies the failing entry, e.g. `keys[2]`.","triggerScenarios":"A receipts keyring JSON with an entry whose key_id is absent, empty, or whitespace; programmatically generated keyrings that skip the id field; hand-merging keyrings from multiple sources.","commonSituations":"Hand-edited keyring files; an upstream publishing pipeline dropping key_id during serialization; schema drift between the issuer's keyring format and the verifier's expectations.","solutions":["Inspect the entry named by the label in the error message","Set key_id to the issuer's published non-empty identifier","Regenerate the keyring from the issuer's canonical source instead of editing it by hand"],"exampleFix":"// before\n{ \"alg\": \"Ed25519\", \"key\": \"MCowBQYDK2VwAyEA...\" }\n// after\n{ \"key_id\": \"caveman-2026-q3\", \"alg\": \"Ed25519\", \"key\": \"MCowBQYDK2VwAyEA...\" }","handlingStrategy":"type-guard","validationCode":"const valid = keyring.every((k) => typeof k?.key_id === 'string' && k.key_id.trim().length > 0);\nif (!valid) throw new Error('keyring has entries without key_id — fetch the canonical keyring');","typeGuard":"const hasKeyId = (k: unknown): k is { key_id: string } =>\n  typeof (k as { key_id?: unknown })?.key_id === 'string' &&\n  (k as { key_id: string }).key_id.trim() !== '';","tryCatchPattern":null,"preventionTips":["Validate the whole keyring shape before starting receipt verification","Source keyrings only from the issuer's published artifact","Use the label in the error to jump straight to the failing entry index"],"tags":["crypto","keyring","validation","receipts"],"backgroundTag":"missing-key-id","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"}