@ggui-ai/protocol-conformance 0.2.0-alpha.3 → 0.3.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +32 -13
  2. package/dist/cli.d.ts +11 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +48 -20
  5. package/dist/conformance-host.d.ts +81 -76
  6. package/dist/conformance-host.d.ts.map +1 -1
  7. package/dist/conformance-host.js +0 -44
  8. package/dist/fixtures/bootstrap-protocol/bootstrap-bundle-fetch-failed.json +3 -3
  9. package/dist/fixtures/bootstrap-protocol/bootstrap-meta-missing.json +3 -3
  10. package/dist/fixtures/bootstrap-protocol/bootstrap-success.json +3 -3
  11. package/dist/fixtures/consume-buffer/action-ack-sequence.json +28 -0
  12. package/dist/fixtures/consume-buffer/action-payload-schema-violation.json +37 -0
  13. package/dist/fixtures/consume-buffer/index.d.ts +6 -0
  14. package/dist/fixtures/consume-buffer/index.d.ts.map +1 -0
  15. package/dist/fixtures/consume-buffer/index.js +41 -0
  16. package/dist/fixtures/consume-buffer/undeclared-action-rejected.json +29 -0
  17. package/dist/fixtures/host-context/host-context-observed-persists.json +34 -0
  18. package/dist/fixtures/host-context/index.d.ts +5 -0
  19. package/dist/fixtures/host-context/index.d.ts.map +1 -0
  20. package/dist/fixtures/host-context/index.js +40 -0
  21. package/dist/fixtures/index.d.ts +28 -9
  22. package/dist/fixtures/index.d.ts.map +1 -1
  23. package/dist/fixtures/index.js +33 -14
  24. package/dist/fixtures/reserved-channel-authority/index.d.ts.map +1 -1
  25. package/dist/fixtures/reserved-channel-authority/index.js +17 -7
  26. package/dist/fixtures/reserved-channel-authority/props-update-roundtrip.json +4 -4
  27. package/dist/fixtures/schema-version-handshake/index.d.ts.map +1 -1
  28. package/dist/fixtures/schema-version-handshake/index.js +12 -4
  29. package/dist/fixtures/schema-version-handshake/version-match.json +7 -5
  30. package/dist/fixtures/schema-version-handshake/version-mismatch.json +9 -10
  31. package/dist/fixtures/subscribe-tenancy/absent-appid-defaults.json +18 -0
  32. package/dist/fixtures/subscribe-tenancy/app-mismatch.json +20 -0
  33. package/dist/fixtures/subscribe-tenancy/index.d.ts +6 -0
  34. package/dist/fixtures/subscribe-tenancy/index.d.ts.map +1 -0
  35. package/dist/fixtures/subscribe-tenancy/index.js +62 -0
  36. package/dist/index.d.ts +4 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -4
  39. package/dist/match-behavior.d.ts +19 -100
  40. package/dist/match-behavior.d.ts.map +1 -1
  41. package/dist/match-behavior.js +289 -246
  42. package/dist/registration-conformance/cases/gate-duplicate-hook.json +26 -0
  43. package/dist/registration-conformance/cases/gate-export-not-registered.json +1 -0
  44. package/dist/registration-conformance/cases/gate-multi-package-distinct-names-accept.json +26 -0
  45. package/dist/registration-conformance/cases/gate-no-gadgets-accept.json +1 -0
  46. package/dist/registration-conformance/cases/gate-package-mismatch.json +1 -0
  47. package/dist/registration-conformance/cases/gate-priority-not-registered-outranks-mismatch.json +1 -0
  48. package/dist/registration-conformance/cases/gate-priority-registry-outranks-public-env.json +22 -0
  49. package/dist/registration-conformance/cases/gate-public-env-missing.json +21 -0
  50. package/dist/registration-conformance/cases/gate-public-env-satisfied-accept.json +21 -0
  51. package/dist/registration-conformance/cases/gate-public-env-undeclared-wrapper-accept.json +26 -0
  52. package/dist/registration-conformance/cases/gate-registered-resolves-accept.json +1 -0
  53. package/dist/registration-conformance/cases/gate-stdlib-hook-accept.json +1 -0
  54. package/dist/registration-conformance/cases/gate-typo-classifies-as-not-registered.json +1 -0
  55. package/dist/registration-conformance/index.d.ts +64 -33
  56. package/dist/registration-conformance/index.d.ts.map +1 -1
  57. package/dist/registration-conformance/index.js +24 -8
  58. package/dist/run-conformance.d.ts +145 -6
  59. package/dist/run-conformance.d.ts.map +1 -1
  60. package/dist/run-conformance.js +531 -93
  61. package/dist/types.d.ts +319 -165
  62. package/dist/types.d.ts.map +1 -1
  63. package/dist/types.js +27 -23
  64. package/dist/ws-transport.d.ts.map +1 -1
  65. package/dist/ws-transport.js +3 -2
  66. package/package.json +3 -3
  67. package/dist/fixtures/observability-events/index.d.ts +0 -5
  68. package/dist/fixtures/observability-events/index.d.ts.map +0 -1
  69. package/dist/fixtures/observability-events/index.js +0 -22
  70. package/dist/fixtures/observability-events/observability-contract-error-emitted.json +0 -28
  71. package/dist/fixtures/observability-events/observability-wired-tool-invoked.json +0 -27
  72. package/dist/fixtures/refresh-semantics/index.d.ts +0 -5
  73. package/dist/fixtures/refresh-semantics/index.d.ts.map +0 -1
  74. package/dist/fixtures/refresh-semantics/index.js +0 -19
  75. package/dist/fixtures/refresh-semantics/stream-refresh-success.json +0 -39
  76. package/dist/fixtures/reserved-channel-authority/stream-schema-violation.json +0 -44
  77. package/dist/fixtures/wired-action-dispatch/index.d.ts +0 -5
  78. package/dist/fixtures/wired-action-dispatch/index.d.ts.map +0 -1
  79. package/dist/fixtures/wired-action-dispatch/index.js +0 -29
  80. package/dist/fixtures/wired-action-dispatch/wired-action-success.json +0 -35
  81. package/dist/fixtures/wired-action-dispatch/wired-action-tool-not-found.json +0 -30
  82. package/dist/fixtures/wired-action-dispatch/wired-action-tool-threw.json +0 -31
  83. package/dist/fixtures/wired-action-dispatch/wired-action-tool-timeout.json +0 -31
