{"record":{"id":"8d27f7823c49fae5","repo":"Hmbown/CodeWhale","slug":"key-id-must-match-cwf-a-z0-9-1-32","errorCode":null,"errorMessage":"--key-id must match cwf-[a-z0-9-]{1,32}","messagePattern":"--key-id must match cwf-\\[a-z0-9-\\](.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":511,"sourceCode":"\nfunction readJson(path) {\n  return JSON.parse(new TextDecoder(\"utf-8\", { fatal: true }).decode(readBoundedFile(path)));\n}\n\nfunction nowIso() {\n  return new Date().toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n}\n\nasync function main(argv) {\n  const { positional, flags } = parseArgs(argv);\n  const cmd = positional[0];\n  if (!cmd || flags.help) {\n    console.log(readFileSync(fileURLToPath(import.meta.url), \"utf8\").split(\"\\n\").slice(1, 26).join(\"\\n\"));\n    return 0;\n  }\n  if (cmd === \"keygen\") {\n    const keyId = String(flags[\"key-id\"] ?? \"\");\n    if (!KEY_ID_RE.test(keyId)) throw new Error(\"--key-id must match cwf-[a-z0-9-]{1,32}\");\n    const out = flags.out ? resolve(String(flags.out)) : null;\n    if (!out) throw new Error(\"--out <path> is required (write the private key OUTSIDE any repository)\");\n    refuseUnderCi();\n    const { privateKey, publicKey } = generateKeyPairSync(\"ed25519\");\n    mkdirSync(dirname(out), { recursive: true, mode: 0o700 });\n    const fd = openSync(out, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);\n    try { writeFileSync(fd, privateKey.export({ type: \"pkcs8\", format: \"pem\" })); }\n    finally { closeSync(fd); }\n    const raw = rawPublicKeyFromKeyObject(publicKey);\n    console.log(JSON.stringify({\n      key_id: keyId,\n      algorithm: \"ed25519\",\n      public_key_b64: raw.toString(\"base64\"),\n      public_key_bytes: [...raw],\n      private_key_file: out,\n      note: \"Private key written with mode 0600. Move it into custody (password manager); never commit it.\",\n    }, null, 2));\n    return 0;","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L493-L529","documentation":"The keygen subcommand validates the --key-id flag against KEY_ID_RE (cwf-[a-z0-9-]{1,32}) and throws this if it does not match. Key IDs are used as database identifiers in the pinned trust table, so the format is enforced up front.","triggerScenarios":"Running `facts-publish.mjs keygen --key-id <bad>` where the id is missing, uppercase, too long (>32 chars after the prefix), missing the cwf- prefix, or contains characters outside [a-z0-9-] (dots, underscores, spaces).","commonSituations":"Typing a human-readable id like \"Codewhale_Prod_2026\" or \"stable.v2\"; omitting --key-id entirely so it becomes an empty string; copying a key id with surrounding whitespace or quotes.","solutions":["Pass an id matching cwf- followed by 1-32 lowercase letters, digits, or hyphens, e.g. --key-id cwf-prod-2026-01","Lowercase and hyphenate the intended name before generating the key","Re-run with quotes removed and no whitespace in the flag value"],"exampleFix":"// before\nnode facts-publish.mjs keygen --key-id \"Codewhale_Prod\"\n// after\nnode facts-publish.mjs keygen --key-id cwf-codewhale-prod","handlingStrategy":"validation","validationCode":"const KEY_ID_RE = /^cwf-[a-z0-9-]{1,32}$/;\nif (!KEY_ID_RE.test(keyId)) throw new Error(`key id must match cwf-[a-z0-9-]{1,32}, got: ${keyId}`);","typeGuard":null,"tryCatchPattern":"try {\n  await run(['keygen', '--key-id', keyId, '--out', outPath]);\n} catch (e) {\n  if (e.message.includes('--key-id must match')) console.error('Use lowercase letters, digits, hyphens only, 1-32 chars after cwf-');\n  throw e;\n}","preventionTips":["Normalize names to lowercase kebab-case before keygen","Never include dots, underscores, or whitespace in key ids","Keep key ids <= 32 chars after the cwf- prefix"],"tags":["cli","validation","keygen"],"backgroundTag":"invalid-cli-argument","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"}