Hmbown/CodeWhale · error · Error

primary signing key is not pinned and active; refusing…

Error message

primary signing key is not pinned and active; refusing publication

What it means

activePublishingKey looks up the envelope's key_id among the repo-pinned TRUSTED_KEYS and requires that key to exist with status "active" before verifying the signature. If no pinned active key matches the envelope's key_id, publication is refused to prevent signing facts with an unpinned, revoked, or retired key.

Solutions

  1. Add the envelope's key_id and its base64 public key to TRUSTED_KEYS in web/lib/cloud-facts/keys.ts with status "active" (commit and land that change first)
  2. If the key was intentionally rotated, regenerate the envelope with the currently active key instead of resurrecting the revoked one
  3. Confirm the keys argument passed to activePublishingKey is the map built by loadTrustedKeysFromRepo, not an empty or stale list

Example fix

// before (keys.ts)
{ keyId: "k1", publicKey: "abc", status: "revoked" }
// after (new key pinned)
{ keyId: "k2", publicKey: "def", status: "active" }
Defensive patterns

Strategy: validation

Validate before calling

import { loadTrustedKeysFromRepo } from "./facts-publish.mjs";
const keys = await loadTrustedKeysFromRepo();
if (!keys.has(envelope.key_id)) throw new Error(`key ${envelope.key_id} is not pinned in keys.ts — pin it before signing/publishing`);
if (keys.get(envelope.key_id).status !== "active") throw new Error(`key ${envelope.key_id} is ${keys.get(envelope.key_id).status}, not active`);

Try / catch

try {
  await activePublishingKey(envelope, keys);
} catch (err) {
  if (err.message.includes("not pinned and active")) {
    console.error(`Key ${envelope.key_id} is missing from TRUSTED_KEYS or not active — pin it or re-sign with the active key`);
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling activePublishingKey with an envelope whose key_id is absent from the keys list, matches a key whose status is not "active" (e.g. "revoked"/"retired"), or when the keys argument itself is empty or unvalidated.

Common situations: The signer generated a new Ed25519 key but never added it to lib/cloud-facts/keys.ts; the key was rotated and the old one marked revoked while the local build still signs with it; the wrong keys file/map was passed in.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/0e8ca0eb8c0120cf. Report an issue: GitHub.

Appendix: source

Thrown at web/scripts/facts-publish.mjs:405

  const table = tables[0];
  const body = table[1].replace(/^\s*\/\/.*$/gm, "");
  const keys = [];
  const remainder = body.replace(/\{\s*keyId:\s*"([^"]+)",\s*publicKey:\s*"([^"]+)",\s*status:\s*"([^"]+)"\s*,?\s*\}/g, (_, keyId, publicKey, status) => {
    keys.push({ keyId, publicKey, status });
    return "";
  });
  if (remainder.replace(/[\s,]/g, "")) throw new Error("unparsed TypeScript TRUSTED_KEYS entry");
  return validateTrustedKeys(keys);
}

function loadTrustedKeysFromRepo() {
  const keys = parseTsKeys(readBoundedFile(resolve(WEB_ROOT, "lib/cloud-facts/keys.ts"), 64 * 1024).toString("utf8"));
  return new Map(keys.map((key) => [key.keyId, key]));
}

export function activePublishingKey(envelope, keys, now = Date.now()) {
  const key = validateTrustedKeys(keys).find((key) => key.keyId === envelope.key_id && key.status === "active");
  if (!key) throw new Error("primary signing key is not pinned and active; refusing publication");
  const check = verifyEnvelope(envelope, key.publicKey);
  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join("; ")}`);
  if (!Number.isFinite(now) || utcTime(check.payload.published_at) > now + 300_000 ||
      (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, "''")}'`;

View on GitHub (pinned to 433685b202)