package/README.md CHANGED
@@ -4,6 +4,16 @@ Conformance test kit for the ggui protocol. Ships a JSON fixture catalog + a run
4
4
 
5
5
  This kit is the protocol's conformance surface: it lets a third-party MCP builder prove their implementation is protocol-conformant without reimplementing the test harness.
6
6
 
7
+ ## Grading mechanisms
8
+
9
+ The kit grades protocol obligations through **three mechanisms**, each matched to where the obligation is observable:
10
+
11
+ 1. **Path-A WS fixtures** — wire-observable behaviors graded from frames the runner collects over the live channel (acks, error frames, canonical `data` deliveries).
12
+ 2. **Pure-function catalogs** — deterministic validation obligations (the SPEC §7.7.2 gadget obligations) graded against a caller-supplied function; no host, render, or transport.
13
+ 3. **Session-state host read-back** — stateful obligations with no wire response (e.g. `host_context_observed` persisting onto `GguiSession.hostContext`): the runner dispatches the C→S frame, waits out the observation window, then reads the GguiSession field back through `ConformanceHost.readSessionField()` and deep-equals it against the fixture's `expected`. No host (or a host without the method, or a read that throws) → the fixture SKIPS with the reason — a host that cannot read state cannot grade it, and the kit never converts that gap into a pass.
14
+
15
+ A fourth surface — the **Path-B browser-host driver** for DOM-level claims (`bootstrap-failure`, `props-update`) — is honestly absent: it is not yet packaged, and those fixtures skip wherever the kit runs (see below).
16
+
7
17
  ## Transport
8
18
 
9
19
  **v1.0 is WebSocket-only.** The canonical ggui transport is WS (see SPEC §12 Transport Bindings). `TransportConfig` is shaped as an extensibly-closed union so later transports (stdio MCP, HTTP long-poll) can be added post-v1.1 without breaking the public API.
@@ -14,23 +24,26 @@ The fixture catalog spans both wire-observable claims and surface-observable cla
14
24
 
15
25
  Path-A-matchable kinds (`matchBehavior` returns `pass` / `fail`):
16
26
 
