{"record":{"id":"432bf1d92e237ae3","repo":"Hmbown/CodeWhale","slug":"key-is-not-pinned-and-active-use-public-key-only-for","errorCode":null,"errorMessage":"key is not pinned and active; use --public-key only for explicit offline verification","messagePattern":"key is not pinned and active; use --public-key only for explicit offline verification","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":556,"sourceCode":"    if (!Number.isSafeInteger(factsVersion) || factsVersion <= 0) throw new Error(\"--facts-version (or source.facts_version) must be a positive integer\");\n    const keyId = String(flags[\"key-id\"] ?? \"\");\n    const privateKey = loadPrivateKeyFromEnv();\n    const publishedAt = String(flags[\"published-at\"] ?? nowIso());\n    const payload = buildPayload(source, { channel, factsVersion, publishedAt });\n    const envelope = buildEnvelope({ privateKey, keyId, payload });\n    const text = `${JSON.stringify(envelope, null, 2)}\\n`;\n    if (flags.out) {\n      writeFileSync(resolve(String(flags.out)), text);\n      console.error(`wrote ${flags.out} (channel=${channel} facts_version=${factsVersion} key_id=${keyId} sha256=${envelope.sha256})`);\n    } else process.stdout.write(text);\n    return 0;\n  }\n  if (cmd === \"verify\") {\n    const envelope = readJson(resolve(String(positional[1] ?? \"\")));\n    let pub = flags[\"public-key\"];\n    if (!pub) {\n      const trusted = loadTrustedKeysFromRepo().get(envelope.key_id);\n      if (!trusted || trusted.status !== \"active\") throw new Error(\"key is not pinned and active; use --public-key only for explicit offline verification\");\n      pub = trusted.publicKey;\n    }\n    const result = verifyEnvelope(envelope, String(pub));\n    console.log(JSON.stringify({ ok: result.ok, errors: result.errors, channel: envelope.channel, facts_version: envelope.facts_version, key_id: envelope.key_id, sha256: result.sha256 ?? null }, null, 2));\n    return result.ok ? 0 : 1;\n  }\n  if (cmd === \"emit-sql\") {\n    const envelope = readJson(resolve(String(positional[1] ?? \"\")));\n    let pub = flags[\"public-key\"];\n    if (!pub) pub = activePublishingKey(envelope, [...loadTrustedKeysFromRepo().values()]).key.publicKey;\n    process.stdout.write(emitSql(envelope, { publishedBy: String(flags[\"published-by\"] ?? \"\"), publicKeyB64: pub ? String(pub) : undefined, notes: String(flags.notes ?? \"\") }));\n    return 0;\n  }\n  if (cmd === \"publish\") {\n    const envelope = readJson(resolve(String(positional[1] ?? \"\")));\n    if (flags[\"public-key\"] !== undefined) throw new Error(\"--public-key is only for offline verify/emit-sql; publication requires the active pinned table\");\n    const { key, check } = activePublishingKey(envelope, [...loadTrustedKeysFromRepo().values()]);\n    const pub = key.publicKey;","sourceCodeStart":538,"sourceCodeEnd":574,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L538-L574","documentation":"The verify subcommand normally loads the trusted key for envelope.key_id from the repo-pinned trust table (loadTrustedKeysFromRepo) and requires its status to be \"active\". If the key_id is unknown, unpinned, or not active (e.g. revoked), verification is refused with this message; --public-key is the explicit opt-in for offline verification against an ad-hoc key.","triggerScenarios":"Verifying an envelope signed with a key never added to the repo trust table; a key whose status in the pinned table is \"revoked\" or \"pending\"; a typo'd or rotated key_id in the envelope; verifying before the key-rotation SQL was applied to the repo.","commonSituations":"Rotating publishing keys and forgetting to commit the new key pin; verifying an old envelope after its key was revoked; receiving an envelope from another fork whose pinned key set differs.","solutions":["If you explicitly trust the key, pass --public-key <pem|base64> for offline verification","Add/activate the key in the repo's pinned trusted-keys table and retry","Check envelope.key_id for typos against the pinned table entries","Re-sign with the currently active publishing key"],"exampleFix":"// before\nnode facts-publish.mjs verify envelope.json\n// after\nnode facts-publish.mjs verify envelope.json --public-key ~/.secrets/cwf-prod.public.pem","handlingStrategy":"validation","validationCode":"const trusted = loadTrustedKeysFromRepo().get(envelope.key_id);\nif (!(trusted && trusted.status === 'active') && !flags['public-key']) {\n  throw new Error(`key ${envelope.key_id} is not pinned and active; pass --public-key for offline verify`);\n}","typeGuard":null,"tryCatchPattern":"const exit = await run(['verify', envelopePath]).catch((e) => {\n  if (e.message.includes('not pinned and active')) {\n    // fall back to explicit offline verification\n    return run(['verify', envelopePath, '--public-key', pubPem]);\n  }\n  throw e;\n});","preventionTips":["Pin and activate every publishing key in the repo trust table before signing envelopes","Complete key rotations (new key active) before distributing envelopes","Check envelope.key_id against the pinned table when verification fails"],"tags":["signing","trust","verification"],"backgroundTag":"authentication-required","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"}