docs: api-artifacts contract revision 4 (luna re-review residuals F1-F4/F6 + F7/F8)
ci/woodpecker/pr/ci Pipeline was canceled

This commit is contained in:
fred
2026-08-26 20:46:07 -05:00
parent 54f6ccf717
commit 6ea6b020f0
+268 -76
View File
@@ -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