Skip to content

Latest commit

 

History

History
828 lines (738 loc) · 62.4 KB

api-foundation.md

File metadata and controls

828 lines (738 loc) · 62.4 KB

API Foundation

The API foundation has three implemented mechanical authorities: an explicit Go schema/operation registry, generated API contracts, and authenticated keyset cursors.

Schema descriptors cover exact objects, arrays, strings with closed formats/enums, bounded int64 integers, booleans, null, named references, and one-of unions. Registry construction rejects an invalid descriptor, duplicate operation ID, duplicate method/path, public/auth mismatch, or an unknown request/response schema. Duplicate route identity ignores placeholder names: GET example/{first} and GET example/{second} cannot coexist, because chi would silently replace one handler and its auth middleware. Method, static segments, namespace and parameter positions remain significant; merging independent registries enforces the same duplicate rule. Runtime response fixtures validate against the same descriptor values consumed by runtime mounting, OpenAPI generation, TypeScript generation, and the committed v1 compatibility pin. Authenticated Soul Recovery and minigame routes mount exclusively from this registry. Mounting checks the entire binding set and every required auth middleware before wrapping handlers or registering routes. Missing, extra, unsorted, or nil runtime bindings, or absent required auth middleware, fail without changing the caller's existing router.

Path-parameter descriptors have unique names in template order (not lexical order), and their resolved kind must be string, integer or boolean. Named-reference chains preserve their declared type and bounds but cannot conceal an object, array, null or union. Invalid descriptors fail with ErrInvalidOperation before generation or mounting. This is descriptor admission, not new URI-value validation middleware or a change to the separate static/parameter route-overlap policy below.

Raw-response hash headers use the same admission rule at registry construction and cached emission: their existing name grammar applies, and they cannot equal ETag, Cache-Control, Content-Type, Content-Length or X-Request-ID case-insensitively. A conflicting descriptor fails with ErrInvalidOperation before mounting/generation; valid custom headers still preserve exact raw bytes and the independently owned metadata.

HTTP registry admission first rejects malformed UTF-8, then checks raw object-member uniqueness before decoding to maps. Invalid encoding returns ErrInvalidSchema rather than letting Go replace corrupt bytes with U+FFFD. This follows the network JSON encoding requirement in RFC 8259 §8.1 and the generated client's existing fatal UTF-8 decoding. Valid Unicode (including literal U+FFFD) and JSON escapes remain admitted. Go's existing BOM refusal is unchanged; this does not choose a new surrogate policy or tighten shared artifact decoding. Raw response validation still hashes original bytes. Duplicate names at any level, including different escape spellings of the same name, return ErrInvalidSchema rather than silently keeping the final value. Valid escaped names, string contents resembling JSON and repeated names in separate objects remain allowed. ValidateRequest and ValidateResponse share this check; it does not add request-validation middleware to handlers that do not already call the registry, or rewrite any successful handler response. Registered exact-response literals require the same admission at construction. The shared ValidateJSON descriptor checker retains encoding/json last-value decoding for artifact owners; formula replay artifacts depend on that existing Go/TypeScript contract. Their original bytes and hash identity are not normalized or rewritten by HTTP validation.

Bootstrap, session creation/refresh, minigame create/command/resolve and all four Soul-Recovery writes now validate original size-bounded request bytes through their registered descriptors before credential or gameplay work. Duplicate/escaped duplicate names and case aliases cannot bypass admission through Go struct decoding; nested tenant commands are checked too, and minigame resolution requires an object rather than null. Existing operation-specific invalid errors, authentication/limiter order and unavailable-service precedence remain. Live HTTP/Postgres tests retain unchanged credential/activity/gameplay/history rows on refusal and the existing successful lifecycle. This does not change the command union, implement Typer/Arcade wire arms, or claim conformance for handwritten import/intents or historical empty-body account/Founder calls.

Operations may declare exact scalar query parameters (string, bounded integer, or boolean). They are byte-sorted by name and never shadow a path parameter. The reserved cursor parameter is a non-required string, and it is present exactly when the operation declares a cursor key. Registry.ParseQuery decodes only declared parameters. Each parameter may appear at most once, integers are strict base-10 (no sign, padding, or leading zero), and every value is checked by the same descriptor validator as response bodies. A failure returns InvalidQueryError naming the parameter; undeclared parameters are ignored. HTTP paged readers use Registry.ParseRawQuery on the original query string before descriptor validation. Malformed percent escapes or unescaped semicolons in a declared field return its existing typed invalid error; they cannot disappear into a default page, conceal a duplicate or produce a conditional304. Valid escaped names/values and ignored undeclared fields retain their existing behavior. The decoded-values ParseQuery helper remains available to callers that already own successful form decoding. String values retain their exact valid UTF-8 content through JSON encoding before descriptor validation, including JSON-escaped control characters. Empty and malformed UTF-8 query strings are rejected; a literal Unicode replacement character is valid, not a malformed-byte substitute.

Query parameters are generated as OpenAPI in: query entries, and TypeScript gets queryParameters plus an optional-aware query type. Query-free operations generate byte-identical output. The compatibility pin records the query set only for operations that have one, and rejects any change to an existing operation's query set. That is stricter than C2 (which would allow a new optional request field), but never looser.

A schema response may also declare a sorted immutable set of exact JSON wire bytes. This is a runtime-validation narrowing layered over the generated DTO, not a generated-client or OpenAPI shape change. Minigame error responses use it to bind each operation/status to the shipped category/detail pairs and encoder newline, so a schema-valid cross-product or appended byte is not accepted as contract evidence.

The shared error-detail enum includes soul_recovery (unavailable/internal recovery errors) and company_stream (start without an active Company), matching existing handlers. These are additive descriptor corrections, not handler or error-mapping changes. Real authenticated Postgres-backed replies are checked against this same registry; invented details still fail.

The generator exports apiErrorCategories and apiErrorDetails from those descriptor fields. It follows validated named-reference chains for the error envelope and scalar enum fields; referencing the same declared enum cannot erase its metadata or make it unbounded. The emitted TypeScript aliases and OpenAPI reference graph are preserved, not flattened. This does not authorize a compatibility-breaking change to the production descriptor or imply general runtime DTO/union validation. The shared Minigame/Soul/Garden operation adapter requires both registered enum memberships before constructing a typed API rejection. Unknown details produce the existing body-free transport error instead of copying reply text into the typed exception. An enum-free detail schema generates null metadata rather than falsely declaring an empty string domain; the current production descriptor has a closed enum. Minigame's operation/status-specific ExactJSON bindings remain the stronger additional check. Global enum membership alone does not prove operation/status/pair conformance for other operations or validate successful DTOs.

