{"record":{"id":"53a2831028dbebb2","repo":"Hmbown/CodeWhale","slug":"rust-public-key-must-contain-32-literal-bytes","errorCode":null,"errorMessage":"Rust public key must contain 32 literal bytes","messagePattern":"Rust public key must contain 32 literal bytes","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/check-cloud-facts.mjs","lineNumber":20,"sourceCode":"/** Local source, release, pinned-key parity and public-fixture gate. */\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { validateSource, verifyEnvelope, parseTsKeys, validateTrustedKeys, readBoundedFile } from \"./facts-publish.mjs\";\n\nconst WEB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst REPO_ROOT = resolve(WEB_ROOT, \"..\");\nexport { parseTsKeys };\n\nexport function parseRustKeys(text) {\n  const source = text.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\").replace(/^\\s*\\/\\/.*$/gm, \"\");\n  const tables = [...source.matchAll(/^\\s*pub\\s+const\\s+TRUSTED_KEYS\\s*:\\s*&\\s*\\[TrustedKey\\]\\s*=\\s*&\\s*\\[([\\s\\S]*?)\\]\\s*;/gm)];\n  if (tables.length !== 1) throw new Error(\"cannot parse exactly one Rust TRUSTED_KEYS table\");\n  const table = tables[0];\n  const body = table[1].replace(/^\\s*\\/\\/.*$/gm, \"\");\n  const keys = [];\n  const remainder = body.replace(/TrustedKey\\s*\\{\\s*key_id:\\s*\"([^\"]+)\",\\s*public_key:\\s*\\[([^\\]]*)\\],\\s*status:\\s*KeyStatus::(Active|Retired)\\s*,?\\s*\\}/g, (_, keyId, encoded, status) => {\n    const pieces = encoded.split(\",\").map((piece) => piece.trim()).filter(Boolean);\n    if (pieces.length !== 32 || pieces.some((piece) => !/^(?:\\d+|0x[0-9a-fA-F]+)$/.test(piece))) throw new Error(\"Rust public key must contain 32 literal bytes\");\n    const bytes = pieces.map(Number);\n    if (bytes.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) throw new Error(\"Rust public key byte out of range\");\n    keys.push({ keyId, publicKey: Buffer.from(bytes).toString(\"base64\"), status: status.toLowerCase() });\n    return \"\";\n  });\n  if (remainder.replace(/[\\s,]/g, \"\")) throw new Error(\"unparsed Rust TRUSTED_KEYS entry\");\n  return validateTrustedKeys(keys);\n}\n\nfunction text(path) { return readBoundedFile(path).toString(\"utf8\"); }\nfunction json(path) { return JSON.parse(text(path)); }\n\nexport function checkCloudFacts() {\n  const failures = [];\n  const source = json(resolve(REPO_ROOT, \"docs/cloud-facts/stable.json\"));\n  for (const error of validateSource(source)) failures.push(`stable.json: ${error}`);\n  if (source.channel !== \"stable\") failures.push(\"stable.json: channel must be stable\");\n  const latest = json(resolve(WEB_ROOT, \"data/latest-published-release.json\"));","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/check-cloud-facts.mjs#L2-L38","documentation":"Within a parsed TrustedKey entry, the `public_key` field must contain exactly 32 numeric literal bytes (decimal or 0x hex), matching an Ed25519 public key size. The parser throws when the encoded array does not yield exactly 32 valid byte literals, e.g. when the Rust literal was truncated, comma formatting confused the splitter, or comments/expressions remain inside the array.","triggerScenarios":"A TrustedKey whose public_key array has fewer/more than 32 elements, contains expressions like `0u8` suffixes or computed values, or elements that are neither decimal nor 0x-hex literals after comment stripping.","commonSituations":"Key was hand-edited and a byte dropped; key stored in a format the parser does not accept (e.g. string literal or `[u8; 32]` const reference); a `u8` suffix on bytes breaks the number regex.","solutions":["Count the bytes in the offending public_key array; it must be exactly 32 numeric literals.","Remove type suffixes (e.g. `12u8` → `12`) and expressions; use plain decimal or 0x hex.","Regenerate the key literal from the canonical base64 key rather than hand-editing.","Check for comments inside the array that survive stripping and split wrongly."],"exampleFix":"// before (Rust)\npublic_key: [0x8au8, 0x1fu8, /* 30 more */],\n// after\npublic_key: [0x8a, 0x1f, /* exactly 32 plain literals */],","handlingStrategy":"validation","validationCode":"const m = entry.match(/public_key:\\s*\\[([^\\]]*)\\]/); const parts = m[1].split(\",\").map(s => s.trim()).filter(Boolean); if (parts.length !== 32 || parts.some(p => !/^(?:\\d+|0x[0-9a-fA-F]+)$/.test(p))) throw new Error(\"bad key literal\");","typeGuard":null,"tryCatchPattern":"try { parseRustKeys(src); } catch (e) { if (e.message.includes(\"32 literal bytes\")) inspectKeyArrays(src); throw e; }","preventionTips":["Generate Rust key literals with a script from base64, never by hand.","Avoid u8 suffixes and expressions inside public_key arrays.","Keep comments out of the array body.","Re-run the checker after every key edit."],"tags":["parsing","rust","validation","crypto-keys"],"backgroundTag":"schema-validation-failed","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"}