docs: api-artifacts contract revision 6 (luna r5 residuals F1/F2/F4/F9 + NF1-NF5)
ci/woodpecker/pr/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
This commit is contained in:
@@ -102,6 +102,33 @@ 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 + NF1–NF5): 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).
|
||||
|
||||
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).
|
||||
@@ -164,12 +191,30 @@ documentation placement rules stay with the docs atlas
|
||||
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
|
||||
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. 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
|
||||
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
|
||||
@@ -196,9 +241,14 @@ documentation placement rules stay with the docs atlas
|
||||
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.
|
||||
once per entry; `Summary:`, `Auth:`, `Permissions:`, and
|
||||
`Errors:` REQUIRED in every entry; `Operations:` 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);
|
||||
`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
|
||||
@@ -357,6 +407,15 @@ documentation placement rules stay with the docs atlas
|
||||
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
|
||||
|
||||
@@ -436,27 +495,69 @@ scanned source roots, and compared files.
|
||||
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
|
||||
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. 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,
|
||||
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
|
||||
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). 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
|
||||
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 — 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
|
||||
(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 —
|
||||
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
|
||||
@@ -505,8 +606,15 @@ scanned source roots, and compared files.
|
||||
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 —
|
||||
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
|
||||
@@ -534,8 +642,10 @@ scanned source roots, and compared files.
|
||||
`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
|
||||
required field label under the §2.2 conditional rule (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
|
||||
@@ -555,8 +665,11 @@ scanned source roots, and compared files.
|
||||
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
|
||||
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). 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.)
|
||||
@@ -584,20 +697,68 @@ scanned source roots, and compared files.
|
||||
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
|
||||
**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) 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-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, 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 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
|
||||
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; a framework-default emission
|
||||
path (implicit 200) is attributed to the route and must appear
|
||||
in its response map.
|
||||
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
|
||||
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
|
||||
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. 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
|
||||
table.
|
||||
|
||||
## 8. Drafting additions (PRD §12.1 disclosure)
|
||||
|
||||
@@ -667,6 +828,22 @@ severable:
|
||||
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.
|
||||
|
||||
## Ruling request
|
||||
|
||||
|
||||
Reference in New Issue
Block a user