{"record":{"id":"fc826bcda3a019aa","repo":"Hmbown/CodeWhale","slug":"public-key-required-to-emit-the-facts-key-row","errorCode":null,"errorMessage":"public key required to emit the facts_key row","messagePattern":"public key required to emit the facts_key row","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":427,"sourceCode":"      (check.payload.not_after != null && utcTime(check.payload.not_after) <= now)) throw new Error(\"publication timestamp is future or expired\");\n  return { key, check };\n}\n\nfunction refuseUnderCi() {\n  for (const marker of CI_MARKERS) {\n    if (process.env[marker] && !/^(0|false|no|off)$/i.test(process.env[marker])) {\n      throw new Error(`refusing to run with a secret under CI (${marker} is set); publish from the founder's machine`);\n    }\n  }\n}\n\nfunction sqlLiteral(value) {\n  if (value === null || value === undefined) return \"null\";\n  return `'${String(value).replace(/'/g, \"''\")}'`;\n}\n\nexport function emitSql(envelope, { publishedBy = \"\", publicKeyB64, notes = \"\" }) {\n  if (!publicKeyB64) throw new Error(\"public key required to emit the facts_key row\");\n  const check = verifyEnvelope(envelope, publicKeyB64);\n  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join(\"; \")}`);\n  const payloadJson = Buffer.from(envelope.payload_b64, \"base64\").toString(\"utf8\");\n  return [\n    \"begin;\",\n    `insert into public.facts_key (key_id, scope, algorithm, public_key, status)`,\n    `  values (${sqlLiteral(envelope.key_id)}, 'global', 'ed25519', ${sqlLiteral(publicKeyB64)}, 'active')`,\n    `  on conflict (key_id) do nothing;`,\n    `insert into public.facts_release (channel_id, facts_version, schema_version, envelope_version, applies_to, key_id, payload_b64, sig_b64, sigs, payload, published_at, not_after, published_by, notes)`,\n    `  select c.id, ${envelope.facts_version}, ${envelope.schema_version}, ${envelope.envelope}, ${sqlLiteral(envelope.applies_to)}, ${sqlLiteral(envelope.key_id)},`,\n    `         ${sqlLiteral(envelope.payload_b64)}, ${sqlLiteral(envelope.sig_b64)}, ${sqlLiteral(JSON.stringify(envelope.sigs ?? []))}::jsonb,`,\n    `         ${sqlLiteral(payloadJson)}::jsonb, ${sqlLiteral(envelope.published_at)}::timestamptz, ${sqlLiteral(check.payload.not_after ?? null)}::timestamptz,`,\n    `         ${sqlLiteral(publishedBy)}, ${sqlLiteral(notes)}`,\n    `    from public.facts_channel c where c.scope = 'global' and c.slug = ${sqlLiteral(envelope.channel)};`,\n    \"commit;\",\n    \"\",\n  ].join(\"\\n\");\n}","sourceCodeStart":409,"sourceCodeEnd":445,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L409-L445","documentation":"emitSql generates the SQL statements that upsert the facts_key row, and it re-verifies the envelope against the caller-provided base64 public key. It throws when publicKeyB64 is empty/missing because the emitted facts_key row cannot be written without the public key, and the envelope could not be independently verified.","triggerScenarios":"Calling emitSql(envelope, opts) with opts.publicKeyB64 undefined, null, or an empty string — e.g. the options object was omitted, or the key was destructured from a source that lacked it.","commonSituations":"A script invoked emitSql with only publishedBy/notes, forgetting the public key; the active key's publicKey field was undefined because key parsing failed upstream; refactored call site dropped the option.","solutions":["Pass the pinned active key's base64 public key: emitSql(envelope, { publicKeyB64: key.publicKey, ... })","If you have the envelope but not the key, resolve it via loadTrustedKeysFromRepo/activePublishingKey first, then hand key.publicKey to emitSql","Confirm the keys.ts parse succeeded upstream so publicKey is not undefined"],"exampleFix":"// before\nemitSql(envelope, { publishedBy: \"founder\" })\n// after\nemitSql(envelope, { publishedBy: \"founder\", publicKeyB64: key.publicKey })","handlingStrategy":"validation","validationCode":"function emitSqlGuarded(envelope, opts) {\n  if (!opts?.publicKeyB64) throw new Error(\"emitSql requires publicKeyB64 (the pinned active key's base64 public key)\");\n  return emitSql(envelope, opts);\n}","typeGuard":"function hasPublicKey(opts) {\n  return typeof opts === \"object\" && opts !== null && typeof opts.publicKeyB64 === \"string\" && opts.publicKeyB64.length > 0;\n}","tryCatchPattern":"try {\n  const sql = emitSql(envelope, opts);\n} catch (err) {\n  if (err.message === \"public key required to emit the facts_key row\") {\n    console.error(\"Pass the pinned active key's publicKey (e.g. from activePublishingKey(...).key.publicKey)\");\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Always derive emitSql options from the result of activePublishingKey so publicKeyB64 is the matching key","Validate options object shape before calling emitSql in wrapper code","Keep key parsing (parseTsKeys) upstream so publicKey is never undefined"],"tags":["publishing-gate","sql","missing-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}