make api-schema regenerates canonical OpenAPI 3.1 at docs/generated/api.json and exact client DTO/operation metadata at client/src/api/generated/types.ts. make api-check regenerates and byte-compares both outputs as part of make verify-server. The committed docs/generated/api-compat-v1.json baseline enforces the v1 additive-only law: existing operations, request unions, statuses, required fields, and bounds cannot narrow or disappear; responses may add optional fields or widen an enum/union. Updating the compatibility baseline is an explicit make api-pin operation, never an incidental effect of ordinary generation. This is a metadata/schema check, not a routing-equivalence guarantee. Registry construction rejects duplicate method/path patterns (including renamed placeholders) but does not reject a static path overlapping an existing parameter path. The six-case registered-chi probe recorded as RP-562 shows that such an addition can pass compatibility while changing the handler and response for a previously admitted URL. Disjoint tenant namespaces are proposed in rfc/minigame-tenant-http-bindings.md; no checker-policy change is adopted, and this experiment does not establish a fault in an existing production route. A single response object may become a closed-object union without replacing that baseline: the first arm must preserve the old contract, and every added arm must be provably disjoint from it through required-key exclusion. Overlapping arms could invalidate old bytes under oneOf and are refused, as are request/shared widening and cases whose disjointness this bounded check cannot prove. Recovery uses this for its already-shipped watchdog terminal on the heartbeat route; ordinary progress and other response descriptors are unchanged. For an existing response union, appended arms must likewise be provably disjoint from the old arms; otherwise oneOf could reject formerly valid bytes by matching them twice. This check resolves each side's named references and recognizes distinct JSON kinds, disjoint string enums, exact integer ranges and object required-key/discriminator exclusions. Arrays are not assumed disjoint merely because their item types differ: both can admit []. Unproven intersections remain conservative. This covers appended-arm growth, not a general proof of all cross-arm interactions when existing arms themselves change; compatibility and full API acceptance remain separate from routing, runtime fixture and complete consumer proofs. Existing string-enum arms are also checked together: every old uniquely admitted literal must still match exactly one current arm. Both old and newly declared literals are examined using the runtime string-format validator and named references, including an old unbounded-string arm. Newly ambiguous values that were never admitted previously do not themselves break backward compatibility; safe response enum growth remains allowed. This is the string-domain check, not a general inclusion solver for numeric/object cross-arm changes. Integer arms receive an exact interval check: collect minima and guarded maximum + 1 cut points from both schema versions, then compare exclusive matches at each region's first int64. Membership is constant inside each region, so this covers the whole integer domain, including missing bounds and both int64 limits, without floating-point conversion or exhaustive value iteration. Old-invalid and new-only overlaps do not invalidate compatibility by themselves. Object cross-arm changes remain separate from these scalar-domain checks. Existing object and array arms now receive an intersection proof as well: for each current arm and different old arm, their shared domain must be provably covered by another old arm. Such values already matched at least twice and were not old valid replies. Otherwise growth is refused; an optional property or required-field demotion is not automatically safe inside an exclusive union. The proof follows side-specific named definitions, closed object field presence/domains, homogeneous array item domains, nested unions/arrays, runtime string formats and exact integer intervals. Whole-array coverage matters: a mixed-element array can have one old match even when every singleton and the empty array were already ambiguous. Scalar membership checks alone do not cover growth of array alternatives. Empty-array overlap is retained rather than treated as proof that no old valid arrays exist. Unchanged arms and provably safe growth remain allowed, including already-invalid overlap covered by a third old arm. Unknown intersections fail closed; this is a sufficient proof, not a general inclusion solver or full C2/API/consumer acceptance. Existing optional fields cannot become required in requests, responses or shared definitions, including through array/reference/union nesting; failures identify the affected field. Numeric bounds compare as exact signed int64 values, not float64 projections, so a one-unit narrowing above 2^53 or at an int64 boundary still fails. This checks schema metadata, not production math. Schema names follow the current-plus-legacy convention: an unversioned name denotes the current shape, while a retained historical shape uses an explicit V<n> suffix. A compatibility-pin refresh must cite its authorizing ruling and be recorded in the owning planning log in the same change; an otherwise valid widening is not permission for a silent re-baseline.

The generated registry is not yet the complete runtime API. Current metadata covers twenty-two operations, including the existing session creation and refresh routes and their actual error alternatives. Both routes mount from the registry with their existing shared unauthenticated IP limiter. Their successful two-string token pairs reuse the BootstrapSession owner descriptor; creation takes {account_id,recovery_code}, refresh takes {refresh_token}. Registered errors are exact operation/status-specific bytes: 400 invalid/body, 429 rate_limited/ip, creation's 401 unauthorized/credential, and refresh's 401 unauthorized/refresh_token or 401 refresh_reused/session_family_revoked. Both session handlers validate their original, size-bounded request bytes against those existing descriptors before decoding/credential use. Duplicate/escaped duplicate members, case aliases, missing members and nulls therefore refuse as 400 invalid/body, rather than bypassing exact-key validation through Go struct decoding. Valid single escaped names remain accepted. The shared IP limiter and no-store wrapper still precede admission. Session schemas/pins and renewal policy are unchanged.

Explicit generated calls can now represent those routes/errors; they do not add automatic renewal, credential replacement, recovery UX or retry policy. The deliberate additive pin refresh under API C1/C2 also captures the already-registered Garden route and optional Garage response shapes; it changes no original operation rows. Tests against the actual committed pin reject removal of either session operation or its 401 alternative. Bootstrap, main state reads and the Minigame/Soul/Garden ports use the generated client; intents still call fetcher outside the generated directory. api-check passes for the registered subset, not AC4 completion. Manual make research-refresh-generated-contract now verifies represented refresh callers and in-memory metadata-removal refusals outside CI/verify; its earlier retained report remains historical evidence of the gap. It does not implement or authorize browser renewal. See the bounded census.

The existing account lifecycle routes also mount exclusively from registered rows: create_account (POST /api/v1/account, unauthenticated), create_founder (POST /api/v1/founder, access token) and get_founder (GET /api/v1/founder, access token). Account creation retains the shared IP limiter and no-store credential reply; both Founder routes retain shared account limiting and failed-auth IP limiting. Their handlers and accepted body forms are unchanged. Generated POST calls use the canonical empty object; the live parser still admits an empty body as well and rejects internally spaced { }.

