typescript-api-query-param-validation
I've diagnosed the problem and produced a verified solution. Here's the summary.
GET /api/v1/games in the Express server had three defects, all rooted in a trust-the-cast anti-pattern combined with two divergent status vocabularies:
req.query.status as 'SETUP' | 'IN_PROGRESS' | 'ENDED' is compile-time only; any string (e.g. ?status=bogus) passed through and returned 200 with data instead of 400.?status=ENDED still surfaced RUNNING legacy games.specs/api-specs.md documented legacy vocabulary (CREATED/RUNNING/FINISHED) inconsistent with the canonical GameStatus union in @mafia/shared.cdaeab5, already in tree)VALID_GAME_STATUSES list mirroring GameStatus (SETUP | IN_PROGRESS | PAUSED | ENDED | CANCELLED), validated the raw ?status= against it → 400 with valid-values error on unknown.RUNNING→IN_PROGRESS, else ENDED) and applied the filter before pushing rows, so non-matching legacy games are excluded.specs/api-specs.md to the canonical values (+ 400 doc on the status param).games-routes.test.ts → 8/8244/244):3004: ?status=bogus → 400 {"success":false,"error":"Invalid status filter \"bogus\"..."}; ?status=IN_PROGRESS → 200 with only IN_PROGRESS rowsapi.test.ts → 10/10The full self-contained write-up is saved at ~/MAF-GAP-049-status-filter-validation.md.
# Evidence - Problem class: typescript-api-query-param-validation - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-16T17:30:29.607Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "GET /api/v1/games in an Express app accepted any ?status= value: the query param was cast to a TS union without runtime validation, and legacy in-memory games were appended to the response unfiltered. Unknown values silently returned data instead of 400. Fix: validate against the canonical status vocabulary from the shared package (400 with valid-values error on unknown), apply the filter to legacy rows before merging, and align the API spec vocabulary with the code. Tests: 400 on bogus, filter returns only matching rows across both sources.", "environment": "", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "typescript-api-query-param-validation", "provider": "openrouter", "solved_at": "2026-08-16T17:30:29.607Z", "version": ""}I've diagnosed the problem and produced a verified solution. Here's the summary.
GET /api/v1/games in the Express server had three defects, all rooted in a trust-the-cast anti-pattern combined with two divergent status vocabularies:
req.query.status as 'SETUP' | 'IN_PROGRESS' | 'ENDED' is compile-time only; any string (e.g. ?status=bogus) passed through and returned 200 with data instead of 400.?status=ENDED still surfaced RUNNING legacy games.specs/api-specs.md documented legacy vocabulary (CREATED/RUNNING/FINISHED) inconsistent with the canonical GameStatus union in @mafia/shared.cdaeab5, already in tree)VALID_GAME_STATUSES list mirroring GameStatus (SETUP | IN_PROGRESS | PAUSED | ENDED | CANCELLED), validated the raw ?status= against it → 400 with valid-values error on unknown.RUNNING→IN_PROGRESS, else ENDED) and applied the filter before pushing rows, so non-matching legacy games are excluded.specs/api-specs.md to the canonical values (+ 400 doc on the status param).games-routes.test.ts → 8/8244/244):3004: ?status=bogus → 400 {"success":false,"error":"Invalid status filter \"bogus\"..."}; ?status=IN_PROGRESS → 200 with only IN_PROGRESS rowsapi.test.ts → 10/10The full self-contained write-up is saved at ~/MAF-GAP-049-status-filter-validation.md.
# Evidence - Problem class: typescript-api-query-param-validation - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-16T17:30:29.607Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "GET /api/v1/games in an Express app accepted any ?status= value: the query param was cast to a TS union without runtime validation, and legacy in-memory games were appended to the response unfiltered. Unknown values silently returned data instead of 400. Fix: validate against the canonical status vocabulary from the shared package (400 with valid-values error on unknown), apply the filter to legacy rows before merging, and align the API spec vocabulary with the code. Tests: 400 on bogus, filter returns only matching rows across both sources.", "environment": "", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "typescript-api-query-param-validation", "provider": "openrouter", "solved_at": "2026-08-16T17:30:29.607Z", "version": ""}