docs: api-artifacts contract revision 7 (luna r6 residuals F1/F2/F4/F9 + NF1-NF4 + NF6)
ci/woodpecker/pr/ci Pipeline was canceled

This commit is contained in:
fred
2026-08-26 21:58:49 -05:00
parent 903c056b0f
commit 82afd633a2
+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 `Operations:` becomes conditional so an event-only family is
grammatical (NF5). 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 This contract binds the artifact definitions (§2), the production
model (§3), coverage obligations (§4), legacy migration (§5), phase model (§3), coverage obligations (§4), legacy migration (§5), phase
timing (§6), witnesses (§7), and disclosed drafting additions (§8). 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 `federation` → exactly one requirement object naming both the
mTLS scheme and the federation-grant scheme (the conjunction as mTLS scheme and the federation-grant scheme (the conjunction as
OpenAPI AND); `bootstrap` → exactly one requirement object OpenAPI AND); `bootstrap` → exactly one requirement object
naming the bootstrap scheme. The table additionally assigns each naming the bootstrap scheme. **Requirement-object exactness**:
class a closed **refusal-status set** — the HTTP statuses its each requirement object in the table contains EXACTLY its named
record-derived guard emits on refusal — consumed by §7.7(d); the scheme key(s) and no other key, and every scheme key maps to
implementing PR states each set from the live guard source, and exactly the empty scope array (these artifacts define no OAuth
the §7.7(e) guard-equivalence witness ties both the scopes); the generator emits these exact objects and §7.4(b)
requirement-set shapes and the refusal statuses to the guards' compares against them — a requirement object with an extra key, a
actual semantics. The mapping is total over the enumeration and 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 injective — no two classes map to the same requirement set; the
permission rule reference (owning contract plus rule identifier); permission rule reference (owning contract plus rule identifier);
and the error-code set, drawn from the contract 5 §4.2 closed 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 Socket.IO events — such a family MUST carry `Events:`, which is
how it satisfies the grammar with no operations to link); how it satisfies the grammar with no operations to link);
`Events:` REQUIRED for a family with Socket.IO events and absent `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 Field contents: `Summary:` — the family's behavior summary, free
prose; `Operations:` — one or more Markdown links, each with link prose; `Operations:` — one or more Markdown links, each with link
text equal to an `operationId` and link target 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 bootstrap module — covering: framework-decorated controllers and
gateways; every raw registration primitive of the HTTP framework gateways; every raw registration primitive of the HTTP framework
(the implementing PR enumerates the primitive list — route, hook, (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) asserts the list against the framework's registration API surface)
wherever it occurs, including middleware modules; SSE wherever it occurs, including middleware modules; SSE
routes; conditional registrations evaluated per §4.5 profile and 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 dispatching on the request path inside the callback, as the raw
auth and MCP mounts do — makes the enclosing registration a auth and MCP mounts do — makes the enclosing registration a
**raw mount**: it must either appear in the §4.4 delegation **raw mount**: it must either appear in the §4.4 delegation
allowlist or carry §1.6 records, one per dispatched prefix, allowlist or carry §1.6 records, **one per (dispatched prefix ×
each record's path template equal to a string constant the accepted HTTP method) branch**. The checker resolves BOTH
checker resolves from the callback's dispatch comparison (the dispatch dimensions: the prefix test against a compile-time
prefix test against a compile-time constant); a dispatch string constant, AND the callback's method dispatch — every
comparison the checker cannot resolve to a constant is a scan comparison of the request method against a constant, in the
failure naming the site. For path-bearing sites, the effective 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 path is computed by the framework's composition rules before
comparison: the class-level prefix (a string or an array — an comparison: the class-level prefix (a string or an array — an
array expands to one route per element) concatenated with the 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 computed decorator path fails; an array class prefix expands to
all elements; an empty method suffix composes with its class all elements; an empty method suffix composes with its class
prefix; hook-internal dispatch on a non-constant prefix fails; 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, directions: no undocumented live route, no documented phantom,
each conditional operation carrying its §4.5 condition field, each each conditional operation carrying its §4.5 condition field, each
delegated surface present exactly once. Socket.IO events are 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 site, excluded from BOTH direction inventories (the TUI socket
hook and the Discord/Telegram plugin clients are client sites); hook and the Discord/Telegram plugin clients are client sites);
a receiver whose type the checker cannot resolve to exactly one a receiver whose type the checker cannot resolve to exactly one
of the two packages is a scan failure naming the site — with of the two packages is a scan failure naming the site.
controls (a client emit is not inventoried as server-emitted; a **Decorator sites** have no receiver expression, so the boundary
client subscription is not inventoried as an inbound handler; there is compile-time package identity instead: a method
an unresolvable receiver fails). The comparison element is the 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, name) pair (§4.6), the registry of the matching
direction is the comparison target, and any non-literal or direction is the comparison target, and any non-literal or
non-registry event name at a server-side site is a scan 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 entry, and every item corresponds to an inventoried event; an
inventoried event absent from the file, and a documented event the inventoried event absent from the file, and a documented event the
inventory does not emit, each fail the witness, with one control 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 route family in `OPENAPI.yaml` has an `ENDPOINTS.md` entry, and
every `ENDPOINTS.md` operation reference resolves to an existing every `ENDPOINTS.md` operation reference resolves to an existing
`operationId` — both directions, mechanical. `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 registry event (a stale key — the map's domain must exactly
equal the registry union), a family name in the map with no 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 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 proves it can fail with one control per reject rule. (Prose
duplication a scanner cannot see is handled by review, but every duplication a scanner cannot see is handled by review, but every
structural duplication channel above is mechanical.) 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 auth-refusal statuses — the refusal-status set the §1.6 mapping
table assigns to the route's declared `ApiAuthClass` (the guard table assigns to the route's declared `ApiAuthClass` (the guard
is record-derived, so its refusal statuses attach to every is record-derived, so its refusal statuses attach to every
route of the class); (2) global-interceptor statuses — every route of the class); (2) interception-component statuses — a
globally registered pipe, filter, or equivalent closure over EVERY binding scope the framework offers, not only
bootstrap-installed interceptor is enumerated by the same the global ones: (i) global-by-call — components installed by
registration-primitive scan as §7.2, and each carries a the composition root's global binding calls (`useGlobalPipes`,
declared status set the witness asserts against its source (the `useGlobalFilters`, `useGlobalInterceptors`,
validation pipe's 400, for example), attributed to every route `useGlobalGuards`), enumerated by the §7.2 primitive scan,
it covers; (3) decorator status metadata — the framework's 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, route-level status metadata (explicit status-code decorators,
each method decorator's default status, and the implicit each method decorator's default status, and the implicit
default success status where no metadata overrides it), 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 graph starting at the handler declaration, resolving each
call site's callee declaration, following every edge into call site's callee declaration, following every edge into
production source (the three roots) to a fixed point, not 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 naming the site, never silently skipping — on any call site
whose callee the checker cannot resolve. Emission sites are whose callee the checker cannot resolve. Emission sites are
identified by symbol identity against the framework's 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`, against Node's `http.ServerResponse` declarations (`writeHead`,
`statusCode` assignment), so raw handlers are covered; (5) `statusCode` assignment), so raw handlers are covered; (5)
framework error defaults — the framework's own route-level framework error defaults — the framework's own route-level
default error statuses, where the implementing PR declares them default error statuses (the default unhandled-error status, the
route-attributable. Every site's status argument must resolve 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 to a literal or compile-time constant; an unresolvable status
is a FAILURE naming the site. Both directions: every resolved is a FAILURE naming the site. Both directions: every resolved
status is a member of the route's record response map, AND 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 emitting an unclassified status (`reply.status(418)`) fails; a
computed status argument fails; an unresolvable call edge computed status argument fails; an unresolvable call edge
fails; a guard refusal status absent from the map fails; a 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 status absent from the map fails; a raw `writeHead` status
absent from the map fails; a phantom map status fails; the absent from the map fails; a phantom map status fails; the
implicit default success status is attributed to the route and 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 `federation` guard refuses a request satisfying only one of its
two schemes and accepts one satisfying both (drive all three two schemes and accepts one satisfying both (drive all three
cases); each single-scheme class accepts its scheme and refuses cases); each single-scheme class accepts its scheme and refuses
its absence; `none` installs no guard. The refusal cases also its absence; `none` installs no guard. Every "satisfying" and
assert the emitted status is in the class's §1.6 refusal-status "not satisfying" request is constructed from the scheme's §1.6
set. Control: a mutated table copy assigning `admin` a credential-predicate binding (location and acceptance
single-scheme requirement set is detected while the guard still predicate), so the cases exercise the declared binding, not an
accepts the alternation. This ties the OpenAPI mapping to the implementer's stand-in. **Refusal-branch closure**: the witness
live guard semantics rather than to a lint-valid but wrong 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. table.
## 8. Drafting additions (PRD §12.1 disclosure) ## 8. Drafting additions (PRD §12.1 disclosure)
@@ -805,7 +980,7 @@ severable:
with its registration-primitive list, symbol-identity detection with its registration-primitive list, symbol-identity detection
with its failure-not-skip rule, and bidirectional with its failure-not-skip rule, and bidirectional
family/operation checks, the §7.3 content and taxonomy witness, 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 pinned validator (Redocly CLI), the §7.7 source-consistency
witness, the per-field fail-closed witness, the per-field fail-closed
controls in §7.1, and the §7.4 canonicalization/derivation/ 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 exact-domain ownership rule (§4.6, §7.5); the §7.4
document-level security inheritance resolution; and the §2.2 document-level security inheritance resolution; and the §2.2
conditional `Operations:` rule for event-only families. 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 ## Ruling request