AccountCreated is {account_id,created_at,recovery_code}. FounderCreated is {id,created_at,imported}; FounderProfile is {id,created_at,display:{}}. Display is an exact empty object, not permission to introduce arbitrary profile data. Unlike bootstrap's fixed .000Z format, these existing handlers use time.Time's UTC millisecond encoding with trailing fractional zeros omitted. The separate date-time-utc-ms string descriptor validates those bytes without relaxing any existing date-time-ms descriptor or re-encoding a reply.

Actual 400 invalid/body, 401 unauthorized/access_token, 404 unknown_id/account|founder, 429 rate_limited/ip|account and 500 internal_invariant/account_create|founder_create errors are registered only on their applicable operations/statuses. The deliberate C1/C2 pin refresh adds these three operations; no existing operation row changes. Real Postgres tests cover creation → session → current Founder → New Founder → refreshed Founder binding, retained archives, refusal nonmutation and shared limiting. This does not add recovery UX or deletion policy. Bodiless deletion responses, intent/import routes, C9 enforcement and full API acceptance remain unfinished.

Exact empty-object descriptors now generate Record<string, never>, not TypeScript {} (which accepts extra fields and primitives). This corrects SDK typing for account creation, New Founder, the empty display object and existing Minigame resolution requests; it changes no OpenAPI shape, HTTP parser or wire acceptance set.

Generated HTTP client

createAPIClient(fetcher?, baseURL?) is emitted inside the generated module from an embedded generator template, with operation-specific method/path, auth, query and response metadata from the registry. call(operationID, input) accepts generated path/request/query types, explicit access tokens only for private operations, and optional request ID/abort signal. Path components are URL-encoded; declared query values are serialized in registry order. Unknown path/query fields, missing auth and inexact integer parameters fail before HTTP. Exact ./.. path components also fail before dispatch because URL parsing treats them as navigation, not resource identities. Ordinary dotted names, percent-encoded literal inputs and query dots retain their existing encoding; this does not validate catalog IDs. Content-Type is sent only for operations with a JSON request body, not bodiless reads. The client never forwards an access token on a public operation. There is no token storage, refresh, retry, timeout, or request-ID policy hidden in it.

OperationResponses preserves status/body associations. Schema responses are JSON-decoded; these TypeScript types are not runtime schema validation. Wrappers retain their existing application decoders. When a status declares multiple representations, the client selects the descriptor by the response's media type (ignoring parameters such as charset) before choosing its decoder; a missing or undeclared type refuses before reading the body. Media type/subtype matching is case-insensitive, as required by HTTP semantics §8.3.1; Application/JSON is the same declared type as application/json, not a new representation. Raw response bytes and their digest header remain exact and are never case-folded. Single-representation schema responses also require their registered JSON media type before reading the body. Missing/undeclared media raises a body-free transport error, even when the bytes happen to be valid JSON. Existing JSON charset handling and status/error payloads are unchanged; this is media conformance, not runtime DTO validation. Raw successes are Uint8Array, not an omitted success type or parsed JSON. They require the registered media type and content-hash header, checked against SHA256 over downloaded bytes. Error statuses on raw operations remain JSON. Undeclared status, malformed JSON, raw type/hash failure and network/abort errors are surfaced, never retried. Schema replies are decoded from bytes with fatal UTF-8 validation before JSON parsing, matching JSON's network encoding requirement. Corrupt encoding cannot be silently repaired into replacement characters. Existing BOM tolerance, valid Unicode (including an explicitly encoded replacement character), and JSON escape handling are preserved; this is encoding validation, not runtime DTO or credential validation. Raw evidence continues to be returned byte-exact after hash verification, not UTF-8/JSON-decoded by the transport. Malformed encoding/JSON reports its HTTP status without echoing reply bytes. Body-read failures propagate unchanged rather than being relabelled as encoding/JSON errors. The generated module exports this same decodeJSONResponse primitive for the remaining legacy intent caller. It owns only UTF-8/JSON decoding, not status/media admission or DTO validation. The intent operation still awaits its typed-dictionary contract and generated dispatch; reusing decoding does not complete C9 migration. The undeclared 304 arm remains open; this client does not invent an alternative for it.

OpenAPI retains every raw hash header across same-status representations, combining header names case-insensitively. A header is marked required at the status level only when every representation declares it; otherwise its generated description names the media type that requires it. This expresses the existing representation contract, not optional raw verification: the SDK still requires and verifies the selected raw descriptor's hash header.

The actual Game UI bootstrap and main state reads now use this transport without changing the bootstrap journal, credential storage, snapshot decoder or revision reconciliation. Lost replies and HTTP/parser failures retain the persisted bootstrap key for an explicit retry; no client-level retry is added. Remaining HTTP callers and the C9 raw-fetch lint are still unfinished, so this is not AC4 completion. Unit/browser tests use controlled fetches; the existing composed journey separately exercises these migrated calls against real HTTP, Postgres and WebSocket services. The composed Exit fixture additionally runs the generated client against live public HTTP downloads and the TypeScript replay verifier, as described below; controlled-byte SDK tests alone are not that integration evidence.

The Minigame, Soul Recovery and Garden ports dispatch by generated operation ID with typed path/request inputs. Their shared adapter reads the current access token once per explicit call and preserves the existing MinigameAPIError/MinigameTransportError classes and surface rejection mappings. Error categories come from the generated registry, not a second list. Undeclared HTTP statuses are transport failures. Error bodies require exactly two fields, a known category and a nonempty detail. Where a response declares ExactJSON, generated operations[].responses[].exactJSON metadata carries those same literals, and the adapter also requires the category/detail pair to belong to that operation and status. Wrong-family, wrong-status and undeclared details become body-free transport failures, not typed gameplay refusals. This compares semantic pairs after JSON decoding, not original whitespace/member order or raw duplicate keys. Garden/Soul rows currently have no literal narrowing and retain the previous guard; neither they nor success bodies gain runtime schema validation here. Existing wire-binding/error tests cover all nine port calls; the real composed journey covers Pitch and default locked-Garden reads, not a complete persisted Soul Recovery or active-Garden workflow.

