From 6ea6b020f009f9b520498dcd5248b7496b29f547 Mon Sep 17 00:00:00 2001 From: fred Date: Wed, 26 Aug 2026 20:46:07 -0500 Subject: [PATCH] docs: api-artifacts contract revision 4 (luna re-review residuals F1-F4/F6 + F7/F8) --- docs/requirements/api-artifacts.md | 344 ++++++++++++++++++++++------- 1 file changed, 268 insertions(+), 76 deletions(-) diff --git a/docs/requirements/api-artifacts.md b/docs/requirements/api-artifacts.md index f9849f91..5f0bb5d6 100644 --- a/docs/requirements/api-artifacts.md +++ b/docs/requirements/api-artifacts.md @@ -8,9 +8,16 @@ human-readable endpoint, authentication, permission, and error index — both declared, neither present (the webui-audit REPORT's prerequisite 9). `docs/SITEMAP.md` records that canonical scope requires maintainer approval; ratification of this contract by the repository maintainer -supplies that approval for these two artifacts. Contract 5 -(`tool-gateway-mapping.md`) §4 binds the per-operation -request/result/error/audit envelope; this contract binds only how the +supplies that approval for these two artifacts. Contract 5 is the +**Tool↔Gateway Mapping Contract (D8)** — S2 contract 5, PR #1438, +landing as `docs/requirements/tool-gateway-mapping.md` in the same S2 +ruling batch as this contract; it is not yet on the integration trunk, +so its file does not exist in this branch's tree. Its §4 binds the +per-operation request/result/error/audit envelope, and its §4.2 +defines the closed per-family error taxonomy this contract's +references bind to. Every "contract 5" reference in this document +resolves against that document as ratified; §6.5 states the ordering +dependency. This contract binds only how the two artifacts are produced, what they must cover, and how they are kept true. @@ -45,6 +52,35 @@ and the legacy-derivation rule, and §7.6 pins a named validator (F4). get a machine-checkable delegation allowlist with a below-prefix boundary check (F6). +Revision 4 (luna re-review residuals + F7/F8): the route-metadata +record is redefined as the route's **registration input** — the +registration primitive consumes the record and derives the installed +auth guard and permission enforcement from it, so record-vs-code +divergence on those fields is structurally impossible, and a new +source-consistency witness (§7.7) cross-checks the remaining fields +(F1). The auth-class enumeration is pinned in this contract (four +values, amendment-only), and Socket.IO event records get +direction-specific field semantics replacing the status-code response +map (F1). The scan roots are `apps/`, `packages/`, and `plugins/` — +all three named everywhere — and registration-primitive detection is +by TypeScript symbol identity against the framework's type +declarations, closing the wrapper/alias/dynamic-name escapes (F2). +§2.2 defines an exact entry grammar (heading form, literal field +labels, cardinality, link syntax, anchor syntax, event-item syntax) +and §7.5's reject rules cover every Markdown node type plus a +one-entry-per-operationId uniqueness rule (F3, and the covering-entry +ambiguity in F4). §7.4 defines cycle handling, `$ref` scope, +per-(path, method) matching, no-body equality, and a common +representation for error-code comparison (F4). The delegation +boundary requires the named module to contain exactly the mount call +and nothing below the prefix from any module, with field-by-field +mirror equality (F6). The exclusion list gets a closed, machine- +checked eligibility rule: an excluded route emitted by any +production-profile inventory pass is a witness failure (F7). The +contract 5 dependency is named explicitly — PR #1438, same ruling +batch — with an ordering clause and a contingency amendment rule +(F8). + 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). @@ -69,23 +105,45 @@ documentation placement rules stay with the docs atlas §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), + (either direction), registered by production source (`apps/`, + `packages/`, and `plugins/` — the three production roots, named + identically in every scan in this contract; 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**: +6. **Route metadata record**: the closed per-route registration input + (§3.1). The record is not documentation of a handler — it IS the + handler's registration configuration: the registration primitive + consumes the record, and the installed authentication guard and + permission enforcement are DERIVED from the record's declared auth + class and permission rule reference, so the record cannot diverge + from the code on those fields; a route registered without a record, + or with enforcement not derived from its record, fails §7.7. Its + fields, all REQUIRED for HTTP and SSE routes: a unique operation + 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 + 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, + cross-checked by §7.7); the authentication requirement, one value + 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 + 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 + fields in place of the response map: an inbound event record has + the event name, direction, payload schema reference, and an ack + schema reference or an explicit no-ack marker; an outbound event + record has the event name, direction, and payload schema + reference; the success-entry rule is defined as inapplicable to + event records. **Completeness predicate**: every field present AND every reference resolvable, evaluated per field; a violation names the route and the exact failing field. @@ -97,19 +155,39 @@ documentation placement rules stay with the docs atlas are documented in `ENDPOINTS.md` and referenced from the OpenAPI description block; inventing a pseudo-path encoding for them inside `OPENAPI.yaml` is non-conformant. -2. **`ENDPOINTS.md`** is the human index, in a structured entry - format: one entry per endpoint family, carrying (a) the family's - behavior summary, (b) its operations referenced by `operationId` - link into `OPENAPI.yaml` — never by restated definition, (c) the - authentication requirement, (d) the permission/authorization rule - naming the owning contract (contract 2 for chain authorization, the - SOT for workspace membership), (e) error semantics including the - contract 5 §4.2 taxonomy, and (f) Socket.IO event entries (both - directions) for event families. `ENDPOINTS.md` MUST NOT contain +2. **`ENDPOINTS.md`** is the human index, in an exact entry grammar: + the file is a sequence of entries, each entry exactly one `##` + heading whose text is the endpoint family name (unique in the + 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. + 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 + `./OPENAPI.yaml#operation/{operationId}` (the anchor grammar: + the fragment is the literal string `operation/` followed by the + `operationId`; the link RESOLVES iff that `operationId` exists as + an operation in the committed `OPENAPI.yaml`) — operations are + referenced by link only, never by restated definition, and each + `operationId` is linked from exactly one entry in the whole file; + `Auth:` — the `ApiAuthClass` value (§1.6); `Permissions:` — the + permission/authorization rule naming the owning contract + (contract 2 for chain authorization, the SOT for workspace + 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. + `ENDPOINTS.md` MUST NOT contain request/response schema definitions for any operation that exists - in `OPENAPI.yaml` (mechanical rule: no `requestBody`/`responses`/ - schema-fragment blocks for such operations — witness §7.5). This - no-duplication rule and the entry format are new policy disclosed + in `OPENAPI.yaml` (mechanical rule: the reserved tokens + `requestBody`, `responses`, `parameters`, `schema`, `properties` + appear nowhere in the file, in any Markdown node type — witness + §7.5; entry prose must avoid the reserved tokens, a disclosed + strictness). This + no-duplication rule and the entry grammar are new policy disclosed in §8 (the `docs/API/README.md` boundary as written binds guide books, not `ENDPOINTS.md` itself). 3. Both artifacts are committed repository files, reviewed in PRs like @@ -135,7 +213,12 @@ documentation placement rules stay with the docs atlas 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 + fields). Because the record is the registration input (§1.6), the + generator's input and the route's installed enforcement share one + source; §7.7 witnesses that no route bypasses record-derived + enforcement and that the record fields the framework does not + consume (error-code set, response map) match the code. 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 @@ -168,6 +251,15 @@ documentation placement rules stay with the docs atlas server-emitted events. Test-only and development-only surfaces are excluded via an exclusion list that is explicit in the generation script and enumerated in `ENDPOINTS.md`, never implicit. The + list has a **closed, machine-checked eligibility rule**: each + entry names the route, its reason class (`test-only` or + `dev-only`), and the exact guard — the deployment-profile branch + or build-time flag — that prevents its registration in every + §4.5 production profile; eligibility is DEFINED as "not emitted + by the §7.2 inventory in any production profile", and the §7.2 + witness fails if any excluded entry is emitted by any + production-profile pass. A live route added to the list is + therefore a witness failure, not a silent omission. The exclusion and delegation lists are new policy disclosed in §8. 2. Every documented operation carries: its authentication requirement; its authorization rule by reference to the owning contract; its @@ -187,11 +279,18 @@ documentation placement rules stay with the docs atlas 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. + boundary in both halves: (a) the named registration module + contains exactly one registration-primitive call — the mount + itself — and no other; (b) any route registration, from ANY + module including the named one, whose path resolves below an + allowlisted prefix fails the witness — the single mount call is + the only permitted registration under the prefix, so an app-owned + handler cannot hide there, inside or outside the named module. + `ENDPOINTS.md`'s delegated-surface entries mirror the allowlist + **field-by-field**: prefix, engine package name and version + constraint, engine contract reference, and registering module are + each equal by string comparison after whitespace trimming — + "mirror one-to-one" means this equality, checked by §7.5. 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 @@ -200,13 +299,18 @@ documentation placement rules stay with the docs atlas 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. +6. **Event-name discipline.** The event registry is two exported + `as const` readonly records in the shared types package — one + inbound, one outbound — whose values are unique string literals; + that shape is part of this contract, not implementing-PR + discretion. Every Socket.IO event name, in both directions, is a + member of the applicable registry, referenced by symbol (a + registry property access), never by a re-typed string; emitting or + subscribing with a dynamically constructed name, or through a + 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. ## 5. Legacy migration @@ -237,6 +341,17 @@ documentation placement rules stay with the docs atlas `ENDPOINTS.md` entry in its implementing PR. 4. The phase placement and rollout sequencing in this section are disclosed in §8. +5. **Contract 5 ordering dependency.** This contract's taxonomy and + envelope references bind to the Tool↔Gateway Mapping Contract + (S2 contract 5, PR #1438, `docs/requirements/tool-gateway- +mapping.md`), ratified in the same S2 batch but not yet on the + integration trunk. No implementing PR under this contract may + land before contract 5's document is on the trunk and the + shared-types taxonomy enums it requires exist. If contract 5 is + not ratified, every §4.2-taxonomy reference here is unresolvable + and this contract requires amendment before implementation — + stated so the dependency is explicit rather than a dangling + file reference (disclosed, §8). ## 7. Verification requirements @@ -253,18 +368,29 @@ scanned source roots, and compared files. 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 ALL production code in `apps/` and `packages/`, + enumeration over ALL production code in `apps/`, `packages/`, and + `plugins/` (the §1.5 production roots), 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 + wherever it occurs, including 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 + check (exactly-one-mount rule and the all-module below-prefix + prohibition). **Detection is by symbol identity, not text:** the + scan identifies registration-primitive call sites through the + TypeScript type-checker, resolving each callee to its declaration + in the framework package's type declarations — so an alias, a + re-export, or a wrapper cannot escape (a wrapper's internal call + to the primitive is itself a detected call site); a dynamic + property access on the framework's registration object, or a + 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 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 @@ -278,48 +404,81 @@ scanned source roots, and compared files. 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 - — presence-checked mechanically against the structured entry - format; every error response documented in `OPENAPI.yaml` uses a - code from the contract 5 §4.2 closed enums, and each family's + — presence-checked mechanically against the §2.2 entry grammar; + every error response documented in `OPENAPI.yaml` uses a + code from the contract 5 §4.2 closed enums **as exported from the + shared types package** (contract 5 §4.1's package; the + implementing PR that lands the enums names the exports, and this + witness compares against those exports — §6.5 orders the + dependency), and each family's documented codes exactly match the enum in the shared types package; where the no-oracle rule applies, the documented refusal 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 this equivalence algorithm, exactly: - (a) canonicalize both documents — dereference every `$ref` with - cycle detection, delete the annotation-only keywords + (a) canonicalize both documents — dereference every `$ref` + in-document only (an external `$ref` is a canonicalization + failure naming the reference), with cycle detection that replaces + a re-entered `$ref` by a canonical cycle marker carrying the + cycle's entry pointer (deterministic, so two documents with the + same cycle structure canonicalize identically), 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 + **operation** — each (path, method) pair independently, so a + 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 forms, so no-body vs any schema is a difference), + equality of the security/authentication + requirement sets, 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 + codes) and the taxonomy-code set as plain strings (any error-code + enum values in the legacy response schemas vs the covering + route's §1.6 error-code set; where the legacy schemas express no + taxonomy codes, this component's comparison target is the + covering route's §1.6 record alone, i.e. the record is asserted + present and closed, exactly as the permissions dimension below); + 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 + covering `ENDPOINTS.md` entry — unique by §7.5's + one-entry-per-`operationId` rule, 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 + operation 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 + per-operation 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 + `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 + a Markdown link in the §2.2 anchor grammar (link text = + `operationId`, target = `./OPENAPI.yaml#operation/{operationId}`); + (c) any of the reserved tokens `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 + `properties` appearing in ANY Markdown node type — fenced code, + indented code, HTML block, table, inline code, or prose — + anywhere in the file (schema content belongs only in + `OPENAPI.yaml`); + (d) an `operationId` link that does not resolve, per the §2.2 + resolution rule; (e) an event + item not matching the §2.2 `inbound {name}` / `outbound {name}` + syntax or whose event name is not in the §4.6 registry's matching + 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 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.) @@ -328,6 +487,25 @@ scanned source roots, and compared files. 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. +7. **Source-consistency witness:** for the record fields the + registration derivation (§1.6) does not structurally guarantee, + the witness cross-checks record against code: (a) **derivation + closure** — every production route's installed authentication + guard and permission enforcement originate from its record's + declared `ApiAuthClass` and permission rule reference (static + assertion that no route installs a guard or permission check + through any path other than record derivation), with a control + route that installs a bypassing guard failing the assertion; + (b) **error-code closure** — per route family, the set of + contract 5 §4.2 taxonomy codes referenced by the family's + production code (by symbol identity against the shared-types + enum exports) equals the union of the family's records' + error-code sets, in both directions, with one control per + direction (a code emitted but not declared; a code declared but + 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. ## 8. Drafting additions (PRD §12.1 disclosure) @@ -359,18 +537,32 @@ severable: 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. + 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 + 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. 12. The §7 witness machinery as such: the §7.2 independent inventory - with its registration-primitive list and bidirectional + 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 five reject rules, the §7.6 - pinned validator (Redocly CLI), the per-field fail-closed + the §7.5 checker script with its seven 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/ covering-entry algorithm. +13. The §2.2 entry grammar as exact syntax (heading form, literal + field labels and cardinality, link and anchor grammar, event-item + syntax, the one-entry-per-`operationId` rule, and the + reserved-token prohibition including its prose strictness). +14. The §4.1 exclusion-list eligibility rule (closed, machine-checked + against every production-profile inventory pass). +15. The §6.5 contract 5 ordering dependency and its contingency + amendment rule. ## Ruling request