Files
stack/docs/requirements/api-artifacts.md
T
2026-08-26 21:05:54 -05:00

681 lines
40 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).
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 carried by
the generation script (§3.1): `none` maps to the empty security
array, and each other value maps to a named security-scheme
requirement set the implementing PR defines once in
`OPENAPI.yaml`'s components; the mapping is total over the
enumeration, and §7.4(b) compares security through it; 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:` — each label at most
once per entry; `Summary:`, `Operations:`, `Auth:`,
`Permissions:`, and `Errors:` REQUIRED in every entry, `Events:`
REQUIRED for a family with Socket.IO events and absent otherwise.
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.
`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 naming the mount, the
delegating engine, and that engine's own contract — not
re-documented route-by-route. **Socket.IO events are covered in
both directions:** subscribed (inbound handler) events and
server-emitted events. Test-only and development-only surfaces are
excluded via an exclusion list that is explicit in the generation
script and enumerated in `ENDPOINTS.md`, never implicit. The
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.
## 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 — 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). **Path-argument resolution:** for every detected
registration call site and every route decorator, the path
argument must be a string literal or resolve through the
type-checker to a compile-time string constant; the resolved
string is then compared for exact equality with the route's §1.6
record path template. A path argument the checker cannot resolve
to a constant (`app.get(computedPath(), handler)`, a decorator
with a computed path 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; a resolved path unequal to its record's
template 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 — with the
§4.6 registry as the comparison target and a scan failure on any
non-literal or non-registry event name. **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 —
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. Additionally, every HTTP
route family in `OPENAPI.yaml` has an `ENDPOINTS.md` entry, and
every `ENDPOINTS.md` operation reference resolves to an existing
`operationId` — both directions, mechanical.
3. **Content and taxonomy witness:** every `ENDPOINTS.md` entry
carries the §2.2 required fields — behavior, operation links,
authentication requirement, owning-contract link, error semantics
— presence-checked mechanically against the §2.2 entry grammar;
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 security requirement set is compared with the set the
mapping assigns to the covering route's declared auth class —
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, 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 fields are not field-by-field
equal to the §4.4 allowlist entry (string comparison after
whitespace trimming); (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, or mapped to a family name
with no entry in the file. 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** — for every production route, every
status-emission site in the handler and its production-source
callee closure (each site identified by symbol identity against
the framework's reply/response type declarations — the status
setter, shorthand send-with-status calls, and framework error
constructors carrying a status) must resolve, as a literal or a
compile-time constant, to a status present in the route's record
response map; a status argument the checker cannot resolve, or a
resolved status absent from the record's response map, is a
witness FAILURE naming the site — with controls: a handler
emitting an unclassified status (`reply.status(418)`) fails; a
computed status argument fails; a framework-default emission
path (implicit 200) is attributed to the route and must appear
in its response map.
## 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 ten 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).
## 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?