17
- - `bootstrap-success` — subscribe → `ack` round-trip
18
- - `version-mismatch` — `error` frame with `code: UPGRADE_REQUIRED`
19
- - `contract-error` — `_ggui:contract-error` envelope on the reserved channel
20
- - `stream-update` — `stream` frame on a named channel matching the declared value
27
+ - `bootstrap-success` — subscribe → `ack` round-trip; optionally (`serverVersion: 'current'`) the ack must also advertise `payload.serverVersion` equal to the kit's compiled `PROTOCOL_SCHEMA_VERSION` — the server half of SPEC §12.2.2's version handshake
28
+ - `version-mismatch` — `error` frame with `code: UPGRADE_REQUIRED`; the provoking client declaration travels on the fixture's `subscribe.supportedVersions` knob (`'current'` resolves to the compiled canonical, keeping fixtures evergreen across version bumps)
29
+ - `action-ack` — the action's `ack` frame (matched by echoed `requestId`) carries a numeric `payload.sequence`, proving the action event persisted to the GguiSession's consume buffer before the ack
30
+ - `error-frame` — `error` frame with the expected `payload.code` (e.g. `CONTRACT_VIOLATION` for an action absent from the declared actionSpec, or one whose `data` violates the declared entry's payload schema)
31
+ - `stream-update` — canonical channel-3 delivery frame (`{type: 'data', payload: StreamEnvelope}`, SPEC §12.2) whose envelope names the declared channel and carries the declared value as its `payload` body — exact deep-equal by default, or declared-keys-subset when the fixture authors `valueMatch: 'subset'` (for payloads carrying non-deterministic fields like generated ids)
21
32
  - `no-op` — silence after input dispatch
22
- - `observability-event` — `wired-tool-invoked` and `contract-error-emitted` arms (the WS evidence the protocol-and-contract bar mandates a conformant host mirror-emit on)
33
+
34
+ One kind is neither Path A nor Path B: `session-state` is graded by the **runner**, not the frame matcher — a post-observation-window read-back via `ConformanceHost.readSessionField()` (mechanism 3 above). `matchBehavior` returns `unmatchable-on-ws` for it, because frames cannot prove state.
23
35
 
24
36
  Path-B-only kinds (`matchBehavior` returns `unmatchable-on-ws` → runner records SKIP):
25
37
 
26
38
  - `bootstrap-failure` — fault surface is the host's bootstrap-fetch + `ui/initialize` postMessage round-trip; `renderer-url-override` / `ui-initialize-response-override` setup directives are MCP-Apps-host concerns, not WS server concerns.
27
39
  - `props-update` — assertion is on rendered DOM after `_ggui:props` is emitted; matchable on WS only as "frame was emitted", not as "DOM reflects it".
28
- - Future `observability-event` arms beyond the two above (e.g. `schema-version-mismatch`, `subscribe-failed`).
29
40
 
30
- A Path-B browser-host harness drives these fixtures via Playwright + `page.route()` fault injection + DOM assertion. A future packaged browser-host adapter will fold that capability into the kit so third-party adopters don't reimplement it.
41
+ The Path-B driver is not yet packaged — no browser-host harness ships with the kit today, so Path-B fixtures skip wherever the kit runs. A future packaged browser-host adapter will fold that capability into the kit so third-party adopters don't reimplement it.
31
42
 
32
43
  The partition is intentional: Path-A FAILs are vendor-neutrality bugs the server owns; Path-B SKIPs are not fails — they are claims a different driver is responsible for.
33
44
 
45
+ One declared grading gap on the action loop: `action-ack` proves the append half of the consume-buffer contract; the retrieval half (the agent draining the buffer via `ggui_consume`) is an MCP tool call a WS-only runner cannot drive. Grading it needs an MCP-binding driver — a future kit surface, not a weaker WS assertion.
46
+
34
47
  ## Pure-function conformance catalogs
35
48
 
36
49
  The behavioral fixture catalog above asserts what an implementation _does_ over the wire. Some protocol obligations aren't transport-observable at all — they are deterministic _validation functions_: given an input, accept or reject it. SPEC §7.7.2's gadget obligations are exactly this. Modeling them as WebSocket fixtures would mean faking wire frames the protocol never emits; instead the kit ships **pure-function catalogs** — accept/reject cases graded against a caller-supplied function, with no host, render, or transport.
@@ -38,7 +51,7 @@ The behavioral fixture catalog above asserts what an implementation _does_ over
38
51
  Each catalog ships its cases as raw JSON so a non-TypeScript implementer can grade their own implementation, and each runner takes the implementation as a callback — the kit never hard-binds a concrete one. Three catalogs ship today:
39
52
 
40
53
  - **`@ggui-ai/protocol-conformance/schema-conformance`** — which `DataContract.clientCapabilities` payloads a conformant parser MUST accept / reject (the gadget wire shape).
41
- - **`@ggui-ai/protocol-conformance/registration-conformance`** — which `(contract, appGadgets)` pairs the push-time gadget registry gate MUST accept / reject, and with which precise reject code (`gadget_not_registered` / `gadget_package_mismatch`).
54
+ - **`@ggui-ai/protocol-conformance/registration-conformance`** — which `(contract, appGadgets, appPublicEnv)` triples the push-time gadget gate stack MUST accept / reject, and with which precise SPEC §7.9 reject code (`gadget_not_registered` / `gadget_package_mismatch` / `gadget_public_env_missing` / `duplicate_gadget_hook`).
42
55
  - **`@ggui-ai/protocol-conformance/resolution-conformance`** — which bundle + style URLs the server MUST compute for a gadget descriptor's transport fields (`bundleHost` precedence, default host, loopback `http` scheme).
43
56
 
44
57
  ```ts
@@ -49,8 +62,8 @@ import { runResolutionConformance } from "@ggui-ai/protocol-conformance/resoluti
49
62
  const schema = runSchemaConformance((clientCapabilities) =>
50
63
  myGadgetWireParser.isValid(clientCapabilities)
51
64
  );
52
- const gate = runRegistrationConformance((contract, appGadgets) =>
53
- myGadgetGate(contract, appGadgets)
65
+ const gate = runRegistrationConformance((contract, appGadgets, appPublicEnv) =>
66
+ myGadgetGate(contract, appGadgets, appPublicEnv)
54
67
  );
55
68
  const urls = runResolutionConformance((entry) => myGadgetUrlResolver(entry));
56
69
 
@@ -61,7 +74,7 @@ if (schema.failed.length + gate.failed.length + urls.failed.length > 0) {
61
74
 
62
75
  The `schema-conformance` meta-test binds its catalog to the live `@ggui-ai/protocol` `clientCapabilitiesSpecSchema` — a drift-catch if the wire schema diverges from the §7.7.2 obligations the catalog freezes. The `registration-conformance` and `resolution-conformance` meta-tests verify catalog coherence against faithful in-test implementations (the kit stays vendor-neutral — it does not depend on a server implementation); grading the _shipping_ gate / resolver is an implementation-side test that drives the corresponding runner.
63
76
 
64
- ## Public API (target)
77
+ ## Public API
65
78
 
66
79
  ```ts
67
80
  import { runConformance } from "@ggui-ai/protocol-conformance";
@@ -71,18 +84,24 @@ const result = await runConformance({
71
84
  auth: { kind: "bearer", token: process.env.TOKEN! },
72
85
  });
73
86
 
74
- if (result.failed.length > 0) {
87
+ // A failed fixture is a red build — and so is a run that executed
88
+ // ZERO fixtures (all skips prove nothing).
89
+ if (result.failed.length > 0 || result.passed.length === 0) {
75
90
  process.exit(1);
76
91
  }
77
92
  ```
78
93
 
94
+ `serverUrl` accepts either a bare origin (`http://localhost:3000` — the runner derives `ws://localhost:3000/ws`) or the full live-channel endpoint (`ws://localhost:3000/ws` — used exactly as given; the runner never appends to an explicit path).
95
+
79
96
  CLI equivalent:
80
97
 
81
98
  ```
82
99
  npx @ggui-ai/protocol-conformance --url ws://localhost:3000/ws --auth bearer:$TOKEN
83
100
  ```
84
101
 
85
- Full adoption guide + fixture-to-contract mapping table land with the fixture-extraction commit.
102
+ The CLI exits `0` only when at least one fixture executed and none failed; `1` on any fixture failure; `2` on invocation errors **or** when every fixture skipped (a zero-executed run never reads as success in CI).
103
+
104
+ To grade setup-dependent fixtures (and any `session-state` fixture), pass a `host` implementing the `ConformanceHost` adapter — `dispatchSetup` / `dispatchTeardown` for the directive vocabulary, plus the optional `readSessionField(sessionId, field)` introspection seam. Fixtures whose requirements the host doesn't meet skip with a precise reason; they never silently pass.
86
105
 
87
106
  ## License
88
107
 
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { type ConformanceResult } from './run-conformance.js';
2
3
  import type { AuthConfig } from './types.js';
3
4
  interface ParsedArgs {
4
5
  readonly url?: string;
@@ -11,5 +12,14 @@ interface ParsedArgs {
11
12
  declare function parseArgs(argv: readonly string[]): ParsedArgs;
12
13
  declare function parseAuth(spec: string): AuthConfig;
13
14
  declare function main(argv: readonly string[]): Promise<number>;
14
- export { main, parseArgs, parseAuth };
15
+ /**
16
+ * Map a {@link ConformanceResult} to the CLI exit code:
17
+ * - `1` — at least one fixture failed.
18
+ * - `2` — zero fixtures executed (every fixture skipped). A run
19
+ * that graded nothing carries no conformance signal and must not
20
+ * read as success in CI.
21
+ * - `0` — at least one fixture executed and none failed.
22
+ */
23
+ declare function exitCodeForResult(result: ConformanceResult): 0 | 1 | 2;
24
+ export { exitCodeForResult, main, parseArgs, parseAuth };
15
25
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA4BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,UAAU,UAAU;IAClB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,iBAAS,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CA8CtD;AAED,iBAAS,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAY3C;AA+BD,iBAAe,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoD5D;AA+BD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA+BA,OAAO,EAAkB,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,UAAU,UAAU;IAClB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,iBAAS,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CA8CtD;AAED,iBAAS,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAY3C;AAoCD,iBAAe,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D5D;AAED;;;;;;;GAOG;AACH,iBAAS,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAI/D;AA+BD,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -8,21 +8,25 @@
8
8
  * --url http://localhost:3000 \
9
9
  * --auth bearer:TOKEN
10
10
  *
11
- * Exits with code 0 if every fixture passed OR skipped; exits 1 if
12
- * ANY fixture failed. Skips are not failures — an implementation may
13
- * legitimately not provide every `ConformanceHost` directive in
14
- * v1.0. Operators who want skip-is-failure semantics can pair
15
- * `--require-host` (future) with a host that implements every known
16
- * directive.
11
+ * Exits 0 only when at least one fixture executed AND none failed.
12
+ * Exits 1 if ANY fixture failed. Exits 2 on invocation errors AND
13
+ * when the run executed zero fixtures (everything skipped) — a
14
+ * conformance run that proved nothing must not read as success in
15
+ * CI. Individual skips are still not failures — an implementation
16
+ * may legitimately not provide every `ConformanceHost` directive in
17
+ * v1.0 — but a run that is ALL skips carries no signal.
17
18
  *
18
19
  * Honest scope of the bundled CLI: no `host` wiring. The CLI runs
19
20
  * the runner with an absent host, meaning every fixture with non-empty
20
- * `setup` skips cleanly with the "no host provided" reason. To get
21
- * real pass/fail signal against an implementation, consumers call
22
- * `runConformance({ host: myImpl })` programmatically. The CLI is the
23
- * lowest-friction entry point for a smoke check, and for CI jobs where
24
- * the server under test is exercised directly over the wire via a
25
- * subprocess harness rather than a `ConformanceHost` injection.
21
+ * `setup` skips cleanly with the "no host provided" reason — and
22
+ * since every fixture in the current catalog declares setup, a
23
+ * hostless CLI run skips them all and exits 2 by design. To get real
24
+ * pass/fail signal against an implementation, consumers call
25
+ * `runConformance({ host: myImpl })` programmatically and apply the
26
+ * same guard: `result.failed.length > 0 || result.passed.length === 0`
27
+ * is a red build. The CLI remains the lowest-friction wire-level
28
+ * smoke check for setups where the server under test is provisioned
29
+ * out-of-band rather than through a `ConformanceHost` injection.
26
30
  */
27
31
  import { createDefaultReporter, formatFailures, formatSkips } from './reporter.js';
28
32
  import { runConformance } from './run-conformance.js';
@@ -95,8 +99,11 @@ Usage:
95
99
  ggui-protocol-conformance --url <URL> --auth <kind:value> [options]
96
100
 
97
101
  Required:
98
- --url <URL> Base URL of the server (http://… or https://…).
99
- The runner derives ws://…/ws from this.
102
+ --url <URL> Server URL. A bare origin (http://… or ws://…
103
+ without a path) gets the default live-channel
104
+ path /ws appended; a URL that already carries
105
+ a path (e.g. ws://host:3000/ws) is used as
106
+ given. http(s) schemes derive ws(s).
100
107
  --auth <kind:value> Auth carried on the WS upgrade.
101
108
  bearer:TOKEN — Authorization header
102
109
  session-cookie:COOKIE — Cookie header
@@ -108,9 +115,11 @@ Options:
108
115
  --help, -h Show this help.
109
116
 
110
117
  Exit codes:
111
- 0 — all fixtures passed or skipped.
118
+ 0 — at least one fixture executed and none failed.
112
119
  1 — at least one fixture failed.
113
- 2 — invocation error (bad args, cannot reach server, etc.).
120
+ 2 — invocation error (bad args, cannot reach server, etc.), or the
121
+ run executed zero fixtures (every fixture skipped) — a run that
122
+ proved nothing never reads as success.
114
123
  `.trimStart();
115
124
  async function main(argv) {
116
125
  let parsed;
@@ -161,11 +170,30 @@ async function main(argv) {
161
170
  if (skips.length > 0)
162
171
  process.stdout.write(`${skips}\n`);
163
172
  }
164
- return result.failed.length > 0 ? 1 : 0;
173
+ const code = exitCodeForResult(result);
174
+ if (code === 2) {
175
+ process.stderr.write(`error: conformance run executed ZERO fixtures (0 passed, ${result.failed.length} failed, ${result.skipped.length} skipped) — an all-skip run proves nothing and must not read as success; exiting 2. Run with --verbose for skip reasons.\n`);
176
+ }
177
+ return code;
178
+ }
179
+ /**
180
+ * Map a {@link ConformanceResult} to the CLI exit code:
181
+ * - `1` — at least one fixture failed.
182
+ * - `2` — zero fixtures executed (every fixture skipped). A run
183
+ * that graded nothing carries no conformance signal and must not
184
+ * read as success in CI.
185
+ * - `0` — at least one fixture executed and none failed.
186
+ */
187
+ function exitCodeForResult(result) {
188
+ if (result.failed.length > 0)
189
+ return 1;
190
+ if (result.passed.length === 0)
191
+ return 2;
192
+ return 0;
165
193
  }
166
194
  // Immediately-invoked — this module IS the bin entry. Tests import
167
- // `parseArgs` / `parseAuth` / `main` via named exports without
168
- // triggering the run.
195
+ // `parseArgs` / `parseAuth` / `exitCodeForResult` / `main` via named
196
+ // exports without triggering the run.
169
197
  if (isEntryPoint()) {
170
198
  main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
171
199
  process.stderr.write(`fatal: ${String(err.message ?? err)}\n`);
@@ -190,4 +218,4 @@ function isEntryPoint() {
190
218
  return false;
191
219
  }
192
220
  }
193
- export { main, parseArgs, parseAuth };
221
+ export { exitCodeForResult, main, parseArgs, parseAuth };
@@ -11,113 +11,96 @@
11
11
  *
12
12
  * When a host IS provided, the runner calls `dispatchSetup()` for
13
13
  * every step before driving the fixture's `inputEnvelope`, then
14
- * `dispatchTeardown()` for every step after the assertion. Unknown
15
- * `kind` values MUST throw so unimplemented directives surface as a
14
+ * `dispatchTeardown()` for every step after the assertion. A host
15
+ * that does not implement a directive MUST throw so it surfaces as a
16
16
  * test SKIP with a clear "host does not implement X" reason — never
17
17
  * a silent pass.
18
18
  *
19
- * ## Extensibly-closed
19
+ * ## Closed vocabulary
20
20
  *
21
- * The concrete step vocabulary below mirrors `./types`' vocabulary
22
- * but is re-exported here as a host-facing narrower form:
23
- * - Every arm carries the full semantic payload (not the JSON-
24
- * loader's `unknown`-fields view).
25
- * - The tail `(Record<string, unknown> & { readonly kind: string & {} })`
26
- * catches fixture-JSON directives the host hasn't wired — the host
27
- * returns by throwing, the runner maps it to a skip.
28
- *
29
- * Third-party hosts MAY extend either union with additional arms by
30
- * augmenting the declaration in their own module. v1.0 of the kit
31
- * does not require exhaustive coverage — it requires *honest*
32
- * coverage: either implement the directive OR throw, never silently
33
- * succeed on an unknown kind.
21
+ * Both step unions here are CLOSED — they carry exactly the directive
22
+ * vocabulary the shipped fixture catalog authors, with the same field
23
+ * names the fixture JSON uses. The runner validates every authored
24
+ * directive against the JSON-authoring union in `./types` before
25
+ * dispatch (unknown / malformed directives are fixture-authoring
26
+ * errors, thrown loudly, never dispatched), so a host only ever
27
+ * receives shape-valid steps of a known kind. Hosts therefore need
28
+ * exactly two behaviors per directive: implement it, or throw
29
+ * "not implemented" — never silently succeed.
34
30
  *
35
31
  * ## Relation to `./types`
36
32
  *
37
- * `./types` defines the JSON-authoring surface: every directive has
38
- * optional-by-default fields so JSON authors don't have to think
39
- * about shape. This module defines the *runtime* surface: once the
40
- * loader has validated a directive, the host receives a narrowed
41
- * form. The two are intentionally parallel but not identical — JSON
42
- * loaders accept what the author wrote; hosts receive what the
43
- * runner confirmed is shape-valid.
33
+ * `./types` defines the JSON-authoring surface (discriminated on
34
+ * `type`); this module defines the *runtime* surface the host
35
+ * receives (discriminated on `kind`). The two are intentionally
36
+ * parallel: same directives, same field names, different
37
+ * discriminator key.
44
38
  */
39
+ import type { ActionSpecEntryDecl } from './types.js';
45
40
  /**
46
41
  * Setup directive the host dispatches BEFORE a fixture's input
47
42
  * envelope drives the system under test.
48
43
  *
49
- * Extensibly-closed: a host that receives a `kind` it doesn't
50
- * recognize MUST throw `Error('conformance-host: unknown setup kind: <kind>')`
51
- * so the runner records the fixture as SKIPPED with that reason —
52
- * never silently succeed.
44
+ * Closed union — the runner validates fixture-authored directives
45
+ * before dispatch, so only these five kinds ever reach a host. A host
46
+ * that does not implement one of them MUST throw (e.g.
47
+ * `Error('host does not implement renderer-url-override')`) so the
48
+ * runner records the fixture as SKIPPED with that reason — never
49
+ * silently succeed.
53
50
  */
54
- export type SetupStep = CreateRenderSetup | RegisterToolSetup | RegisterActionSpecSetup | EmitEnvelopeSetup | RendererUrlOverrideSetup | UiInitializeResponseOverrideSetup | ServerVersionOverrideSetup | UnknownSetupStep;
55
- export interface CreateRenderSetup {
56
- readonly kind: 'create-render';
57
- readonly renderId: string;
51
+ export type SetupStep = CreateGguiSessionSetup | RendererUrlOverrideSetup | ServerVersionOverrideSetup | UiInitializeResponseOverrideSetup | EmitEnvelopeSetup;
52
+ export interface CreateGguiSessionSetup {
53
+ readonly kind: 'create-session';
54
+ readonly sessionId: string;
58
55
  readonly appId?: string;
56
+ /**
57
+ * Optional actionSpec declared on the GguiSession at creation. See
58
+ * `CreateGguiSessionStep.actionSpec` in `./types` — hosts that
59
+ * receive the field MUST install it as the render's declared-action
60
+ * contract before the fixture's input envelope is dispatched.
61
+ */
62
+ readonly actionSpec?: Readonly<Record<string, ActionSpecEntryDecl>>;
59
63
  }
60
- /**
61
- * Register a tool on the render by symbolic handler name. Known
62
- * handlers the reference implementation supports:
63
- * - `'echo'` — returns `{received: args}`.
64
- * - `'throw'` — rejects with `Error('tool_threw_for_fixture')`.
65
- * - `'timeout'` — never resolves; runner times out per policy.
66
- * - `'malformed'`— returns `{wrong:'shape'}` to exercise
67
- * `SCHEMA_VIOLATION`.
68
- */
69
- export interface RegisterToolSetup {
70
- readonly kind: 'register-tool';
71
- readonly name: string;
72
- readonly handler: 'echo' | 'throw' | 'timeout' | 'malformed' | (string & {});
73
- }
74
- export interface RegisterActionSpecSetup {
75
- readonly kind: 'register-actionspec';
76
- readonly name: string;
77
- readonly tool: string;
78
- }
79
- export interface EmitEnvelopeSetup {
80
- readonly kind: 'emit-envelope';
81
- readonly channel: string;
82
- readonly payload: unknown;
83
- }
64
+ /** Runtime form of `RendererUrlOverrideStep` (see `./types`). */
84
65
  export interface RendererUrlOverrideSetup {
85
66
  readonly kind: 'renderer-url-override';
67
+ readonly sessionId: string;
86
68
  readonly url: string;
87
69
  }
70
+ /** Runtime form of `ServerVersionOverrideStep` (see `./types`). */
71
+ export interface ServerVersionOverrideSetup {
72
+ readonly kind: 'server-version-override';
73
+ readonly sessionId: string;
74
+ readonly advertiseVersion: string;
75
+ }
76
+ /** Runtime form of `UiInitializeResponseOverrideStep` (see `./types`). */
88
77
  export interface UiInitializeResponseOverrideSetup {
89
78
  readonly kind: 'ui-initialize-response-override';
90
- readonly response: unknown;
79
+ readonly sessionId: string;
80
+ readonly override: unknown;
91
81
  }
92
- export interface ServerVersionOverrideSetup {
93
- readonly kind: 'server-version-override';
94
- readonly version: string;
82
+ /** Runtime form of `EmitEnvelopeStep` (see `./types`). */
83
+ export interface EmitEnvelopeSetup {
84
+ readonly kind: 'emit-envelope';
85
+ readonly channel: string;
86
+ readonly payload: unknown;
95
87
  }
96
88
  /**
97
- * Future-compat catch. A host that reaches this arm MUST throw rather
98
- * than return silently — the runner interprets the throw as a skip
99
- * with the error message as the reason.
89
+ * Closed-and-empty, mirroring `./types`' `TeardownStep`: no teardown
90
+ * vocabulary exists in this kit version (renders decay via TTL), so
91
+ * `dispatchTeardown` is never invoked today. The slot stays so future
92
+ * cleanup directives land additively as union arms.
100
93
  */
101
- export type UnknownSetupStep = Record<string, unknown> & {
102
- readonly kind: string & {};
103
- };
104
- export type TeardownStep = UnregisterToolTeardown | UnknownTeardownStep;
105
- export interface UnregisterToolTeardown {
106
- readonly kind: 'unregister-tool';
107
- readonly name: string;
108
- }
109
- export type UnknownTeardownStep = Record<string, unknown> & {
110
- readonly kind: string & {};
111
- };
94
+ export type TeardownStep = never;
112
95
  /**
113
96
  * Adapter the implementation under test provides to the runner. An
114
97
  * implementation MAY:
115
98
  *
116
99
  * - Implement every directive the kit authors (full coverage — the
117
100
  * reference `@ggui-ai/mcp-server`-backed host target).
118
- * - Implement a subset + throw on unknown kinds (partial coverage
119
- * — the kit records unimplemented directives as SKIPPED rather
120
- * than failed).
101
+ * - Implement a subset + throw on the rest (partial coverage — the
102
+ * kit records unimplemented directives as SKIPPED rather than
103
+ * failed).
121
104
  * - Not be provided at all (the runner drops every fixture with a
122
105
  * non-empty `setup` or `teardown` as skip-with-reason).
123
106
  *
@@ -147,5 +130,27 @@ export interface ConformanceHost {
147
130
  * the cleanup gap without corrupting the pass/fail tally.
148
131
  */
149
132
  dispatchTeardown(step: TeardownStep): Promise<void>;
133
+ /**
134
+ * Read one field off a live GguiSession — the introspection seam a
135
+ * `session-state` expectation (`./types`' `SessionStateBehavior`)
136
+ * grades against. The runner calls this AFTER the fixture's input
137
+ * envelope has been dispatched and the observation window has
138
+ * elapsed, then deep-equals the result against the fixture's
139
+ * `expected`.
140
+ *
141
+ * Optional. A host that does not provide it makes every
142
+ * `session-state` fixture SKIP — the kit cannot honestly grade a
143
+ * stateful obligation it has no way to observe. Throwing signals
144
+ * "this field is not exposed" and the runner records a SKIP with
145
+ * the error's message; a throw is never treated as a pass or a
146
+ * fail — a host that cannot read state cannot grade it.
147
+ *
148
+ * Honest-grade contract: the returned value MUST reflect the
149
+ * GguiSession's true post-dispatch state. A host that fabricates a
150
+ * passing value is cheating its own conformance audit — that is the
151
+ * implementer's integrity to keep, exactly as with every other
152
+ * host-mediated directive.
153
+ */
154
+ readSessionField?(sessionId: string, field: string): Promise<unknown>;
150
155
  }
151
156
  //# sourceMappingURL=conformance-host.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conformance-host.d.ts","sourceRoot":"","sources":["../src/conformance-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAMH;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,uBAAuB,GACvB,iBAAiB,GACjB,wBAAwB,GACxB,iCAAiC,GACjC,0BAA0B,GAC1B,gBAAgB,CAAC;AAErB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,IAAI,EAAE,iCAAiC,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC;CAC5B,CAAC;AAMF,MAAM,MAAM,YAAY,GACpB,sBAAsB,GACtB,mBAAmB,CAAC;AAExB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC;CAC5B,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;;;;OAKG;IACH,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD"}
1
+ {"version":3,"file":"conformance-host.d.ts","sourceRoot":"","sources":["../src/conformance-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAMtD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,SAAS,GACjB,sBAAsB,GACtB,wBAAwB,GACxB,0BAA0B,GAC1B,iCAAiC,GACjC,iBAAiB,CAAC;AAEtB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;CACrE;AAED,iEAAiE;AACjE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,mEAAmE;AACnE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED,0EAA0E;AAC1E,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,IAAI,EAAE,iCAAiC,CAAC;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAMD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,KAAK,CAAC;AAMjC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;;;;OAKG;IACH,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,gBAAgB,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE"}
@@ -1,45 +1 @@
1
- /**
2
- * `ConformanceHost` — the seam between the kit's authored fixtures
3
- * and the implementation under test.
4
- *
5
- * Most fixtures declare `setup` / `teardown` directives (see
6
- * {@link SetupStep} / {@link TeardownStep} in `./types`) that the host
7
- * dispatches against its live server. v1.0 of the kit does NOT
8
- * require implementations to ship a host — fixtures that need setup
9
- * directives skip cleanly when `runConformance()` is called without
10
- * a `host`.
11
- *
12
- * When a host IS provided, the runner calls `dispatchSetup()` for
13
- * every step before driving the fixture's `inputEnvelope`, then
14
- * `dispatchTeardown()` for every step after the assertion. Unknown
15
- * `kind` values MUST throw so unimplemented directives surface as a
16
- * test SKIP with a clear "host does not implement X" reason — never
17
- * a silent pass.
18
- *
19
- * ## Extensibly-closed
20
- *
21
- * The concrete step vocabulary below mirrors `./types`' vocabulary
22
- * but is re-exported here as a host-facing narrower form:
23
- * - Every arm carries the full semantic payload (not the JSON-
24
- * loader's `unknown`-fields view).
25
- * - The tail `(Record<string, unknown> & { readonly kind: string & {} })`
26
- * catches fixture-JSON directives the host hasn't wired — the host
27
- * returns by throwing, the runner maps it to a skip.
28
- *
29
- * Third-party hosts MAY extend either union with additional arms by
30
- * augmenting the declaration in their own module. v1.0 of the kit
31
- * does not require exhaustive coverage — it requires *honest*
32
- * coverage: either implement the directive OR throw, never silently
33
- * succeed on an unknown kind.
34
- *
35
- * ## Relation to `./types`
36
- *
37
- * `./types` defines the JSON-authoring surface: every directive has
38
- * optional-by-default fields so JSON authors don't have to think
39
- * about shape. This module defines the *runtime* surface: once the
40
- * loader has validated a directive, the host receives a narrowed
41
- * form. The two are intentionally parallel but not identical — JSON
42
- * loaders accept what the author wrote; hosts receive what the
43
- * runner confirmed is shape-valid.
44
- */
45
1
  export {};
@@ -3,16 +3,16 @@
3
3
  "description": "Thin shell reads a valid `_meta.ggui.bootstrap.runtimeUrl` but the `<script src>` errors (network failure / 404 / CSP reject). Shell emits `postMessage({type:'ggui:bootstrap-failed', reason:'BUNDLE_FETCH_FAILED', message})` to the parent. No WS opens.",
4
4
  "skipReason": null,
5
5
  "setup": [
6
- { "type": "create-render", "renderId": "ck-bundle-fetch-failed-1" },
6
+ { "type": "create-session", "sessionId": "ck-bundle-fetch-failed-1" },
7
7
  {
8
8
  "type": "renderer-url-override",
9
- "renderId": "ck-bundle-fetch-failed-1",
9
+ "sessionId": "ck-bundle-fetch-failed-1",
10
10
  "url": "http://127.0.0.1:1/does-not-exist.js"
11
11
  }
12
12
  ],
13
13
  "inputEnvelope": {
14
14
  "type": "render",
15
- "renderId": "ck-bundle-fetch-failed-1",
15
+ "sessionId": "ck-bundle-fetch-failed-1",
16
16
  "resource": {
17
17
  "uri": "ui://ggui/render"
18
18
  }
@@ -3,10 +3,10 @@
3
3
  "description": "Host's `ui/initialize` response returns a tool-output without `_meta.ggui.bootstrap` (or missing the `runtimeUrl` field). Thin shell emits `postMessage({type:'ggui:bootstrap-failed', reason:'BOOTSTRAP_META_MISSING', message})` to the parent. No WS connection opens — live-channel envelopes are out of scope pre-handshake.",
4
4
  "skipReason": null,
5
5
  "setup": [
6
- { "type": "create-render", "renderId": "ck-bootstrap-meta-missing-1" },
6
+ { "type": "create-session", "sessionId": "ck-bootstrap-meta-missing-1" },
7
7
  {
8
8
  "type": "ui-initialize-response-override",
9
- "renderId": "ck-bootstrap-meta-missing-1",
9
+ "sessionId": "ck-bootstrap-meta-missing-1",
10
10
  "override": {
11
11
  "toolOutput": {
12
12
  "_meta": {
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "inputEnvelope": {
20
20
  "type": "render",
21
- "renderId": "ck-bootstrap-meta-missing-1",
21
+ "sessionId": "ck-bootstrap-meta-missing-1",
22
22
  "resource": {
23
23
  "uri": "ui://ggui/render"
24
24
  }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "bootstrap-success",
3
- "description": "Happy-path boot: host reads `_meta.ui.resourceUri`, fetches `ui://ggui/render`, the thin shell loads the renderer bundle, `ui/initialize` returns valid `_meta.ggui.bootstrap.{wsUrl, token, renderId, appId, runtimeUrl}`, WS handshake succeeds, the render mounts with `data-ggui-code-ready=\"true\"`. No bootstrap-failed postMessage, no contract-error, no upgrade-required.",
3
+ "description": "Happy-path boot: host reads `_meta.ui.resourceUri`, fetches `ui://ggui/render`, the thin shell loads the renderer bundle, `ui/initialize` returns valid `_meta.ggui.bootstrap.{wsUrl, token, sessionId, appId, runtimeUrl}`, WS handshake succeeds, the render mounts with `data-ggui-code-ready=\"true\"`. No bootstrap-failed postMessage, no upgrade-required.",
4
4
  "skipReason": null,
5
- "setup": [{ "type": "create-render", "renderId": "ck-bootstrap-success-1" }],
5
+ "setup": [{ "type": "create-session", "sessionId": "ck-bootstrap-success-1" }],
6
6
  "inputEnvelope": {
7
7
  "type": "render",
8
- "renderId": "ck-bootstrap-success-1",
8
+ "sessionId": "ck-bootstrap-success-1",
9
9
  "resource": {
10
10
  "uri": "ui://ggui/render"
11
11
  }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "action-ack-sequence",
3
+ "description": "A declared action dispatched on the live channel persists to the GguiSession's consume buffer before the ack: the server appends the envelope as an event, assigns a monotonic sequence, and the ack frame echoes the action's requestId carrying `payload.sequence`. Proves the persistence half of the single action-routing model — the agent drains the buffer via `ggui_consume` on its next turn (an MCP tool call outside this WS kit's observation window).",
4
+ "skipReason": null,
5
+ "setup": [
6
+ {
7
+ "type": "create-session",
8
+ "sessionId": "ck-action-ack-1",
9
+ "actionSpec": { "toggleTask": {} }
10
+ }
11
+ ],
12
+ "inputEnvelope": {
13
+ "type": "action",
14
+ "requestId": "ck-action-ack-1-req",
15
+ "payload": {
16
+ "sessionId": "ck-action-ack-1",
17
+ "type": "data:submit",
18
+ "payload": {
19
+ "action": "toggleTask",
20
+ "data": { "id": "seed-1" }
21
+ }
22
+ }
23
+ },
24
+ "expectedBehavior": {
25
+ "kind": "action-ack",
26
+ "requestId": "ck-action-ack-1-req"
27
+ }
28
+ }