From 54f6ccf717eee791bea6502ef7a6997a5086a6d7 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 20:11:49 -0500 Subject: [PATCH] docs: api-artifacts contract revision 3 (luna re-review residuals + F6) 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. --- docs/requirements/api-artifacts.md | 223 ++++++++++++++++++++++------- 1 file changed, 169 insertions(+), 54 deletions(-) diff --git a/docs/requirements/api-artifacts.md b/docs/requirements/api-artifacts.md index 474b38a2..f9849f91 100644 --- a/docs/requirements/api-artifacts.md +++ b/docs/requirements/api-artifacts.md @@ -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