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
(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
model (§3), coverage obligations (§4), legacy migration (§5), phase
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
is delegated wholesale to an embedded third-party engine (for
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
@@ -82,23 +118,30 @@ documentation placement rules stay with the docs atlas
## 3. Production model
1. **Generation (per the ruling).** `OPENAPI.yaml` is GENERATED from
the Gateway source by a deterministic repository script whose input
is a **declared route-metadata layer**: every production HTTP/SSE
route carries machine-extractable metadata — route decorators plus
typed request/response schemas (the contract 5 §4.1 shared DTOs
where the operation is a mapped tool family; declared per-route
schemas elsewhere) and its error-code set. **The generator fails
closed:** a production route lacking complete extractable metadata
is a generation error, never a silently omitted or partially
emitted operation. Today's local-DTO, inline-typed, and
raw-handler routes therefore migrate to declared metadata in the
implementing PR — that migration is in the implementing PR's
scope, and §7.2's independent inventory is the cross-check that no
route escaped it. 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`.
the Gateway source by a deterministic repository script whose
input is the **route metadata record** (§1.6) of every production
route (§1.5): route decorators plus typed request/response schemas
(the contract 5 §4.1 shared DTOs where the operation is a mapped
tool family; declared per-route schemas elsewhere), each record
satisfying the §1.6 completeness predicate. **The generator fails
closed:** it first computes the §7.2 inventory's route set, then
requires a complete metadata record for every route in it; a
missing record, a missing field, or an unresolvable reference is a
generation error naming the route and field — never a silently
omitted or partially emitted operation. Because the route set
comes from the inventory and completeness is the per-field §1.6
predicate, a byte-stable but incomplete artifact cannot pass: an
unrecorded route fails generation, and a recorded route emits
every §1.6 field into its OpenAPI operation (operation id,
schemas, per-status responses, security requirement, and the
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
ratify it. The S2 contracts and their witnesses remain 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
needed `ENDPOINTS.md` entry in the same PR, enforced by the §7.1
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
@@ -177,25 +246,35 @@ scanned source roots, and compared files.
1. **Drift witness:** CI regenerates `OPENAPI.yaml` with the
repository script and fails on any byte difference from the
committed file; the witness proves it can fail by a control run
against a mutated copy. A second control proves the generator's
fail-closed rule (§3.1): a fixture route stripped of its metadata
makes generation fail, not emit a partial document.
against a mutated copy. Fail-closed controls (§3.1): one control
per §1.6 field class — a fixture route with the whole record
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
enumeration over `apps/` production code, tests excluded (the A5
§1.2 inventory style), covering framework-decorated controllers
and gateways, raw mount/hook registrations in the bootstrap
path, SSE routes, conditional registrations (evaluated per
deployment profile and unioned), and delegated-surface mounts —
equals the documented set in both directions: no undocumented live
route, no documented phantom, each conditional route carrying its
condition, each delegated surface present as a delegated-surface
entry. Socket.IO events are inventoried in both directions —
inbound subscription handlers by handler scan, server-emitted
events by emit-call scan over production code — and compared to
`ENDPOINTS.md`'s event entries in both directions. 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.
enumeration over ALL production code in `apps/` and `packages/`,
tests excluded (the A5 §1.2 inventory style), not limited to the
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
asserts the list against the framework's registration API surface)
wherever it occurs, including plugins and middleware modules; SSE
routes; conditional registrations evaluated per §4.5 profile and
unioned; and delegated-surface mounts, with the §4.4 boundary
check that nothing app-owned registers below an allowlisted
prefix. 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
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
carries the §2.2 required fields — behavior, operation links,
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.
4. **Retirement witness:** before `docs/openapi-tess.yaml` is deleted,
an automated comparison proves each of its 17 paths is covered by
`OPENAPI.yaml` under a defined equivalence algorithm: both
documents canonicalized (references dereferenced, keys sorted),
then per path — structural schema equality, security/authentication
requirement equality, and documented error-code set equality —
with permissions compared against the covering `ENDPOINTS.md`
entries, and the comparison emitting 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:** `ENDPOINTS.md` contains no schema definition
for any operation that exists in `OPENAPI.yaml` — mechanically: no
`requestBody`/`responses`/schema-fragment block attached to a
linked operation, and every operation mention is an `operationId`
link (§2.2's structured format makes this checkable); the witness
proves it can fail by a control run against a mutated copy
containing an inline schema block.
`OPENAPI.yaml` under this equivalence algorithm, exactly:
(a) canonicalize both documents — dereference every `$ref` with
cycle detection, delete the annotation-only keywords
(`description`, `summary`, `example`, `examples`, `title`,
`deprecated`), rename path-template parameters positionally,
lowercase HTTP methods, then sort all object keys; (b) per legacy
path: deep structural equality of the canonicalized request and
response schemas, equality of the security/authentication
requirement sets, and equality of the documented error-code sets —
a legacy error-code set is DERIVED as the legacy operation's
documented non-2xx status codes plus any error-code enum values in
its response schemas; where the legacy document does not express a
dimension at all (permissions), the comparison target is the live
§1.6 metadata record of the covering route instead; (c) the
covering `ENDPOINTS.md` entry — selected as the entry containing
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
OpenAPI 3.1 under a named validator run in CI; the witness proves
it can fail by a control run against an invalidated copy.
OpenAPI 3.1 in CI under Redocly CLI (`redocly lint`), pinned as a
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)
@@ -256,6 +358,19 @@ severable:
10. The §6 phase placement: implementing-PR-after-ratification, the
drift gate as a required trunk check from first commit, and the
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