typescript-api-game-detail-winner-exposure
Solution written to typescript-api-game-detail-winner-exposure.md (workspace had no actual project source, so this is the self-contained diagnosis/fix doc).
Root-cause chain (4 distinct defects):
- RC-1 getPlayers() maps SELECT * rows but drops the won column → no per-player result.
- RC-2 getGame() never reads row.winner nor config.winner → no top-level winner.
- RC-3 legacy "done" handler writes winner only into the config blob via json_set, never the games.winner column → summary counts under-count decided games.
- RC-4 legacy-fallback route branch hand-rolls the payload without result fields.
Fixes (each paired with its root cause):
1. Map won, preserving NULL as absent (no fabrication).
2. Expose winner from the games.winner column with config.winner fallback.
3. Write the games.winner column in the done handler for decided outcomes.
4. Legacy-fallback derives winner from GAME_ENDED.data.winner and per-player won from side.
Lint gate (gitrepos tier1): document the full-workspace npx eslint . behavior (vs. diff-scoped guard), the required .eslintignore patterns (**/dist/, .opencode/, **/*.d.ts — note that in the ignore file these should be unescaped *.d.ts; my write used **/*.d.ts which is correct for .eslintignore glob semantics), and the instruction to file remaining lint debt as a separate task.
Verification: unit checks per fix (NULL-absence, column vs config fallback, decided write), curl/jq integration check on a decided game, SELECT COUNT(*) ... WHERE winner IS NOT NULL summary check, npx eslint . exit-0 check, and non-fabrication regression for undecided games.
One caveat worth noting: the workspace ~ contains no project source, so the exact method names/DB calls (getPlayers, getGame, markDone, json_set) are reconstructed from the problem's stated locations rather than verified against real code. If you point me at the actual repo, I'll apply these as concrete edits and run the verification commands.
# Evidence - Problem class: typescript-api-game-detail-winner-exposure - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-25T06:47:37.005Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "MAF-GAP-056: game detail API omitted winner/won though data existed in DB. Root cause chain: (1) getPlayers() mapped SELECT * rows but dropped the won column; (2) getGame() never read row.winner nor config.winner; (3) legacy adapter done handler wrote winner only into config blob (json_set) never the games.winner column, so summary counts undercounted; (4) legacy-fallback route branch hand-rolled payload without result fields. Fix pattern: expose winner from games.winner column with config.winner fallback; map players.won (NULL stays absent \u2014 no fabrication); write games.winner column in done handler for decided outcomes; legacy-fallback derives winner from GAME_ENDED event data.winner + per-player won from side. Also: gitreins judge tier1 runs FULL-WORKSPACE `npx eslint .` (not diff-scoped like guard) \u2014 pre-existing lint errors in untouched files FAIL the judge; fix eslint ignores (**/dist/, .opencode/, **/*.d.ts) and file remaining debt as a separate task.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "typescript-api-game-detail-winner-exposure", "provider": "openrouter", "solved_at": "2026-08-25T06:47:37.005Z", "version": ""}Solution written to typescript-api-game-detail-winner-exposure.md (workspace had no actual project source, so this is the self-contained diagnosis/fix doc).
Root-cause chain (4 distinct defects):
- RC-1 getPlayers() maps SELECT * rows but drops the won column → no per-player result.
- RC-2 getGame() never reads row.winner nor config.winner → no top-level winner.
- RC-3 legacy "done" handler writes winner only into the config blob via json_set, never the games.winner column → summary counts under-count decided games.
- RC-4 legacy-fallback route branch hand-rolls the payload without result fields.
Fixes (each paired with its root cause):
1. Map won, preserving NULL as absent (no fabrication).
2. Expose winner from the games.winner column with config.winner fallback.
3. Write the games.winner column in the done handler for decided outcomes.
4. Legacy-fallback derives winner from GAME_ENDED.data.winner and per-player won from side.
Lint gate (gitrepos tier1): document the full-workspace npx eslint . behavior (vs. diff-scoped guard), the required .eslintignore patterns (**/dist/, .opencode/, **/*.d.ts — note that in the ignore file these should be unescaped *.d.ts; my write used **/*.d.ts which is correct for .eslintignore glob semantics), and the instruction to file remaining lint debt as a separate task.
Verification: unit checks per fix (NULL-absence, column vs config fallback, decided write), curl/jq integration check on a decided game, SELECT COUNT(*) ... WHERE winner IS NOT NULL summary check, npx eslint . exit-0 check, and non-fabrication regression for undecided games.
One caveat worth noting: the workspace ~ contains no project source, so the exact method names/DB calls (getPlayers, getGame, markDone, json_set) are reconstructed from the problem's stated locations rather than verified against real code. If you point me at the actual repo, I'll apply these as concrete edits and run the verification commands.
# Evidence - Problem class: typescript-api-game-detail-winner-exposure - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-25T06:47:37.005Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "MAF-GAP-056: game detail API omitted winner/won though data existed in DB. Root cause chain: (1) getPlayers() mapped SELECT * rows but dropped the won column; (2) getGame() never read row.winner nor config.winner; (3) legacy adapter done handler wrote winner only into config blob (json_set) never the games.winner column, so summary counts undercounted; (4) legacy-fallback route branch hand-rolled payload without result fields. Fix pattern: expose winner from games.winner column with config.winner fallback; map players.won (NULL stays absent \u2014 no fabrication); write games.winner column in done handler for decided outcomes; legacy-fallback derives winner from GAME_ENDED event data.winner + per-player won from side. Also: gitreins judge tier1 runs FULL-WORKSPACE `npx eslint .` (not diff-scoped like guard) \u2014 pre-existing lint errors in untouched files FAIL the judge; fix eslint ignores (**/dist/, .opencode/, **/*.d.ts) and file remaining debt as a separate task.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "typescript-api-game-detail-winner-exposure", "provider": "openrouter", "solved_at": "2026-08-25T06:47:37.005Z", "version": ""}