Hmbown/CodeWhale · error · Error

unparsed TypeScript TRUSTED_KEYS entry

Error message

unparsed TypeScript TRUSTED_KEYS entry

What it means

After parseTsKeys extracts the TRUSTED_KEYS table body, it peels out every entry matching the strict `{ keyId: "...", publicKey: "...", status: "..." }` shape and requires that nothing but whitespace and commas remains. Any leftover text means an entry did not match the strict shape, so the gate refuses rather than publishing with a partially parsed key list.

Solutions

  1. Reformat the offending entry in keys.ts to exactly `{ keyId: "...", publicKey: "...", status: "..." }` with double-quoted literal strings in that field order
  2. Move any inline comments to their own line (line comments on separate lines are stripped) or remove them from inside the array
  3. Run the publish script's parse step locally on the edited keys.ts to confirm the error is gone before publishing

Example fix

// before
{ status: "active", keyId: "k1", publicKey: "abc" } // rotated
// after
{ keyId: "k1", publicKey: "abc", status: "active" }
Defensive patterns

Strategy: validation

Validate before calling

const body = text.match(/TRUSTED_KEYS[^=]*=\s*\[([\s\S]*?)\];/)?.[1] ?? "";
const unparsed = body
  .replace(/\{\s*keyId:\s*"[^"]+",\s*publicKey:\s*"[^"]+",\s*status:\s*"[^"]+"\s*,?\s*\}/g, "")
  .replace(/[\s,]/g, "");
if (unparsed) throw new Error("keys.ts contains entries outside the strict entry shape: " + unparsed.slice(0, 80));

Try / catch

try {
  const keys = parseTsKeys(text);
} catch (err) {
  if (err.message === "unparsed TypeScript TRUSTED_KEYS entry") {
    console.error("An entry in TRUSTED_KEYS does not match { keyId, publicKey, status } with double-quoted literals in that order");
    process.exit(1);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling parseTsKeys when a TRUSTED_KEYS entry uses different field order, single quotes, missing/extra fields, spread syntax, computed values, helper constructors, or trailing inline comments that survive the line-comment strip inside the array body.

Common situations: A contributor added a key entry with fields in a different order or via an object variable; a key was added with a trailing comma-comment on the same line as the closing brace; the status field uses a type or template literal instead of a plain double-quoted string.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

    if (!KEY_ID_RE.test(key.keyId) || seen.has(key.keyId) || !["active", "retired"].includes(key.status) || strictBase64(key.publicKey, 32).length !== 32) throw new Error("invalid or duplicated pinned key");
    seen.add(key.keyId);
  }
  return keys;
}

/** Deliberately narrow syntax: a changed/unparseable table must fail the gate. */
export function parseTsKeys(text) {
  const source = text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
  const tables = [...source.matchAll(/^\s*export\s+const\s+TRUSTED_KEYS\s*:\s*readonly\s+TrustedKey\[\]\s*=\s*\[([\s\S]*?)\]\s*;/gm)];
  if (tables.length !== 1) throw new Error("cannot parse exactly one TypeScript TRUSTED_KEYS table");
  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 };
}

View on GitHub (pinned to 433685b202)