OpenAPI contract SSOT
Principle
Committed YAML under contracts/ remains the published contract for Populi, MCP HTTP gateway, Codex, and similar surfaces. Runtime code and tests prove alignment; we do not silently derive the contract from Axum routes without an explicit ADR.
Layers of enforcement
- Structural parse — The spec must deserialize as OpenAPI 3.x. We use the
openapiv3crate in tests (seecrates/vox-populi/tests/openapi_paths.rs, testopenapi_spec_parses_as_openapiv3) so invalid YAML or schema shape fails early. - Path / schema parity — Integration tests keep an explicit list of paths (and key schemas) aligned with
transport::routerand DTO serde keys. This catches drift that a parse-only check would miss. - CI substring guards —
vox cistill uses targeted substring checks for Codex (OPENAPI_SUBSTRINGSincrates/vox-cli/src/commands/ci/constants.rs) as a cheap backstop. Over time, prefer replacing these withopenapiv3+ operation-id or tag assertions where possible.
Optional: generated clients
When to adopt progenitor (or similar):
- After path stability and auth middleware story are clear.
- Start with read-only or internal crates (e.g.
PopuliHttpClientshape incrates/vox-populi/src/http_client.rs) -> shrink repetitivereqwestcalls.
Risks: naming of types, feature flags (transport, mens), and hand-written auth headers must stay in thin wrappers.
What we are not doing (without ADR)
- utoipa-from-routes as SSOT — Fine for greenfield; inverting SSOT from committed YAML requires an explicit decision and publish pipeline for the generated spec.
References
contracts/populi/control-plane.openapi.yamlcontracts/mcp/http-gateway.openapi.yamlcontracts/codex-api.openapi.yamlcrates/vox-populi/tests/openapi_paths.rscrates/vox-mcp/tests/http_gateway_openapi_paths.rs