Contract-first workflow
CLAUDE.md's invariant 1: packages/contract/openapi.yaml is the source of truth. Controllers conform to it (and are tested for that), the SDK is generated from it, and neither is ever hand-written to differ from it. This page is the concrete workflow that invariant implies.
The contract itself
packages/contract/openapi.yaml is a single ≈5,300-line file describing 85 paths under /v1. It's lint-checked (redocly lint openapi.yaml, packages/contract's own lint script) and is what the API Reference is generated from — the same file, no separate description to keep in sync.
Adding or changing an endpoint
┌─────────────────────┐
│ 1. Edit │
│ openapi.yaml │
└──────────┬───────────┘
│
▼
┌─────────────────────┐ regenerates packages/sdk/src/generated/
│ 2. pnpm --filter │ {types,paths}.ts from the contract via
│ @loombre/contract │ openapi-typescript + a small hand-rolled
│ run codegen │ operations table
└──────────┬───────────┘
│
▼
┌─────────────────────┐ gate step: git diff --exit-code -- packages/sdk
│ 3. Commit the │ — the generated SDK must be committed and
│ generated SDK diff │ must exactly match what codegen produces.
└──────────┬───────────┘ Never hand-edit files under packages/sdk/src/
│ generated/ — the "GENERATED — do not edit"
│ banner at the top of each is not a suggestion.
▼
┌─────────────────────┐ gate step: oasdiff breaking PREVIOUS.yaml
│ 4. oasdiff checks │ CURRENT.yaml, against `main`. Field removal,
│ for breaking changes │ rename, or type change fails here (docs/PLAN.md
└──────────┬───────────┘ §4.1: "additive-only within a major version").
│
▼
┌─────────────────────┐
│ 5. Implement/adjust │
│ the controller │
└──────────┬───────────┘
│
▼
┌─────────────────────┐ apps/server/test/conformance.spec.ts +
│ 6. Conformance tests │ seeded-conformance.spec.ts must pass — every
│ must pass │ non-public op returns a proper 401 when
└──────────┬───────────┘ unauthenticated, public ops validate against
│ their documented schema, and every mounted
│ route maps to something actually documented
│ (no undocumented surface area).
▼
┌─────────────────────┐
│ 7. pnpm gate │
└─────────────────────┘What conformance testing actually checks
apps/server/test/conformance.spec.ts boots a real Nest application against a seeded database and walks the contract itself as its test data — there's no separately maintained list of "endpoints to check." For every documented, non-public operation it asserts an unauthenticated request gets a proper RFC 9457 401; for the public operations (POST /auth/login, POST /auth/refresh, GET /system/capabilities) it validates the response against the contract's own schema (via Ajv); for authenticated requests it asserts they're not walled off; and it asserts /healthz stays public. Crucially, it also asserts that every route Express actually has mounted maps to a documented contract path — an endpoint that exists in code but not in openapi.yaml fails this check, not just the reverse.
Why this order
sdk-drift and oasdiff run early in pnpm gate (right after codegen) because they're structural checks over the contract and its generated artifact — cheap, and a failure there means the rest of the gate is checking against a contract state that isn't what will actually ship. See scripts/gate.mjs's own header comment for the full step order and the reasoning behind each step's position.