Files
stack/docs/requirements/api-artifacts.md
T
2026-08-27 19:18:39 +00:00

1361 lines
81 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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?