577 lines
34 KiB
Markdown
577 lines
34 KiB
Markdown
# API Contract Artifacts Contract (S2 contract 9)
|
||
|
||
Status: DRAFT — awaiting ratification (webui-audit S2, contract 9 of 9).
|
||
Authority: `docs/API/README.md` declares the single API documentation
|
||
boundary: `docs/API/OPENAPI.yaml` as the canonical machine-readable
|
||
HTTP/WebSocket contract and `docs/API/ENDPOINTS.md` as the
|
||
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 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.
|
||
|
||
Revision 2 (luna review F1–F5): the generation model now defines its
|
||
complete input — a declared route-metadata layer every production
|
||
route must carry, with the generator failing closed on any route
|
||
lacking extractable metadata, and the migration of today's local-DTO,
|
||
inline-typed, and raw-handler routes named as implementing-PR scope
|
||
(F1). The §7.2 inventory is closed over the real surface: raw
|
||
framework mounts, delegated surfaces, conditional/tier-gated
|
||
controllers, SSE, and Socket.IO events in both directions, with an
|
||
explicit delegation/exclusion list (F2). The `ENDPOINTS.md`
|
||
no-duplication rule is reattributed as a new disclosed policy with a
|
||
structured entry format and a mechanical checker (F3). Witnesses are
|
||
added or made executable for 3.1 validity, family coverage in
|
||
`ENDPOINTS.md`, per-entry required content, and the retirement
|
||
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).
|
||
|
||
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).
|
||
Operation semantics stay with contract 5 and the owning S2 contracts;
|
||
documentation placement rules stay with the docs atlas
|
||
(`docs/README.md`).
|
||
|
||
## 1. Definitions
|
||
|
||
1. **The artifacts**: `docs/API/OPENAPI.yaml` and
|
||
`docs/API/ENDPOINTS.md`, exactly as declared by `docs/API/README.md`.
|
||
2. **Coverage**: the set of Gateway HTTP routes (however registered),
|
||
SSE routes, and Socket.IO events (both directions) the artifacts
|
||
describe.
|
||
3. **Drift**: any difference between an artifact and the production
|
||
surface it documents — an undocumented live route or event, a
|
||
documented phantom, or a divergent schema/auth/error description.
|
||
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. 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/`,
|
||
`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 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.
|
||
|
||
## 2. Artifact contract
|
||
|
||
1. **`OPENAPI.yaml`** is one file, valid OpenAPI 3.1 (witness §7.6),
|
||
and the canonical machine-readable contract for the Gateway's HTTP
|
||
surface. Socket.IO events, which OpenAPI cannot express natively,
|
||
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 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: 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
|
||
any contract text; neither is a build output that exists only in CI.
|
||
|
||
## 3. Production model
|
||
|
||
1. **Generation (per the ruling).** `OPENAPI.yaml` is GENERATED from
|
||
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). 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
|
||
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
|
||
measured surface visible and machine-checkable. A generated
|
||
description of a non-conformant route is drift evidence against
|
||
that route's owning contract, not authority for it.
|
||
3. `ENDPOINTS.md` is hand-authored (its content — permission
|
||
rationale, cross-contract links, event semantics — is judgment, not
|
||
extraction), against the §7.2 coverage and §7.3 content witnesses.
|
||
|
||
## 4. Coverage obligations
|
||
|
||
1. **Closure over the real surface.** Coverage extends to every
|
||
production route however registered: framework-decorated
|
||
controllers and gateways AND raw framework mounts registered
|
||
outside them (the bootstrap's own route/hook registrations), SSE
|
||
routes, and conditionally registered (deployment-tier-gated)
|
||
controllers — documented in the union, each conditional route
|
||
marked with its condition. **Delegated surfaces** (§1.4) are
|
||
documented as delegated-surface entries naming the mount, the
|
||
delegating engine, and that engine's own contract — not
|
||
re-documented route-by-route. **Socket.IO events are covered in
|
||
both directions:** subscribed (inbound handler) events and
|
||
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
|
||
error responses drawn from the contract 5 §4.2 closed taxonomy.
|
||
Where contract 2's no-existence-oracle rule applies, the
|
||
documentation shows authorization refusal and not-found as
|
||
indistinguishable on the wire — documenting a distinguishing
|
||
response there is a coverage defect, not a documentation style
|
||
choice.
|
||
3. New surface ships documented: a PR that adds or changes a Gateway
|
||
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 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
|
||
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.** 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
|
||
|
||
1. `docs/openapi-tess.yaml` (17 paths, Tess-scoped) remains labeled a
|
||
scoped legacy artifact until the consolidated `OPENAPI.yaml`
|
||
demonstrably covers it: the `docs/API/README.md` retirement rule
|
||
(verify paths, schemas, authentication, permissions, error
|
||
behavior, and generated/client references) is binding, witnessed by
|
||
§7.4's equivalence algorithm.
|
||
2. Retirement is deletion in a reviewed PR after the §7.4 witness
|
||
passes; until then no consumer may treat the legacy artifact as
|
||
canonical for anything beyond its own 17 paths. (The
|
||
deletion-in-reviewed-PR form and the interim-consumer restriction
|
||
are disclosed in §8.)
|
||
|
||
## 6. Phase timing
|
||
|
||
1. The artifacts, the metadata migration (§3.1), and the generation
|
||
script are produced by an implementing PR after this contract is
|
||
ratified; they are S4-phase platform work, not part of any S2
|
||
ruling PR.
|
||
2. From the first commit of `OPENAPI.yaml` onward, the §7.1 drift gate
|
||
is a required CI check on the integration trunk; the §4.3
|
||
same-PR rule binds every subsequent surface-changing PR.
|
||
3. The build-first tool families (A5 ranks, contracts 1–3, 5, 8) are
|
||
documented as they land, under the same rule — the roll-up query,
|
||
for example, ships with its OpenAPI operation and its
|
||
`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
|
||
|
||
Binding on the implementing PRs; each witness names its commands,
|
||
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. 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 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 middleware modules; SSE
|
||
routes; conditional registrations evaluated per §4.5 profile and
|
||
unioned; and delegated-surface mounts, with the §4.4 boundary
|
||
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
|
||
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
|
||
— 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`
|
||
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
|
||
**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 — 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
|
||
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-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 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` 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.)
|
||
6. **Validity witness:** `OPENAPI.yaml` parses and validates as
|
||
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.
|
||
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)
|
||
|
||
The two artifacts, their roles, and the retirement verification
|
||
dimensions are traced to `docs/API/README.md` and `docs/SITEMAP.md`.
|
||
Proposed drafting additions, visible here for ratification, each
|
||
severable:
|
||
|
||
1. The generated-with-committed-output production model, the declared
|
||
route-metadata layer with its fail-closed generator, the metadata
|
||
migration scope, and the CI drift gate (§3.1, §7.1) — the ruling
|
||
below.
|
||
2. The same-PR documentation rule for surface changes (§4.3).
|
||
3. The explicit exclusion list and the delegated-surface
|
||
documentation form (§4.1).
|
||
4. The Socket.IO placement rule (events in `ENDPOINTS.md`, no
|
||
pseudo-paths) and the both-direction event coverage (§2.1, §4.1).
|
||
5. The no-oracle documentation requirement (§4.2).
|
||
6. The `ENDPOINTS.md` structured entry format and its no-duplication
|
||
rule with the mechanical boundary witness (§2.2, §7.5).
|
||
7. The committed-repository-file rule — neither artifact exists only
|
||
in CI (§2.3).
|
||
8. The normative-direction rule — generation documents, never
|
||
ratifies (§3.2).
|
||
9. The retirement deletion-in-reviewed-PR form and the
|
||
interim-consumer restriction (§5.2), and the §7.4 equivalence
|
||
algorithm.
|
||
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 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, 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 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
|
||
|
||
Ruling requested (one decision): shall `OPENAPI.yaml` be generated
|
||
from Gateway source through a declared, fail-closed route-metadata
|
||
layer with a committed output and a CI drift gate (recommended — the
|
||
45-controller surface already exists, so generation is the only route
|
||
that starts true and stays true, and fail-closed metadata makes
|
||
incompleteness a build error) — or hand-authored contract-first, with
|
||
code conformance to the hand-written contract enforced by witnesses
|
||
instead?
|