Files
stack/docs/requirements/api-artifacts.md
T

81 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 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 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).

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 + NF1NF5): 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 + NF1NF4 + 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 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.
  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?