From bec2eb118ba40d1e9daff5bada14f55f7f8d0cf2 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 27 Aug 2026 19:18:39 +0000 Subject: [PATCH] docs: API contract artifacts contract (S2 contract 9) (#1443) --- docs/requirements/api-artifacts.md | 1360 ++++++++++++++++++++++++++++ 1 file changed, 1360 insertions(+) create mode 100644 docs/requirements/api-artifacts.md diff --git a/docs/requirements/api-artifacts.md b/docs/requirements/api-artifacts.md new file mode 100644 index 00000000..8e0a44cb --- /dev/null +++ b/docs/requirements/api-artifacts.md @@ -0,0 +1,1360 @@ +# 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 is the +**Tool↔Gateway Mapping Contract (D8)** — S2 contract 5, PR #1438, +landing as `docs/requirements/tool-gateway-mapping.md` in the same S2 +ruling batch as this contract; it is not yet on the integration trunk, +so its file does not exist in this branch's tree. Its §4 binds the +per-operation request/result/error/audit envelope, and its §4.2 +defines the closed per-family error taxonomy this contract's +references bind to. Every "contract 5" reference in this document +resolves against that document as ratified; §6.5 states the ordering +dependency. This contract binds only how the +two artifacts are produced, what they must cover, and how they are +kept true. + +Revision 2 (luna review F1–F5): 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). + +Revision 4 (luna re-review residuals + F7/F8): the route-metadata +record is redefined as the route's **registration input** — the +registration primitive consumes the record and derives the installed +auth guard and permission enforcement from it, so record-vs-code +divergence on those fields is structurally impossible, and a new +source-consistency witness (§7.7) cross-checks the remaining fields +(F1). The auth-class enumeration is pinned in this contract (four +values, amendment-only), and Socket.IO event records get +direction-specific field semantics replacing the status-code response +map (F1). The scan roots are `apps/`, `packages/`, and `plugins/` — +all three named everywhere — and registration-primitive detection is +by TypeScript symbol identity against the framework's type +declarations, closing the wrapper/alias/dynamic-name escapes (F2). +§2.2 defines an exact entry grammar (heading form, literal field +labels, cardinality, link syntax, anchor syntax, event-item syntax) +and §7.5's reject rules cover every Markdown node type plus a +one-entry-per-operationId uniqueness rule (F3, and the covering-entry +ambiguity in F4). §7.4 defines cycle handling, `$ref` scope, +per-(path, method) matching, no-body equality, and a common +representation for error-code comparison (F4). The delegation +boundary requires the named module to contain exactly the mount call +and nothing below the prefix from any module, with field-by-field +mirror equality (F6). The exclusion list gets a closed, machine- +checked eligibility rule: an excluded route emitted by any +production-profile inventory pass is a witness failure (F7). The +contract 5 dependency is named explicitly — PR #1438, same ruling +batch — with an ordering clause and a contingency amendment rule +(F8). + +Revision 5 (luna r4 residuals F1/F2/F4 + F9): the auth-class +enumeration grows to six values so the live alternation guards are +representable — `admin` (session-with-admin-privilege OR admin bearer +token) and `federation` (mTLS client identity plus federation grant) +— and the generator carries a closed `ApiAuthClass` → OpenAPI +security-requirement mapping (F1, F4). §7.7 gains an emitted-status +closure: every status-emission site in a route's handler closure must +resolve to a literal status contained in the record's response map, +with unresolvable emissions a scan failure (F1). The §7.2 scan +requires every registered path argument and decorator path value to +resolve to a compile-time string constant equal to the record's path +template; an unresolvable or mismatched path is a scan failure (F2). +The response map admits an explicit no-body marker per status, and +§7.4 canonicalizes body-less responses (including legacy +description-only responses) to that marker so the deep equality has a +defined input (F4). The §4.6 registry gains a total, single-valued +event-ownership map to `ENDPOINTS.md` family names; §2.2 binds each +event item to its owning family's entry; §7.5 rejects duplicate and +wrong-family event items; and §7.2/§7.5 assert bidirectional set +equality between the event inventory and the file's event items (F9). + +Revision 6 (luna r5 residuals F1/F2/F4/F9 + NF1–NF5): the +emitted-status closure becomes a defined, total algorithm over five +named site classes — record-derived auth-guard refusals (each +`ApiAuthClass` carries a closed refusal-status set in the §1.6 +mapping table), bootstrap-registered global interceptors, decorator +status metadata, the handler's type-checker call-graph closure with +unresolved-edge failure, and raw response sinks by Node symbol +identity — and the comparison becomes bidirectional, rejecting +phantom map statuses no site produces (F1, NF1). §7.2 classifies +registration primitives as path-bearing or non-path-bearing, defines +the raw-mount rule for hook-internal URL dispatch, and defines the +framework path composition (class-prefix arrays × method suffixes, +default empty paths, normalization) applied before the exact +template comparison (F2, NF2). The `ApiAuthClass` → OpenAPI mapping +table is pinned in-contract with exact requirement-set shapes under +OpenAPI 3.1 OR/AND semantics (admin = two requirement objects; +federation = one object with two schemes), a new guard-equivalence +witness (§7.7(e)) ties the mapping to the live guard semantics, and +§7.4 resolves document-level security inheritance before comparison +(F4, NF3). The event scan gains a mechanical server/client boundary +by socket-package type identity, the comparison element is defined +as the (direction, name) pair, and the two registries' value sets +must be pairwise disjoint (F9, NF4). The ownership map's domain must +exactly equal the registry union (stale keys rejected), and +`Operations:` becomes conditional so an event-only family is +grammatical (NF5). + +Revision 7 (luna r6 residuals F1/F2/F4/F9 + NF1–NF4 + NF6): the +§7.7(d) status-site class (2) becomes an interception-component +closure over EVERY binding scope — global-by-call, global-by-DI-token, +controller-scoped, and route-scoped pipes/filters/interceptors/guards +— with per-component source-asserted status sets, scope-exact route +attribution, and unenumerated-binding failure, and the §7.2 primitive +list names the global binding calls and DI tokens (F1). The raw-mount +rule records one route per (dispatched prefix × accepted method) +branch, requires segment-boundary prefix semantics, and fails on +non-constant method dispatch or a boundary-escaping prefix predicate +(F2, NF2). The §1.6 mapping table pins requirement-object exactness +(exact scheme keys, empty scope arrays), binds each scheme to a +concrete credential predicate, declares multi-credential evaluation +order, and REDEFINES each refusal-status set as the computed union of +the guard's refusal-branch statuses; §7.7(e) drives every enumerated +refusal branch and every mixed presentation and asserts the written +set equal to the computed union in both directions (F4, NF3). The +socket-event boundary gains a decorator-site rule — a subscribe +decorator resolved to the socket framework package on a +gateway-decorated class is a server-side inbound site by compile-time +package identity — and the event closure additionally requires the +registry union to equal the inventoried server-event set, rejecting +stale registry events (F9, NF4). §7.7(d)(4) call edges into +non-production packages that receive the reply/response object become +declared delegated-emission sites joined to a pinned external status +contract, and §7.7(d)(5) framework defaults each require a +pinned-version citation plus a runtime probe (NF1). §2.2 requires +every family to have a non-empty surface, rejected by a new §7.5 rule +(NF6). + +Revision 8 (luna r7 residuals F1/F2/NF1/NF2 + NF7): §7.7(d)(2) +attributes interception components to their actual execution +pipeline — framework components (all four binding scopes) attach to +framework-routed routes ONLY, adapter-level hooks attach to exactly +the routes their dispatch predicate accepts, and a raw-mounted route +(dispatched by a hook before the framework router) never carries a +framework component's status — with a raw-hook phantom control (F1). +The §7.2 raw-mount rule gains method-partition exhaustiveness: every +control-flow path is an acceptance path (pinned to one method +constant, one record), a refusal path (constant-status refusal, no +forwarding — the only place a complement or unguarded test is +admissible), or a scan failure; a forwarding edge into a +non-production package escapes failure only through a declared +external method contract, which then defines the branch's record set +(F2, NF2). The §7.7(d)(5) default rule becomes an exhaustive +inventory: total emitting/non-emitting classification over every +option key of the pinned framework and adapter options types plus +every configured composition-root option, an unclassified key a +witness failure (the adapter body-size limit is the migration +example), citation-plus-probe per emitting entry (NF1). §2.2 defines +three entry kinds — surface, delegated-surface (`Delegated:` with +exact four-sub-item syntax mirroring the §4.4 allowlist), and +exclusion (`Excluded:` with exact per-route sub-item syntax +mirroring the §4.1 script list) — so non-surface entries are +grammatical without phantom operations; §7.5(k) becomes the +kind-partition rule and new rule (l) checks the exclusion mirror +(NF7). + +Revision 9 (luna r8 residual NF1 + NF8/NF9): the §7.7(d)(5) default +inventory attributes by execution pipeline, never by configuration +scope alone — each response-emitting entry names its emitting +lifecycle stage, and its status attributes to framework-routed +routes plus exactly those raw-mounted routes whose dispatch point +executes after that stage; a pre-parsing raw hijack therefore never +carries a body-parsing rejection status, the body-size-limit probe +becomes a two-pipeline pair, and a body-limit status on a raw route +must have a status site in the route's own callback chain (NF1). +§7.3's content witness partitions by the §2.2 entry kind — surface +fields are checked on surface entries only; delegated-surface and +exclusion entries are checked for their own kinds' field content, +with the §4.4/§4.1 mirrors staying in §7.5(g)/(l) (NF8). The §7.2 +method partition gains a URL fall-through class: a path dominated +by the negation of the mount's URL-dispatch predicate may forward +only to the hook chain's continuation, emits nothing, needs no +method pinning, and contributes no record — the method partition +binds exactly the URL-accepted branch, and a URL-missed path +forwarding anywhere but the continuation is still a scan failure +(NF9). + +Revision 10 (luna r9 residual NF1): the §7.7(d)(5) AFTER relation +becomes decidable. The witness produces a **pinned lifecycle-order +map** — the totally ordered, citation-backed list of the pinned +adapter version's documented request-lifecycle phases — and the +§7.2 primitive classification records each hook registration's +lifecycle phase as part of the primitive's identity. A raw mount's +dispatch point resolves to the map entry of its recorded hook +phase; an emitting entry resolves through its citation; AFTER is +strict map order. An unresolved dispatch point or emitting entry, +an unresolvable hook phase, and a same-stage pair are each a +FAILURE naming the site — attribution is never assumed for an +unordered pair, and the repair is either restoring resolvability or +a per-(route, entry) probe pair in the body-size pair's style whose +recorded observations decide the attribution; an attribution +carried by neither the map order nor a recorded probe pair is a +phantom. + +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/`, + `packages/`, and `plugins/` — the three production roots, named + identically in every scan in this contract; 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 registration input + (§3.1). The record is not documentation of a handler — it IS the + handler's registration configuration: the registration primitive + consumes the record, and the installed authentication guard and + permission enforcement are DERIVED from the record's declared auth + class and permission rule reference, so the record cannot diverge + from the code on those fields; a route registered without a record, + or with enforcement not derived from its record, fails §7.7. Its + fields, all REQUIRED for HTTP and SSE routes: a unique operation + id; the HTTP method and path template (or the SSE marker); the + request schema reference (or an explicit no-body marker); the + response map — every status code the route can emit, mapped to a + schema reference **or an explicit no-body marker** (a body-less + response is declared, never implied by omission), with at least + one success entry, and with one + error entry per HTTP status the route's declared error-code set + maps to under the contract 5 §4.2 status mapping (so the reachable + error statuses are exactly the declared taxonomy's statuses, + cross-checked by §7.7); the authentication requirement, one value + from the closed enumeration **`ApiAuthClass`**, exported from the + shared types package with exactly the values `none` + (unauthenticated), `session` (authenticated account), `api-key` + (agent/service key), `admin` (the alternation guard: an + authenticated session holding platform-admin privilege OR the + admin bearer token — the alternation itself is the class, so the + live admin surface is representable without collapsing it into + `session`), `federation` (mTLS client identity plus a valid + federation grant — both required), and `bootstrap` (identity §3 + pre-epoch surface) — amendment-only, pinned here rather than + deferred. Each `ApiAuthClass` value maps to exactly one OpenAPI + security-requirement set under a closed mapping table pinned + HERE (the generation script carries it verbatim, and §7.4(b) + compares security through it). OpenAPI 3.1 semantics are + binding: a `security` array with multiple requirement objects + means OR across objects; multiple schemes inside one requirement + object mean AND. The table, over scheme names the implementing + PR defines exactly once in `OPENAPI.yaml`'s + `components.securitySchemes`: `none` → the empty security array; + `session` → exactly one requirement object naming the session + scheme; `api-key` → exactly one requirement object naming the + API-key scheme; `admin` → exactly two requirement objects, one + naming the admin-privileged-session scheme and one naming the + admin-bearer scheme (the alternation as OpenAPI OR); + `federation` → exactly one requirement object naming both the + mTLS scheme and the federation-grant scheme (the conjunction as + OpenAPI AND); `bootstrap` → exactly one requirement object + naming the bootstrap scheme. **Requirement-object exactness**: + each requirement object in the table contains EXACTLY its named + scheme key(s) and no other key, and every scheme key maps to + exactly the empty scope array (these artifacts define no OAuth + scopes); the generator emits these exact objects and §7.4(b) + compares against them — a requirement object with an extra key, a + missing named key, or a non-empty scope array is not the mapped + set. **Scheme binding**: the table binds each scheme name to a + concrete credential predicate — the credential's transport + location (header, cookie, or TLS peer identity) and the + acceptance predicate the record-derived guard implements for it — + so "a request satisfying the scheme" is constructed from the + binding, never improvised; a scheme with no bound predicate, or a + binding the guard source does not implement, fails §7.7(e). + **Evaluation order**: for a class whose guard evaluates multiple + credential alternatives in a fixed order (the live admin guard + checks the bearer credential before the session), the table + declares that order as part of the class's entry; the OpenAPI OR + states which credentials MAY authorize, the declared order states + what a mixed presentation does, and §7.7(e) drives the mixed + cases against the declaration. The table additionally assigns + each class a closed **refusal-status set**, DEFINED rather than + merely stated: the set EQUALS the union, over the refusal + branches of the class's record-derived guard, of each branch's + emitted status — branches and statuses enumerated mechanically by + §7.7(e) applying the §7.7(d)(4) emission-site rules to the guard + and its callee closure; the implementing PR writes each set into + the table from that enumeration, and §7.7(e) asserts the written + set equal to the enumerated union in both directions (a written + status no branch emits, and a branch status missing from the + written set, each fail). The sets are consumed by §7.7(d) class + (1). The mapping is total over the enumeration and + injective — no two classes map to the same requirement set; the + permission rule reference (owning contract plus rule identifier); + and the error-code set, drawn from the contract 5 §4.2 closed + taxonomy. **Socket.IO event records** carry direction-specific + fields in place of the response map: an inbound event record has + the event name, direction, payload schema reference, and an ack + schema reference or an explicit no-ack marker; an outbound event + record has the event name, direction, and payload schema + reference; the success-entry rule is defined as inapplicable to + event records. **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 an exact entry grammar: + the file is a sequence of entries, each entry exactly one `##` + heading whose text is the endpoint family name (unique in the + file), followed by a bullet list whose items begin with exactly + one of the literal field labels `Summary:`, `Operations:`, + `Auth:`, `Permissions:`, `Errors:`, `Events:`, `Delegated:`, + `Excluded:` — each label at most once per entry. **Entry + kinds**: every entry is exactly one of three kinds, decided by + its labels. A **surface entry** carries `Summary:`, `Auth:`, + `Permissions:`, and `Errors:` (REQUIRED) and neither + `Delegated:` nor `Excluded:`; within a surface entry, + `Operations:` is REQUIRED for a + family with at least one `OPENAPI.yaml` operation and ABSENT for + an **event-only family** (a family whose entire surface is + Socket.IO events — such a family MUST carry `Events:`, which is + how it satisfies the grammar with no operations to link), and + `Events:` is REQUIRED for a family with Socket.IO events and + absent otherwise. A **delegated-surface entry** carries exactly + `Summary:` and `Delegated:` and no other label. An **exclusion + entry** carries exactly `Summary:` and `Excluded:` and no + other label, and the file contains at most one exclusion entry + (absent when the §4.1 exclusion list is empty). **Non-empty + surface**: every surface entry carries at least + one of `Operations:` or `Events:` — a family with neither + documents nothing; a delegated-surface or exclusion entry + documents its surface through `Delegated:`/`Excluded:` + instead, needing no phantom operation. An entry matching no + kind, or more than one, is not grammatical (reject rule + §7.5(k)), + so a stale zero-surface family cannot survive the grammar. + Field contents: `Summary:` — the family's behavior summary, free + prose; `Operations:` — one or more Markdown links, each with link + text equal to an `operationId` and link target + `./OPENAPI.yaml#operation/{operationId}` (the anchor grammar: + the fragment is the literal string `operation/` followed by the + `operationId`; the link RESOLVES iff that `operationId` exists as + an operation in the committed `OPENAPI.yaml`) — operations are + referenced by link only, never by restated definition, and each + `operationId` is linked from exactly one entry in the whole file; + `Auth:` — the `ApiAuthClass` value (§1.6); `Permissions:` — the + permission/authorization rule naming the owning contract + (contract 2 for chain authorization, the SOT for workspace + membership); `Errors:` — error semantics including the contract 5 + §4.2 taxonomy codes; `Events:` — one bullet sub-item per event, + each exactly `inbound {event-name}` or `outbound {event-name}` + where `{event-name}` is a member of the §4.6 event registry, the + item appears in exactly the entry of the family the §4.6 + ownership map assigns to that event, and each (direction, name) + pair appears at most once in the whole file; `Delegated:` — + exactly four bullet sub-items, each exactly one of + `prefix {mount-prefix}`, `engine {package-name} +{version-constraint}`, `contract {engine-contract-reference}`, + and `module {registering-module}` (each sub-item label exactly + once), whose values mirror one §4.4 allowlist entry + field-by-field (string equality after whitespace trimming — + the §4.4 mirror rule, reject rule §7.5(g)); `Excluded:` — one + bullet sub-item per excluded route, each exactly + `{HTTP-method} {path-template} {reason-class} {guard}` where + `{reason-class}` is `test-only` or `dev-only`, mirroring the + §4.1 generation-script exclusion list field-by-field under the + same string equality (reject rule §7.5(l)). + `ENDPOINTS.md` MUST NOT contain + request/response schema definitions for any operation that exists + in `OPENAPI.yaml` (mechanical rule: the reserved tokens + `requestBody`, `responses`, `parameters`, `schema`, `properties` + appear nowhere in the file, in any Markdown node type — witness + §7.5; entry prose must avoid the reserved tokens, a disclosed + strictness). This + no-duplication rule and the entry grammar 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). Because the record is the registration input (§1.6), the + generator's input and the route's installed enforcement share one + source; §7.7 witnesses that no route bypasses record-derived + enforcement and that the record fields the framework does not + consume (error-code set, response map) match the code. 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 (the §2.2 entry kind, + `Delegated:` field) 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` (the §2.2 exclusion + entry, one `Excluded:` sub-item per route), never implicit. The + list has a **closed, machine-checked eligibility rule**: each + entry names the route, its reason class (`test-only` or + `dev-only`), and the exact guard — the deployment-profile branch + or build-time flag — that prevents its registration in every + §4.5 production profile; eligibility is DEFINED as "not emitted + by the §7.2 inventory in any production profile", and the §7.2 + witness fails if any excluded entry is emitted by any + production-profile pass. A live route added to the list is + therefore a witness failure, not a silent omission. 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 in both halves: (a) the named registration module + contains exactly one registration-primitive call — the mount + itself — and no other; (b) any route registration, from ANY + module including the named one, whose path resolves below an + allowlisted prefix fails the witness — the single mount call is + the only permitted registration under the prefix, so an app-owned + handler cannot hide there, inside or outside the named module. + `ENDPOINTS.md`'s delegated-surface entries mirror the allowlist + **field-by-field**: prefix, engine package name and version + constraint, engine contract reference, and registering module are + each equal by string comparison after whitespace trimming — + "mirror one-to-one" means this equality, checked by §7.5. +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.** The event registry is two exported + `as const` readonly records in the shared types package — one + inbound, one outbound — whose values are unique string literals; + that shape is part of this contract, not implementing-PR + discretion. Every Socket.IO event name, in both directions, is a + member of the applicable registry, referenced by symbol (a + registry property access), never by a re-typed string; emitting or + subscribing with a dynamically constructed name, or through a + helper whose name argument does not resolve (by the type-checker's + symbol identity, §7.2) to a registry member, is non-conformant + (§7.2 scans for non-registry names). The registry is the + comparison target for the event inventory. **Event ownership:** + the shared types package additionally exports one `as const` + readonly ownership record mapping every registry event name (both + directions) to exactly one `ENDPOINTS.md` family name (the §2.2 + `##` heading text). The map is total over the union of both + registries and single-valued — every event has exactly one owning + family — and it is the deterministic rule for which entry + documents which event; a registry event absent from the map, or + mapped to a family with no `ENDPOINTS.md` entry, fails §7.5. + The map's domain EXACTLY equals the union of the two registries' + event names: a map key naming no registry event (a stale key) + fails §7.5 just as a missing key does. The two registries' + literal value sets are pairwise DISJOINT — no event name appears + in both the inbound and the outbound registry — witnessed by + §7.5; disjointness is what makes the name-keyed ownership map + well-defined over directional events. Everywhere this contract + compares event sets (§7.2, §7.5), the comparison element is the + **(direction, name) pair**, never the bare name. + +## 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 1–3, 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. +5. **Contract 5 ordering dependency.** This contract's taxonomy and + envelope references bind to the Tool↔Gateway Mapping Contract + (S2 contract 5, PR #1438, `docs/requirements/tool-gateway- +mapping.md`), ratified in the same S2 batch but not yet on the + integration trunk. No implementing PR under this contract may + land before contract 5's document is on the trunk and the + shared-types taxonomy enums it requires exist. If contract 5 is + not ratified, every §4.2-taxonomy reference here is unresolvable + and this contract requires amendment before implementation — + stated so the dependency is explicit rather than a dangling + file reference (disclosed, §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/`, `packages/`, and + `plugins/` (the §1.5 production roots), + 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, PLUS the + application-composition calls and DI tokens that install + cross-route interception components: the framework's global + binding calls — `useGlobalPipes`, `useGlobalFilters`, + `useGlobalInterceptors`, `useGlobalGuards` — and its `APP_PIPE`/ + `APP_FILTER`/`APP_INTERCEPTOR`/`APP_GUARD` provider tokens, both + consumed by §7.7(d)(2) — and the witness + asserts the list against the framework's registration API surface) + wherever it occurs, including middleware modules; SSE + routes; conditional registrations evaluated per §4.5 profile and + unioned; and delegated-surface mounts, with the §4.4 boundary + check (exactly-one-mount rule and the all-module below-prefix + prohibition). **Detection is by symbol identity, not text:** the + scan identifies registration-primitive call sites through the + TypeScript type-checker, resolving each callee to its declaration + in the framework package's type declarations — so an alias, a + re-export, or a wrapper cannot escape (a wrapper's internal call + to the primitive is itself a detected call site); a dynamic + property access on the framework's registration object, or a + callee the type-checker cannot resolve, is a scan FAILURE naming + the site, never a silent skip — with one control per case + (aliased call detected; wrapped call detected; dynamic-name + access fails). **Primitive classification and path + resolution:** the primitive list classifies every registration + primitive as **path-bearing** (route registrations and mounts + that take a path or prefix argument) or **non-path-bearing** + (hook registrations such as request-lifecycle hooks, and plugin + registrations with no path/prefix option); the classification + is part of the asserted list, and an unclassified primitive is + a witness failure. Path resolution applies to path-bearing + sites; a non-path-bearing site is inventoried as a hook or + plugin site with no path witness and is never a route entry by + itself. Each hook registration's classification records the + adapter lifecycle phase its registration primitive names (the + phase is part of the primitive's identity in the asserted + list); §7.7(d)(5)'s lifecycle-order map resolves raw-mount + dispatch points to lifecycle stages through exactly this + recorded phase, and a hook registration whose phase the + checker cannot resolve is a scan failure naming the site. A hook or plugin callback that routes requests by URL — + dispatching on the request path inside the callback, as the raw + auth and MCP mounts do — makes the enclosing registration a + **raw mount**: it must either appear in the §4.4 delegation + allowlist or carry §1.6 records, **one per (dispatched prefix × + accepted HTTP method) branch**. The checker resolves BOTH + dispatch dimensions: the prefix test against a compile-time + string constant, AND the callback's method dispatch — every + comparison of the request method against a constant, in the + callback and its callee closure (the §7.7(d)(4) call-graph + rules) — enumerating the accepted (prefix, method) pairs. Each + pair carries a record whose path template equals the prefix + constant and whose HTTP method equals the branch's method + constant; an accepted branch with no matching record, a record + no branch accepts, and a dispatch comparison (prefix or method) + the checker cannot resolve to a constant are each a scan + failure naming the site. **Prefix-boundary semantics**: a raw + mount's record covers exactly the URLs equal to its prefix or + extending it at a `/` segment boundary, and the code predicate + must implement exactly that match — a predicate that also + accepts a longer non-boundary URL (a bare prefix test accepting + `/mcpfoo` under the prefix `/mcp`) is a scan failure naming the + site: the code is corrected to a boundary test; the widened + match is never absorbed by documentation. + **Method-partition exhaustiveness**: enumerating constant + method comparisons is necessary but not sufficient — the + checker proves the callback's dispatch partitions its control + flow. Every path from callback entry to a request-terminating + action (a response emission or a forwarding of the request) is + classified as exactly one of: (i) a **URL fall-through path** — + dominated by the NEGATION of the mount's URL-dispatch predicate + (the request does not match the dispatched prefix under the + boundary semantics above): it forwards the request ONLY to the + hook chain's continuation (the callback's done/next + continuation, handing the request to later hooks and the + framework router), emits nothing, needs no method pinning, and + contributes no raw record — the raw MCP hook's prefix-miss + branch that returns the request to the router is this class, + not a scan failure; a URL-missed path that instead forwards + into a handler, another dispatcher, or any package IS a scan + failure naming the site; (ii) an **acceptance path** — + dominated by the mount's URL-dispatch predicate holding AND + positive equality tests of the request method + against string constants that pin the path to exactly one + method; that (prefix, method) pair carries a record; (iii) a + **refusal path** — the ONLY classification where a complement + test (`method !== X`) or an unguarded method test is + admissible: the URL-dispatch predicate holds, and the + path terminates in a constant-status refusal emission (by the + §7.7(d)(4) emission-site rules) and forwards the request + nowhere; or (iv) a **scan failure** — a URL-accepted + complement-guarded or + unguarded path that forwards the request (into a handler, + another dispatcher, or any package — including back into the + hook continuation, which for a URL-accepted request would + double-dispatch it) accepts an unbounded + method set and fails naming the site. The method partition + therefore binds exactly the URL-accepted branch of the + callback; the URL-miss branch is bound only to forward to the + continuation and emit nothing. One carve-out mirrors + the §7.7(d)(4) delegated-emission rule: a forwarding edge into + a non-production package may instead carry a **declared + external method contract** — the accepted-method set the + external package's pinned version documents for that entry + point, declared alongside the edge's external status + contract; the branch then contributes exactly one + (prefix × method) record per documented method, the branch's + record set equal to the declared set in both directions, and a + forwarding edge with neither dominating single-method tests + nor a declared method contract is a scan failure naming the + edge (the live MCP existing-session path, which forwards + requests of any method into the external transport, is the + migration example; its sibling non-`POST` complement branch + must classify as a refusal path). For path-bearing sites, the effective + path is computed by the framework's composition rules before + comparison: the class-level prefix (a string or an array — an + array expands to one route per element) concatenated with the + method-level suffix (which may be empty — the default empty + path contributes nothing), then normalized (exactly: join + segments with `/`, collapse duplicate separators, strip one + trailing slash, ensure one leading slash); the normalized + string is compared for exact equality with the route's §1.6 + record path template, normalized by the same rule. A path + argument, prefix element, or suffix the checker cannot resolve + to a compile-time string constant (`app.get(computedPath(), +handler)`, a computed decorator value) is a scan FAILURE naming + the site, never an inventory entry with an undefined path — + with one control per case (computed path argument fails; + computed decorator path fails; an array class prefix expands to + all elements; an empty method suffix composes with its class + prefix; hook-internal dispatch on a non-constant prefix fails; + a resolved path unequal to its record's template fails; a + raw-mount method branch with no matching record fails; a + raw-mount record no branch accepts fails; a non-constant method + comparison fails; a non-boundary prefix predicate — accepting + `/mcpfoo` under `/mcp` — fails; a complement method branch that + forwards the request fails; a complement branch terminating in + a constant-status refusal is admitted as a refusal path; a + forwarding edge with a declared external method contract + yields exactly the contract's records; a forwarding edge with + neither single-method domination nor a declared method + contract fails). 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 — over + **server-side sites only**, with a mechanical boundary: a + subscribe/emit call site is server-side iff its receiver + expression's type resolves, through the type-checker, to the + server package's declarations (the `socket.io` package's + `Server`, `Namespace`, or server-side `Socket` types); a + receiver typed by the `socket.io-client` package is a client + site, excluded from BOTH direction inventories (the TUI socket + hook and the Discord/Telegram plugin clients are client sites); + a receiver whose type the checker cannot resolve to exactly one + of the two packages is a scan failure naming the site. + **Decorator sites** have no receiver expression, so the boundary + there is compile-time package identity instead: a method + decorated with the socket framework's subscribe decorator — + resolved by the type-checker to the framework socket package's + `SubscribeMessage` declaration — inside a class decorated with + that same package's gateway decorator (`WebSocketGateway`) is a + server-side inbound handler site; its event name is the + subscribe decorator's argument, which must resolve to a §4.6 + registry member by symbol reference exactly as call-site names + must; a subscribe decorator whose argument does not so resolve, + or one on a class without the gateway decorator, is a scan + failure naming the site. Controls: a client emit is not + inventoried as server-emitted; a client subscription is not + inventoried as an inbound handler; an unresolvable receiver + fails; a decorator-declared handler IS inventoried as an + inbound site; a subscribe decorator with a re-typed string + argument fails. The comparison element is the + (direction, name) pair (§4.6), the registry of the matching + direction is the comparison target, and any non-literal or + non-registry event name at a server-side site is a scan + failure. **Event-set closure:** the + inventoried event set (both directions) and the set of `Events:` + sub-items across all `ENDPOINTS.md` entries are equal as sets of + (direction, name) pairs — + every inventoried event appears as an item in its owning family's + entry, and every item corresponds to an inventoried event; an + inventoried event absent from the file, and a documented event the + inventory does not emit, each fail the witness, with one control + per direction. The closure has a THIRD leg: the inventoried + server-event set also equals the §4.6 registry union, as sets of + (direction, name) pairs, in both directions — a server-side site + using a non-registry name is already a scan failure above, and a + registry event no server-side site subscribes to or emits (a + stale registry event, which §7.5(j)'s ownership-map checks alone + cannot catch because a mapped family entry can exist without the + item) fails the witness naming the event — with a + stale-registry-event control. 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:** the content check partitions by + the §2.2 entry kind, the same partition §7.5(k) enforces. A + **surface entry** carries the §2.2 surface fields — behavior, + operation links, authentication requirement, owning-contract + link, error semantics — presence-checked mechanically against + the §2.2 entry grammar. A **delegated-surface entry** carries + exactly `Summary:` and a `Delegated:` field whose four §2.2 + sub-items are each present and well-formed (their field-by-field + equality to the §4.4 allowlist is §7.5(g)'s check); the surface + fields are neither required of nor permitted on it. An + **exclusion entry** carries exactly `Summary:` and an + `Excluded:` field with at least one well-formed §2.2 sub-item + (the two-way mirror to the §4.1 script list is §7.5(l)'s + check). A content check that demands a surface field of a + non-surface entry, or that passes a non-surface entry whose own + kind's field content is missing or malformed, fails; + every error response documented in `OPENAPI.yaml` uses a + code from the contract 5 §4.2 closed enums **as exported from the + shared types package** (contract 5 §4.1's package; the + implementing PR that lands the enums names the exports, and this + witness compares against those exports — §6.5 orders the + dependency), 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` + in-document only (an external `$ref` is a canonicalization + failure naming the reference), with cycle detection that replaces + a re-entered `$ref` by a canonical cycle marker carrying the + cycle's entry pointer (deterministic, so two documents with the + same cycle structure canonicalize identically), 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 + **operation** — each (path, method) pair independently, so a + multi-method legacy path yields one match per method: deep + structural equality of the canonicalized request and + response schemas (an absent request body matches only the + canonical no-body marker; a response entry with no body schema — + including a legacy response carrying only a description with no + `content` — canonicalizes to the same explicit no-body marker, so + the deep equality is defined for body-less responses and a + no-body response matches only a no-body response; equality is + evaluated over the + canonical forms, so no-body vs any schema is a difference), + equality of the security/authentication + requirement sets — compared through the §1.6 + `ApiAuthClass` → security-requirement mapping, i.e. the legacy + operation's EFFECTIVE security requirement set is compared with + the set the mapping assigns to the covering route's declared + auth class, where "effective" resolves OpenAPI inheritance + before comparison: an operation object with no `security` + member inherits the legacy document's top-level `security`; an + operation-level `security` member, including the explicit empty + array, overrides the document default; step (a) materializes + the effective set onto every canonicalized operation, so the + comparison never reads the document level — + and equality of the documented error semantics + compared in a **common representation with two components** — the + HTTP error-status set (the legacy operation's documented non-2xx + status codes vs the covering operation's emitted non-2xx status + codes) and the taxonomy-code set as plain strings (any error-code + enum values in the legacy response schemas vs the covering + route's §1.6 error-code set; where the legacy schemas express no + taxonomy codes, this component's comparison target is the + covering route's §1.6 record alone, i.e. the record is asserted + present and closed, exactly as the permissions dimension below); + 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 — unique by §7.5's + one-entry-per-`operationId` rule, selected as the entry containing + the `operationId` link of the operation that matched the legacy + operation 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-operation 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 grammar via a Markdown AST + and rejects, exactly: (a) an entry violating the grammar — a + non-`##` entry heading, a duplicate family name, a missing + required field label under the §2.2 per-kind rules (including + `Operations:` absent from a family with operations, or present + on an event-only family), a duplicated field label, or an + `Events:` field on a non-event family; (b) an operation mention that is not + a Markdown link in the §2.2 anchor grammar (link text = + `operationId`, target = `./OPENAPI.yaml#operation/{operationId}`); + (c) any of the reserved tokens + `requestBody`, `responses`, `parameters`, `schema`, or + `properties` appearing in ANY Markdown node type — fenced code, + indented code, HTML block, table, inline code, or prose — + anywhere in the file (schema content belongs only in + `OPENAPI.yaml`); + (d) an `operationId` link that does not resolve, per the §2.2 + resolution rule; (e) an event + item not matching the §2.2 `inbound {name}` / `outbound {name}` + syntax or whose event name is not in the §4.6 registry's matching + direction; (f) an `operationId` linked from more than one entry; + (g) a delegated-surface entry whose `Delegated:` sub-item + values are not field-by-field + equal to the §4.4 allowlist entry (string comparison after + whitespace trimming), a §4.4 allowlist entry with no + delegated-surface entry, a `Delegated:` prefix naming no + allowlist entry, or a malformed `Delegated:` sub-item (not the + exact §2.2 four-sub-item syntax); (h) a (direction, event-name) item appearing + more than once in the file, whether within one entry or across + entries; (i) an event item in an entry other than the one the + §4.6 ownership map assigns to that event; (j) a §4.6 registry + event absent from the ownership map, a map key that names no + registry event (a stale key — the map's domain must exactly + equal the registry union), a family name in the map with no + entry in the file, or an event name present in both the inbound + and outbound registries (the §4.6 disjointness rule); (k) an + entry that is not exactly one §2.2 kind — a surface entry + carrying neither `Operations:` nor `Events:` (the + non-empty-surface rule — a zero-surface family documents + nothing), an entry mixing kind-defining labels (`Delegated:` + or `Excluded:` together with `Operations:`/`Events:`, or with + each other), a delegated-surface or exclusion entry carrying + any label beyond its exact §2.2 set, or a second exclusion + entry in the file; (l) an exclusion mismatch — an `Excluded:` + sub-item not field-by-field equal to a §4.1 generation-script + exclusion entry (string comparison after whitespace trimming), + a script exclusion entry with no sub-item, a sub-item with no + script entry (both directions), or a malformed sub-item (not + the exact §2.2 `{HTTP-method} {path-template} {reason-class} +{guard}` syntax). 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. +7. **Source-consistency witness:** for the record fields the + registration derivation (§1.6) does not structurally guarantee, + the witness cross-checks record against code: (a) **derivation + closure** — every production route's installed authentication + guard and permission enforcement originate from its record's + declared `ApiAuthClass` and permission rule reference (static + assertion that no route installs a guard or permission check + through any path other than record derivation), with a control + route that installs a bypassing guard failing the assertion; + (b) **error-code closure** — per route family, the set of + contract 5 §4.2 taxonomy codes referenced by the family's + production code (by symbol identity against the shared-types + enum exports) equals the union of the family's records' + error-code sets, in both directions, with one control per + direction (a code emitted but not declared; a code declared but + never emitted); (c) **response-map closure** — each record's + error entries are exactly the HTTP statuses its error-code set + maps to under the contract 5 §4.2 status mapping (mechanical + recomputation), with a mutated-record control; (d) + **emitted-status closure** — a TOTAL, bidirectional witness per + production route. The route's **status-site set** is the union + of five site classes, each mechanically identified: (1) + auth-refusal statuses — the refusal-status set the §1.6 mapping + table assigns to the route's declared `ApiAuthClass` (the guard + is record-derived, so its refusal statuses attach to every + route of the class); (2) interception-component statuses — a + closure over EVERY binding scope the framework offers, not only + the global ones: (i) global-by-call — components installed by + the composition root's global binding calls (`useGlobalPipes`, + `useGlobalFilters`, `useGlobalInterceptors`, + `useGlobalGuards`), enumerated by the §7.2 primitive scan, + whose list names those calls; (ii) global-by-provider — + components registered under the framework's DI binding tokens + (`APP_PIPE`, `APP_FILTER`, `APP_INTERCEPTOR`, `APP_GUARD`), + identified by symbol identity against the framework's token + declarations; (iii) controller-scoped and (iv) route-scoped — + components bound by the framework's binding decorators + (`UsePipes`, `UseFilters`, `UseInterceptors`, `UseGuards`) at + class and at method level, resolved per route. Each bound + component carries a status set asserted against the + component's own source by the class-(4) emission-site rules + (the global validation pipe's 400; a route-scoped exception + filter's mapped status — the live interaction controller's + runtime-approval-denied filter emitting 403 is the migration + example), attributed to exactly the routes its binding scope + covers WITHIN the component's execution pipeline. + **Execution-pipeline attribution**: the production surface + partitions into **framework-routed routes** — registered + through the framework's routing primitives (decorated + controllers/gateways and framework route registrations) and + dispatched by the framework router — and **raw-mounted + routes** — the §7.2 raw-mount records, dispatched by a hook + callback before or outside the framework router. Framework + interception components in all four binding scopes execute + only in the framework dispatch pipeline, so they attribute to + framework-routed routes ONLY: global (by call or by DI token) + to every framework-routed route, controller-scoped to the + declaring class's routes, route-scoped to that route alone; a + framework component's status attributed to a raw-mounted + route is a phantom — the component never runs for it — and + the bidirectional comparison rejects it exactly as it rejects + any other phantom map status. A raw-mounted route's + interception statuses come instead from the **adapter hook + chain**: every non-path-bearing hook registration (§7.2) + whose callback can emit a response (per the class-(4) rules + over the callback closure) is an interception component + attributed to exactly the routes whose URLs its dispatch + predicate accepts — a hook with no URL dispatch attributes to + every route, framework-routed and raw alike; a raw mount's + own dispatch attributes to its (prefix × method) records + alone (the live raw MCP hook, which answers before the + framework router runs and is therefore untouched by the + framework's global pipes and guards, is the motivating + example). A binding site in any of the four scopes the scan + does not enumerate, a bound component with no source-asserted + status set, and an emitting hook registration with no + dispatch-derived route attribution, are each a witness + failure naming the site; (3) + decorator status metadata — the framework's + route-level status metadata (explicit status-code decorators, + each method decorator's default status, and the implicit + default success status where no metadata overrides it), + resolved per route; (4) handler-closure emissions — every + status-emission site in the handler and its callee closure, + where the closure is COMPUTED, not assumed: the type-checker + call graph starting at the handler declaration, resolving each + call site's callee declaration, following every edge into + production source (the three roots) to a fixed point, not + following edges into non-production packages — with ONE + carve-out: a call edge into a non-production package that + passes the framework reply/response object or the raw Node + request/response pair as an argument is a **delegated-emission + site**, because the external callee can emit statuses the + closure cannot see; such a site requires a declared external + status contract — the statuses the external package's pinned + version documents for that entry point, written into the + route's record response map and attributed to the site (the + live MCP raw handler passing the response into the external + MCP transport is the migration example) — and a + delegated-emission site with no declared external status + contract is a FAILURE naming the site, never a silent cut — + and FAILING — + naming the site, never silently skipping — on any call site + whose callee the checker cannot resolve. Emission sites are + identified by symbol identity against the framework's + reply/response type declarations (the status setter, + send-with-status calls, status-carrying error constructors) AND + against Node's `http.ServerResponse` declarations (`writeHead`, + `statusCode` assignment), so raw handlers are covered; (5) + framework and adapter defaults — an EXHAUSTIVE default + inventory, not a declared-only list: the implementing PR + classifies, as response-emitting (with the emitted status and + triggering condition) or non-emitting, every member of a + closed domain with two mechanically enumerated parts — (i) + every property key of the pinned framework + application-options type AND the pinned adapter + construction-options type, resolved from the packages' type + declarations, and (ii) every key of every options object the + composition root actually passes to the framework/adapter + construction and server-setup calls (call sites identified by + symbol identity). The classification is TOTAL: a domain key + absent from the inventory is a witness failure naming the + key, so an applicable default cannot be omitted by silence + (the adapter-level body-size limit set at the composition + root — an adapter rejection path the revision-7 declared-only + rule could silently omit — is the migration example). The + framework's documented route-level default statuses (the + default unhandled-error status, the default + validation/parse-failure statuses, the default not-found on a + matched mount) are fixed members of the inventory regardless + of configuration. Each response-emitting entry carries BOTH a + citation into the pinned version's source or documentation + for that behavior AND a runtime probe that triggers the + default and asserts the observed status; a response-emitting + entry missing its citation or its probe is a witness failure. + Each entry's status attributes by the §7.7(d)(2) + execution-pipeline rule, never by configuration scope alone: + every response-emitting entry names its emitting pipeline + stage — the lifecycle point where the pinned version's + mechanism runs (connection handling, request-line/header + processing, body parsing, routing, response serialization) — + and the status attributes to framework-routed routes and to + exactly those raw-mounted routes whose §7.2 dispatch point + executes AFTER the emitting stage in the adapter's lifecycle. + The AFTER relation is decided against a **pinned + lifecycle-order map**, an explicit witness artifact: the + totally ordered list of the pinned adapter version's documented + request-lifecycle phases — connection handling, + request-line/header processing, each named hook phase the + pinned version documents, body parsing, routing and handler + dispatch, response serialization — each map entry carrying a + citation into the pinned version's lifecycle documentation, + and the map itself failing the witness if any two of its + entries lack a documented relative order. Every raw mount's + §7.2 dispatch point resolves to the map entry of the hook + phase its registration primitive names (§7.2 records the + phase as part of each hook registration's classification), and + every response-emitting entry resolves its emitting stage to a + map entry through its citation; AFTER means strictly later in + the map's total order. A dispatch point or emitting entry that + resolves to no map entry, or a dispatch point whose map entry + EQUALS the emitting entry's, is a witness FAILURE naming the + site and the entry — attribution is never assumed in either + direction for an unordered or same-stage pair. The + implementation repairs such a failure either by making the + order resolvable or by adding, for exactly that (route, entry) + pair, a per-pipeline probe pair in the style of the body-size + pair below — one triggering request down each pipeline — whose + observed outcomes then decide the attribution and are recorded + in the witness output; an attribution carried by neither the + map order nor a recorded probe pair is a phantom. + A raw mount whose hook terminates or forwards the request + BEFORE a stage runs never carries that stage's status: the raw + MCP mount hijacks in a pre-parsing hook and hands the unparsed + request to the external transport, so the adapter's + body-parsing rejection cannot fire there, and attributing the + body-size-limit status to that route (or requiring it of the + route's record) is a phantom the bidirectional comparison + rejects. If a raw-mounted route's record declares a + body-limit-shaped status, the route's own callback chain must + contain a status site for it — a server-wide option is not + that site. Every site's status argument must resolve + to a literal or compile-time constant; an unresolvable status + is a FAILURE naming the site. Both directions: every resolved + status is a member of the route's record response map, AND + every response-map status is produced by at least one site in + the status-site set — a map status no site produces (a phantom) + is a failure naming the route and status. Controls: a handler + emitting an unclassified status (`reply.status(418)`) fails; a + computed status argument fails; an unresolvable call edge + fails; a guard refusal status absent from the map fails; a + global-pipe status absent from the map fails; a route-scoped + filter status absent from the map fails; a component bound + through a DI binding token is detected and attributed; a + framework global component's status attributed to (or + required of) a raw-mounted fixture route fails as a phantom — + the raw-hook control; a fixture adapter hook emitting a + constant status with no URL dispatch is attributed to every + route, raw mounts included; a + fixture handler passing the reply object into a stub external + package with no declared external status contract fails; a + decorator + status absent from the map fails; a raw `writeHead` status + absent from the map fails; a phantom map status fails; an + option key present in the pinned adapter options type but + absent from the default inventory fails; a configured + composition-root option key absent from the inventory fails; + the body-size-limit probe is a PAIR driving one oversized + request down each pipeline: the framework-routed fixture + observes the adapter rejection status, and the pre-parsing + raw-mounted fixture observes NO adapter rejection (the request + reaches the raw callback unparsed) — with the body-limit + status attributed to (or required of) the raw route failing as + a phantom; the + implicit default success status is attributed to the route and + must appear in its response map; (e) **guard-equivalence** — + for each `ApiAuthClass`, the witness asserts the derived + guard's acceptance structure against the §1.6 mapping table's + requirement-set shape: the `admin` guard accepts a request + satisfying EITHER named scheme and refuses one satisfying + neither (drive both alternatives and the refusal); the + `federation` guard refuses a request satisfying only one of its + two schemes and accepts one satisfying both (drive all three + cases); each single-scheme class accepts its scheme and refuses + its absence; `none` installs no guard. Every "satisfying" and + "not satisfying" request is constructed from the scheme's §1.6 + credential-predicate binding (location and acceptance + predicate), so the cases exercise the declared binding, not an + implementer's stand-in. **Refusal-branch closure**: the witness + additionally ENUMERATES the guard's refusal branches — every + status-emission site in the guard and its callee closure, + computed by the §7.7(d)(4) rules — and drives each branch with + a request constructed to take exactly that branch, asserting + the emitted status equals the branch's resolved status (the + live admin guard's distinct 401 and 403 paths are the + motivating example: representative alternatives are not + enough); the class's written §1.6 refusal-status set is + asserted EQUAL to the enumerated branch-status union in both + directions — a written status no branch emits, and a branch + status outside the written set, each fail. **Mixed + presentations**: for a multi-credential class the witness + drives each mixed case — an invalid credential of the + earlier-evaluated scheme presented together with a valid + credential of the later scheme, and the converse — asserting + the outcome the §1.6 declared evaluation order requires (the + live admin guard rejects an invalid bearer before considering + a valid session; that precedence must be declared and is + asserted, not discovered); a guard whose mixed-presentation + behavior diverges from the declared order fails. Controls: a + mutated table copy assigning `admin` a single-scheme + requirement set is detected while the guard still accepts the + alternation; a mutated written refusal-status set missing one + branch's status is detected. This ties the OpenAPI mapping to + the live guard semantics rather than to a lint-valid but wrong + table. + +## 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 over the three named roots, the closed route-metadata + record with its per-field completeness predicate, its + record-as-registration-input derivation rule, the pinned + six-value `ApiAuthClass` enumeration with its closed + security-requirement mapping, and the event-record + field semantics — and the §4.4 delegation allowlist with + its exactly-one-mount and all-module below-prefix rules and + field-by-field mirror equality, the §4.5 profile enumeration and + condition field, and the §4.6 typed event registry with its + two-record `as const` shape, symbol-reference rule, and total + single-valued event-ownership map. +12. The §7 witness machinery as such: the §7.2 independent inventory + with its registration-primitive list, symbol-identity detection + with its failure-not-skip rule, and bidirectional + family/operation checks, the §7.3 content and taxonomy witness, + the §7.5 checker script with its twelve reject rules, the §7.6 + pinned validator (Redocly CLI), the §7.7 source-consistency + witness, the per-field fail-closed + controls in §7.1, and the §7.4 canonicalization/derivation/ + covering-entry algorithm. +13. The §2.2 entry grammar as exact syntax (heading form, literal + field labels and cardinality, link and anchor grammar, event-item + syntax, the one-entry-per-`operationId` rule, and the + reserved-token prohibition including its prose strictness). +14. The §4.1 exclusion-list eligibility rule (closed, machine-checked + against every production-profile inventory pass). +15. The §6.5 contract 5 ordering dependency and its contingency + amendment rule. +16. The revision-5 closure rules: the `admin` and `federation` auth + classes representing the live alternation and mTLS-plus-grant + guards (§1.6); the response-map no-body marker and its §7.4 + body-less canonicalization; the §7.2 path-argument resolution + rule (compile-time constant, equal to the record's path + template, failure otherwise); the §7.7(d) emitted-status + closure; and the event-set closure machinery — the §4.6 + ownership map, the §2.2 owning-family and at-most-once item + rules, the §7.2 bidirectional event-set equality, and §7.5 + reject rules (h)–(j). +17. The revision-6 closure rules: the pinned `ApiAuthClass` → + security-requirement table with its OpenAPI OR/AND + requirement-set shapes, per-class refusal-status sets, and + injectivity, plus the §7.7(e) guard-equivalence witness + (§1.6); the §7.7(d) five-class status-site algorithm with + computed call-graph closure, unresolved-edge failure, Node + raw-sink symbols, and the bidirectional no-phantom + comparison; the §7.2 primitive classification + (path-bearing/non-path-bearing), the raw-mount + hook-dispatch rule, and the path composition and + normalization algorithm; the §7.2 server/client socket + boundary by package type identity, the (direction, name) + comparison element, registry disjointness, and the + exact-domain ownership rule (§4.6, §7.5); the §7.4 + document-level security inheritance resolution; and the §2.2 + conditional `Operations:` rule for event-only families. +18. The revision-7 closure rules: the §7.7(d)(2) + interception-component closure over all four binding scopes + with scope-exact attribution and unenumerated-binding failure, + and the §7.2 primitive-list extension naming the global binding + calls and DI binding tokens; the §7.2 raw-mount + per-(prefix × method) record rule with segment-boundary prefix + semantics; the §1.6 requirement-object exactness, scheme → + credential-predicate bindings, declared evaluation order, and + computed refusal-status-set definition, with §7.7(e)'s + refusal-branch closure and mixed-presentation cases; the §7.2 + decorator-site server boundary by socket-package identity and + the registry-union third leg of the event-set closure; the + §7.7(d)(4) delegated-emission rule with its pinned external + status contract and the §7.7(d)(5) citation-plus-probe rule + for framework defaults; and the §2.2 non-empty-surface rule + with §7.5 reject rule (k). +19. The revision-8 closure rules: the §7.7(d)(2) + execution-pipeline attribution — framework interception + components to framework-routed routes only, adapter + hook-chain components attributed by dispatch predicate, + phantom rejection for raw-mounted routes, with the raw-hook + control; the §7.2 method-partition exhaustiveness rule with + its acceptance/refusal path classification, forwarding + complement-branch failure, and declared external method + contract; the §7.7(d)(5) exhaustive default inventory (total + classification over the pinned options-type keys and the + configured composition-root options, citation-plus-probe per + emitting entry); and the §2.2 entry kinds with the + `Delegated:`/`Excluded:` field syntax, the revised §7.5(k) + kind-partition rule, and the §7.5(l) exclusion mirror. The + revision-9 refinements: per-stage pipeline attribution of + inventory defaults with the two-pipeline body-size probe pair + (§7.7(d)(5)); the per-kind §7.3 content partition; and the + §7.2 URL fall-through classification scoping the method + partition to the URL-accepted branch. The revision-10 + refinement: the pinned lifecycle-order map with hook-phase + recording in the §7.2 primitive classification, the strict + stage-order AFTER rule, the unresolved/same-stage failure + rule, and the per-(route, entry) probe-pair fallback + (§7.7(d)(5)). + +## 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?