docs: API contract artifacts contract (S2 contract 9) #1443

Merged
fred merged 10 commits from contract/api-artifacts into next 2026-08-27 19:18:39 +00:00
Showing only changes of commit 82afd633a2 - Show all commits
+231 -40
View File
@@ -129,6 +129,36 @@ 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).
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).
@@ -207,13 +237,38 @@ documentation placement rules stay with the docs atlas
`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. The table additionally assigns each
class a closed **refusal-status set** — the HTTP statuses its
record-derived guard emits on refusal — consumed by §7.7(d); the
implementing PR states each set from the live guard source, and
the §7.7(e) guard-equivalence witness ties both the
requirement-set shapes and the refusal statuses to the guards'
actual semantics. The mapping is total over the enumeration and
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
@@ -248,7 +303,10 @@ documentation placement rules stay with the docs atlas
Socket.IO events — such a family MUST carry `Events:`, which is
how it satisfies the grammar with no operations to link);
`Events:` REQUIRED for a family with Socket.IO events and absent
otherwise.
otherwise. **Non-empty surface**: every entry carries at least
one of `Operations:` or `Events:` — a family with neither
documents nothing and 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
@@ -479,7 +537,13 @@ scanned source roots, and compared files.
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
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
@@ -509,12 +573,26 @@ scanned source roots, and compared files.
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,
each record's path template equal to a string constant the
checker resolves from the callback's dispatch comparison (the
prefix test against a compile-time constant); a dispatch
comparison the checker cannot resolve to a constant is a scan
failure naming the site. For path-bearing sites, the effective
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. 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
@@ -532,7 +610,11 @@ handler)`, a computed decorator value) is a scan FAILURE naming
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). The inventory equals the documented set in both
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). 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
@@ -547,10 +629,24 @@ handler)`, a computed decorator value) is a scan FAILURE naming
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 — with
controls (a client emit is not inventoried as server-emitted; a
client subscription is not inventoried as an inbound handler;
an unresolvable receiver fails). The comparison element is the
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
@@ -562,7 +658,15 @@ handler)`, a computed decorator value) is a scan FAILURE naming
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
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.
@@ -669,7 +773,10 @@ handler)`, a computed decorator value) is a scan FAILURE naming
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). The witness
and outbound registries (the §4.6 disjointness rule); (k) an
entry carrying neither `Operations:` nor `Events:` (the §2.2
non-empty-surface rule — a zero-surface family documents
nothing). 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.)
@@ -703,13 +810,32 @@ handler)`, a computed decorator value) is a scan FAILURE naming
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) global-interceptor statuses — every
globally registered pipe, filter, or equivalent
bootstrap-installed interceptor is enumerated by the same
registration-primitive scan as §7.2, and each carries a
declared status set the witness asserts against its source (the
validation pipe's 400, for example), attributed to every route
it covers; (3) decorator status metadata — the framework's
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: global to every route, controller-scoped to the
declaring class's routes, route-scoped to that route alone. A
binding site in any of the four scopes the scan does not
enumerate, and a bound component with no source-asserted
status set, 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),
@@ -719,7 +845,20 @@ handler)`, a computed decorator value) is a scan FAILURE naming
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, and FAILING —
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
@@ -728,8 +867,15 @@ handler)`, a computed decorator value) is a scan FAILURE naming
against Node's `http.ServerResponse` declarations (`writeHead`,
`statusCode` assignment), so raw handlers are covered; (5)
framework error defaults — the framework's own route-level
default error statuses, where the implementing PR declares them
route-attributable. Every site's status argument must resolve
default error statuses (the default unhandled-error status, the
default validation/parse-failure statuses, the default
not-found on a matched mount): each default the implementing PR
declares route-attributable carries BOTH a citation into the
pinned framework version's source or documentation for that
default AND a runtime probe that triggers the default and
asserts the observed status; a declared default missing its
citation or its probe is a witness failure — declaration alone
attributes nothing. 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
@@ -739,7 +885,12 @@ handler)`, a computed decorator value) is a scan FAILURE naming
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 decorator
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
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; the
implicit default success status is attributed to the route and
@@ -752,12 +903,36 @@ handler)`, a computed decorator value) is a scan FAILURE naming
`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. The refusal cases also
assert the emitted status is in the class's §1.6 refusal-status
set. Control: a mutated table copy assigning `admin` a
single-scheme requirement set is detected while the guard still
accepts the alternation. This ties the OpenAPI mapping to the
live guard semantics rather than to a lint-valid but wrong
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)
@@ -805,7 +980,7 @@ severable:
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
the §7.5 checker script with its eleven 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/
@@ -844,6 +1019,22 @@ severable:
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).
## Ruling request