docs: api-artifacts contract revision 3 (luna re-review residuals + F6)
ci/woodpecker/pr/ci Pipeline was successful

F1: route metadata record defined as a closed required-field list with
a per-field completeness predicate; production route made a decidable
term; generator computes the inventory route set first and fails
naming route and field.
F2: inventory scans all production source in apps/ and packages/ with
an enumerated registration-primitive list; deployment profiles bound
to the closed tier enumeration with a declared condition field; event
names bound to one typed registry with a literal-name rule.
F3: ENDPOINTS.md checker defined as a repository script with five
exact reject rules over a Markdown AST parse, one control per rule.
F4: retirement equivalence algorithm fully enumerated
(canonicalization steps, legacy error-code derivation, covering-entry
selection, permission equality); validator pinned to Redocly CLI.
F5: section 8 items 11-12 disclose the definitions and the witness
machinery.
F6: delegation allowlist (prefix, engine package, engine contract,
registering module) with a below-prefix boundary check; non-allowlisted
prefixes are ordinary route-by-route surface.
This commit is contained in:
fred
2026-08-26 20:11:49 -05:00
parent 6043b9ebff
commit 54f6ccf717
+169 -54
View File
@@ -31,6 +31,20 @@ equivalence algorithm (F4). §8 discloses every untraced binding rule,
and the SITEMAP approval claim is restated in the sitemap's own terms and the SITEMAP approval claim is restated in the sitemap's own terms
(F5). (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).
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).
@@ -51,7 +65,29 @@ documentation placement rules stay with the docs atlas
4. **Delegated surface**: a mounted path prefix whose request handling 4. **Delegated surface**: a mounted path prefix whose request handling
is delegated wholesale to an embedded third-party engine (for is delegated wholesale to an embedded third-party engine (for
example an auth engine's own route tree): the Gateway registers the example an auth engine's own route tree): the Gateway registers the
mount; the engine defines the routes. 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/` and
`packages/`, 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 generator input
(§3.1). Its fields, all REQUIRED: a unique operation id; the HTTP
method and path template (or the SSE marker, or the Socket.IO
event name and direction); the request schema reference (or an
explicit no-body marker); the response map — every documented
status code mapped to a schema reference, with at least one
success entry; the authentication requirement, one value from the
closed auth-class enumeration the implementing PR declares in the
shared types package; the permission rule reference (owning
contract plus rule identifier); and the error-code set, drawn from
the contract 5 §4.2 closed taxonomy. **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 ## 2. Artifact contract
@@ -82,23 +118,30 @@ documentation placement rules stay with the docs atlas
## 3. Production model ## 3. Production model
1. **Generation (per the ruling).** `OPENAPI.yaml` is GENERATED from 1. **Generation (per the ruling).** `OPENAPI.yaml` is GENERATED from
the Gateway source by a deterministic repository script whose input the Gateway source by a deterministic repository script whose
is a **declared route-metadata layer**: every production HTTP/SSE input is the **route metadata record** (§1.6) of every production
route carries machine-extractable metadata — route decorators plus route (§1.5): route decorators plus typed request/response schemas
typed request/response schemas (the contract 5 §4.1 shared DTOs (the contract 5 §4.1 shared DTOs where the operation is a mapped
where the operation is a mapped tool family; declared per-route tool family; declared per-route schemas elsewhere), each record
schemas elsewhere) and its error-code set. **The generator fails satisfying the §1.6 completeness predicate. **The generator fails
closed:** a production route lacking complete extractable metadata closed:** it first computes the §7.2 inventory's route set, then
is a generation error, never a silently omitted or partially requires a complete metadata record for every route in it; a
emitted operation. Today's local-DTO, inline-typed, and missing record, a missing field, or an unresolvable reference is a
raw-handler routes therefore migrate to declared metadata in the generation error naming the route and field — never a silently
implementing PR — that migration is in the implementing PR's omitted or partially emitted operation. Because the route set
scope, and §7.2's independent inventory is the cross-check that no comes from the inventory and completeness is the per-field §1.6
route escaped it. The generated output is committed, and CI predicate, a byte-stable but incomplete artifact cannot pass: an
regenerates and byte-compares it (§7.1). Hand edits to unrecorded route fails generation, and a recorded route emits
`OPENAPI.yaml` are non-conformant — a description that cannot be every §1.6 field into its OpenAPI operation (operation id,
expressed from source annotations goes into the annotations, or schemas, per-status responses, security requirement, and the
into `ENDPOINTS.md`. permission reference and error-code set as declared extension
fields). 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 2. **Normative direction.** Generation documents the code; it does not
ratify it. The S2 contracts and their witnesses remain the ratify it. The S2 contracts and their witnesses remain the
normative gates on what the surface may be; the artifacts make the normative gates on what the surface may be; the artifacts make the
@@ -138,6 +181,32 @@ documentation placement rules stay with the docs atlas
route or event lands with the regenerated `OPENAPI.yaml` and any route or event lands with the regenerated `OPENAPI.yaml` and any
needed `ENDPOINTS.md` entry in the same PR, enforced by the §7.1 needed `ENDPOINTS.md` entry in the same PR, enforced by the §7.1
drift gate — there is no "docs to follow" state. 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: any route registration whose path falls under an
allowlisted prefix but does not originate from that entry's named
registration module fails the witness — an app-owned handler
cannot hide under a delegated prefix. `ENDPOINTS.md`'s
delegated-surface entries mirror the allowlist one-to-one.
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.** Every Socket.IO event name, in both
directions, is a literal member of one exported typed event
registry in the shared types package; emitting or subscribing with
a dynamically constructed name, or through a helper that does not
take its name from the registry, is non-conformant (§7.2 scans for
non-registry names). The registry is the comparison target for the
event inventory.
## 5. Legacy migration ## 5. Legacy migration
@@ -177,25 +246,35 @@ scanned source roots, and compared files.
1. **Drift witness:** CI regenerates `OPENAPI.yaml` with the 1. **Drift witness:** CI regenerates `OPENAPI.yaml` with the
repository script and fails on any byte difference from the repository script and fails on any byte difference from the
committed file; the witness proves it can fail by a control run committed file; the witness proves it can fail by a control run
against a mutated copy. A second control proves the generator's against a mutated copy. Fail-closed controls (§3.1): one control
fail-closed rule (§3.1): a fixture route stripped of its metadata per §1.6 field class — a fixture route with the whole record
makes generation fail, not emit a partial document. 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 2. **Coverage witness:** an independent surface inventory — static
enumeration over `apps/` production code, tests excluded (the A5 enumeration over ALL production code in `apps/` and `packages/`,
§1.2 inventory style), covering framework-decorated controllers tests excluded (the A5 §1.2 inventory style), not limited to the
and gateways, raw mount/hook registrations in the bootstrap bootstrap module — covering: framework-decorated controllers and
path, SSE routes, conditional registrations (evaluated per gateways; every raw registration primitive of the HTTP framework
deployment profile and unioned), and delegated-surface mounts — (the implementing PR enumerates the primitive list — route, hook,
equals the documented set in both directions: no undocumented live middleware-mount, and plugin-registration calls — and the witness
route, no documented phantom, each conditional route carrying its asserts the list against the framework's registration API surface)
condition, each delegated surface present as a delegated-surface wherever it occurs, including plugins and middleware modules; SSE
entry. Socket.IO events are inventoried in both directions — routes; conditional registrations evaluated per §4.5 profile and
inbound subscription handlers by handler scan, server-emitted unioned; and delegated-surface mounts, with the §4.4 boundary
events by emit-call scan over production code — and compared to check that nothing app-owned registers below an allowlisted
`ENDPOINTS.md`'s event entries in both directions. Additionally, prefix. The inventory equals the documented set in both
every HTTP route family in `OPENAPI.yaml` has an `ENDPOINTS.md` directions: no undocumented live route, no documented phantom,
entry, and every `ENDPOINTS.md` operation reference resolves to an each conditional operation carrying its §4.5 condition field, each
existing `operationId` — both directions, mechanical. 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
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 3. **Content and taxonomy witness:** every `ENDPOINTS.md` entry
carries the §2.2 required fields — behavior, operation links, carries the §2.2 required fields — behavior, operation links,
authentication requirement, owning-contract link, error semantics authentication requirement, owning-contract link, error semantics
@@ -207,25 +286,48 @@ scanned source roots, and compared files.
and not-found responses are identical in code, status, and shape. and not-found responses are identical in code, status, and shape.
4. **Retirement witness:** before `docs/openapi-tess.yaml` is deleted, 4. **Retirement witness:** before `docs/openapi-tess.yaml` is deleted,
an automated comparison proves each of its 17 paths is covered by an automated comparison proves each of its 17 paths is covered by
`OPENAPI.yaml` under a defined equivalence algorithm: both `OPENAPI.yaml` under this equivalence algorithm, exactly:
documents canonicalized (references dereferenced, keys sorted), (a) canonicalize both documents — dereference every `$ref` with
then per path — structural schema equality, security/authentication cycle detection, delete the annotation-only keywords
requirement equality, and documented error-code set equality — (`description`, `summary`, `example`, `examples`, `title`,
with permissions compared against the covering `ENDPOINTS.md` `deprecated`), rename path-template parameters positionally,
entries, and the comparison emitting the exact per-path lowercase HTTP methods, then sort all object keys; (b) per legacy
differences on failure. A repository-wide reference scan shows no path: deep structural equality of the canonicalized request and
remaining consumer (including generated clients) resolves the response schemas, equality of the security/authentication
legacy file. requirement sets, and equality of the documented error-code sets —
5. **Boundary witness:** `ENDPOINTS.md` contains no schema definition a legacy error-code set is DERIVED as the legacy operation's
for any operation that exists in `OPENAPI.yaml` — mechanically: no documented non-2xx status codes plus any error-code enum values in
`requestBody`/`responses`/schema-fragment block attached to a its response schemas; where the legacy document does not express a
linked operation, and every operation mention is an `operationId` dimension at all (permissions), the comparison target is the live
link (§2.2's structured format makes this checkable); the witness §1.6 metadata record of the covering route instead; (c) the
proves it can fail by a control run against a mutated copy covering `ENDPOINTS.md` entry — selected as the entry containing
containing an inline schema block. the `operationId` link of the operation that matched the legacy
path 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-path 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 structure via a Markdown AST
and rejects, exactly: (a) an entry missing any §2.2 required
field; (b) an operation mention that is not a Markdown link whose
target is an `OPENAPI.yaml` `operationId` anchor and whose link
text is that `operationId`; (c) a fenced code block, anywhere in
the file, containing any of the OpenAPI schema keywords
`requestBody`, `responses`, `parameters`, `schema`, or
`properties` — schema content belongs only in `OPENAPI.yaml`;
(d) an `operationId` link that does not resolve; (e) an event
entry whose event name is not in the §4.6 registry. 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 6. **Validity witness:** `OPENAPI.yaml` parses and validates as
OpenAPI 3.1 under a named validator run in CI; the witness proves OpenAPI 3.1 in CI under Redocly CLI (`redocly lint`), pinned as a
it can fail by a control run against an invalidated copy. 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.
## 8. Drafting additions (PRD §12.1 disclosure) ## 8. Drafting additions (PRD §12.1 disclosure)
@@ -256,6 +358,19 @@ severable:
10. The §6 phase placement: implementing-PR-after-ratification, the 10. The §6 phase placement: implementing-PR-after-ratification, the
drift gate as a required trunk check from first commit, and the drift gate as a required trunk check from first commit, and the
build-first documentation sequencing. build-first documentation sequencing.
11. The §1.5/§1.6 definitions themselves — the production-route
boundary and the closed route-metadata record with its per-field
completeness predicate — and the §4.4 delegation allowlist with
its below-prefix boundary rule, the §4.5 profile enumeration and
condition field, and the §4.6 typed event registry with the
literal-name rule.
12. The §7 witness machinery as such: the §7.2 independent inventory
with its registration-primitive list and bidirectional
family/operation checks, the §7.3 content and taxonomy witness,
the §7.5 checker script with its five reject rules, the §7.6
pinned validator (Redocly CLI), the per-field fail-closed
controls in §7.1, and the §7.4 canonicalization/derivation/
covering-entry algorithm.
## Ruling request ## Ruling request