{"record":{"id":"8d0f5081c1b9e51b","repo":"ruvnet/ruflo","slug":"unknown-game-key-known-object-keys-games","errorCode":null,"errorMessage":"unknown game \"${key}\". Known: ${Object.keys(GAMES).join(', ')}","messagePattern":"unknown game \"(.+?)\"\\. Known: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/ruflo-arena/src/domain/games.ts","lineNumber":59,"sourceCode":"  {\n    '0|0': [1, -1],\n    '1|1': [1, -1],\n    '0|1': [-1, 1],\n    '1|0': [-1, 1],\n  },\n  true,\n);\n\nexport const GAMES: Record<string, GameSpec> = {\n  'prisoners-dilemma': prisonersDilemma,\n  pd: prisonersDilemma,\n  'match-or-not': matchOrNot,\n  mon: matchOrNot,\n};\n\nexport function getGame(key: string): GameSpec {\n  const g = GAMES[key];\n  if (!g) throw new Error(`unknown game \"${key}\". Known: ${Object.keys(GAMES).join(', ')}`);\n  return g;\n}\n","sourceCodeStart":41,"sourceCodeEnd":62,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/plugins/ruflo-arena/src/domain/games.ts#L41-L62","documentation":"Thrown by getGame() in the ruflo-arena domain when the supplied key is absent from the GAMES registry. The registry (plugins/ruflo-arena/src/domain/games.ts:50) is a fixed Record<string, GameSpec> populated at module load; lookups are exact string matches against its keys. The message lists the known keys at throw time so the caller can see the valid set.","triggerScenarios":"Calling getGame(key) with any string that is not one of 'prisoners-dilemma', 'pd', 'match-or-not', or 'mon'. Examples: getGame('prisoner-dilemma') (typo, missing 's'), getGame('rock-paper-scissors') (unregistered game), getGame('PD') (case-sensitive miss), getGame('') (empty).","commonSituations":"CLI flag or config value with a typo or wrong casing; reusing the canonical name when only the alias is registered (or vice versa); adding a new game spec but forgetting to register it in the GAMES object; user input from an HTTP/form field passed straight into getGame without an allowlist check.","solutions":["Use one of the exact keys printed in the error message: 'prisoners-dilemma', 'pd', 'match-or-not', or 'mon'.","If you need a new game, add it to the GAMES Record in games.ts alongside its alias, then call getGame with the new key.","Normalize inbound input before lookup: trim whitespace, lowercase, and map friendly names to canonical keys.","Validate the key against Object.keys(GAMES) (or a Zod enum) at the API boundary so the error surfaces as a 400 rather than an uncaught throw."],"exampleFix":"// before\nconst game = getGame(req.body.gameKey); // throws on typo\n\n// after\nimport { GAMES, getGame } from './games';\nconst key = String(req.body.gameKey ?? '').trim().toLowerCase();\nif (!(key in GAMES)) {\n  return res.status(400).json({ error: `unknown game; valid: ${Object.keys(GAMES).join(', ')}` });\n}\nconst game = getGame(key);","handlingStrategy":"validation","validationCode":"import { GAMES } from './games';\nconst KNOWN_GAMES = Object.keys(GAMES);\nfunction resolveGame(key: unknown): string | null {\n  if (typeof key !== 'string') return null;\n  const k = key.trim().toLowerCase();\n  return KNOWN_GAMES.includes(k) ? k : null;\n}\n// before getGame:\nconst k = resolveGame(input);\nif (!k) return res.status(400).json({ error: `valid games: ${KNOWN_GAMES.join(', ')}` });","typeGuard":"import { GAMES } from './games';\nfunction isKnownGame(key: string): key is keyof typeof GAMES {\n  return key in GAMES;\n}","tryCatchPattern":"try { const game = getGame(key); } catch (e) { if (e instanceof Error && e.message.startsWith('unknown game')) return res.status(400).json({ error: e.message }); throw e; }","preventionTips":["Validate user/config game keys against Object.keys(GAMES) at the API boundary.","Normalize keys (trim + lowercase) before lookup.","Register both canonical name and alias whenever you add a game."],"tags":["arena","game-registry","lookup","validation"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}