feat: declare OpenAPI response schemas for every route#35
Merged
Conversation
Every /v1 route now carries a concrete response schema in the spec
instead of the permissive `z.object({}).passthrough()` placeholder.
`npm run check:openapi` will now catch wire-shape drift between the
formatters in memory-response-formatters.ts and the committed spec —
the guardrail I flagged as missing during the snake_case wire flip.
- New src/schemas/responses.ts declares Zod schemas for every emitted
shape (25 top-level + internal sub-schemas): IngestResponse,
SearchResponse (+ nested observability / consensus / lesson_check /
tier_assignments / scope subschemas), MemoryRow / LessonRow /
ClaimVersionRow / MemoryConflictRow for passthrough DB rows,
HealthConfig shared by /health and /config, Consolidation
(scan|execute discriminated union), Decay, Cap, LessonStats,
LessonReport, Reconciliation, ReconcileStatus, ResetSource,
MutationSummary, AuditTrailEntry/AuditTrail/AuditRecent, Trust /
ConflictsList / ResolveConflict / AutoResolveConflicts, and the
shared Success envelope.
- src/schemas/openapi.ts imports those schemas and wires each route's
200 response to the right one. Error envelopes (400/404/410/500)
stay as-is.
- openapi.yaml + openapi.json regenerated (+3788 lines of declared
response shapes).
All 1037 core tests pass; check:openapi produces no diff after regen.
Follow-up worth tracking: the route handlers don't yet parse their
response through these schemas before sending (runtime drift
detection). The spec-level check is enough to catch intentional
formatter changes that forget to update the schemas; runtime parsing
would catch accidental Record<unknown> leaks but adds a per-request
cost. Noted in tech-debt.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Every
/v1route in the OpenAPI spec now carries a concrete response schema instead of the permissivez.object({}).passthrough()placeholder that shipped before.npm run check:openapi(CI step) will now catch wire-shape drift between the formatters and the committed spec — the guardrail missing from the snake_case wire flip in #32.What's in the PR
src/schemas/responses.ts(new, ~400 lines) — Zod schemas mirroring every response shape emitted bysrc/routes/memory-response-formatters.tsand the inline route literals:Ingest,Search,Expand,List,GetMemory,Stats,Health,ConfigUpdate,Consolidate(scan∪execute discriminated union),Decay,Cap,LessonsList,LessonStats,LessonReport,Reconciliation,ReconcileStatus,ResetSource,Success,MutationSummary,AuditRecent,AuditTrail,Trust,ConflictsList,ResolveConflict,AutoResolveConflictsScope,MemoryRow,SearchMemoryItem,TierAssignment,LessonCheck,Consensus,RetrievalTrace/PackagingTrace/AssemblyTrace/Observability,ClusterCandidate,DecayCandidate,LessonRow,ClaimVersionRow,MemoryConflictRow,HealthConfig,AuditTrailEntry. These stay non-exported — they only compose the top-level exports.src/schemas/openapi.ts— everyok('...', GenericObjectResponse)replaced with its specific schema import. Error envelopes (400/404/410/500) unchanged.openapi.yaml+openapi.jsonregenerated deterministically — +3788 lines now document real response bodies.Follow-up worth tracking
The route handlers don't yet parse their response through these schemas before sending (runtime drift detection). The spec-level check catches intentional formatter changes that forget to update schemas; runtime parsing would catch accidental
Record<unknown>leaks but adds per-request cost. Noted as a follow-up.Test plan
npx tsc --noEmitcleannpm run generate:openapiproduces zero diff after regennpm run check:openapipasses locally