Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-1854Done

11.4 The OpenAPI 3.1 spec as the SINGLE response-schema source + the published API reference

The story that turns a set of routes into an API: a published OpenAPI 3.1 document that is not documentation about the API but the single source of its response schemas — CI-checked against the real routes, rendered as a browsable reference, and the thing clients generate types from.

blocked_by 11.2 + 11.3: a spec CI-checked against the routes needs the routes to exist — and, per the correction below, it ASSEMBLES the schemas those stories ship.

Why this is a story and not a doc chore

Both mirrors publish a spec (GitLab ships an OpenAPI document; Gitea ships Swagger), and every client-generation story downstream depends on it. But the load-bearing decision is which direction the schemas flow. Hand-writing a spec beside hand-written response types produces two artifacts that drift, and drift in a published contract is a breaking change nobody noticed. So the spec is not a description — it is the schema source, and both the routes and every client derive from it.

This is the fix for a defect the CLI already demonstrates: packages/cli/src/mcpClient.ts carries ~20 hand-mirrored interfaces (ReadyItemSummary, SearchItemSummary, WorkItemDetail, …) of server DTOs, validated by nothing — the only bridge is structuredContent as T, an unchecked cast, so a renamed field yields undefined in a rendered column rather than an error. One schema source plus generated types makes that class of drift a compile error instead.

⚠️ Corrected 2026-08-03 — who authors the per-resource schemas

This story originally claimed "one schema module per resource" as its own deliverable. 11.2.1 reverses that direction, and the reason is this story's own: authoring the schemas HERE would mean 11.2 and 11.3 ship routes with ad-hoc inline shapes that this story then retrofits — performing, deliberately, the drift this story exists to prevent. Each resource story now declares its zod request/response schemas next to its routes (11.2 already does); this story ASSEMBLES them. That is also how every schema-first HTTP stack works: the schema is declared with the operation, the document is generated from the operations.

What to build

  1. The shared schema layer + the emission machinery — the envelope, error and pagination schemas every resource composes, and the generator that turns the per-resource operation schemas (11.2's lib/api/v1/workItems/schema.ts and 11.3's equivalents) into an OpenAPI document. zod is already a dependency and already defines the MCP tool INPUT schemas (lib/mcp/tools/searchWorkItems.ts), so the idiom is in the house.
  2. The emitted openapi.json (3.1) — paths, operations, per-operation scopes, the shared error envelope, the pagination envelope, and the security scheme, generated from those schemas rather than authored beside them.
  3. A CI conformance check — the spec and the routes cannot disagree: every /api/v1 route file has an operation in the spec and vice versa, and each endpoint's real response validates against its declared schema. A route added without a schema fails CI, which is what makes the "single source" claim true rather than aspirational.
  4. The published reference — a browsable API reference served from the spec, plus a getting-started page (mint a token → first call → pagination → errors → rate limits).
  5. A documented deprecation + stability policy — what v1 promises, what counts as additive, how a field is deprecated, and how a v2 would be introduced. An unversioned promise is the thing that makes an API un-evolvable.

The reference page is UI — it has a design subtask

The rendered reference is a real user-facing surface, so this story carries a type: design subtask for it — 11.4.2, the three-file asset under design/api-docs/including its ACCESS PATH, and it draws BOTH doors: the public entry in the shipped public chrome, and the entry from the settings API-tokens page. No reference page is planned as a code card without a design reference; that is the design gate, and it fires here even though the rest of the epic is headless.

PLACEMENT — settled 2026-08-05, against shipped reality rather than left open. The reference lives in motir-core, in the public route group app/(public)/. The alternative — a page in the separate marketing site — is not dispatchable: motir-marketing does not exist as a repository, and its provisioning card 8.3.10 is todo beneath a todo story in another epic. This epic's own boundary already pins every deliverable in it to motir-core, and app/(public)/explore/ is the shipped precedent for an unauthenticated page group. 11.4.1 records the placement, the renderer and the localization stance in the ADR.

Scope BOUNDARY

Ends at the shared schema layer, the spec, its CI guard, the reference and the policy. It does NOT author per-resource request/response shapes — those ship with their own endpoints in 11.2 / 11.3, and re-declaring one here would recreate the two-artifact drift. It does NOT generate or publish client SDKs in any language (that becomes possible here and is its own epic); does NOT add or change any endpoint (if the spec work reveals an inconsistency in 11.2/11.3, that is a corrective card, not an edit smuggled in here); does NOT change the MCP surface (11.6 derives MCP payloads from these schemas); and does NOT migrate the CLI (11.5).

Acceptance criteria

  • openapi.json is emitted from the schema modules — the shared ones here plus each resource story's own — not hand-maintained, and validates as OpenAPI 3.1.
  • Every /api/v1 operation appears in the spec with its method, path, parameters, response schema, required scope and error responses; the security scheme documents the bearer PAT.
  • CI fails when a /api/v1 route exists with no spec operation, when a spec operation names no route, or when an endpoint's real response does not validate against its declared schema — asserted by deliberately introducing each of the three drifts in the test.
  • Response validation is exercised against a REAL request/response, not a fixture of itself.
  • The spec's documented statuses cover every one the routes can actually return, the additions 11.2 made to the ADR §4 table (409, 412) included.
  • The reference renders every operation with a copy-pasteable authenticated example, and is reachable from the drawn access path.
  • The getting-started page walks: mint a token → first authenticated call → paginate → read an error → read the rate-limit headers.
  • The deprecation/stability policy is published and states what v1 guarantees, what additive means, and the deprecation path.
  • The spec is served at a stable URL so a client generator can fetch it.
  • The per-file coverage floor (≥90%) holds on every new file.

How it decomposes (expanded 2026-08-05)

11.4.1 decision → 11.4.2 design → 11.4.3 shared wire schemas → 11.4.4 registry + emitter + spec route → 11.4.5 the remaining operations → 11.4.6 the route↔spec drift guard → 11.4.7 the reference page + both doors → 11.4.8 the guide + the published policy → 11.4.9 the vitest gate → 11.4.10 the E2E + acceptance video. Every card ships in motir-core; none straddles a repo boundary.

Context refs

  • lib/api/v1/workItems/schema.ts — the per-resource schema module 11.2 ships, and the pattern this story assembles from.
  • lib/mcp/tools/searchWorkItems.ts — the shipped zod INPUT-schema idiom to extend to responses.
  • lib/dto/workItems.ts · lib/dto/ready.ts — the internal DTOs the resource schemas map from.
  • packages/cli/src/mcpClient.ts — the ~20 hand-mirrored interfaces and the structuredContent as T cast this story's schemas exist to replace.
  • lib/mcp/registry.ts + lib/mcp/scopes.ts — the registry-driven totality pattern (a tool without a scope fails typecheck) that the route↔spec guard should mirror.
  • docs/decisions/public-api-conventions.md — the contract, including 11.2's amendment recording this ownership split.
  • Producers: 11.2 · 11.3. Consumers: 11.5 · 11.6. Parent epic: the public REST API.