Problem
The frontend's client types are generated from a committed snapshot (app/frontend/openapi.json) with no automated check that the snapshot matches the backend's live spec. package.json provides only a manual regen:
The backend can export its spec (spec:export in app/backend/package.json runs scripts/export-spec.ts), but nothing links the two in CI. If the backend changes a route, DTO, or status code, openapi.json stays stale and src/lib/generated/api.ts continues to compile against the old contract.
Consequence: the typed client silently lies. A frontend developer sees a correctly-typed request that 400s (or silently misinterprets a field) at runtime because the generated types predate the backend change. This is the exact failure mode that makes a generated client worse than untyped code — false confidence — and it cannot be caught by pnpm type-check because the types are self-consistent against the stale spec.
Root cause
The spec export (backend) and type generation (frontend) are two unconnected scripts; the committed openapi.json is the single point of drift with no CI reconciliation.
Why this is architecturally hard
- The drift gate must run across two packages. The backend must export a fresh spec in CI, then the frontend must regenerate and diff against the committed
openapi.json/api.ts; wiring this into the existing per-service workflows (.github/workflows/backend-ci.yml, frontend-ci.yml) requires cross-service artifact or job orchestration, not a one-line lint.
- The diff must be actionable. A raw JSON diff is noisy; the gate should fail with a clear "run
pnpm generate:api" message and ideally show which paths/types changed, or contributors will disable it.
- Committed-vs-generated policy is a decision. Either
openapi.json/api.ts stay committed (and the gate enforces freshness) or they become generated artifacts (and build steps change); each has different implications for CI caching and local dev.
Proposed design
Add a CI step that exports the backend spec and runs the frontend's generate:api, then fails if the committed files differ. Prefer the backend exporting to a known path in CI and the frontend job comparing. Document the two-step contract in the contributing guide.
Acceptance criteria
CI
Docs
Out of scope
JWT token wiring in api-client.ts and frontend integration tests for the generated client are separate issues.
Getting started
Files: app/frontend/openapi.json, app/frontend/src/lib/generated/api.ts, app/frontend/package.json (generate:api), app/backend/package.json (spec:export), .github/workflows/frontend-ci.yml, .github/workflows/backend-ci.yml.
cd app/backend && npm run spec:export
cd app/frontend && pnpm generate:api
git diff --stat app/frontend/openapi.json app/frontend/src/lib/generated/api.ts
Good first files to read: app/frontend/package.json (the generate:api script) and .github/workflows/frontend-ci.yml to see where the gate would run.
Problem
The frontend's client types are generated from a committed snapshot (
app/frontend/openapi.json) with no automated check that the snapshot matches the backend's live spec.package.jsonprovides only a manual regen:The backend can export its spec (
spec:exportinapp/backend/package.jsonrunsscripts/export-spec.ts), but nothing links the two in CI. If the backend changes a route, DTO, or status code,openapi.jsonstays stale andsrc/lib/generated/api.tscontinues to compile against the old contract.Consequence: the typed client silently lies. A frontend developer sees a correctly-typed request that 400s (or silently misinterprets a field) at runtime because the generated types predate the backend change. This is the exact failure mode that makes a generated client worse than untyped code — false confidence — and it cannot be caught by
pnpm type-checkbecause the types are self-consistent against the stale spec.Root cause
The spec export (backend) and type generation (frontend) are two unconnected scripts; the committed
openapi.jsonis the single point of drift with no CI reconciliation.Why this is architecturally hard
openapi.json/api.ts; wiring this into the existing per-service workflows (.github/workflows/backend-ci.yml,frontend-ci.yml) requires cross-service artifact or job orchestration, not a one-line lint.pnpm generate:api" message and ideally show which paths/types changed, or contributors will disable it.openapi.json/api.tsstay committed (and the gate enforces freshness) or they become generated artifacts (and build steps change); each has different implications for CI caching and local dev.Proposed design
Add a CI step that exports the backend spec and runs the frontend's
generate:api, then fails if the committed files differ. Prefer the backend exporting to a known path in CI and the frontend job comparing. Document the two-step contract in the contributing guide.Acceptance criteria
CI
app/frontend/openapi.json(andsrc/lib/generated/api.ts) fails CI.Docs
Out of scope
JWT token wiring in
api-client.tsand frontend integration tests for the generated client are separate issues.Getting started
Files:
app/frontend/openapi.json,app/frontend/src/lib/generated/api.ts,app/frontend/package.json(generate:api),app/backend/package.json(spec:export),.github/workflows/frontend-ci.yml,.github/workflows/backend-ci.yml.Good first files to read:
app/frontend/package.json(thegenerate:apiscript) and.github/workflows/frontend-ci.ymlto see where the gate would run.