/** * Current protocol version. Used for cache invalidation and capability discovery. * * The block comment below is a chronological changelog of protocol * wire-shape changes. Each dated entry describes a wire-shape or * schema change; the most recent change anchors {@link PROTOCOL_VERSION}. * * -------------------------------------------------------------------- * The runtime sends the render-failure report (2026-10-02, ggui#1679: no * schema change, same draft stamp). The emit step of ggui#1609's * `ggui_runtime_report_render_failure`: `@ggui-ai/iframe-runtime` reports a * card whose component threw past its error boundary's retry, once per * session, with `{sessionId, appId, phase, errorName, catches}` and the view * proof on the call, over the same host relay every runtime tool rides. A * report that is refused, unanswered or met with tool-not-found (a server * from before the tool) is dropped in silence and never resent (SPEC §4.9). * `renderFailureErrorName` now reads "is an Error" by brand * (`[object Error]`) rather than `instanceof`, so an Error thrown in another * realm (a document-injected module) still reports its class name; a plain * object with a `name` is still `"Error"`. The vocabulary moves to its own * PURE-CONST module with a new subpath export, `@ggui-ai/protocol/render-failure` * (as `runtime-telemetry` is): importing these names from the package root * pulled the whole `schemas/mcp` module into the card runtime's bundle * (+34 KB against its budget). The root re-exports the same bindings, so * nothing on the root surface moves; `RenderFailurePhase` is newly exported. * No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * The server refuses a second gesture on a spent one-shot (2026-10-02, * ggui#1424: no schema change, same draft stamp). `ggui_runtime_submit_action` * claims the spend before the pipe append; a second committed dispatch of a * declared `oneShot` on the same card under a different `actionId`, once the * holder's delivery is marked, answers `{ok:false, code:'CONTRACT_VIOLATION'}` * with one violation at `actionSpec..oneShot` — the normative * discriminator from "bad payload" — and reaches no agent. Both the code and * `violations` were declared and emitted since ggui#1358, so no reader * changes. The `oneShot` docblock names the exception to "the RELAY never * collapses distinct actionIds"; SPEC's spent-oneShot clause gains the * refusal. No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * `ggui_render` captures the host-session slice; its fields are bounded * (2026-10-02, ggui#1339: same draft stamp). The request's * `_meta["ai.ggui/host-session"]` pair is stored on the session the render * creates, as the extension doc's section 2.4 already said, so * `ggui_list_sessions` filtered by the pair returns it. The render request * is the one that must carry the slice; the handshake creates no session. * `parseMcpAppAiGguiHostSessionMeta` now refuses a `hostName` or * `hostSessionId` longer than `MCP_APP_HOST_SESSION_FIELD_MAX_LENGTH` (256, * a new export) as `MALFORMED_HOST_SESSION`, and a server treats a * malformed slice as absent: the render succeeds, ungrouped. No server * stored the slice before, so no stored pair is affected. No * `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * Coverage on a judged reuse compares load-bearing action members * (2026-10-02, ggui#1428: no schema change, same draft stamp). A * `COVERAGE_GAP` finding's `path` may now be `actionSpec..` * for `oneShot`, `confirm` or `nextStep`: the draft declares the member on * an action both contracts carry and the proposed stored entry does not * match. `SuggestionFinding.path` is an open string, so an earlier reader * reads it as one. The finding stays `warn`; reuse stays the default. * -------------------------------------------------------------------- * A render whose generator returns no contract commits the agreed one * (2026-10-02, ggui#1429: no schema change, same draft stamp). * `UIGenerationResponse.contract` states it: a returned contract is * complete, never a patch, and an omitted one means the negotiated contract * unchanged. The committed session then carries the agreed `actionSpec`, * `streamSpec`, `contextSpec` and `clientCapabilities`, where before it * carried only the agreed `propsSpec`. * -------------------------------------------------------------------- * A repaired handshake draft keeps every declared member on every spec * (2026-10-01, ggui#1430: no schema change, same draft stamp). The * `origin: 'synth'` repair keeps, under the same key, every member the * draft declared on its props, context slots, stream channels and tools, * beside its actions (ggui#1421), changing only what a repair may * re-author. A draft tool the repair dropped is restored whole and named * `REPAIR_ENTRY_RESTORED` (`warn`) at `agentCapabilities.tools.`, a new * `validationFindings` code; `SuggestionFinding.code` is an open string, so * a reader on an earlier release reads it as one. `REPAIR_ENTRY_DROPPED` now * names a renamed, moved or removed entry on every spec. SPEC's "Repair * fidelity" clause is widened to match. No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * The self-contained shell shows a runtime it cannot load (2026-10-01, * ggui#1518: PATCH-class, no schema change, same draft stamp). After its * bundle-failure script posts the one `BUNDLE_FETCH_FAILED` (ggui#1503), it * removes `[data-ggui-shell-loading]` and paints a failure line * (`[data-ggui-shell-failure]`, `role="alert"`) with the thin shell's * wording and a Retry that reloads the frame. Nothing is painted while the * unhashed twin is still being tried. `gguiShellHtml` stays a pure function * of its inputs and gains no option. * -------------------------------------------------------------------- * `ggui_runtime_refresh_ws_token` stops declaring `REFRESH_WINDOW_CLOSED` * (2026-10-01, a wire removal, ggui#1510: same draft stamp). The output * schema's `code` enum names `BOOTSTRAP_INVALID` and `BOOTSTRAP_NOT_SUPPORTED`, * and `GguiRefreshWsTokenOutput` types those two. No server has sent the * member since the refresh became an authorized re-mint (ggui#1496 part B * slice 1, `79b4ced2e`: in npm 0.25.0, and on dev, staging and prod), and * it stayed declared one release after that, so a client holding an older * schema accepts every answer. The advertised schema is pinned byte for * byte (tag 14's without the member). The ack's `sessionToken` and the * `'session'` token kind are NOT removed here: npm 0.25.0 still mints the * ack's reconnect token, so they wait for the release after the one that * stops it on npm. No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * The build stamp rides the reference registry's row, a portable export and * a seed-pool import (2026-10-01, ggui#1476: no schema change, same draft * stamp). The registry writes `build` into its vector-store row (a JSON * string, written only when present) and reads it back through one reader * that validates it with `generatorBuildSchema` — an unreadable value drops * the fact, never the row — so a dedup return and a reuse name the MINTING * build. The exporter ships `PortableBlueprint.build` (declared with * ggui#1280), and a seed-pool import keeps it. The admission rule gains a * bound: a stamp over 1024 bytes serialized is dropped with a line, because * the stamp rides every row's vector metadata. Release order: the #1280 * emit (2026-09-27, below) serves on dev, staging and prod, and npm 0.25.0 * declares `PortableBlueprint.build`, so an importer from 0.25.0 reads an * exported stamp. An older importer reads only the keys it names, so it * ignores the stamp and imports the row. * -------------------------------------------------------------------- * Runtime telemetry vocabulary (2026-10-01, additive, ggui#1381 — MINOR, * same draft stamp): a new subpath, `@ggui-ai/protocol/runtime-telemetry`, * holds the kinds `ggui_runtime_telemetry` carries: `RUNTIME_TELEMETRY_KINDS` * (each kind, its batch `health` | `diagnostic`, and the one `detail` shape it * admits), `CHANNEL_LOG_EVENTS` / `ChannelLogEvent` (moved here from * `@ggui-ai/live-channel`, which re-exports them), `CONNECTION_STATUSES`, and * the kind types. The emitter and a host's door import one list. PURE-CONST * by contract (no runtime imports; a test pins it). The server stays * tolerant: an unknown kind is logged, never refused (N−1). No wire change. * -------------------------------------------------------------------- * The render result's `effort` is now SENT (2026-10-01, ggui#1459 emit step: * no schema change, same draft stamp). `ggui_render` states the named level * the generation that produced the render's code applied, from that * generation's own `GenerationMetadata.effort`: on `rendered`, and on * `failed` when a generation ran and failed after applying a level (its own * failure metadata, or a generation whose commit was rejected). Absent on a * reuse, a probe or placeholder render, a refusal, a generator that threw, * and when no level applied. `structuredContent` only — not the * `ai.ggui/render` slice. Release order: the declare step (2026-09-27, * below) serves on dev, staging and prod and shipped on npm in 0.25.0, so a * server's own `tools/list` names the member wherever it is sent. An npm * reader before 0.25.0 strips it (`renderOutputSchema` is not strict). * -------------------------------------------------------------------- * JSON-value schema emits a model-API-safe shape (2026-10-01, ggui#1637 — * PATCH-class wire fix, same draft stamp): `jsonValueSchema`'s object arm * checks its members in code instead of recursing, so the JSON Schema served * on `tools/list` has no definition that reaches itself through required * members (`additionalProperties: {}` in place of a `$ref` back to itself). * Google's function-declaration validator refused the old shape for every * tool that embeds it (`ggui_handshake`, `ggui_render`, * `ggui_runtime_sync_context`). The accepted set is unchanged, pinned against * the recursing form case by case; a deep invalid member is reported at its * member path. The array arm still recurses. No export changes. * -------------------------------------------------------------------- * Theme carry read NAMES an uninterpretable stored document (2026-09-30, * additive, ggui#1175 — MINOR, same draft stamp): `appThemeGetResponseSchema` * gains an optional `uninterpretable: { issueCount, issues: [{ path, code }] }`, * present exactly when the row holds a `theme` that fails the carry shape * (`theme` is then `null`; never beside `interpreted`, refused by the schema). * New exports: `THEME_ISSUE_CODES` / `ThemeIssueCode` (a closed set the * protocol owns, never zod's codes), `THEME_ISSUES_MAX` (20), * `THEME_ISSUE_SEGMENT_MAX` (100), `THEME_ISSUE_PATH_MAX` (16), * `appThemeUninterpretableSchema` / `AppThemeUninterpretable`, and * `describeUninterpretableTheme` (the one mapping; unknown issues map to * `other`). No stored value is carried. N−1: an additive optional member; * the previous release's three payloads parse unchanged (pinned), and the * one outside reader drops an unknown member. Kit: a second * `app-theme-carry` forward case. * -------------------------------------------------------------------- * A copied row now SAYS it is a copy: `Blueprint.clonedFrom` is EMITTED * (2026-09-30, ggui#1570 emit step: no schema change, same draft stamp). * The declare step served on dev, staging and prod before any writer set * it, because the previous release's strict `blueprintSchema` refuses a row * with a member it does not name. `ggui_ops_register_blueprint` * gains an optional input, `clonedFrom` (the parent's id), which * `createRegisterGeneratedBlueprint` parses too. The durable row records it, * and an empty id, the row's own id, and any id that is not a row of the * registering app are refused before anything is persisted. Nothing that * serves reads it. N−1: over MCP a server from before this STRIPS the member * (the SDK registers the input's shape), so a newer client's copy lands * unmarked, silently; a writer outside the server's own image checks the * server's `tools/list` before relying on the mark. * No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * A card whose render failed can say so: `ggui_runtime_report_render_failure` * is declared (2026-09-30, additive, ggui#1609 declare step: MINOR, same * draft stamp). It is a new app-only runtime tool, the seventh (SPEC §4.9): * `{sessionId, appId, phase, errorName, catches}`, the thrown value's class * name and never its message or stack. The server records a report only for * the session its view proof binds, answers `ok` whatever the verdict, and * never enqueues it as a turn. `VIEW_PROOF_V1_BOUND_ARGS` gains the tool's * row. Adding a row for a new tool is not a new label: no earlier signer * sends it and no earlier verifier accepts it, so the v1 vectors stand. No * runtime sends it until relays admit the name and this release's server * serves on that env. New exports: `reportRenderFailureInputShape`, * `reportRenderFailureInputSchema`, `reportRenderFailureOutputSchema`, * `GguiReportRenderFailureInput`, `GguiReportRenderFailureOutput`, * `RENDER_FAILURE_PHASES`, `RENDER_FAILURE_ERROR_NAME_PATTERN`, * `RENDER_FAILURE_MAX_CATCHES`, `RENDER_FAILURE_MAX_ID_LENGTH` (`sessionId` and * `appId` are 1 to 256 characters, the bound a relaying door applies too) and * `renderFailureErrorName`. A card sends at most one report per session. * No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * `ggui_ops_generate_blueprint`'s output drops `validatorScore` * (2026-09-30, a removal, ggui#1579: MINOR, same draft stamp). * `opsGenerateBlueprintOutputSchema` no longer declares the optional * member. No release ever populated it: the handler read it off the * generator's metadata through an undeclared field that no generator * sets. So §3.6's N−1 clock is already satisfied: the release that * "stops sending" it is every release, and a client holding a previous * `tools/list` that declares it meets outputs without it, as it always * did. `Blueprint.validatorScore`, on durable rows, is a different field * and is unchanged. No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * A copied blueprint row can say it is a copy (2026-09-29, additive, * ggui#1570 declare step: MINOR, same draft stamp). `Blueprint.clonedFrom?` * names the row whose code bytes the row copies; the copy's `source`, * `build` and `codeHash` stay its original's. Nothing that serves reads it. * `blueprintSchema` (strict) and so `ggui_ops_list_blueprints`' output * declare it; no writer emits it until this release serves everywhere, * because the previous release's strict schema would refuse such a row. The * portable form does not carry it. No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * A retried `ggui_runtime_submit_action` dispatch is one gesture * (2026-09-29, additive, ggui#1519: MINOR, same draft stamp). SPEC §11.1 * promises it as a SHOULD this release and a MUST from the next: a repeat of * an earlier committed `(sessionId, actionId)` is answered as the first was, * with no second pending event, `user.submitted` row or spend, even in * flight at once. An `actionId` reused for a different gesture (its * `intent` and `actionData`, compared by a digest of * `dispatchGestureBytes`) keeps the first. `ggui_runtime_submit_action` * declares `ACTION_ID_REUSED` on its closed output in `tools/list`, one * release before any server answers it; until then a reuse is answered as * a duplicate, and logged. New exports `dispatchGestureBytes`, * `DISPATCH_GESTURE_LABEL_V1` and `ACTION_ID_REUSED` * (`@ggui-ai/protocol/integrations/mcp-apps`). * No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * A session's stream counter carries a generation, `streamEpoch` * (2026-09-29, additive, ggui#1531: MINOR, same draft stamp). Three * optional fields: `StreamEnvelope.streamEpoch` beside every `seq`, * `AckPayload.streamEpoch` beside `streamSeq`, and * `SubscribePayload.fromEpoch` beside `fromSeq`. A new epoch means the * counter restarted, so `seq` values may repeat: a client that dedupes by * `seq` resets to nothing applied, and a server holding another epoch * than a resume's `fromEpoch` replays everything it retains. An absent * epoch is unknown, never a mismatch, so either side may predate it. * No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * The view-origin proof v1 is declared (2026-09-29, additive, ggui#1415: * MINOR, same draft stamp). Nothing sends a proof or refuses a call at this * release. Three pieces reach the wire: * - the request key `params._meta["ai.ggui/view"]` and its v1 grammar * (`@ggui-ai/protocol/integrations/mcp-apps`, `view-proof.ts`), which * no runtime sends yet; * - a ws token minted by a key-issuing door carries two optional claims, * `kid` and `src` (`VIEW_ROOT_SRC`, a closed set widened only a release * ahead of use); a verifier from before this release ignores them, and * no door mints them yet; * - `ggui_runtime_submit_action` and `ggui_runtime_sync_context` declare * `VIEW_ORIGIN_UNPROVEN` on their closed outputs in `tools/list` (SPEC * §4.7, §7.9), one release before any server answers it. * No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * A fresh subscribe's reserved-channel replay is written into SPEC §12.2.1 * (2026-09-28, ggui#1521: an erratum, same draft stamp; no byte on the wire * changes). A subscribe without `fromSeq` still replays no agent-declared * channel. The server SHOULD send known-reserved channels' retained envelopes * after the ack, at `seq <= streamSeq`, which both reference servers have * done since before the old text said "the server MUST NOT replay history". * New client obligation: apply those frames, and dedupe against the highest * `seq` applied, never against `streamSeq`. * -------------------------------------------------------------------- * The self-contained shell reports a runtime bundle that fails to load * (2026-09-28, ggui#1503: MINOR, same draft stamp). It posts ONE * `ggui:bootstrap-failed` with `BUNDLE_FETCH_FAILED` (SPEC §5.5.2), after the * #1501 twin when it has one, at once otherwise, as the thin shell does. Every * external runtime URL's shell now carries the capture-phase listener and a * `data-ggui-runtime="src"` mark on its runtime element, so those shells' * bytes change; an inlined runtime has nothing to fetch and is unchanged. * -------------------------------------------------------------------- * The subscribe ack stops carrying a reconnect `sessionToken` (2026-09-28, * ggui#1488 / ggui#1496 part B slice 3: MINOR, same draft stamp). No server * ever verified the token and no client read it, so the SHOULD that asked * servers to mint one is withdrawn. `AckPayload.sessionToken` stays declared * optional and `@deprecated` through this release and is removed in the * next (VERSION-POLICY §3.6), so a client parsing an ack from either * release accepts it. * -------------------------------------------------------------------- * The durable blueprint's build stamp is now EMITTED (2026-09-27, ggui#1280 * emit step: no schema change, same draft stamp). A generation mint writes * `Blueprint.build` from the generation's `GenerationMetadata.build`: the * `ggui_render` cold-generation registration and `ggui_ops_generate_blueprint` * (its durable row, and, under the same id, the row its cache mirror writes * through to a separate durable store; ggui#1497). Operator-registered, installed and seed-imported rows * carry none, and a dedup keeps the row's MINTING build. One admission rule * (`admitGeneratorBuild`, mcp-server-handlers) keeps a stamp only on * `llm`-sourced code and only when it passes `generatorBuildSchema`; * otherwise the stamp is dropped with a line and the row registers * unstamped. The stamp lives on the durable record; the reference * registry's vector-store row does not carry it. Release order, per channel: * the hosted declare step (`d9132ae6f`) serves everywhere since tag 14 * (`b68b964a7`), and on npm the declare ships in 0.25.0 and the emit from the * release after — so every rolling reader's `blueprintSchema` accepts a * stamped row before any writer produces one. * -------------------------------------------------------------------- * `GET /api/sessions/:id/state` renewals are bounded (2026-09-28, ggui#1496 * part B, slice 2: MINOR, same draft stamp). The ws token gains an optional * `rootIat` claim. A Path C renewal carries its chain's root forward (the * presented token's `rootIat`, else its `iat`) and clamps its `exp` to * `rootIat + refresh window`, with the window at least the TTL; every other * door mints a root without it. So every token of a chain is past its `exp` * by `rootIat + window`, and every gate refuses it in the same second, while * the window has not shrunk since the chain's root was minted and every * server sharing the secret uses the same window. A * verifier that predates the claim ignores it, and a token without it is its * own root (the N−1 token fixture is pinned in `@ggui-ai/mcp-server-core`). * -------------------------------------------------------------------- * `ggui_runtime_refresh_ws_token` is an AUTHORIZED re-mint (2026-09-28, * ggui#1496 part B, slice 1: MINOR, same draft stamp). It re-mints a view's * live credential, at any age, only for a caller the render read door's * own predicate admits to the envelope's session (the app, then the * subject, on the live row). A missing, foreign or subject-refused session * gets the same not-found `ggui_runtime_pull` throws, and no refresh window * applies. * * The advertised output schema is byte-identical to the previous release's. * It still declares `REFRESH_WINDOW_CLOSED`, which only a previous-release * server sends, so a client that cached either release's schema accepts * every answer (the N−1 fixture is pinned in `@ggui-ai/mcp-server`). * * New export `withWsToken(url, wsToken)`: the inverse of * {@link composeSessionApiUrls}' token parameter, so a view that holds a * refreshed token re-derives its token-bearing URLs without the refresh * sending them. The package-API removals this slice makes ride VERSION-POLICY * §3.7 (a pre-1.0 lockstep minor). * -------------------------------------------------------------------- * A shell retries a content-hashed runtime bundle ONCE through its unhashed * twin (2026-09-28, additive, ggui#1501: MINOR, same draft stamp); the thin * shell then reports `BUNDLE_FETCH_FAILED` as before, and the self-contained * shell still reports no bundle failure (a known §5.5.2 gap, ggui#1503). New exports `runtimeBundleHashedNameSource` and * `runtimeBundlePlainTwin` define the twin: the failed URL with only * `.<12 hex>` removed (same origin, same path; the inverse of the server's * hash insertion), and nothing for any other URL. `GguiShellHtmlOptions` * gains `runtimeBundlePlainName?`. When it is set and the URL is hashed, the * self-contained shell adds a capture-phase `error` listener script and * marks its runtime tag `data-ggui-runtime="src"`. Without it, the document * is byte-identical. The thin postMessage shell resolves the same pattern in * the frame. Why: a rolling deploy, a rollback or a replayed envelope can * name a hash no serving replica has, and every replica serves the unhashed * name. N−1: a slice is unchanged. A retried card runs the replica's current * runtime against its envelope, which is the N−1 direction the runtime * already owes. * -------------------------------------------------------------------- * The render result declares `effort?` (2026-09-27, additive, ggui#1459 * declare step: MINOR, same draft stamp). `renderOutputSchema.effort` is the * named level (`APP_GENERATION_PROFILE_EFFORTS`) the generation that produced * the render's code RAN — the engine reports it on * `GenerationMetadata.effort?` (core port, additive) and ui-gen sets it * exactly when a named level's dials were applied, on success and on * harness-failed metadata. Two-step by * design: this release DECLARES the member (the output reaches `tools/list` * closed, so a host must cache a schema naming it first); no server sends * it until this release serves everywhere, and then only on `rendered` / * `failed` results whose generation applied a named level. * -------------------------------------------------------------------- * The durable blueprint carries its minting engine's build (2026-09-27, * additive, ggui#1280 declare step: MINOR, same draft stamp). * `GeneratorBuild { version?, mode?, digests }` is declared HERE (the core * port re-exports the same name), `Blueprint.build?` and * `PortableBlueprint.build?` carry it, `generatorBuildSchema` mirrors it * (strict; digest values are lowercase hex sha256). Readers treat absence * as `mintedBy: unknown` — a reported category, never an error. Two-step by * design: this release DECLARES the field and every store persists it when * given; no writer EMITS it until this release serves everywhere, because * the previous release's `ggui_ops_list_blueprints` output validation parses * rows with its strict `blueprintSchema` and would refuse a stamped row * (N−1). The emit — handlers passing `metadata.build` into * `registerBlueprint` — lands one release later. * -------------------------------------------------------------------- * Text blocks carry an optional `phase` (2026-09-27, additive, ggui#1441: * MINOR, same draft stamp). `textBlockSchema` gains `phase?: string` — * `"interim"` is narration between tool calls, absent or any other value is * answer text; the vocabulary is open (it mirrors AgJSON's `phase`, not ours * to narrow), and a consumer never refuses a value it does not recognize. * N−1 both ways by construction: the previous release's payload (no * `phase`) parses unchanged, a payload with `phase` keeps it, an older * reader strips it and shows answer text, an older server never sends it. * No PROTOCOL_VERSION move. * -------------------------------------------------------------------- * `ggui_consume`'s `nextStep` is now SENT (2026-09-27, ggui#1399 step 2: * no schema change, same draft stamp). Declared in 0.24.0 (step 1), the * member rides every non-empty drain as `{ tool: 'ggui_amend', * description, example, args: { sessionId } }` — the input's session id * verbatim, the example in copy-paste form — and is absent on an empty * drain and on a late drain from an expired render. A host that cached the 0.24.0 output schema accepts it; the * model-visible content leads with the example in plain text, as the * render → consume hint does. No PROTOCOL_VERSION move. * -------------------------------------------------------------------- * `MODELS.openrouter` gains `openai/gpt-6-sol`, `openai/gpt-6-luna` and * `anthropic/claude-opus-5.5` (2026-09-27, additive, ggui#1267: MINOR, * same draft stamp). Autocomplete only — every `/` string * was already accepted by shape under `openrouter`. Listed after the * OpenRouter tools smoke the founder gated them on (D3) passed on a clean * session: 3/3 tool calls returned per id with `tool_choice: required`, * 0 errors. Opus 5.5 followed the two GPT-6 ids by one landing: its dotted * id listed only once it resolved to a price row (the dashed Anthropic * tail tried after the dotted one). No PROTOCOL_VERSION move. * -------------------------------------------------------------------- * The register door takes the fit facts (2026-09-26, additive, * ggui#1427: MINOR, same draft stamp). `opsRegisterBlueprintInputSchema` * gains four optional non-identity inputs: `intent` (the request's own * sentence, symmetric with `*_generate_*`'s), `judgedCanvases` (a * non-empty list — unknown is omission, never an empty list, which would * declare a card judged on no canvas), `aestheticPreset { id, version? }` * and `directionDigest` (normalised sha256 hex), plus `directionScope` * (`app` — the direction the app's profile carries; `request` — a * direction given with the request itself; only beside a digest, refused * without one; a digest without a scope stays accepted). None joins the cache * identity; each rides the cache row for the semantic reuse pre-filter * (`fits()`: data-shape, surface, direction), and an omitted fact reads * not-evaluated. The schema is strict and its only senders are the * server's own in-process registrars, so an older control-plane caller * never sends the keys and nothing skews across a release. No * `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * The relay enforces the action contract at receipt (2026-09-26, ggui#1358 * step 2: a first-party server meets a MUST it had missed on one path — * VERSION-POLICY §1.3, "the bar was always there"; same draft stamp). * `ggui_runtime_submit_action` now validates a dispatch's `actionData` * against the render's `actionSpec[intent].schema` before the pipe and * ledger writes, exactly as the live channel does, and answers a failing * dispatch `{ ok: false, code: 'CONTRACT_VIOLATION', message, violations }` * — the code and member declared one release earlier (entry below). A card * with no `actionSpec`, a non-component render, or a server with no render * store has nothing to enforce and passes as before; a store read that * fails is named on one warn line and the dispatch passes ungated. No * `PROTOCOL_VERSION` move. * The consume result declares its next-call hint (2026-09-26, additive, * ggui#1399: MINOR, same draft stamp). `gguiConsumeOutputSchema` gains an * optional `nextStep: { tool: 'ggui_amend', description, example, args: * { sessionId } }` — the render → consume chain's shape, closing the loop * from the drained gesture back to the card: present iff `events` is * non-empty, absent on an empty drain, the decision the agent's. SPEC §7.3 * names it. DECLARED here, EMITTED one release later: the handler returns * no hint yet, and the tool's output reaches `tools/list` closed * (ggui#1333), so a host must cache a schema naming the member before any * server answers with it. No `PROTOCOL_VERSION` move. * * -------------------------------------------------------------------- * The relay declares its contract-violation answer (2026-09-25, * additive, ggui#1358: MINOR, same draft stamp). `contractViolationSchema` * is the zod mirror of `ContractViolation`; `ggui_runtime_submit_action`'s * output `code` enum gains `CONTRACT_VIOLATION` and the output gains an * optional `violations: ContractViolation[]`. SPEC §4.7's handler contract * and §2.4's failure modes name the relay's answer beside the live * channel's frame. DECLARED here, ENFORCED one release later: the relay * does not yet run the `actionSpec` gate, and the tool's output reaches * `tools/list` closed (ggui#1333), so a host must cache a schema naming the * code before any server answers with it. No `PROTOCOL_VERSION` move. * * -------------------------------------------------------------------- * The handshake suggestion names the card a judged hit proposes * (2026-09-25, additive, ggui#1336: MINOR, same draft stamp). * `BlueprintMeta.matchedIntent?` carries the stored intent the reuse judge * compared with the request, capped at `MATCHED_INTENT_MAX_CHARS` (280, * the judge's own truncation), so the agent (which holds the * conversation) can decline a proposal it reads as the wrong card. Present * only on a judged hit from the requesting app's own pool, never from a * pool shared across apps. DECLARED in this change, EMITTED one release * later: every output object reaches `tools/list` closed * (`additionalProperties: false`, ggui#1333), so a host that cached the * previous release's schema would refuse a member it did not name. The * member carries a `.describe()` naming its meaning and the decline path * (`forceCreate`), so the agent that receives it knows what it is for. The * `intent` input's description now says it is stored and shown to later * requests of the same app, and names the task, never the end user. * Kit: the `n1-compat` catalog gains the `handshake-suggestion` wire and * tag 9.1's cache-hit suggestion as a BACKWARD case. No * `PROTOCOL_VERSION` move. * * -------------------------------------------------------------------- * The read-plane-only posture is normative, and graded (2026-09-24, * additive, ggui#1304: MINOR, same draft stamp). SPEC §7.10.6 states the * posture the reference server has run since ggui#537: a result publishes * the render locator on `structuredContent.resourceUri` and the same * value on `_meta.ui.resourceUri`, and carries neither the * `ai.ggui/render` slice nor a live-channel token; the locator's read * then mounts. A host that mounts the declaration-level shell against * such a server MUST answer that read, and a host that advertises * `serverResources` MUST answer `resources/read`. Kit: the new * `read-plane-only-conformance` catalog grades the server; the * host-helper catalog's H2 probe map gains `serverResources` → * `resources/read`. Kit, same row: the `bootstrap-protocol` fixtures * describe the live delivery paths (the inline global, the forwarded * tool result's top-level `_meta`), a new `tool-result-override` setup * directive drives the missing-slice failure, and that fixture expects * `MISSING_META_GGUI_BOOTSTRAP`, the spelling emitters send (§5.5.2 now * says so; `BOOTSTRAP_META_MISSING` names the same condition). No wire * shape moves. No `PROTOCOL_VERSION` move. * * -------------------------------------------------------------------- * Host-declared gesture delivery (2026-09-24, additive, ggui#1309: MINOR, * same draft stamp). New exports: `GGUI_HOST_CAPABILITIES_HEADER` * (`ggui-host-capabilities`), `HOST_CAPABILITY_UI_MESSAGE_TURN` * (`ui-message-turn`), `parseHostCapabilitiesHeader`. A host's own code * declares the token on its MCP connection's header; `ggui_render` then * omits its `nextStep: ggui_consume` hint, so the agent ends its turn at * paint. Unknown tokens are ignored. No `PROTOCOL_VERSION` move. * * -------------------------------------------------------------------- * A member-level clear on a theme write (2026-09-23, additive, ggui#1308: * MINOR, same draft stamp). New exports: `APP_THEME_CLEARABLE_MEMBERS` * (`mode`, `name`, `frameless`, `fonts`, `imagery` — the optional members * outside the attestation), `AppThemeClearableMember`, * `appThemeWriteSchema` / `AppThemeWrite` (the write form, where those * members also accept `null`), and `splitAppThemeWrite`. `appThemeSchema` * and `AppTheme` are unchanged and null-free. The #1124 refusal text names * the member-level clear for each clearable dropped member. The shared * writer's half (storing the split document, naming the cleared members) * lands WITH this (cloud). No `PROTOCOL_VERSION` move. * * -------------------------------------------------------------------- * The card's spent `oneShot` names on the render slice (2026-09-23, * additive, ggui#1223: MINOR, same draft stamp). * `McpAppAiGguiRenderMeta.spentOneShots?: readonly string[]`, projected only * by `deriveRenderMeta` from the stored `ComponentGguiSession.spentOneShots` * record (`SpentOneShotsRecord` = `{ epoch, actions }`) — only when the * record's epoch is the projected card's, filtered to the names that card * declares `oneShot`. A consumed action re-served after a reload rendered * live again; the runtime now seeds its guard from the slice. New exports: * `spentOneShotsSchema`, `SpentOneShotsRecord`; the parser options gain * `onInvalidSpentOneShots` (a malformed value is dropped and named, never * fatal). Kit: the `render-meta` forward case carries the member. The store * write (ggui#1305) and the runtime seed land separately. No * `PROTOCOL_VERSION` move. * * -------------------------------------------------------------------- * `actionSpec` on the static render slice (2026-09-23, additive, ggui#1178: * MINOR, same draft stamp). `McpAppAiGguiRenderMeta.actionSpec?: ActionSpec`, * projected only by `deriveRenderMeta` (component renders) and WHOLE, the * same value the live WS session carries. A runtime painting without a live * session frame (past the wsToken TTL, the pre-WS window, no live trio) held * `{}` and failed open: no `oneShot` guard, permissive validation. New * exports: `actionSpecReadSchema`, `parseActionSpecAtReadDoor`, and * `ActionSpecReadDoorResult`, the tolerant READ door that strips and names * unknown entry members (`actionEntrySchema` is `.strict()`, so a strict read * would drop the whole spec on a newer server's member). The parser options * gain `onInvalidActionSpec` and `onStrippedActionSpecMembers`. Kit: wire * `render-meta`, forward case. The runtime half (seed + the * `one-shot-unenforceable` trace) is rnd's, landing AFTER this. No * `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * Model registry: `anthropic/claude-opus-5-5`, `openai/gpt-6-sol`, * `openai/gpt-6-luna` (2026-09-23, additive, ggui#1252 — MINOR, same draft * stamp; the protocol half of the new-models support batch ggui#1251, WITH * cloud's LiteLLM re-vendor). Three `ModelId` rows + `MODELS.anthropic` / * `MODELS.openai` entries; all `active`, none in the lineup, no default * moves. Opus 5.5 4 / 20 / 5 / 0.2 (0.05× cache read), `maxTokens` * 1000000, `retireNotBefore` 2027-09-22; Sol 2 / 10 / 2.5 / 0.2 and Luna * 0.1 / 0.5 / 0.125 / 0.01, `maxTokens` 1050000 (the context window), no * retirement floor published. Every field quoted from the vendor page on * the issue. `mistralai/mistral-large-2512` leaves `MODELS.openrouter` * (retired upstream: LiteLLM dropped it, OpenRouter no longer lists it); * the route stays accepted by shape like any uncurated slug. Bedrock Opus * 5.5 is not registered (held). No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * Theme CARRY read (2026-09-17, additive, ggui#1155 — MINOR, same draft * stamp): `appThemeCarrySchema` / `AppThemeCarry` and * `appThemeGetResponseSchema` / `AppThemeGetResponse` — the REPRODUCE side * of the two-read-paths rule. A theme GET returns the stored document * VERBATIM (unknown members kept; a shape check over the named members, * never a strip) or `null`, with an optional `interpreted.stripped` * (`.min(1)`: present ⇔ at least one name) naming what an INTERPRET * reader would drop. Exists so a writer's carry cannot be turned into the * ggui#1124 defect by the reader it carries from. New exports only; no * existing shape changes. Kit: wire `app-theme-carry`, forward case. * -------------------------------------------------------------------- * `ggui:dismiss` (2026-09-16, additive, ggui#1109 — MINOR, same draft * stamp): a user dismiss GESTURE forwarded from the card to its host as * an INTENT, on a new protocol-owned tag in the `ggui:` postMessage * family beside `ggui:lifecycle`. The card MUST NOT act on it (the host * owns what dismissal means) and emits at most one intent per gesture; * the host decides, and ignoring is CONFORMANT — so adoption breaks no * host. `reason` is extensibly-closed ('escape' today), and the type * guard validates SHAPE only: checking today's reason set would make a * release-N host silently drop a release-N+1 intent (§3.6). Not on * `ggui:observe` (a host may ignore telemetry by contract) and not a * `ui/notifications/*` name (a frozen external namespace). * -------------------------------------------------------------------- * Motion tempo override (2026-09-16, additive, ggui#1093 P1c — MINOR, * same draft stamp; no WIRE change). The theming spec's §2.3 retirement * of per-app motion is AMENDED (protocol + rnd): layer-1 keeps the motion * SCALE, and the document may state a BOUNDED tempo override — `duration` * (fast/base/slow), `easing` (standard/emphasized/exit, each validated at * the door as a CSS keyword, cubic-bezier or steps) and `reduce` — the * standing `palette` has always had. The retired FREE-MAP ladders stay * refused and the door's guard test pins the narrower rule. Reversed, not * widened: §2.3's stated ground ("zero consumers today") expired when * ggui#1075 created the consumer. * -------------------------------------------------------------------- * Theme DOCUMENT members (2026-09-16, additive, ggui#1093 P1b — MINOR, * same draft stamp; no WIRE change at all). `ThemeDocumentV2` and design's * `DtcgTheme` gain optional `typeScale` (five roles; `leading` is a ratio), * `rhythm` (bounded `base` + named steps) and `scrim`; they reach the card * through `deriveThemeVariables` → the overlay, so the wire's `AppTheme` * is untouched and this entry moves no stamp. Motion is NOT here: it would * reverse the theming spec's §2.3 layer-1 ruling and lands as ggui#1093 * P1c with that reversal, co-signed. * -------------------------------------------------------------------- * AppTheme carrier, wire half (2026-09-15, additive, ggui#1093 P1a — * MINOR, same draft stamp; #1075 Track C (a), closes #990's wire half). * Two OPTIONAL members on `appThemeSchema`: `fonts` (1..16 faces on * `fontFaceDeclarationSchema` — #987 §5's document grammar lifted into * the protocol; `@ggui-ai/project-config` imports it back, one grammar * for the document door and the wire) and `imagery` (`mark` / `hero` / * `pattern`, each `{ src https, alt? ≤ 200, tone? light|dark }`). Grammar * at the door, never fetched; outside the attestation (`OverlayHashInput` * unchanged). Belt fixtures moved to a synthetic `futureMember` because * `fonts` is now a named member. * -------------------------------------------------------------------- * AppTheme read-door posture (2026-09-15, additive, ggui#1093 belt — * MINOR, same draft stamp; VERSION-POLICY §3.6 forward twin, ruled with * cloud). `appThemeReadSchema` + `parseAppThemeAtReadDoor`: READ doors * strip unknown TOP-LEVEL members, keep the overlays, name the stripped * keys; WRITE doors stay on the strict `appThemeSchema`. The render-meta * reader adopts it (`onStrippedThemeMembers`). The kit's `n1-compat` * catalog gains its first FORWARD case (`app-theme-read`). Wire bytes * unchanged; the belt under the #1093 carrier (`fonts?` / `imagery?`). * -------------------------------------------------------------------- * `ggui_ops_generate_blueprint` gains `intent` (2026-09-13, additive, * ggui#1046 — MINOR, same draft stamp): the generation prompt as a * NON-identity field, bounded at `OPS_GENERATE_BLUEPRINT_INTENT_MAX_CHARS` * (2000); `seedPrompt` stays a variance key (cache identity). The handler's * silent placeholder is observable this release and refused next * (`prompt_required`). The kit's `n1-compat` catalog pins Release 2's * input (contract + seedPrompt, no intent). * -------------------------------------------------------------------- * Generator profile widened (2026-09-13, additive, ggui#1058 — MINOR, same * draft stamp; founder's A2 "profile OVER engine"). Two optional members on * `appGenerationProfileSchema`: `effort` (one of five names — the wire * carries the name, the reader owns the dials table; an unavailable level * is refused `{ profile: { effort: 'unavailable' } }`, never downgraded) and * `aesthetic { id, version? }` (a grammar-checked reference into the * deployment's catalogue, resolved at read, unresolvable = non-fatal + * `profile_aesthetic_unresolved`). The kit's `n1-compat` catalog gains the * `generation-profile` wire (Release 2's three-member payload accepted). * -------------------------------------------------------------------- * Generator profile member `direction` (2026-09-11, additive, ggui#1027 * follower on the ggui#991 slot — MINOR, same draft stamp). A fourth * optional member on `appGenerationProfileSchema`: one variant's * COMPOSITION direction — what leads, what is left out, motion or none — * free text under the same `profileText` rule, door-bounded at * `APP_GENERATION_PROFILE_BOUNDS.direction = 600`, named by the refusal * body. §3.6 (N−1): a door on the previous release validates with the * `.strict()` schema that lacks the member and REFUSES a payload carrying * it, so the WRITER emits `direction` only where the reader is on this * line — the bootstrap lane composing variant profiles server-side in * the same publication holds it by construction; a console or platform * writer waits for the release carrying it. No `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * Rendering-context vocabulary: `renderingContextSchema` (2026-09-11, * additive, ggui#1000 — MINOR). The `{ shell, device, viewport? }` * vocabulary that lived as two hand-declared unions (mcp-server-core * `UiGenerateInput.rendering`, ui-gen `RenderingContext`) becomes ONE * schema here; both consumers now derive their type from it, and the * bootstrap door validates `items[].rendering?` with it. No existing wire * changes shape; no `PROTOCOL_VERSION` move. * -------------------------------------------------------------------- * Generator profile slot: `appGenerationProfileSchema` (2026-09-10, * additive, ggui#991 — MINOR; the D7 mechanism of ggui#987). One new * schema on the app's `generation` section — `profile?: { styling?, * density?, layout? }`, free text, trimmed, door-bounded at * `APP_GENERATION_PROFILE_BOUNDS` (2000 / 200 / 200), control characters * refused — plus its refusal body. A generation-time input, never a * token; absent or empty leaves the generator's prompts byte-identical. * No existing type changes; readers and doors land with the #991 set. * -------------------------------------------------------------------- * Theming revision — the overlay is the projection, the host owns runtime * mode (2026-09-10, ggui#987 — **BREAKING on the draft wave**, named by the * conformance kit: `protocol-conformance/src/theme-binding-conformance` * promoted today's pins in the prior commit, and this change fails them — * VERSION-POLICY §1.1; shipped under §1.4's `draft-` clause, §3.5's window * waived pre-v1.0). Founder rulings D1–D7 (2026-09-09/10), joint spec * `docs/superpowers/specs/2026-09-09-theming-revision-protocol-half.md`. * `appThemeSchema` v2: `overlays: { light, dark }` REQUIRED (the derived * projection for both modes — one producer, `@ggui-ai/design`'s * `deriveThemeVariables`), `overlayHash` REQUIRED (`canonicalOverlayHash`, * recomputed at every write door), `mode` optional and a DEFAULT only, * `name` a label; `base` DELETED with the registration tier (D2 = B). The * client projection of `themeMode` flips — `hostAnnounced ?? stamped ?? * sessionSidecar` (D4 = A: M1 + M2, "follow the widget") — the server stamp * is unchanged; `themeId` loses its `sidecarName` leg. `AppThemeRefusalBody` * is the one write-door refusal shape. `parseMcpAppAiGguiRenderMeta` gains * `onInvalidTheme` so the read door is never silent. The render shell paints * `--ggui-color-ground` (the surface-layering roles of §2.1). * -------------------------------------------------------------------- * Model registry: `openai/gpt-6-astra` (2026-09-09, additive, ggui#977 — * MINOR; Exp 008's founder-ruled second arm, ggui#972). One new `ModelId` * union member and one `MODELS.openai` allowlist entry; premium, active, * not in the lineup; costs 10 / 50 / 12.5 / 1.0 per 1M (input / output / * cache write / cache read), `maxTokens` 922000, tools — every field a * receipt on the issue; `retireNotBefore` unset (none published). * -------------------------------------------------------------------- * Refusal registry v11 — the retail plan model retired (2026-09-08, * wire-code, pre-launch, ggui#960 — MINOR on the 0.16.0 draft wave; the * protocol half of the pricing publication ggui#949, WITH cloud's arm * deletion). Founder's ruling: one product, prepaid pay-as-you-go at flat * rates, trial and tiers retired, the welcome credit as the free entry. * Eleven codes lose their emitting arms and retire in the same slice — * render-gate `trial_exhausted`, `trial_expired`, `app_canceled` * (`RENDER_GATE_REFUSAL_CODES` 14 → 11); owner-api `subscription_exists`, * `no_subscription`, `subscription_unchanged`, `portal_unavailable`, * `card_update_unavailable`, `managed_app_no_portal`, * `managed_app_no_card_update`, `managed_app_no_checkout` (9 → 1: the * prepaid wallet's one Stripe surface is the top-up, user-scoped). Four * rows re-described in the new model's words: `billing_path_missing`, * `model_not_allowed` (per-account grant), `checkout_unavailable` * (top-ups), `insufficient_credit` (pool or BYOK). New typed exports * `OWNER_API_REFUSAL_CODES` + `OwnerApiRefusalCode`. Not breaking under * §2: pre-launch draft wave, every retired code had lost its emitter in * the same publication; the kit's non-render-surface case re-aims to the * surviving owner-api code. `managed_default_cap_exceeded` stays (ggui#965). * -------------------------------------------------------------------- * Blueprint provenance de-modeled (2026-09-06, wire-tightening, pre-launch, * ggui#924 — MINOR; half of ggui#923, one WITH publication across six * lanes). `LlmBlueprintSource.generator` is the identity `ui-gen-` * (one tier token — `ui-gen-default` / `ui-gen-advanced` / an operator * tier; `GENERATOR_ID_PATTERN`, `isGeneratorId`) and `model` is the run's route * in the registry's spelling (`ModelRef` = `/`, composed only * by `modelRefOfRoute`, recovered by `parseModelRef`; registry `ModelId`s * are the subset — a self-hoster's bedrock/OpenRouter route is a ref the * registry does not list; `MODEL_IDS`, `isModelId` narrow to the subset). * Before: both were `string`, and the identity embedded the model * (`ui-gen-default-haiku-4-5`) — a model retirement (Haiku 4.5, * 2026-10-15) would rename the identity against every stored record. * `parseBlueprintSource` and `llmBlueprintSourceSchema` refuse a modeled * identity and a bare model name; the handshake draft's `generator` hint * and the operator tools' `generator` inputs take the same grammar. No * alias, no dual-read (pre-launch): a row written before the rename reads * as no provenance; stores reseed. Not breaking under VERSION-POLICY §2 * (no kit fixture asserts a modeled id or a bare model); MINOR under §1.2 * as a schema tightening on the 0.16.0 draft wave. Post-launch this would * be a MAJOR with a migration doc and a dual-read shim — the reason it * ships now. * -------------------------------------------------------------------- * Five phantom numeric codes retired-reserved + the emitter census * (2026-09-06, wire-code, pre-launch, ggui#910 — PATCH). `CAPABILITY_DENIED` * (-32005), `GENERATION_QUOTA_EXCEEDED` (-32010), `APP_LIMIT_EXCEEDED` * (-32011), `CONCURRENT_SESSION_LIMIT` (-32012) and the numeric * `CONTRACT_VIOLATION` (-32020 — the census found it once the gate * existed; the live `CONTRACT_VIOLATION` is a string on the channel and * the Plane-3 render error, and tools/call has Plane-2 * `contract_violation`) were declared, * SPEC-fenced and mirrored into four tables, and emitted by nothing * first-party — a census by constant and by number over oss/packages, * cloud and backend. The constants leave; the numbers stay reserved * (SPEC §7.9); an unauthorised key is a bare 403 carrying -32007; quota, * app-limit and concurrency states are refusals or implementation-range * codes. Cheap only before launch: after the `draft-` rule flips each * removal would be a MAJOR with a migration doc. The mirrors gate now * also asserts the inverse — every declared non-standard code has a * first-party emitter (one `git grep` per code; seeded self-test) — so a * phantom cannot be declared again silently. Pinned by * `types/__tests__/retired-error-codes.test.ts` (eight retired numbers). * Not breaking under VERSION-POLICY §2; PATCH under §1.3. * -------------------------------------------------------------------- * * -------------------------------------------------------------------- * `-32013 RATE_LIMIT_EXCEEDED` retired-reserved (2026-09-06, wire-code, * pre-launch, ggui#890 — PATCH). After ggui#886 (the per-app cap denies as * the registry's `app_rate_limited` refusal) and the `RateLimitedError` * deletion, no first-party implementation emits the number: the reference * server refuses in-band, the hosted transport's only 429 is a per-IP * backstop with no JSON-RPC body. The constant leaves * `PLATFORM_ERROR_CODES` and SPEC §7.9's platform fence; the number stays * reserved (the `-32001` / `-32004` convention) so no future canonical code * reuses it. Not breaking under VERSION-POLICY §2 — no kit fixture asserts * it; PATCH under §1.3. Pinned by `types/__tests__/retired-error-codes.test.ts` * (no constant declares a retired number; no tracked source emits one) and * by the mirrors gate, which reds the docs tables' rows until they leave * in the same publication. * * -------------------------------------------------------------------- * The registries export their literal-typed rows (2026-09-06, types-only, * pre-launch, ggui#889 — PATCH). `PRE_GENERATION_REFUSAL_CODES` and * `DOMAIN_ERROR_REGISTRY` are the normalized views (`Record`), * which erase the per-key literal the `const` definers preserve — so a * producer that must satisfy a wire enum from a row had to parse it * through the enum (ggui#886). `PRE_GENERATION_REFUSAL_ROWS` and * `DOMAIN_ERROR_ROWS` are the SAME objects with the literal types kept: * each row's `code` is typed as its own key. No wire change; * the kit's registry-completeness catalog reads the normalized view as * before. Not breaking under VERSION-POLICY §2; PATCH under §1.3. * * -------------------------------------------------------------------- * Plane-2 slugs lead the wire text (2026-09-06, wire-text, pre-launch, * ggui#880 — MINOR). SPEC * §7.9 promised "the `code` field on each class is the wire literal" * while the MCP SDK ships every thrown handler error to the agent as * `{content: [{type: 'text', text: error.message}], isError: true}` and * nothing else — executed against the built server: `session_not_found` * and `handshake_not_found` reached neither a field nor the text, and * descriptions, presets and the SPEC taught agents to branch on them. * * de1. **`DomainError` base** (`errors/domain-error.ts`) — the ONLY * composer of a Plane-2 error's `message`: `${code}: ${detail}`. * Refuses an empty detail and a detail that begins with any * registered domain OR refusal code + `': '` * (`DomainErrorDetailCollisionError`, a `TypeError` the emitter's * own suite sees — never the wire); a tool-name prefix is prose. * `isDomainError` detects by `Symbol.for('ai.ggui.domainError')` * marker + shape, never `instanceof`; `parseDomainErrorText` is * the reader side. * de2. **`DOMAIN_ERROR_CODES` registry** (`types/domain-error-codes.ts`) * — the CLOSED Plane-2 set, fifteen rows from the executed census, * each with the data-plane `tools` that emit it, a `recovery` class * (`retry-same-id` | `re-mint` | `later`), an `emitter` and a * self-hoster `description`. Pinned disjoint from * `PRE_GENERATION_REFUSAL_CODES` (one code, one plane). SPEC §7.9's * Plane-2 table is its mirror (pinned from the registry's own suite): * the four phantom classes the table listed (`ContractRequiredError`, * `ContractHashMismatchError`, `UnknownActionToolError`, * `EventNotAllowedError`) are gone; `cross_reference_unresolved` / * `contract_schema_invalid` are not wire codes (no caller throws * them — the lint gate throws `contract_validation_failed`). * de3. **The two protocol-owned emitters extend the base** — * `ContractViolationError` (`contract_violation`; `toErrorData()` * keeps its `{error, tool, violations, hint, propsSchemaHash?}` * shape) and `ContractValidationError` (`contract_validation_failed`, * phase + issues kept). Consumers detecting them by `instanceof` * are unchanged; their message now leads with the slug. * de4. **The wire plane is the MCP spec's** — a Plane-2 failure is a * tool RESULT with `isError: true`, never a JSON-RPC error frame; * no `structuredContent` (the SDK client validates it against the * tool's `outputSchema` whenever present, so a typed envelope would * demote every success field to optional — refused). SPEC §7.9.1: * `SESSION_NOT_FOUND` (-32002) is the live-channel / runtime Plane-1 * code; on `tools/call` the same state is `session_not_found`. * de5. **The conformance kit's first `tools/call` driver** — catalog * `domain-error`: six no-setup scenarios (unknown `handshakeId` on * `ggui_render`; unknown `sessionId` on `ggui_consume` / * `ggui_get_session` / `ggui_update` / `ggui_amend` / `ggui_emit`) * graded on the raw result via `runConformance({ toolCallDriver })` * / `--tool-call-driver `. Before this wave every first-party * server failed all six on `slug-leads`. * * Conformance-kit verdict: not breaking under VERSION-POLICY §2 — no * prior fixture asserts Plane-2 text; the leading slug is additive to * prose and the new catalog is an addition. MINOR under §1.2 (new * exported base, registry and kit catalog); rides the 0.16.0 wave. The * adoption of the base by the handler / core / mcp-server classes lands * WITH this entry (oss's half of ggui#880); until both are on a server, * that server fails the catalog — which is the point. * * -------------------------------------------------------------------- * The pending-event row is a schema (2026-09-06, store-boundary, pre-launch, * ggui#839 — the #817 C2 follower; cite `3f3d86b86`). The consume pipe's * stored row — what `submit_action` / the WS ingress append and * `PendingEventConsumer.consumeAndClear` drains — was a hand-written * interface the adapters typed as `Record` and the consume * handler coerced with defaults; nothing validated it. A store-boundary * contract (producers ↔ adapters ↔ the consume handler), never wire: * `ggui_consume` returns the entries, never the wrapper. * * pe1. **`pendingEventSchema` + `PendingEvent` derived** — * `{ id: string.min(1), envelope: consumeEventEntrySchema, createdAt: * string }`; `createdAt` stays a string (the relay copies a client * `firedAt` the ingress accepts as a diagnostic). * pe2. **`sequence` deleted** — zero writers (both producers append * `{id, envelope, createdAt}`; sqlite's `seq` never joined the row; * the pod stores the literal), zero readers (the only read was the * handler's own default-to-0), named in neither SPEC nor the kit. * pe3. **`id` required and non-empty** — the drain_ack key and the * idempotency key per `(sessionId, id)`; the id-less append branches * (core docstring, in-memory, sqlite, the pod's `ddb.ts`) deleted in * the same publication. * pe4. **No string envelope arm** — every writer passes the object; a * store that serializes the whole row. `parsePendingEnvelope` * collapsed into the row parse (`envelope-adapters.ts` deleted). * pe5. **`PendingEventMalformedError` + the per-adapter failure mode** — * `consumeAndClear` MUST NOT return a row that fails the schema and * MUST NOT drop a well-formed row because a sibling failed: a * transactional drain (sqlite) refuses whole and rolls back; a * destructive drain (DynamoDB) quarantines per row with a * `pending_event_malformed` structured log; in-memory holds the typed * struct it validated on append. `append` refuses a malformed row * before storing it. The consume handler maps the error to a * `HandlerFailure` carrying `{ events: [], status }`, never a JSON-RPC * error, and the parse runs before any `drain_ack` fires. * * Conformance-kit verdict: not breaking under VERSION-POLICY §2 — the kit * never names the wrapper (`git grep PendingEvent -- oss/packages/protocol-conformance` * = 0); its only `sequence` is the `action-ack-sequence` fixture's WS ack * `payload.sequence`, the ledger's `appendEvent` seq, never the pipe row's * deleted field. The observable * violation is `@ggui-ai/mcp-server-core`'s published contract-tests suite * (a seeded malformed row is refused or quarantined per form). PATCH-class * under §1.3 for `@ggui-ai/protocol`; rides the 0.16.0 wave. * * -------------------------------------------------------------------- * The endpoint-level refusal carries the app as DATA (2026-09-05, wire * field, pre-launch, ggui#870 — the ggui#782 ↔ guuey#708 re-sitting's * D6, guuey#836's blocker). `transportRefusalSchema` — what rides * `error.data.refusal` on a per-app endpoint's typed 403 (ggui#825/#836) * — was strict `{ code, message, fix, retry }` with the app named only * in `message`; a tenant's repair loop cannot parse prose safely. * * ai1. **`data.refusal.appId: string` — REQUIRED** — the app id the * refused endpoint serves, equal to the endpoint path's `{appId}`. * The ggui id the bound caller already holds, never the tenant's * own `ownerRef`; the tenant maps it to its own id from the * `gguiAppId` it stored at create. Required, not optional: a field * a repair loop cannot rely on is a hope, not a contract. * * ai2. **Who receives the typed face** (the fact from the pod's code, * ggui#812 identity-first): a correctly bound federated identity * only — an anonymous request is refused by the auth adapter as * 401 before this arm and learns nothing about the app; a native * key mismatch gets the bare default-deny 403. SPEC §7.1's * endpoint paragraph says so now; the anonymous typed face is * deliberately not a contract (it would trade disclosure). * * Conformance-kit verdict: BREAKING by the letter of VERSION-POLICY §2 — * the kit's `transport-refusal` cases now carry `appId` and the strict * schema refuses a projection without it, so an emitter built against * 0.15.0 fails the 0.16.0 kit — and, the wire being `z.strictObject`, a * 0.15.0 STRICT reader of the body is failed by a 0.16.0 emitter too * (zero such readers exist today; guuey's is step 3, not started). Shipped under §1.4's `draft-` clause in * the 0.16.0 wave; the pod's emitter (cloud, ggui#870's other half) * lands WITH this change, after the tombstone fix for ggui#785/G26. * * Package version — classification MADE here: MAJOR-class change carried * by a MINOR wave under `draft-` (§1.4) for `@ggui-ai/protocol` and * `@ggui-ai/protocol-conformance`. PROTOCOL_VERSION unchanged — no WS * envelope moved. * * -------------------------------------------------------------------- * Two refusal codes lose the word "tier" (2026-09-05, rename, pre-launch, * ggui#802 — #786 review finding F6). A code name ships to npm and, for a * render-gate code, reaches every self-hoster's LLM as JSON-Schema enum * metadata on `tools/list`; "tier" names a plan ladder a self-hoster does * not have (docs/principles/oss-purity.md, the type-literal class). Ruled * by a three-lens judge panel under the registry's naming rules: * * rt1. **`model_not_in_tier` → `model_not_allowed`** (render-gate, * after-fix, fixBy caller) — the state the row's own description, * the kit case's `fix` and SPEC §7.9 already name ("not among those * the app is allowed to use"); subject-first like the rest of the * registry; true on a deployment with one allow-list per app and no * prices. Not `model_not_available`: `*_unavailable` is this * registry's `later` / operator class, the wrong retry class for the * one code an agent may act on itself. * * rt2. **`already_on_tier` → `subscription_unchanged`** (owner-api, * after-fix, fixBy owner) — the owner-api noun the registry already * uses (`subscription_exists`, `no_subscription`); the state is * "requested == held", no ladder word. * * rt3. `tier_unrecognized` is NOT a registry code (deleted from the * wire at registry v9): a backend allowance state read by the * console, whose column is literally `tier`. Unchanged. * * Conformance-kit verdict: BREAKING by the letter of VERSION-POLICY §2 — * the kit's `refuse-after-fix-caller` case pinned the old name, so an * emitter built against 0.15.0 fails the 0.16.0 kit's `renderRefusalSchema` * enum. Shipped under §1.4's `draft-` clause (semver describes intent * pre-v1) in the 0.16.0 wave, with the changelog's Unreleased section * naming the move; every mirror moves in one change (the kit case, SPEC, * the docs, the console's copy, the backend's owner-api refusals, the pod's * emitter, cs macros). The registry's purity pin now allows NO code name to * carry plan-tier vocabulary — the grandfather list is gone. * * Package version — classification MADE here: MAJOR-class change carried * by a MINOR wave under `draft-` (§1.4) for `@ggui-ai/protocol` and * `@ggui-ai/protocol-conformance`, pre-1.0 and pre-launch. * PROTOCOL_VERSION unchanged — no WS envelope moved. * * -------------------------------------------------------------------- * `UNAUTHORIZED` moves from `-32001` to `-32007` (2026-09-05, renumber, * pre-launch, ggui#853; found by ggui-main in the prod skew-gate log). * `-32001` is the MCP SDK client's own `ErrorCode.RequestTimeout` * (`@modelcontextprotocol/sdk` types.js, beside `ConnectionClosed` * -32000) — minted LOCALLY, never sent by a server — so a client reading * the number could not tell a server's UNAUTHORIZED from its own * timeout: the class ggui#836 closed for -32000, on a number ggui had * chosen long before #836 (0.14.0 and earlier). * * uc1. **`MCP_ERROR_CODES.UNAUTHORIZED = -32007`** — the next free * canonical slot per this table's own note; `-32001` joins * `-32004` as retired-reserved. Every mirror moves in this one * change (SPEC §7.9 + its table, the gated doc mirrors, the * endpoint routes' tests, the kit's transport-refusal wording, the * pod's auth arm, the live journeys). HTTP status and message are * unchanged: 401/403 + the same text. * * uc2. **The guard #836 lacked** — `types/error-codes-vs-sdk.test.ts` * pins every ggui-chosen code (`MCP_ERROR_CODES` minus the five * JSON-RPC standard codes, plus `PLATFORM_ERROR_CODES`) disjoint * from every code the SDK's `ErrorCode` enum owns, and ggui's * copies of the standard five equal to the SDK's. It was RED on * -32001 before uc1 and is what reds the next collision. * * Conformance-kit verdict: no fixture on 0.15.0 pinned `-32001` (the * transport-refusal catalog grades `-32003 + data.refusal` and `null`; * its prose named -32001 and now names -32007) — a renumbered * canonical code with no kit regression; guuey's clients branch on * -32002/-32006 only (ggui#836 record). Bytes on the wire: one number. * * Package version — classification MADE here: MINOR for * `@ggui-ai/protocol` and `@ggui-ai/mcp-server` (a canonical code * renumbered, `draft-` intent per VERSION-POLICY §1.4), pre-1.0 and * pre-launch. PROTOCOL_VERSION unchanged — no WS envelope moved. * * -------------------------------------------------------------------- * SPEC §7.1's refused arm is ONE primitive (2026-09-05, additive, * pre-launch, ggui#803 leg 9). The tool result a render gate answers a * pre-generation refusal with was built by hand in * `@ggui-ai/mcp-server-handlers` (`buildRenderRefusalText` + an inline * `{ outcome: 'refused', refusal }`), the conformance kit carried its * own reference, and the reference server could not project it at all * — so the reference server was not a reference for the refused * envelope, and a third hand-built copy there would only have re-graded * the kit's reference. * * rr1. **`projectRenderRefusal(refusal): RenderRefusalResult`** (new * export, `envelopes/render-refusal.ts`) — `isError: true`, * `content[0].text` = `: `, `structuredContent` * = `{ outcome: 'refused', refusal }` typed as **`RefusedRenderOutput`** * (`z.infer`, new export), no `_meta`. * Pure and synchronous. The four facts have one source. * * rr2. **Consumers**: `mcp-server-handlers`' `ggui_render` builds the * refused result here (its local builder, inline object and * `RenderRefusedOutput` alias are deleted — no shim); the * reference server supplies the kit's `refusalProjector` from the * same primitive at the kit's stringly boundary (a code off * `RENDER_GATE_REFUSAL_CODES` → `null`; anything else parsed by * `renderRefusalSchema`), so its six `refusal-envelope/*` rows are * GRADED, not skipped. The kit's own hand-built reference stays as * the second source that catches the primitive drifting. * * Conformance-kit verdict: no new kit entry — the six `refusal-envelope` * cases already grade exactly these four facts; what changed is that * two implementations now answer them through one function, and the * reference server's exact-set assertion moved the rows from SKIPPED to * PASSING. Bytes on the wire: unchanged. * * Package version — classification MADE here: MINOR for * `@ggui-ai/protocol` (two new exports, additive) and for * `@ggui-ai/mcp-server-handlers` (same result, one source), pre-1.0 and * pre-launch. PROTOCOL_VERSION unchanged — no WS envelope moved. * * -------------------------------------------------------------------- * Authorization refusals carry JSON-RPC `data` (2026-09-05, additive, * pre-launch, ggui#825). `@ggui-ai/mcp-server`'s per-app `authorize` * hook refuses by throwing, and the route answered every throw with a * bare `403 {code: -32000, message: "Forbidden"}` — correct in effect, * illegible to a client that needs to tell "this app is no longer served" from * "your credential is wrong". * * ar1. **`ErrorMapperResult.data?: JsonValue`** — a deployment's * `errorMapper` may attach JSON-RPC 2.0 error `data` (any JSON * value) to a mapped result; serialized verbatim on `error.data`, * omitted when absent. Applies to every mapped error, not only * authorization refusals. * * ar2. **The per-app authorization refusal consults the mapper**, * bounded to 401 / 403: a mapper answering any other status, or * throwing, is ignored and logged (`per_app_authorize_mapper_out_ * of_bounds` / `error_mapper_failed` on the route logger) and the * default-deny 403 stands byte-identical to a deployment with no * mapper. The allow path never consults the mapper. * * Conformance-kit verdict: no kit entry required — an optional field on * an operator-side hook result, no MCP tool name/shape touched; the * shape of any `data.refusal` a deployment chooses to attach is that * deployment's declaration, not this package's. * * Package version — classification MADE here: MINOR for * `@ggui-ai/mcp-server` (new optional field on `ErrorMapperResult` + * the authorization refusal path consults `errorMapper`), pre-1.0 and * pre-launch. PROTOCOL_VERSION unchanged — no WS envelope moved. * -------------------------------------------------------------------- * Authored source rides blueprint reuse (2026-08-14, additive, * pre-launch). Cache-reuse renders (a repeated prompt semantic-matches * a cached blueprint) previously committed without authored * `sourceCode` — structurally, not incidentally: the reuse-commit path * never carried it, and blueprint registry rows never persisted it, so * `ggui_get_render_source` always returned `render_source_unavailable` * for every reuse render. * * sc1. **`Blueprint.sourceCodeHash?: string`** added to * `@ggui-ai/protocol` — content hash (`CodeStore.hashOf`) of the * AUTHORED, pre-compile source body, distinct from the existing * `codeHash` (compiled `componentCode`). The body itself is never * stored on the row — persistent adapters keep vector/metadata * rows small and content-address the body through the same * `CodeStore` seam `codeHash` already implies. Absent on legacy * rows and on blueprints with no authored form distinct from * their compiled output. * * sc2. **Registration threads it through.** `registerBlueprint` * (`@ggui-ai/mcp-server-handlers`) gains an optional * `sourceCode` input; when present, distinct from the compiled * `componentCode` (fallback-collapse symmetry — a byte-identical * pair is never worth persisting, since the read-side envelope * guard would reject it anyway), and a `CodeStore` is bound, the * registry computes + persists `sourceCodeHash` on the row and * writes the body through the durability `CodeStore` (body- * before-row ordering, same orphan-body-beats-dangling-pointer * posture as the existing compiled-code write). New durability * event `blueprint_source_write_failed` (additive, sibling to * `blueprint_code_write_failed`) on a failed body write — the row * still commits with the hash; a reuse read against a missing * body degrades gracefully to `render_source_unavailable`, never * an error. * * sc3. **Reuse commit threads it back.** The cache-hit reuse branch * resolves the body from `CodeStore` by the stored hash and * stamps it onto `GenerationCacheHit.sourceCode`, which * `commitCachedGguiSession` sidecars onto the render exactly like * the cold-gen commit path already does — reuse renders now serve * `ggui_get_render_source` identically to cold-gen renders. * * Conformance-kit verdict: no kit entry required — additive optional * fields + a new durability event, no existing wire shape changed, no * MCP tool name/shape touched. * * Package version — classification MADE here: PATCH for * `@ggui-ai/protocol` (additive optional field, no new export), * MINOR for `@ggui-ai/mcp-server-handlers` (new optional input field + * new event), pre-1.0 and pre-launch. PROTOCOL_VERSION unchanged. * -------------------------------------------------------------------- * ops-blueprint appId input + app-access seam (2026-08-14, additive, * pre-launch, ggui#501). Cross-app curation seam for the operator- * class blueprint family — the prerequisite for the family mounting * beyond the OSS default server. * * ob1. **Optional `appId` input** on all five `ggui_ops_*_blueprint` * tools (`generate`, `register`, `list`, `update`, `delete`). * Omitted ⇒ resolves to the caller's bound `ctx.appId` (unchanged * default behavior). Present ⇒ the tool curates the NAMED app * instead, subject to the authorizer below. * * ob2. **`authorizeAppAccess` dep seam** * (`OpsBlueprintAppAuthorizer`, exported from * `@ggui-ai/mcp-server-handlers`'s ops-blueprint barrel): * consulted via `resolveEffectiveAppId` on every resolution, * bound or unbound `appId` input alike. Seam unbound ⇒ legacy * bound-only posture — a cross-app `appId` input (different from * `ctx.appId`) rejects with the new `cross_app_curation_unavailable` * named code, mirroring the `app_scoped_keys_unavailable` idiom * from the 2026-07-29 ops slice. Seam bound and denying ⇒ * `app_curation_denied` with a `not_found` / `not_owner` reason. * `@ggui-ai/mcp-server`'s default server binds an allow-all * authorizer when the operator supplies none — the * single-operator trust model: whoever runs the server operates * every app on it. * * ob3. **Deployment boundary retired.** The restriction that pinned * this family to the OSS default server only (2026-08-13 ruling, * ggui#496) is retired: the family now mounts on any control * plane whose deployment supplies the store/search/authorizer * deps. Single-operator deployments bind an allow-all * authorizer; multi-user deployments supply one that enforces * their ownership model. * * Conformance-kit verdict: ops tools are not conformance-kit surface — * NO protocol stamp roll. * * Package version — classification MADE here: MINOR for * `@ggui-ai/protocol`, `@ggui-ai/mcp-server-handlers`, * `@ggui-ai/mcp-server` (additive), pre-1.0 and pre-launch. * PROTOCOL_VERSION unchanged. * -------------------------------------------------------------------- * `ggui_get_render_source` data-plane tool (2026-08-14, additive, * pre-launch, ggui#282). New bare-wire-name MCP tool, `agent` * audience — the calling app reads the generated source of its OWN * render as `{sessionId, blueprint: {source, contract?, * fixtureProps?}}`. New input shape `getRenderSourceInputShape` * (mirrors `getSessionInputShape`) and output type * `GguiGetRenderSourceOutput` added to `@ggui-ai/protocol`; new * factory `createGguiGetRenderSourceHandler` (modeled on * `createGguiGetSessionHandler` — same tenancy posture via * `ctx.appId`, no heartbeat) plus the shared * `buildRenderSourceEnvelope` helper added to * `@ggui-ai/mcp-server-handlers`. The pre-existing control-plane * `ggui_ops_get_render_source` (cloud-only, connector-key tenancy — * a different caller, unchanged contract) now imports the SAME * shared helper so the two tools' envelope shape cannot drift apart; * a behavior-neutral refactor pinned by an envelope-equivalence test. * No existing wire shape changed. * * Conformance-kit verdict: no kit entry required. The kit's WS * live-channel conformance surface (`@ggui-ai/protocol-conformance`) * has zero enumeration of MCP tool names anywhere in its source — * verified by grepping the package for `ggui_` tool-name references * outside its fixtures/tests, which returns nothing; it grades * `GguiSessionChannelServer` wire-frame behavior (ack/error/data, * echoed requestId, session-state read-back), a surface this * addition never touches. * * Package version — the classification is MADE here, not deferred: * MINOR for `@ggui-ai/protocol` (new exports, additive) and * `@ggui-ai/mcp-server-handlers` (new factory + shared helper, * additive), pre-1.0 and pre-launch. PROTOCOL_VERSION is unchanged — * no WS envelope moved. * -------------------------------------------------------------------- * MCP tool bindings & discovery (2026-08-10, additive, pre-launch, * ggui#259). Artifacts gain an optional MCP tool-binding list and * registry search gains a tool dimension, connecting the two * namespaces the registry serves: signed UI artifacts and the MCP * tools they render. SPEC §7.7.4.1 is the normative home. * * tb1. **`mcpTools` on both manifest kinds** * (`@ggui-ai/artifact-manifest`): 1–16 `{server?, tool}` * entries, charset `/^[A-Za-z0-9_.-]{1,128}$/`, * exact-duplicate `(server, tool)` pairs rejected with the * existing `manifest_invalid` code. Declared wins entirely; * a blueprint without the field derives bare `{tool}` entries * from the union of its contract's per-prop `sourceTool` and * `streamSpec` `source.tool` names (`resolveMcpToolBindings`, * marked `derived`). Bindings are search metadata only — they * never enter contract canonicalization or `blueprintKey`. * NOTE: manifest schemas are strict-rooted, so an OLDER * manifest parser REJECTS a manifest file carrying the field — * the optionality guarantee below is wire-response-scoped * (search/read), not manifest-file-scoped. * * tb2. **Registry wire additions** (`@ggui-ai/registry-core`), * all optional: `SearchResultEntry` += `mcpTools?`, * `mcpToolsSource?: 'declared' | 'derived'`, * `scopeVerification?: 'verified' | 'unverified'`, * `verifiedDomain?`; the single-version read response += the * same two verification fields; search input += `tool?` / * `server?` exact filters (AND-composed with the existing * filters, `matchesMcpToolFilters` semantics; an invalid * charset value is the existing `invalid_request` 400). * Pre-existing consumers see `undefined` and behave as before. * * tb3. **Agent surface** (`@ggui-ai/mcp-server-handlers`): * `ggui_search_blueprints` gains an opt-in `registry` source * merged after the local sources; unreachable / timeout / * unparseable-body answers degrade typed — * `degradedSources: [{source: 'registry', reason: * 'unreachable' | 'timeout' | 'invalid_response'}]` — never a * tool failure, never a thrown error. * * Conformance-kit verdict: additive, minor-intent while `draft-`, * so PROTOCOL_VERSION is unchanged — no WS envelope moved; the * change is confined to the manifest, registry HTTP, and MCP * tool-result surfaces. The new `binding-conformance` catalog in * `@ggui-ai/protocol-conformance` (`runBindingResolutionCases` / * `runBindingFilterCases`) arbitrates resolution precedence and * filter semantics; search/read response optionality is pinned by * registry-core unit tests, because the strict-rooted manifest * schema makes a manifest-file optionality fixture false (tb1). * * Package version — the classification is MADE here, not deferred: * MINOR for the `@ggui-ai/*` wave, additive under the version * policy's minor rule (every fixture that passed against the * current wave still passes; the delta is new surface), pre-1.0 * and pre-launch. FOUR packages carry minor-class changes into * that wave — `@ggui-ai/artifact-manifest` (the field + resolver), * `@ggui-ai/registry-core` (wire + filters), * `@ggui-ai/mcp-server-handlers` (the registry source), and * `@ggui-ai/protocol-conformance` (the catalog that decides them). * Only the mechanical write is deferred: every `@ggui-ai/*` * package carries ONE wave version, so the release owner takes all * four bumps together at the next wave cut. * -------------------------------------------------------------------- * Typed `resources/read` failures (2026-08-09, additive, pre-launch, * ggui#430). A read of a render locator * (`ui://ggui/render/{sessionId}/{blueprintKey}`) gains a closed failure * union and a canonical JSON-RPC number, so the read has exactly two * exits: a live mount, or a typed error. Before this, an unresolvable * read returned a SUCCESS-shaped result carrying a loading shell that * would never come alive — the same class of defect ruling B fixed for * `ggui_render`, on the resource surface instead of the tool surface. * * rr1. **New closed enum `ResourceReadErrorCode`** = NOT_FOUND | * BLUEPRINT_UNRESOLVABLE | NOT_SUPPORTED | NOT_MOUNTABLE, with * `resourceReadErrorSchema {code, message, detail?}`. Deliberately * NOT an extension of `renderErrorCodeSchema` (rb2): that enum * classifies a `ggui_render` tool call that ran and failed and * rides IN the tool result; this one classifies a resource read * and rides ON a JSON-RPC error. Two surfaces, two closed enums, * same UPPER_SNAKE house style. * * rr2. **`-32006 MOUNT_UNAVAILABLE` claimed** — the first draw from * the `-32006` onwards range that rb4 reserved when it retired * `-32004`. The next free canonical slot is `-32007`. It covers * BLUEPRINT_UNRESOLVABLE / NOT_SUPPORTED / NOT_MOUNTABLE. * Deliberately NOT `INTERNAL_ERROR`: a component that is gone, a * server that keeps no durable record, and a render with no * delivery channel are all deterministic outcomes of a correctly * functioning server, and `-32603` would report a malfunction and * invite a retry that cannot succeed. The fine-grained class rides * on `error.data.code`. * * rr3. **NOT_FOUND maps to `-32002`**, which MCP uses for a missing * resource and which this table already assigns to a missing * session — for a locator keyed by `sessionId` those are one * condition, not two. * * rr4. **NOT_FOUND carries a CONSTANT body.** The mapper substitutes * a fixed message and drops `detail`, so a read refused by the * authorization check and a read of a locator that never existed * are byte-identical on the wire. Anything that varies between the * two makes the read an existence oracle for other callers' * renders. The mapper closes the message half; the ordering half * is a server obligation — a branch whose outcome VARIES WITH THE * LOCATOR MUST NOT run before the authorization check, because * reaching one tells the caller the locator resolved for somebody. * A deployment-global answer is not such a branch: NOT_SUPPORTED * is identical for every locator on the server that emits it, so * answering it early discloses nothing. (Scoped this way in the * same slice the conformance catalog landed, which grades the * indistinguishability the rule exists to produce and deliberately * does not grade ordering. The unscoped form would have declared * a correct substrate-less server non-conformant.) * * rr5. **A read that cannot mount is now an ERROR, not a * success-shaped shell.** This is the observable wire change, and * it is what rr1–rr4 exist to give a shape to. Every failure * branch of the render-locator read used to return a result whose * `contents` carried a loading shell — a page that waited forever * for a render that was never coming. Those branches now throw, * and the transport serializes them as the JSON-RPC errors above; * the shell builder behind them is DELETED rather than left * unreferenced, so no branch can produce one. Reads that CAN * mount are unchanged byte for byte. A host that treated any * successful read as mountable was right only by accident and is * now right by construction; a host that never handled the error * exit at all now has one to handle. The invariant this buys is * the whole point: any successful `contents` result IS a live * mount. * * Conformance-kit verdict: additive, minor-intent while `draft-`, so * PROTOCOL_VERSION is unchanged — no WS envelope moved, and the change * is confined to the MCP resource surface (same reasoning as rb's stamp * adjudication). * * The kit arbitrates this surface now. `resource-read-conformance` in * `@ggui-ai/protocol-conformance` binds the `resources/read` method: * `runResourceReadConformance()` drives an adopter-supplied scenario * driver through a 12-case catalog and grades the two numbers (rr1–rr3), * the closed classification on `error.data.code`, NOT_FOUND's absent * `detail` and constant message (rr4), the refused-equals-missing byte * identity across every server shape, and rr5's invariant on a live row * and a re-minted one alike. What it does NOT arbitrate is `tools/call`: * no driver is bound to that method, so the `ggui_consume` and * `ggui_emit` obligations stay kit-invisible and this entry closes * nothing for them. * * Because that catalog exists and ships, the package-version decision * below is stated outright rather than made conditional on a driver * that has yet to arrive. * * Five things stay ungraded on purpose and MUST NOT be read as * obligations: the ORDER in which a substrate-less server answers * NOT_SUPPORTED; `detail` wording on any code; the NOT_FOUND message * literal (its constancy is the obligation, not its prose); `-32603` * message text; and the NUMBER a URI naming no locator receives — * including negatively, since MCP itself assigns the resource-missing * number to a read of a URI a server does not serve, and banning it * would make every framework that leans on that assignment * non-conformant. On that last one the law is classification-only: * such a URI MUST NOT carry one of the four codes on `error.data.code`. * * Package version — the classification is MADE here, not deferred. * The resource-read surface and the new canonical code are a MINOR for * the `@ggui-ai/*` wave: additive under the version policy's minor rule * (every fixture that passed against the current wave still passes; the * delta is new surface), pre-1.0 and pre-launch. TWO packages carry * minor-class changes into that wave — `@ggui-ai/protocol` (the closed * enum, the canonical number, the projection) and * `@ggui-ai/protocol-conformance` (the catalog that decides them). * * What is deferred is only the mechanical write. Every `@ggui-ai/*` * package carries ONE wave version, and a wave cut moves all of them to * it in a single commit — no package's number can move on its own — so * the release owner takes both bumps together at the next cut. One * deferral, one owner, two named packages. Nothing about what the * change IS remains open. * * #457 (2026-08-10): the substrate stores' `durability` declaration — * a MINOR surface that SHIPPED IN 0.7.0 (the wave was published from * main after it landed; an earlier revision of this note deferred it * to the next cut, which the publish overtook). `@ggui-ai/mcp-server-core` * adds a required member to three published ports (pre-launch * no-compat: every in-tree impl moved in the same slice; out-of-tree * implementors add one literal) and `@ggui-ai/protocol-conformance` * adds the `all-ephemeral` wiring arm + its fusion case (additive — * the catalog's public-API additive-only rule holds; the SPEC §7.10.4 * amendment defines "durable" as declared, which was previously * undefined, not different). * * On the kit's half, one thing is worth saying plainly rather than * calling its delta "purely additive": `parseCase` rejects unknown * keys, so a case file with a typo'd key throws instead of being * quietly ignored. That strictness is free on a sub-module with no * prior published version — nothing can be built against it yet — but * extending it over the existing fixture catalog would be a MAJOR, not * a minor, and must be adjudicated as one. * * #471 (2026-08-10): the fetch-free delivery surface — a MINOR for the * `@ggui-ai/protocol` wave, deferred to the next cut under the same * one-wave-version rule as above. Three additive pieces, one shared * motivation (hosts whose iframe CSP forbids every URL-scheme load * while permitting inline scripts): * * if1. **`McpAppAiGguiRenderMeta.codeB64`** — optional base64 * compiled component source, the fetch-free twin of `codeUrl` * (coexists with it; exclusive with `kind`). Every envelope that * parsed before parses identically; the parser's new rejection * arm (`codeB64` + `kind` both set) rejects a shape no producer * ever emitted. `hasMountModeDiscriminator` widens PERMISSIVELY * (a codeB64-only slice becomes mountable — previously * undefined, not different). * * if2. **`GguiShellHtmlOptions.runtimeInlineSource` + * `escapeInlineScript`** — host-helper additions; the default * (external `