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
- Reformat the offending entry in keys.ts to exactly `{ keyId: "...", publicKey: "...", status: "..." }` with double-quoted literal strings in that field order
- Move any inline comments to their own line (line comments on separate lines are stripped) or remove them from inside the array
- 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
- Always add new keys in the exact field order keyId, publicKey, status with double-quoted string literals
- Put comments on their own lines, never inline inside an entry object
- Lint keys.ts with a small check script so malformed entries are caught before publishing
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
- cannot parse exactly one TypeScript TRUSTED_KEYS table
- Codewhale metadata event was not a terminal receipt
- Duplicate .
- Invalid Engine pet metadata.
- Invalid Engine pet metadata fields.
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)