GU-C26 authorized the Game UI schema v3 compatibility-pin baseline. Accepted Garage Player Surfaces GS0.1 (2026-09-25) authorizes the v4 re-baseline. The unversioned GameUISnapshot is the current v4 shape. GameUISnapshotV1, GameUISnapshotV2 and GameUISnapshotV3 retain exact stored-bootstrap receipt validation, and the live Founder-state operation returns only v4.

Public pagination cursors contain canonical {filter_sha256,key,op,v} JSON followed by an HMAC-SHA256 signature, encoded as unpadded base64url. The codec verifies the signature with the current or previous deployment key before parsing JSON, binds the cursor to operation and complete normalized query, and rejects noncanonical or oversized input. Board variables use canonical exact-key JSON with integer booleans and explicit-null faction.

Public reads

server/publicread owns the unauthenticated /api/public/v1/ registry. publicapi.APIErrorSchema() is the single APIError definition that both registries reference. publicapi.MergeRegistries unions the private and public registries into one generation authority. A schema name may be shared only if the definition is byte-identical, and operation IDs must not collide. As a result, docs/generated/api.json, the TypeScript module and the compatibility pin cover both surfaces, while each surface still mounts from its own registry.

The shared error schema belongs to publicapi, not the private Account repository. Public readers no longer import Account to construct their registry. A production-source import guard checks both publicread and publicapi, rejecting direct Account/Session/Save/Production owner imports (including aliases and subpackages). It ignores comments and test fixtures and refuses unparseable source. This is a direct package-boundary check, not proof of every transitive repository dependency or runtime disclosure; those remain separate privacy acceptance gates. The move preserves the error fields/enums, all operation/status-specific exact replies, and byte-identical generated OpenAPI, TypeScript and compatibility pins.

GET /api/public/v1/epochs (list_public_epochs) returns the C12 PublicEpochPage:

  • items are newest-first. Each has accepted_hashes (byte-sorted in Go, an empty list when there are none), the exact UTF-8 changelog_markdown read from the staged changelog_ref, and UTC millisecond started_at/ended_at (ended_at is explicit null while open).
  • next_cursor is a C15 keyset cursor on epoch_id, bound to the normalized filter {"limit":N}. limit defaults to 50 and is bounded 1..100.
  • Rejections: a malformed limit returns exactly 400 {"category":"invalid","detail":"limit"}. A malformed, tampered or filter-mismatched cursor returns 400 invalid/cursor.
  • Failures: repository or changelog failures, including a missing or non-UTF-8 staged changelog, return 500 internal_invariant/public_api.
  • Caching and validation: successful bytes are validated against the registry row. They are then served through the shared cache/limiter runtime with the catalogs_epochs class (public,max-age=3600, strong ETag, and a 304 that spends no rate token).
  • Page source: leaderboard.Repository.PublicEpochPage, which reads the page from a single SQL statement.

GET /api/public/v1/boards/{category} (list_public_board) returns the C12 BoardPage {category_id, epoch_id, items, mandate_level, next_cursor, ranking_kind, variables}.

  • Query: C13's normalized query. variables is the base64url canonical JSON {advisor,commons,faction,glitched} (sorted keys, 0/1 flags, explicit-null faction). epoch, mandate (0..20) and variables are required; limit (1..100, default 50) and cursor are optional.
  • Ranking kind: resolved from the pinned categories artifact of every stored constants hash accepted into the epoch. Timed categories (rta/attended) are time_ms and the untimed valuation category is magnitude. count is declared for the union, but no category produces it yet. Catalogs that disagree, or a stored catalog that cannot load, are internal invariants. The accepted-hash result must complete successfully: a row-iteration failure is propagated as a server failure, never resolved as an unknown category from an incomplete result.
  • Items: {founder_id, key, rank, run_id, verified_at, world_first}, where key is the closed union {kind:"time_ms"|"count",value} / {kind:"magnitude",exponent,quantized_mantissa}. rank is the competition rank over the whole board.
  • Cursor: the keyset cursor ({key|exponent+quantized_mantissa, run_id}) is MAC-bound to the complete normalized filter (category, variables, epoch, mandate, limit). A cursor whose arm doesn't match the ranking kind is rejected.
  • Paging: the reader fetches limit+1 rows, so a page that is exactly full carries no cursor.
  • Rejections:
    • 400 invalid/{cursor,epoch,limit,mandate,variables};
    • 404 unknown_id/{category,epoch};
    • 429 rate_limited/ip;
    • 500 internal_invariant/public_api.
  • Caching: the boards class (public,max-age=60).

GET /api/public/v1/registry/routes (list_public_routes) returns the C12 RoutePage {items, next_cursor}.

  • Source: registry_routes joined with account_founders, through routeprojection.Projector.PublicRoutePage.
  • Items: {adoption_count, credited_at, first_executor_founder_id, naming_deadline, naming_status, public_name, route_id}.
    • public_name is the approved player name only when published. Reserved, pending (unmoderated) and expired names show the house name.
    • first_executor_founder_id is null once that Founder's account is anonymized (or no ownership row exists).
    • Moderation state beyond the status enum and all account identity are absent.
    • naming_status is the stored projection state. The composed server primes unused-name expiry before readiness and repeats it once per minute; an unused row can report reserved until the next successful pass. The exact deadline already prevents a late backend name submission. The public house-name fallback and expiry driver do not supply a player naming/moderation workflow.
  • Order and paging: ordered by the immutable route_id; credited_at can move when an earlier execution re-credits a route. The keyset cursor is bound to {"limit":N}, and limit is 1..100 (default 50).
  • Rejections: 400 invalid/{cursor,limit}, 429 rate_limited/ip, 500 internal_invariant/public_api.
  • Caching: the registry class (public,max-age=300).

Privacy (AC5) is enforced two ways:

  • Structural: publicread/privacy_test.go requires every public operation to be an unauthenticated GET under /api/public/v1/ with no request body. Every named schema, including APIError, must have a contract-derived publication field set; full field paths are checked against it, so innocent-looking extra fields and nested reuse of an allowed name cannot bypass the check. The existing account/email/token/session/recovery/password/secret/stream/save/IP/ device/presence exclusions also remain. Founder identity is permitted only at PublicBoardItem.founder_id and PublicRoute.first_executor_founder_id. The field expectations are independent test assertions, not a second runtime schema or permission to publish new data.
  • Composed: the composed-server witness seeds a real private account. It then requests every public registry operation (a missing request builder fails the test) and asserts that no response body or header contains the seeded account ID, recovery code, tokens, founder ID or company stream ID. Evidence endpoints return identical, non-cacheable 404s for private and unknown runs. A separate actual verified-run download check permits C4's public Founder/Company history but rejects account IDs, recovery codes and session tokens in the manifest, genesis and decompressed replay archive. Raw-response privacy requires this content check; the schema walker cannot inspect a gzip body.

