feat: runtime response-schema validation in dev/test#37
Merged
Conversation
Codex flagged that `check:openapi` only catches schema↔spec drift —
a formatter change that forgets to update schemas still slips past.
This closes that gap with a dev/test-mode middleware that wraps
`res.json()` and parses the outgoing body against the route's
declared response schema. Prod is a pass-through no-op.
- src/middleware/validate-response.ts — the wrapper. Throws loudly
in dev/test when a 2xx body doesn't match its schema; no-op when
NODE_ENV=production.
- src/routes/response-schema-map.ts — the route→schema map. Keyed
by Express's router-relative `${method} ${route.path}` so the
lookup works for parameterized paths like `/:id`.
- Wired into both createMemoryRouter and createAgentRouter via
`router.use(validateResponse(...))` before any handler.
The validator caught two real formatter↔schema drift issues on its
first run, both fixed here:
- `Date` leaking into search memory `created_at`. Express serializes
Date → ISO string, so the wire shape is string, but `res.json()`
sees the raw Date before serialization. Added an `IsoDateString`
preprocess that accepts both and validates as string.
- `NaN` leaking into search similarity/score/importance.
`JSON.stringify(NaN)` writes `null` on the wire, so the wire shape
is `number | null`, but `res.json()` sees NaN. Added a `NumberOrNaN`
preprocess that converts NaN → null and validates as nullable number.
Schemas now report the accurate wire types (string, nullable number)
and the OpenAPI spec regenerates with zero further drift. All 1037
core tests pass with the middleware active.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the gap Codex flagged: `check:openapi` only catches schema↔spec drift, not formatter↔schema drift. A formatter change that forgets to update the schema would still slip past the gate. This PR adds a dev/test-mode middleware that wraps `res.json()` and parses the outgoing body against the route's declared response schema — prod is a pass-through no-op, so zero per-request cost in production.
What's in the PR
Real drift caught on first run (both fixed here)
Schemas now report accurate wire types (string for dates, nullable number for NaN-possible floats). OpenAPI spec regenerates with zero further diff.
Test plan