/** * 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}. * * -------------------------------------------------------------------- * 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 `