publicread.NewRouter composes the surface. It loads the strict policy, resolves the named cursor secrets (CursorSecretResolver, see docs/gameserver.md), builds the request-ID runtime and limiter, and mounts every public registry operation (and only those) through Registry.Mount. Policy admission requires every exact, non-null root and nested member; case aliases and case-alias collisions refuse before mounting. In particular, missing/null trusted_proxy_hops cannot silently select zero. Explicit zero remains valid for a direct deployment, and escaped canonical JSON member names remain equivalent. Configured values and proxy/limiter semantics are otherwise unchanged. Unknown public paths and non-GET methods return 404 unknown_id/route and never fall through to the account router, subject to the same IP budget as registered operations. The gameserver mounts it at /api/public/v1/, and composition fails closed without a valid policy or cursor pair. The composed-server Postgres witness checks all of this: the served epoch page, request-ID echo, cache headers, a 304 on a matching ETag, the unknown-route 404, and fail-closed composition. The composed Game UI lane also fetches the page through the Vite proxy.

The public runtime charges one token per non-304 response, including malformed queries, private/unknown identities, router refusals and operational failures. Before exhaustion their existing statuses and exact error bytes are unchanged; after exhaustion the response is the same uncached 429 rate_limited/ip used for successful uncached reads. Request IDs remain present; rejected response bodies and stale cache/length/encoding metadata are discarded. The cached writer and outer response writer share one admission decision, not two tokens. Authorized matching 304s remain free even after errors exhaust the bucket. IP attribution, burst/refill/entry limits and authentication policy are unchanged. This is response-budget enforcement: representation authorization/reader work still happens before the response decision, so it is not a pre-reader database-work bound. Mounted error/cache regressions use controlled readers; the existing composed Postgres checks separately exercise the live server.

Gameserver composition binds shared request-ID mechanics to both API surfaces, using the same C20 policy grammar/bounds. Valid caller IDs echo unchanged; invalid/missing IDs become UUIDv7, never truncated. The resolved ID is available through httpapi.RequestID (and the compatibility publicapi.RequestID accessor), without adding it to response JSON. Nested public middleware reuses that context rather than generating another ID. Composition installs private publicapi.WithRequestID and logging at Server's outer API boundary, before intent admission, so the existing drain503 also echoes/logs a valid or generated ID. PublicRead resolves through its runtime so identity failures still use its existing public response bucket. Public, unauthenticated Account and authenticated Account budgets remain separately owned. Standalone Account router callers must supply this HTTP composition themselves. This implements response/context correlation. Composition also supplies its existing structured logger to both API surfaces. After successful identity assignment, one api request entry records the resolved request_id before application handling, including cache hits, typed errors and limiter refusals. The entry adds no method, URL/query, body, headers, IP, account/Founder identity or credentials. The ID is caller-controlled correlation material, not an authenticated identity. It is not a new metrics label or gameplay telemetry stream.

httpapi.LogRequestID reads only the shared context, never a raw caller header; downstream header replacement cannot change the logged authority. Failed identity assignment does not fabricate an ID or a correlated request entry. Embedded public router callers can omit the optional logger; embedded Compose's existing nil-logger default is a discard sink. The production gameserver supplies its JSON stdout logger. This proves emission, not supported-host journal retention, delivery or capacity.

Identity-generation failure refuses before application work with uncached 500 {"category":"internal_invariant","detail":"request_id"}. It fabricates no request ID and never exposes the underlying error. One RequestIDFailureResponse declaration supplies the middleware bytes and BindRequestIDFailure's per-operation500 alternatives. Existing exact errors remain valid; broad500 descriptors are not narrowed. An existing gzip500 representation is retained alongside the JSON failure, ordered by status and media type; binding is idempotent and does not mutate its input registry. Conflicting JSON500 descriptors still refuse binding. With an available clock, public identity failures charge the same IP bucket and become429 at exhaustion; valid-ID authorized304s remain free even while entropy is unavailable. A broken clock cannot truthfully classify a caller as rate-limited. Raw HTTP mechanics retain their generic failure callback for non-API users; production API composition supplies the typed owner response.

The drain boundary still rejects only the existing POST /api/v1/intents path, with unchanged503 server_draining/retry_same_intent_id bytes. This request-ID correction does not broaden admission to other mutators or implement the draft tenant-binding contract. If private identity assignment itself fails, its existing typed500/no-ID refusal occurs before admission. Ordinary private/public reads remain available during drain; the request-ID regression observes no changes to the selected persisted application rows after refused intents.

The intentional C2-compatible pin refresh adds the request-ID error and three missing500 statuses. It also captures the already-generated optional upgrade ineligible_reason and Soul progress active/terminal union; those are not new behavior or acceptance in this batch.

The composed Postgres/HTTP error-budget check exhausts an exact fresh forwarded-IP burst with real private/unknown run reads and invalid queries, retains authorized304 and a second IP's404, and reads the authenticated account-owned Founder. It also exhausts the actual unforwarded peer's public budget before creating and reading the SQL linkage for a new Account, with credential no-store retained. That second loop accounts for earlier spent tokens rather than claiming another fresh-burst measurement. No pre-reader work bound, production Caddy/clean-host, supported-host log retention or whole-API acceptance follows.

Its entropy-failure supplement checks real Account/bootstrap/public responses, unchanged account/save/credential row counts, declared error bytes, the actual public failure budget, and valid-ID SQL/cache controls. These are controlled infrastructure faults, not an observed failure of deployed system entropy or a whole-release claim.

Public verification evidence

The registry mounts three unauthenticated GETs at /api/public/v1/runs/{stream}/{seq}/{genesis,replay-log,verdict}. The stream is a canonical Company UUID; the sequence is an exact positive integer without signs or leading zeros. Within the shared IP budget, private/unverified, unknown and invalid run identities return exactly 404 {"category":"unknown_id","detail":"run"}; corrupt authorized evidence or operational failures return 500 internal_invariant/public_api. Neither response exposes evidence or carries cache/content-hash metadata.

