Files
stack/docs/requirements/api-artifacts.md
T
fred 54f6ccf717
ci/woodpecker/pr/ci Pipeline was successful
docs: api-artifacts contract revision 3 (luna re-review residuals + F6)
F1: route metadata record defined as a closed required-field list with
a per-field completeness predicate; production route made a decidable
term; generator computes the inventory route set first and fails
naming route and field.
F2: inventory scans all production source in apps/ and packages/ with
an enumerated registration-primitive list; deployment profiles bound
to the closed tier enumeration with a declared condition field; event
names bound to one typed registry with a literal-name rule.
F3: ENDPOINTS.md checker defined as a repository script with five
exact reject rules over a Markdown AST parse, one control per rule.
F4: retirement equivalence algorithm fully enumerated
(canonicalization steps, legacy error-code derivation, covering-entry
selection, permission equality); validator pinned to Redocly CLI.
F5: section 8 items 11-12 disclose the definitions and the witness
machinery.
F6: delegation allowlist (prefix, engine package, engine contract,
registering module) with a below-prefix boundary check; non-allowlisted
prefixes are ordinary route-by-route surface.
2026-08-26 20:11:49 -05:00

22 KiB
Raw Blame History

API Contract Artifacts Contract (S2 contract 9)

Status: DRAFT — awaiting ratification (webui-audit S2, contract 9 of 9). Authority: docs/API/README.md declares the single API documentation boundary: docs/API/OPENAPI.yaml as the canonical machine-readable HTTP/WebSocket contract and docs/API/ENDPOINTS.md as the human-readable endpoint, authentication, permission, and error index — both declared, neither present (the webui-audit REPORT's prerequisite 9). docs/SITEMAP.md records that canonical scope requires maintainer approval; ratification of this contract by the repository maintainer supplies that approval for these two artifacts. Contract 5 (tool-gateway-mapping.md) §4 binds the per-operation request/result/error/audit envelope; this contract binds only how the two artifacts are produced, what they must cover, and how they are kept true.

Revision 2 (luna review F1F5): the generation model now defines its complete input — a declared route-metadata layer every production route must carry, with the generator failing closed on any route lacking extractable metadata, and the migration of today's local-DTO, inline-typed, and raw-handler routes named as implementing-PR scope (F1). The §7.2 inventory is closed over the real surface: raw framework mounts, delegated surfaces, conditional/tier-gated controllers, SSE, and Socket.IO events in both directions, with an explicit delegation/exclusion list (F2). The ENDPOINTS.md no-duplication rule is reattributed as a new disclosed policy with a structured entry format and a mechanical checker (F3). Witnesses are added or made executable for 3.1 validity, family coverage in ENDPOINTS.md, per-entry required content, and the retirement equivalence algorithm (F4). §8 discloses every untraced binding rule, and the SITEMAP approval claim is restated in the sitemap's own terms (F5).

Revision 3 (luna re-review residuals + F6): the route-metadata layer is now a defined closed record with a per-field completeness predicate, and "production route" is a defined, decidable term (F1). The §7.2 scans cover all production source with a named registration-primitive list, a defined deployment-profile set, and a literal-event-name rule backed by one typed event registry (F2). ENDPOINTS.md gets an exact entry grammar and a repository checker script with defined reject rules (F3). §7.4 enumerates its normalization and comparison rules, defines covering-entry selection and the legacy-derivation rule, and §7.6 pins a named validator (F4). §8 discloses the §7 witness machinery itself (F5). Delegated surfaces get a machine-checkable delegation allowlist with a below-prefix boundary check (F6).

This contract binds the artifact definitions (§2), the production model (§3), coverage obligations (§4), legacy migration (§5), phase timing (§6), witnesses (§7), and disclosed drafting additions (§8). Operation semantics stay with contract 5 and the owning S2 contracts; documentation placement rules stay with the docs atlas (docs/README.md).

1. Definitions

  1. The artifacts: docs/API/OPENAPI.yaml and docs/API/ENDPOINTS.md, exactly as declared by docs/API/README.md.
  2. Coverage: the set of Gateway HTTP routes (however registered), SSE routes, and Socket.IO events (both directions) the artifacts describe.
  3. Drift: any difference between an artifact and the production surface it documents — an undocumented live route or event, a documented phantom, or a divergent schema/auth/error description.
  4. Delegated surface: a mounted path prefix whose request handling is delegated wholesale to an embedded third-party engine (for example an auth engine's own route tree): the Gateway registers the mount; the engine defines the routes. Only a prefix listed in the §4.4 delegation allowlist is a delegated surface; any other prefix is ordinary surface, documented route-by-route.
  5. Production route: any HTTP or SSE route, or Socket.IO event (either direction), registered by production source (apps/ and packages/, tests excluded) under ANY deployment profile (§4.5), excluding only entries on the §4.1 exclusion list and routes below a §4.4 delegated prefix. The term is decidable: a route is a production route iff the §7.2 inventory emits it, and the §7.2 scan definition — not intent — is the boundary.
  6. Route metadata record: the closed per-route generator input (§3.1). Its fields, all REQUIRED: a unique operation id; the HTTP method and path template (or the SSE marker, or the Socket.IO event name and direction); the request schema reference (or an explicit no-body marker); the response map — every documented status code mapped to a schema reference, with at least one success entry; the authentication requirement, one value from the closed auth-class enumeration the implementing PR declares in the shared types package; the permission rule reference (owning contract plus rule identifier); and the error-code set, drawn from the contract 5 §4.2 closed taxonomy. Completeness predicate: every field present AND every reference resolvable, evaluated per field; a violation names the route and the exact failing field.

2. Artifact contract

  1. OPENAPI.yaml is one file, valid OpenAPI 3.1 (witness §7.6), and the canonical machine-readable contract for the Gateway's HTTP surface. Socket.IO events, which OpenAPI cannot express natively, are documented in ENDPOINTS.md and referenced from the OpenAPI description block; inventing a pseudo-path encoding for them inside OPENAPI.yaml is non-conformant.
  2. ENDPOINTS.md is the human index, in a structured entry format: one entry per endpoint family, carrying (a) the family's behavior summary, (b) its operations referenced by operationId link into OPENAPI.yaml — never by restated definition, (c) the authentication requirement, (d) the permission/authorization rule naming the owning contract (contract 2 for chain authorization, the SOT for workspace membership), (e) error semantics including the contract 5 §4.2 taxonomy, and (f) Socket.IO event entries (both directions) for event families. ENDPOINTS.md MUST NOT contain request/response schema definitions for any operation that exists in OPENAPI.yaml (mechanical rule: no requestBody/responses/ schema-fragment blocks for such operations — witness §7.5). This no-duplication rule and the entry format are new policy disclosed in §8 (the docs/API/README.md boundary as written binds guide books, not ENDPOINTS.md itself).
  3. Both artifacts are committed repository files, reviewed in PRs like any contract text; neither is a build output that exists only in CI.

3. Production model

  1. Generation (per the ruling). OPENAPI.yaml is GENERATED from the Gateway source by a deterministic repository script whose input is the route metadata record (§1.6) of every production route (§1.5): route decorators plus typed request/response schemas (the contract 5 §4.1 shared DTOs where the operation is a mapped tool family; declared per-route schemas elsewhere), each record satisfying the §1.6 completeness predicate. The generator fails closed: it first computes the §7.2 inventory's route set, then requires a complete metadata record for every route in it; a missing record, a missing field, or an unresolvable reference is a generation error naming the route and field — never a silently omitted or partially emitted operation. Because the route set comes from the inventory and completeness is the per-field §1.6 predicate, a byte-stable but incomplete artifact cannot pass: an unrecorded route fails generation, and a recorded route emits every §1.6 field into its OpenAPI operation (operation id, schemas, per-status responses, security requirement, and the permission reference and error-code set as declared extension fields). Today's local-DTO, inline-typed, and raw-handler routes therefore migrate to complete metadata records in the implementing PR. The generated output is committed, and CI regenerates and byte-compares it (§7.1). Hand edits to OPENAPI.yaml are non-conformant — a description that cannot be expressed from source annotations goes into the annotations, or into ENDPOINTS.md.
  2. Normative direction. Generation documents the code; it does not ratify it. The S2 contracts and their witnesses remain the normative gates on what the surface may be; the artifacts make the measured surface visible and machine-checkable. A generated description of a non-conformant route is drift evidence against that route's owning contract, not authority for it.
  3. ENDPOINTS.md is hand-authored (its content — permission rationale, cross-contract links, event semantics — is judgment, not extraction), against the §7.2 coverage and §7.3 content witnesses.

4. Coverage obligations

  1. Closure over the real surface. Coverage extends to every production route however registered: framework-decorated controllers and gateways AND raw framework mounts registered outside them (the bootstrap's own route/hook registrations), SSE routes, and conditionally registered (deployment-tier-gated) controllers — documented in the union, each conditional route marked with its condition. Delegated surfaces (§1.4) are documented as delegated-surface entries naming the mount, the delegating engine, and that engine's own contract — not re-documented route-by-route. Socket.IO events are covered in both directions: subscribed (inbound handler) events and server-emitted events. Test-only and development-only surfaces are excluded via an exclusion list that is explicit in the generation script and enumerated in ENDPOINTS.md, never implicit. The exclusion and delegation lists are new policy disclosed in §8.
  2. Every documented operation carries: its authentication requirement; its authorization rule by reference to the owning contract; its error responses drawn from the contract 5 §4.2 closed taxonomy. Where contract 2's no-existence-oracle rule applies, the documentation shows authorization refusal and not-found as indistinguishable on the wire — documenting a distinguishing response there is a coverage defect, not a documentation style choice.
  3. New surface ships documented: a PR that adds or changes a Gateway route or event lands with the regenerated OPENAPI.yaml and any needed ENDPOINTS.md entry in the same PR, enforced by the §7.1 drift gate — there is no "docs to follow" state.
  4. Delegation allowlist. The generation script contains one machine-readable delegation allowlist; each entry names: the mount prefix; the delegating engine package (name and version constraint); a reference to the engine's own route contract (the engine package's published API document); and the one production module that registers the mount. The §7.2 witness enforces the boundary: any route registration whose path falls under an allowlisted prefix but does not originate from that entry's named registration module fails the witness — an app-owned handler cannot hide under a delegated prefix. ENDPOINTS.md's delegated-surface entries mirror the allowlist one-to-one.
  5. Deployment profiles. The authoritative profile set is the closed deployment-tier enumeration in the Gateway's configuration schema (the same enumeration the conditional registrations branch on); the implementing PR names it. The inventory is evaluated once per profile and unioned; each conditional operation carries a declared condition extension field naming the profile(s) it exists in. A conditional branch on anything outside the named enumeration fails the §7.2 witness.
  6. Event-name discipline. Every Socket.IO event name, in both directions, is a literal member of one exported typed event registry in the shared types package; emitting or subscribing with a dynamically constructed name, or through a helper that does not take its name from the registry, is non-conformant (§7.2 scans for non-registry names). The registry is the comparison target for the event inventory.

5. Legacy migration

  1. docs/openapi-tess.yaml (17 paths, Tess-scoped) remains labeled a scoped legacy artifact until the consolidated OPENAPI.yaml demonstrably covers it: the docs/API/README.md retirement rule (verify paths, schemas, authentication, permissions, error behavior, and generated/client references) is binding, witnessed by §7.4's equivalence algorithm.
  2. Retirement is deletion in a reviewed PR after the §7.4 witness passes; until then no consumer may treat the legacy artifact as canonical for anything beyond its own 17 paths. (The deletion-in-reviewed-PR form and the interim-consumer restriction are disclosed in §8.)

6. Phase timing

  1. The artifacts, the metadata migration (§3.1), and the generation script are produced by an implementing PR after this contract is ratified; they are S4-phase platform work, not part of any S2 ruling PR.
  2. From the first commit of OPENAPI.yaml onward, the §7.1 drift gate is a required CI check on the integration trunk; the §4.3 same-PR rule binds every subsequent surface-changing PR.
  3. The build-first tool families (A5 ranks, contracts 13, 5, 8) are documented as they land, under the same rule — the roll-up query, for example, ships with its OpenAPI operation and its ENDPOINTS.md entry in its implementing PR.
  4. The phase placement and rollout sequencing in this section are disclosed in §8.

7. Verification requirements

Binding on the implementing PRs; each witness names its commands, scanned source roots, and compared files.

  1. Drift witness: CI regenerates OPENAPI.yaml with the repository script and fails on any byte difference from the committed file; the witness proves it can fail by a control run against a mutated copy. Fail-closed controls (§3.1): one control per §1.6 field class — a fixture route with the whole record absent, and fixture routes each missing one field class (operation id, schema reference, response map, auth requirement, permission reference, error-code set) — each makes generation fail naming the route and field, not emit a partial document.
  2. Coverage witness: an independent surface inventory — static enumeration over ALL production code in apps/ and packages/, tests excluded (the A5 §1.2 inventory style), not limited to the bootstrap module — covering: framework-decorated controllers and gateways; every raw registration primitive of the HTTP framework (the implementing PR enumerates the primitive list — route, hook, middleware-mount, and plugin-registration calls — and the witness asserts the list against the framework's registration API surface) wherever it occurs, including plugins and middleware modules; SSE routes; conditional registrations evaluated per §4.5 profile and unioned; and delegated-surface mounts, with the §4.4 boundary check that nothing app-owned registers below an allowlisted prefix. The inventory equals the documented set in both directions: no undocumented live route, no documented phantom, each conditional operation carrying its §4.5 condition field, each delegated surface present exactly once. Socket.IO events are inventoried in both directions — inbound subscription handlers by handler scan, server-emitted events by emit-call scan — with the §4.6 registry as the comparison target and a scan failure on any non-literal or non-registry event name. Additionally, every HTTP route family in OPENAPI.yaml has an ENDPOINTS.md entry, and every ENDPOINTS.md operation reference resolves to an existing operationId — both directions, mechanical.
  3. Content and taxonomy witness: every ENDPOINTS.md entry carries the §2.2 required fields — behavior, operation links, authentication requirement, owning-contract link, error semantics — presence-checked mechanically against the structured entry format; every error response documented in OPENAPI.yaml uses a code from the contract 5 §4.2 closed enums, and each family's documented codes exactly match the enum in the shared types package; where the no-oracle rule applies, the documented refusal and not-found responses are identical in code, status, and shape.
  4. Retirement witness: before docs/openapi-tess.yaml is deleted, an automated comparison proves each of its 17 paths is covered by OPENAPI.yaml under this equivalence algorithm, exactly: (a) canonicalize both documents — dereference every $ref with cycle detection, delete the annotation-only keywords (description, summary, example, examples, title, deprecated), rename path-template parameters positionally, lowercase HTTP methods, then sort all object keys; (b) per legacy path: deep structural equality of the canonicalized request and response schemas, equality of the security/authentication requirement sets, and equality of the documented error-code sets — a legacy error-code set is DERIVED as the legacy operation's documented non-2xx status codes plus any error-code enum values in its response schemas; where the legacy document does not express a dimension at all (permissions), the comparison target is the live §1.6 metadata record of the covering route instead; (c) the covering ENDPOINTS.md entry — selected as the entry containing the operationId link of the operation that matched the legacy path in (b) — must carry a permission rule reference equal (same owning contract, same rule identifier) to that covering route's §1.6 permission reference. The comparison emits the exact per-path differences on failure. A repository-wide reference scan shows no remaining consumer (including generated clients) resolves the legacy file.
  5. Boundary witness: the repository checker script (shipped by the implementing PR and run in CI alongside §7.1) parses ENDPOINTS.md into the §2.2 entry structure via a Markdown AST and rejects, exactly: (a) an entry missing any §2.2 required field; (b) an operation mention that is not a Markdown link whose target is an OPENAPI.yaml operationId anchor and whose link text is that operationId; (c) a fenced code block, anywhere in the file, containing any of the OpenAPI schema keywords requestBody, responses, parameters, schema, or properties — schema content belongs only in OPENAPI.yaml; (d) an operationId link that does not resolve; (e) an event entry whose event name is not in the §4.6 registry. The witness proves it can fail with one control per reject rule. (Prose duplication a scanner cannot see is handled by review, but every structural duplication channel above is mechanical.)
  6. Validity witness: OPENAPI.yaml parses and validates as OpenAPI 3.1 in CI under Redocly CLI (redocly lint), pinned as a repository devDependency (substituting a different validator is an amendment to this contract); the witness proves it can fail by a control run against an invalidated copy.

8. Drafting additions (PRD §12.1 disclosure)

The two artifacts, their roles, and the retirement verification dimensions are traced to docs/API/README.md and docs/SITEMAP.md. Proposed drafting additions, visible here for ratification, each severable:

  1. The generated-with-committed-output production model, the declared route-metadata layer with its fail-closed generator, the metadata migration scope, and the CI drift gate (§3.1, §7.1) — the ruling below.
  2. The same-PR documentation rule for surface changes (§4.3).
  3. The explicit exclusion list and the delegated-surface documentation form (§4.1).
  4. The Socket.IO placement rule (events in ENDPOINTS.md, no pseudo-paths) and the both-direction event coverage (§2.1, §4.1).
  5. The no-oracle documentation requirement (§4.2).
  6. The ENDPOINTS.md structured entry format and its no-duplication rule with the mechanical boundary witness (§2.2, §7.5).
  7. The committed-repository-file rule — neither artifact exists only in CI (§2.3).
  8. The normative-direction rule — generation documents, never ratifies (§3.2).
  9. The retirement deletion-in-reviewed-PR form and the interim-consumer restriction (§5.2), and the §7.4 equivalence algorithm.
  10. The §6 phase placement: implementing-PR-after-ratification, the drift gate as a required trunk check from first commit, and the build-first documentation sequencing.
  11. The §1.5/§1.6 definitions themselves — the production-route boundary and the closed route-metadata record with its per-field completeness predicate — and the §4.4 delegation allowlist with its below-prefix boundary rule, the §4.5 profile enumeration and condition field, and the §4.6 typed event registry with the literal-name rule.
  12. The §7 witness machinery as such: the §7.2 independent inventory with its registration-primitive list and bidirectional family/operation checks, the §7.3 content and taxonomy witness, the §7.5 checker script with its five reject rules, the §7.6 pinned validator (Redocly CLI), the per-field fail-closed controls in §7.1, and the §7.4 canonicalization/derivation/ covering-entry algorithm.

Ruling request

Ruling requested (one decision): shall OPENAPI.yaml be generated from Gateway source through a declared, fail-closed route-metadata layer with a committed output and a CI drift gate (recommended — the 45-controller surface already exists, so generation is the only route that starts true and stays true, and fail-closed metadata makes incompleteness a build error) — or hand-authored contract-first, with code conformance to the hand-written contract enforced by witnesses instead?