11.4.4 The operation registry + the OpenAPI 3.1 emitter, and the route that serves the spec — proven on the work-item resource
Repo: motir-core. One PR. The machinery that turns declared schemas into a document, plus the door that serves it. This is where "the spec is the schema source" becomes a mechanism rather than an intention.
Nothing today connects a schema to an endpoint: lib/api/v1/workItems/schema.ts knows the work-item shape and app/api/v1/work-items/[key]/route.ts knows the path, and no value in the repo says they belong to the same operation. That missing value is the registry.
What to build
- The operation descriptor + registry. A typed
V1Operation— method, path template, path/query parameters, request-body schema, the response envelope it returns (one of the two 11.4.3 declares), the required scope, the error statuses it can raise, and a summary — and a registry keyed by`${METHOD} ${path}`. Build it the way 11.4.1's Amendment 4 pins it (per-resourceoperations.tsmodules assembled by one registry, unless the amendment says otherwise), and mirror the totality disciplinelib/mcp/registry.ts+lib/mcp/scopes.tsalready use: an operation that names no scope must fail typecheck, not review. - The emitter. Assemble the OpenAPI 3.1 document from the registry plus the schema modules:
info,servers,pathswith one operation each,componentsholding the resource schemas and 11.4.3's shared envelopes / error body / rate-limit headers, and the bearer security scheme applied per operation with its scope. Each collection operation references the envelope its route actually returns — the ranked one carryingtotalCountor the plain cursor page — per Amendment 3 Q2. - Key the emitter by API MAJOR version. There is one document today and that is the degenerate case, not the model:
v2must be a second document, not an edit of the first.info.versionmeans the API's version, not the app's release number — state which in the code. - Declare the WORK-ITEM operations as the proving resource (
app/api/v1/work-items/**and the project-scoped work-item collection). One resource is enough to prove the machinery end to end; 11.4.5 declares the rest. - Serve the document at the URL Amendment 4 pins, unauthenticated and cacheable. If that URL sits under
app/api/v1, the shipped route audit will flag it —tests/helpers/v1RouteAudit.ts:38walks everyroute.tsthere and raisesbypasses-wrapperfor a handler not wrapped inwithV1Route(tests/helpers/v1RouteAudit.ts:84-99), and the wrapper authenticates. Implement the exemption exactly as Amendment 4 specifies it, and assert its bounds: it applies to that one path, and that file authenticates nothing, touches no database, reads no user input.
Scope BOUNDARY
Ends at the machinery, the work-item operations and the served route. It declares no operations for projects, sprints, the backlog, the ready set, workspaces or /me — those are 11.4.5, and the document is legitimately incomplete until that card lands. It authors no schema: the shared ones are 11.4.3's and the per-resource ones are already shipped. It builds no route↔spec CI guard — that needs the complete registry and is 11.4.6. It renders no page. It changes no existing endpoint's behaviour, path or response.
Acceptance criteria
- A typed operation registry exists, keyed by method + path, and an operation missing its scope or its response schema fails
pnpm typecheck— proven by a type-level test or an equivalent compile-failure assertion. - The emitter produces a document that validates as OpenAPI 3.1 against a real validator, not a hand-written shape check.
- Every work-item operation appears with its method, path, parameters, request body where it has one, response schema, required scope and error statuses; the collection operations reference the correct one of the two envelopes.
- The bearer security scheme is applied per operation with the scope that operation requires, and that scope equals the scope its route file declares — asserted, not assumed.
- The emitter takes the API major version as input and produces a distinct document per version; a test drives it with a second version and gets a second document, not a mutated first.
- The spec route returns the document with a JSON content type and no authentication, and a request carrying no
Authorizationheader succeeds. - The route-audit exemption (if the pinned URL requires one) is implemented and BOUNDED by assertions: it names exactly one path, and a test proves a second exempted path, or an exempted file that authenticates or reads the database, FAILS.
- New files join the per-file coverage thresholds in
vitest.config.tsat the ≥90% floor.
Context refs
lib/api/v1/openapi/— 11.4.3's shared schemas, envelopes and security scheme this assembles from.lib/api/v1/workItems/schema.ts— the work-item request + response schemas, declared beside their routes precisely so this card can emit from them (see its// Story 11.4 emits the OpenAPI operations from this modulenote at line 521).app/api/v1/work-items/**·app/api/v1/projects/[projectKey]/work-items/route.ts— the routes the proving operations describe.lib/mcp/registry.ts·lib/mcp/scopes.ts— the registry-driven totality pattern.tests/helpers/v1RouteAudit.ts— the route walker and thebypasses-wrapperrule the spec route collides with.lib/api/v1/route.ts—withV1Route, and why an unauthenticated route cannot use it.docs/decisions/public-api-conventions.md— Amendment 3 Q2 (envelope choice per operation), Amendment 4 (registry shape, spec URL, exemption).- Blocker: 11.4.3. Parent story: 11.4.