# Canonical OpenAPI `GET /openapi.json` serves the OpenAPI 3 document bundled with the backend release. The Swagger UI and its Swagger 2 export remain available at `/`. The source is the actual DRF serializers and view annotations. `build.mjs` converts Swagger 2 to OpenAPI 3, adds the source-record discriminated variants and the named ordering enum with comma-separated ordering, then validates the resulting document. Do not edit `src/core/openapi.json` or generated TypeScript by hand. From the backend root (Python dependencies installed with uv): ```sh PYTHONPATH=src uv run python src/manage.py generate_swagger tools/openapi/swagger.json --overwrite --format=json --settings=settings.test cd tools/openapi bun install --frozen-lockfile --ignore-scripts bun build.mjs bun run apigen bun run type-check ``` The runtime gate also validates rendered responses from the real DRF list/detail views against Orval's generated Zod schemas, including every documented HTTP status. Generate its synthetic fixtures in the isolated test database, then run: ```sh # From the backend root. The parent directory of this explicit path must exist. OPENAPI_RUNTIME_FIXTURES=/tmp/mostovik-openapi-runtime.json PYTHONPATH=src uv run pytest tests/apps/organizations/test_openapi_runtime_fixtures.py -q cd tools/openapi bun run apigen bun run contract-check /tmp/mostovik-openapi-runtime.json ``` Zod is pinned to 4.3.6. The gate covers a full Budget payload with unknown raw fields, all five SME size units, nullable SRO admission dates, mixed/filtered lists, empty arrays and zero counts, and 400/404 errors. Parsed responses must equal the original JSON so that an apparently successful parser cannot silently discard raw keys. Ordinary pytest runs write no fixture file; writing is enabled only by `OPENAPI_RUNTIME_FIXTURES`. No live upstream requests or business databases are used. The generated validators are disposable build output, never handwritten. Bun is pinned to 1.3.9 in CI; the tool dependencies are locked in `bun.lock`. CI regenerates the source Swagger document, runs `bun build.mjs --check`, generates a client and compiles a strict TypeScript fixture. It fails if the bundled contract is stale, invalid, loses discrimination, or removes the named ordering enum. For the frontend, copy the bundled document to its `openapi.json`, then run its own `bun run apigen` and `bun run type-check`. Keep source-group checks before reading source-specific payloads. Multipart upload operations now have one accurately documented content type, so Orval generates `...Mutation` rather than the former multi-content-type `...MutationWithFormData` name. The generated function still constructs FormData and sends multipart data. Before accepting an upgraded live dataset: ```sh python src/manage.py migrate --noinput python src/manage.py normalize_published_registry_payloads python src/manage.py normalize_published_registry_payloads --apply python src/manage.py build_source_record_exports ``` The first normalization command is a dry run. Applying it preserves record UIDs, publication timestamps and artifact IDs, but atomically increments the affected artifact content revisions. New export tickets return 503 until a matching export generation is built; already issued tickets keep their pinned generation. SRO access remains gated as described in `docs/sro-membership-source.md`.