verdict returns the exact nine-field PublicRunVerdict descriptor: catalog_url, constants_hash, engine_version, genesis_sha256, genesis_url, replay_log_sha256, replay_log_url, run_id, verdict. The only verdict is verified. URLs are relative public API paths; hashes use sha256: prefixes. The catalogs URL names the run's pinned constants, never current constants. Its HTTP reader is still unavailable. Genesis version is present in the archive rather than an extra manifest field.

genesis serves the original stored JSON as application/json; replay-log serves the original gzip bytes as application/gzip. Both raw registry arms require X-Content-SHA256 (unprefixed hex over served bytes). All three successful responses have strong SHA256 ETags and public,max-age=31536000,immutable. Matching conditional reads return bodiless 304s before charging the shared IP bucket, retaining cache/ETag and raw hash headers. Exhausted uncached reads return non-cacheable 429 rate_limited/ip without a raw hash header. The shared cache runtime evaluates If-None-Match using HTTP weak comparison: strong/weak tags, comma-separated lists (including repeated field lines), optional whitespace and a lone * match an authorized current representation. Quoted commas stay inside their opaque tag; malformed conditions and nonmatching tags take the ordinary rate-limited path. The entire combined field is validated, so a matching first field cannot hide a malformed second field. Authorization/evidence validation still precedes caching: private or unknown run requests with * return the same non-cacheable 404 while within budget, and the shared 429 when exhausted, never a 304 or evidence. See RFC 9110 §13.1.2. This runtime conformance repair does not declare the still-held generated-client 304 arm.

leaderboard.Repository.PublicRunEvidence supplies C14's stored bytes and pinned metadata in one statement snapshot. Only an actual verified_runs record authorizes retrieval; a queue verdict, run pin or archive alone does not. Invalid/unknown run identities return ErrUnknownPublicRun without evidence. Missing or corrupt evidence for an authorized public run returns ErrInvalidPublicRunEvidence; database errors propagate as operational errors.

The source returns the original genesis JSON bytes/version, the original gzip+json.v1 replay archive, engine version and constants hash. Genesis must be valid UTF-8 as well as JSON; json.Valid alone would admit corrupt string encoding. Invalid encoding is corrupt public evidence, not normalized/repaired output. Valid Unicode, including literal U+FFFD, retains its original bytes and digest. This source check is separate from opaque raw transport. It verifies that genesis uses the pinned constants hash and that archive bytes match the stored SHA256, and computes the genesis SHA256 over the stored bytes. It does not re-encode JSON, recompress gzip, substitute current engine/constants, or expose account/session metadata. Caller mutation does not rewrite stored evidence.

The repository's real-Postgres test uses synthetic evidence to verify retrieval and refusal, including queue-only verification, missing bytes, corrupt hashes and cancellation. The normal composed lane requires this test to execute. It also executes the existing composed Exit/board witness: a real queue-verified run is downloaded over unauthenticated HTTP, every served byte and digest is checked against immutable storage, and its Company/founder_advanced event projection reverifies with the pinned DB catalog bundle. This matches the database verifier's selection; the archive retains additional Founder history (including Fiscal events), which that kernel projection does not reverify. A deliberately corrupted downloaded receipt is rejected. Embedded genesis JSON is compared with whitespace-only compaction because the existing archive encoder embeds the stored JSON value compactly; endpoint/storage equality remains byte-exact.

The same witness also advances its disposable database through the real epoch writer to a distinct active constants hash, closing the run's original epoch without accepting its hash in the new one. It reloads the historical bundle from Postgres and repeats the HTTP byte/hash/cache checks and replay refusal controls. The composed lane additionally repeats generated-SDK/TypeScript replay after that rollover. This proves historical evidence retrieval under changed database epoch state; the running composition is not restarted, no deployed content is minted, and catalogs still come from the DB fixture, not public HTTP. It is not an upgrade/release-artifact or complete public catalog proof.

The root composed lane additionally requires public-replay-composed.mjs to execute while that Go fixture's HTTP server remains alive. It loads the actual generated SDK and TypeScript verifier with the existing Vite SSR mechanism, downloads manifest/genesis/archive without credentials, binds engine/constants/URLs, and checks raw bytes and hashes against the Go fixture's storage-checked evidence. The TypeScript verifier accepts the downloaded Company projection and rejects changed receipts/events, a missing first entry and wrong constants. Pinned catalog bytes come from the database fixture, not a public catalog endpoint or current filesystem fallback. This is source-module SDK/HTTP/Postgres-to-TypeScript replay integration, not a browser-mounted or release-bundled public verification UI. Ordinary Go lanes explicitly do not run or claim this Node seam. No cross-epoch public catalog retrieval, complete third-party download loop, whole API acceptance or release-artifact proof follows.

Historical catalog database source

leaderboard.Repository.PublicCatalog now supplies the exact stored artifact set for an epoch-accepted constants hash, including historical epochs. It reads acceptance and bytes in one statement snapshot; a stored but unaccepted set is indistinguishable from an unknown hash (ErrUnknownPublicCatalog). Multiple epochs accepting the same hash do not duplicate artifacts.

The internal bundle carries the constants hash and artifacts sorted by name, each with its original bytes and sha256: digest. It recomputes the bundle identity using the existing length-framed constants-hash authority. Missing, additional or altered bytes, invalid names, duplicate names, invalid UTF-8 or malformed JSON fail as ErrInvalidPublicCatalog; database and interrupted-row errors propagate without returning partial evidence. Returned buffers do not alias the source. The reader neither consults current filesystem catalogs nor generates formulas. A historical set lacking formulas remains exactly that set.

leaderboard.VerifyPublicCatalogBundle also verifies an already assembled internal bundle: every advertised child digest must match its exact bytes, and the complete artifact set must match the existing length-framed constants hash. It reuses the same source validator, returning a detached, name-sorted snapshot or no result. It preserves surrounding whitespace and existing last-value JSON semantics; hash-matching malformed JSON/UTF-8 still refuses. This is a consumer construction primitive, not epoch authorization, owner semantic validation or a mounted HTTP consumer. The current manifest's complete nineteen-source population verifies unchanged; compacting any formatted source refuses even if its child digest is relabelled.

These are internal byte sources, not registered wire DTOs. The real-Postgres test retrieves the committed artifact set, mints a test-only newer formula-bearing set through the epoch repository, and retrieves both identities without substituting newer bytes into the older one. It also checks unaccepted sets, corrupt/missing accepted evidence and cancellation. The test mint does not modify the product manifest or authorize a release mint; it does not establish the complete public HTTP/TypeScript verification loop. The separate replay integration below now covers loading that kind of stored set.

