Hmbown/CodeWhale · error · Error
public key required to emit the facts_key row
Error message
public key required to emit the facts_key row
What it means
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.
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
Example fix
// before
emitSql(envelope, { publishedBy: "founder" })
// after
emitSql(envelope, { publishedBy: "founder", publicKeyB64: key.publicKey }) Defensive patterns
Strategy: validation
Validate before calling
function emitSqlGuarded(envelope, opts) {
if (!opts?.publicKeyB64) throw new Error("emitSql requires publicKeyB64 (the pinned active key's base64 public key)");
return emitSql(envelope, opts);
} Type guard
function hasPublicKey(opts) {
return typeof opts === "object" && opts !== null && typeof opts.publicKeyB64 === "string" && opts.publicKeyB64.length > 0;
} Try / catch
try {
const sql = emitSql(envelope, opts);
} catch (err) {
if (err.message === "public key required to emit the facts_key row") {
console.error("Pass the pinned active key's publicKey (e.g. from activePublishingKey(...).key.publicKey)");
process.exit(1);
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- bad_args
- cannot parse exactly one TypeScript TRUSTED_KEYS table
- Checkpoint continuation requires a source agent
- Cloudflare SQL request failed
- Invalid Engine batch.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/fc826bcda3a019aa.
Report an issue: GitHub.
Appendix: source
Thrown at web/scripts/facts-publish.mjs:427
(check.payload.not_after != null && utcTime(check.payload.not_after) <= now)) throw new Error("publication timestamp is future or expired");
return { key, check };
}
function refuseUnderCi() {
for (const marker of CI_MARKERS) {
if (process.env[marker] && !/^(0|false|no|off)$/i.test(process.env[marker])) {
throw new Error(`refusing to run with a secret under CI (${marker} is set); publish from the founder's machine`);
}
}
}
function sqlLiteral(value) {
if (value === null || value === undefined) return "null";
return `'${String(value).replace(/'/g, "''")}'`;
}
export function emitSql(envelope, { publishedBy = "", publicKeyB64, notes = "" }) {
if (!publicKeyB64) throw new Error("public key required to emit the facts_key row");
const check = verifyEnvelope(envelope, publicKeyB64);
if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join("; ")}`);
const payloadJson = Buffer.from(envelope.payload_b64, "base64").toString("utf8");
return [
"begin;",
`insert into public.facts_key (key_id, scope, algorithm, public_key, status)`,
` values (${sqlLiteral(envelope.key_id)}, 'global', 'ed25519', ${sqlLiteral(publicKeyB64)}, 'active')`,
` on conflict (key_id) do nothing;`,
`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)`,
` select c.id, ${envelope.facts_version}, ${envelope.schema_version}, ${envelope.envelope}, ${sqlLiteral(envelope.applies_to)}, ${sqlLiteral(envelope.key_id)},`,
` ${sqlLiteral(envelope.payload_b64)}, ${sqlLiteral(envelope.sig_b64)}, ${sqlLiteral(JSON.stringify(envelope.sigs ?? []))}::jsonb,`,
` ${sqlLiteral(payloadJson)}::jsonb, ${sqlLiteral(envelope.published_at)}::timestamptz, ${sqlLiteral(check.payload.not_after ?? null)}::timestamptz,`,
` ${sqlLiteral(publishedBy)}, ${sqlLiteral(notes)}`,
` from public.facts_channel c where c.scope = 'global' and c.slug = ${sqlLiteral(envelope.channel)};`,
"commit;",
"",
].join("\n");
}View on GitHub (pinned to 433685b202)