OpenAPI spec generation from zod + /api/openapi.json + CI drift gate #178

Closed
opened 2026-06-20 15:43:44 +00:00 by james · 0 comments
Owner

Generate an OpenAPI 3.1 document from the API's zod schemas, serve it at /api/openapi.json, and fail CI when a route is missing from the spec or the spec disagrees with the zod source.

The contract between the API and the universal client is OpenAPI, generated from zod — never hand-authored. Drift is a build failure, not a code-review nit.

Scope

  • Adopt @asteasolutions/zod-to-openapi (or equivalent) to derive operation schemas from the zod request/response definitions added by the contract-hardening ticket.
  • Wire up a build-time generator that walks app/api/**/route.ts, collects the zod schemas, and emits openapi.json.
  • Serve at GET /api/openapi.json — add to lib/auth/public-routes.ts allowlist.
  • CI step: regenerate the spec and diff against the committed copy. Fail on any change not in the PR.
  • CI step: every app/api/**/route.ts must register an operation in the spec (lint script that enumerates routes vs. spec paths).
  • Versioning convention: spec at openapi.info.version tracks the API minor; bump rules documented in ADR-0027.

Acceptance criteria

  • GET /api/openapi.json returns a valid OpenAPI 3.1 document.
  • CI fails if the committed spec doesn't match the generator output.
  • CI fails if any app/api/**/route.ts lacks a registered operation.
  • Public-route allowlist (lib/auth/public-routes.ts) includes /api/openapi.json with a comment explaining why.
  • At least one route uses the generated spec end-to-end in a smoke test (via the typed client ticket).

Out of scope

  • API versioning paths (/api/v1/...) — see the contract hardening ticket.
  • Generating the client — separate ticket.

Composes with

API contract hardening, Generated typed API client + TanStack hooks, ADR-0027.

Part of

#176

Generate an **OpenAPI 3.1** document from the API's zod schemas, serve it at `/api/openapi.json`, and fail CI when a route is missing from the spec or the spec disagrees with the zod source. The contract between the API and the universal client is OpenAPI, **generated from zod — never hand-authored**. Drift is a build failure, not a code-review nit. ## Scope - Adopt `@asteasolutions/zod-to-openapi` (or equivalent) to derive operation schemas from the zod request/response definitions added by the contract-hardening ticket. - Wire up a build-time generator that walks `app/api/**/route.ts`, collects the zod schemas, and emits `openapi.json`. - Serve at `GET /api/openapi.json` — add to `lib/auth/public-routes.ts` allowlist. - CI step: regenerate the spec and `diff` against the committed copy. Fail on any change not in the PR. - CI step: every `app/api/**/route.ts` must register an operation in the spec (lint script that enumerates routes vs. spec paths). - Versioning convention: spec at `openapi.info.version` tracks the API minor; bump rules documented in ADR-0027. ## Acceptance criteria - [ ] `GET /api/openapi.json` returns a valid OpenAPI 3.1 document. - [ ] CI fails if the committed spec doesn't match the generator output. - [ ] CI fails if any `app/api/**/route.ts` lacks a registered operation. - [ ] Public-route allowlist (`lib/auth/public-routes.ts`) includes `/api/openapi.json` with a comment explaining why. - [ ] At least one route uses the generated spec end-to-end in a smoke test (via the typed client ticket). ## Out of scope - API versioning paths (`/api/v1/...`) — see the contract hardening ticket. - Generating the client — separate ticket. ## Composes with API contract hardening, Generated typed API client + TanStack hooks, ADR-0027. ## Part of #176
james closed this issue 2026-06-21 01:06:31 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#178
No description provided.