Catalog schema composition (constructed, not mounted)

publicread.ComposeCatalogSchemas(servedFamilies, owners) builds C12/C18's closed PublicCatalogArtifact name-discriminated union and PublicCatalogBundle {artifacts, constants_hash} from explicit owner definitions. The composition root must supply the complete authoritative current/historical served-family set; the helper checks coverage of that input, not its inventory provenance. Every owner must declare its root. Missing/extra/repeated owners, reserved or repeated definitions (including identical redeclarations), unknown references, cycles and unsupported schema kinds refuse with no partial result. Shared dependencies are defined once and may be referenced across owner groups. Arms/definitions are sorted; returned trees do not alias inputs.

The actual epoch8 nineteen-family manifest with fifteen real exports produces a named coverage error for economy, minigame_api, relevance and routes. The existing fifteen exports share one validated namespace and accept their original current source bytes. Successful two-family unit contexts prove constructor/generator behavior, not a completed real nineteen-family union. Historical bundles may contain fewer artifacts than the full union; exact ordering, membership, hash identity and owner semantics still require their existing readers/loaders.

No production registry/router/generated-wire change or catalog endpoint is added yet. A real category-source diagnostic also proves that json.Marshal over json.RawMessage compacts the embedded bytes: the envelope passes its exact descriptor while the received value's digest no longer matches the claimed original-source digest (RP-597). The complete public writer/consumer must preserve/recover original bytes; object reserialization is not third-party verification.

A second diagnostic uses the actual category loader and a duplicate-member candidate. Both the loader and C18 structural descriptor accept it; registry HTTP admission rejects it (RP-598). This is a construction conflict, not evidence of duplicate members in a deployed catalog. The artifact-byte transport amendment proposes a narrow, owner-declared lexical boundary plus exact-byte recovery. It is draft, not implemented: the current global HTTP uniqueness rule, schema language, generated SDK and endpoint set are unchanged. Neither a generic raw-JSON escape nor owner-grammar narrowing is authorized.

server/formulas owns the extracted version-14 production-formula model and exports its closed ProductionFormulasV14 descriptor through Schemas(). The actual generator consumes that model and validates its output before writing; the published artifact remains byte-identical. Validate checks stored bytes without rewriting or regenerating them, rejecting unsupported versions, undeclared/missing fields, wrong nested shapes/enums, malformed UTF-8/JSON, invalid digests/Decimal strings and integers outside signed int64. The integer domain mirrors the existing model, not newly chosen operating limits. This is a grammar check, not proof that arbitrary prose is mathematically true or belongs to a particular epoch.

The descriptor covers the current formula version only, including a null axis pin and all three declared populated input variants. Historical version support must be explicit; no open-JSON arm or current regeneration fallback is introduced. This owner export is not yet registered in the catalog response union or product epoch manifest.

Both replay loaders now accept an optional stored formulas artifact, validate its version-14 grammar, and include its original bytes in the full constants identity. Go's subsequent catalog resolution rechecks that grammar and identity, including caller mutation. Older sets without formulas remain loadable and never gain current formulas. make formulas derives the client's closed descriptor metadata from the Go owner; signed int64 bounds are decimal strings, and the client preserves raw number tokens before validating them with BigInt. No JavaScript Number rounding, formula regeneration, new command or simulation arithmetic is introduced. The acceptance-set extension advances the mirrored replay kernel to 0.3.168; formula validator sources and generated metadata now belong to its affecting-path guard.

Shared controls exercise required shapes, null/populated branches, malformed values, exact int64 limits/overflow, JSON last-value semantics and string formats in both runtimes. A real Postgres fixture mint loads and resolves alongside the older epoch, and its public repository bytes also load without rewriting; a hash-matching unsupported formula version refuses. These are test-only epochs, not adopted balance or proof of the HTTP/third-party verifier loop.

leaderboard.CategorySchemas() now exports the closed CategoryCatalogV1 owner descriptor. Its five existing category arms bind each ID to its name key, timer and predicate shape; fact-set keys and nested predicate fields are closed. Current, historical and candidate catalog bytes validate without normalization. Unknown categories/fields, mismatched row bindings, unsupported versions, wrong types and invalid int64 literals refuse. The descriptor does not replace LoadCategoryCatalog: array cardinality/order, gate correspondence, fact membership and canonical policy values remain loader checks. A grammar-valid count of 41 is still refused by that loader's existing Phase-0 policy. No balance rule or historical bytes changed.

achievements.AchievementSchemas() additionally exports the existing v1 owner descriptor. Its five condition arms are unrolled through the owner's depth-four limit, avoiding a recursive schema; all three proof arms and the root/row keys are closed. The current catalog and two stored copies validate unchanged. Copy/provenance/scope compatibility, order, cardinality and total-node limits still belong to LoadCatalog, not the structural descriptor. The accompanying exact-key admission correction is real kernel behavior and carries version0.3.191; no metadata-only bump, balance change, duplicate/numeric policy rewrite or artifact mint is claimed.

meters.MeterSchemas() exports the existing v1 closed structural descriptor. Current and stored bytes, both signed input arms, all registered contribution slots and null/active decay validate. Loader-only order/cardinality/uniqueness/reseed relationships remain explicit. Exact-name Go admission and the omitted TS axis_stack slot are corrected under existing contracts, carrying the genuine kernel version0.3.192; no balance retune or metadata-only bump.

activeplay.ActivePlaySchemas() exports the v1 opportunities grammar with four closed effect arms, declared sampler/substream literals, canonical Decimal strings and bounded positive integers. Current/stored artifacts load with their corresponding Economy catalogs. Semantic binding, cardinality/order, Decimal relationships and production horizon checks remain mandatory. Actual A9 case-alias/wrong-arm-null/unsafe-duration admissions are corrected with kernel0.3.193; no balance, scheduling, payout or buff semantics are retuned.

doctrine.DoctrineSchemas() exports the existing identity-only v1 grammar: exact root/row keys, mechanical IDs and bounded integer source tiers. Current and stored catalogs validate with their corresponding Routes catalogs. Boundary agreement, branching/order and cross-artifact binding remain loader checks. Real case-alias/implicit-zero/final-null admissions are corrected with kernel0.3.194; no doctrine effects, choices, burst arithmetic or content mint is added.

