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 26b3560ca2 - Show all commits
+118 -14
View File
@@ -81,6 +81,27 @@ 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).
@@ -125,7 +146,9 @@ documentation placement rules stay with the docs atlas
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, with at least one success entry, and with one
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,
@@ -133,8 +156,20 @@ documentation placement rules stay with the docs atlas
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), and `bootstrap` (identity §3 pre-epoch
surface) — amendment-only, pinned here rather than deferred; the
(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
@@ -179,7 +214,10 @@ documentation placement rules stay with the docs atlas
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.
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
@@ -310,7 +348,15 @@ documentation placement rules stay with the docs atlas
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.
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
@@ -390,14 +436,32 @@ 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). The inventory equals the documented set in both
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. Additionally, every HTTP
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.
@@ -431,10 +495,19 @@ scanned source roots, and compared files.
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; equality is evaluated over 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, and equality of the documented error semantics
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
@@ -478,7 +551,12 @@ scanned source roots, and compared files.
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). The witness
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.)
@@ -505,7 +583,21 @@ scanned source roots, and compared files.
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.
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)
@@ -540,17 +632,19 @@ severable:
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
four-value `ApiAuthClass` enumeration, and the event-record
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 and symbol-reference rule.
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 seven reject rules, the §7.6
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/
@@ -563,6 +657,16 @@ severable:
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