fiscal.FiscalSchemas() exports the existing v1 root/policy/row shapes with safe-integer/PPM bounds, IDs and slot enums derived from the shared registered authority. Current/stored bytes validate with their matching Economy catalogs; relationships, sequence horizon, nonempty/order and declaration binding remain loader checks. Exact-name/non-null admission corrections carry kernel0.3.195; no harvest/risk/cost/production policy or content mint changes.

soul.SoulSchemas() exports the v1 policy/band/debit/activity/ending grammar, including heartbeat and presentation fields. Current/stored artifacts resolve against the tracked copy registry. Partition, copy/value/catch-up/watchdog relationships, epoch-versus-fixture rules and original-byte uniqueness remain loader checks. Null-to-zero/false admissions are corrected with kernel0.3.196; no drain/recovery/ending policy or source mint changes.

curriculum.CurriculumSchemas() exports the current first-hour v1 grammar with three closed branch/starter pairings. Current, epoch-8 stored and candidate data validate against matching Economy/Routes and tracked copy declarations; order/cardinality, binding and positive Decimal relations remain loader checks. Case-alias/default-zero/wrong-arm admissions are corrected with kernel0.3.197. Generic Reputation starter validation and payoff literals remain unchanged.

prestige.PrestigeSchemas() exports the existing policy grammar, including a closed five-key payout object, bounded spawn entries and the Go expiry representation bound. Current/stored bytes and valid retunes pass; positive threshold semantics, table cardinality and Economy resource binding remain owner/composition checks. Go exact-name/non-null admissions and the TS duration mirror are corrected with kernel0.3.198; no payout formula, configured value or offer policy changes.

pitch.PitchSchemas() exports the current v1 card/hack/funding grammar and four closed effect arms with the existing policy literals and safe-integer domains. Current, fixture and stored bytes validate unchanged; cardinality/order, positive Decimal values, copy bindings, chain partners and raw-member uniqueness remain owner checks. Kernel0.3.199 corrects Go's null-price admission without forbidding explicit free prices or changing shop/scoring/payout mechanics.

pet.PetSchemas() exports the closed v1/v2 care-policy union, including the version-bound presence of each action's Soul gate, owner vocabulary and existing integer/PPM domains. Required null numeric values no longer default to zero in Go (kernel0.3.200). Current, historical and valid-zero controls remain; policy relations, eligibility, deterministic FSM uniqueness and Soul context still require owner/composition validation. Mutable-state codecs, care/attendance arithmetic and content bytes are unchanged.

faction.FactionSchemas() exports the existing Phase-0 root/row/nullable-Compact grammar, closed faction/resource sets and stock domains. Kernel0.3.201 corrects case aliases and missing nullable fields in Go. The complete cycle, copy/Compact relationships, declared Commons tithe band and empty modifier lists remain mandatory loader/composition checks; no incorporation, stock, tithe or content values change.

guild.GuildSchemas() exports the current closed Phase-0 literal policy. Kernel0.3.202 fixes Go's case aliases and absent/null bonus defaults while preserving explicit zero and complete last-value controls. All eleven declared policy values and current/stored bytes remain unchanged; this metadata does not widen policy admission or substitute for Guild lifecycle/clearing proof.

commons.CommonsSchemas() exports the closed root/source grammar, current non-Commons slot registry and shared integer/PPM/Decimal domains. Kernel0.3.203 corrects Go aliases/defaults/ unsafe representations and the TS omission of axis_stack. Relations and Economy source bindings remain mandatory; current/stored/generated bytes and formula math are unchanged. Formula source-weight DTOs no longer transitively import Commons into production, restoring the existing C6 dependency guard without weakening it or changing document wire bytes.

minigame.MinigameSchemas() exports the historical-v2/current-v3 policy union with ranked scaling, payout, three fallback arms, offline-quality, rating and closed scalar unlock values. Kernel0.3.204 repairs required-null defaults and incomplete curve/fallback rows. Existing policy values, result nullability and payout/quality/clock mechanics remain; ordering, relations and composition bindings still require the owner. Formula generation changes only the current source fingerprint, not expressions or historical artifacts.

Economy's CostCurveSchemas() exports the closed K2 pricing component with original arm keys. Kernel0.3.205 rejects aliases/wrong-arm nulls without changing price queries or data. State and step/ratio semantics remain owner checks. This component does not yet describe the complete Economy artifact and is not counted as a completed catalog-owner export.

economy.ResourceSchemas() adds the K1 row/cap component with the existing four scopes and explicit nullable cap. Kernel0.3.206 fixes Go's literal-key admission without changing resource values, cap/ledger semantics or stored bytes. Relationships remain owner checks; the complete Economy artifact is still unexported and uncounted.

economy.GeneratorSchemas() and OperatingPolicySchemas() add version-bound generator rows, four closed role arms and manual/offline policy components. Kernel0.3.207 fixes original-key and wrong-arm/optional-object null admission. Current/stored rows and legacy compatibility controls validate without rewriting bytes; relationships and version selection remain owner requirements. These are components, not a full Economy descriptor or a registered public response arm.

Kernel0.3.208 repairs the remaining Economy version/root/row admission and the Route owner's shared condition admission. It does not export a full Economy or Route descriptor. The draft Artifact Schema Ownership now proposes one Route-owned companion and reviewed kernel-neutral descriptor delivery without changing the pure evaluator's imports. No adoption, companion, guard exception or new public arm follows from that proposal. The existing integer/dictionary/bodiless amendments remain separate.

The catalog HTTP reader, remaining generated-client caller migration and full public TypeScript verification loop remain open. Categories, Achievements, Meters, Opportunities, Doctrines, Fiscal, Soul, Curriculum, Prestige, Pitch, Pets, Factions, Guilds, Commons and Minigames provide fifteen owner descriptors for the nineteen currently pinned artifact families; the other four and the composed C18 catalog union remain unfinished. The separately exported formula artifact needs a protocol-compliant product mint and public registration. No open JSON wire arm or current-formula fallback is introduced.

The relevance-policy owner accepts exact integral decimal/exponent JSON tokens within its safe-integer domain, while the registry's existing SchemaInteger validator accepts only plain integer tokens. An actual owner/descriptor readiness probe records this mismatch (RP-566). A complete C18 relevance descriptor remains unimplemented until the representation contract preserves those historical bytes and admissible tokens; rejecting them merely to fit the current DSL is not a fix. Quoted numbers are not part of that dialect and now refuse in the Go owner, matching the client. No descriptor extension, catalog union or public route is implied. The integer token encoding draft now specifies an opt-in exact-safe mode while preserving default integer behavior. It is not adopted or implemented.