@ggui-ai/protocol-conformance 0.2.0-alpha.4 → 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.
- package/README.md +32 -13
- package/dist/cli.d.ts +11 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +48 -20
- package/dist/conformance-host.d.ts +81 -76
- package/dist/conformance-host.d.ts.map +1 -1
- package/dist/conformance-host.js +0 -44
- package/dist/fixtures/bootstrap-protocol/bootstrap-bundle-fetch-failed.json +3 -3
- package/dist/fixtures/bootstrap-protocol/bootstrap-meta-missing.json +3 -3
- package/dist/fixtures/bootstrap-protocol/bootstrap-success.json +3 -3
- package/dist/fixtures/consume-buffer/action-ack-sequence.json +28 -0
- package/dist/fixtures/consume-buffer/action-payload-schema-violation.json +37 -0
- package/dist/fixtures/consume-buffer/index.d.ts +6 -0
- package/dist/fixtures/consume-buffer/index.d.ts.map +1 -0
- package/dist/fixtures/consume-buffer/index.js +41 -0
- package/dist/fixtures/consume-buffer/undeclared-action-rejected.json +29 -0
- package/dist/fixtures/host-context/host-context-observed-persists.json +34 -0
- package/dist/fixtures/host-context/index.d.ts +5 -0
- package/dist/fixtures/host-context/index.d.ts.map +1 -0
- package/dist/fixtures/host-context/index.js +40 -0
- package/dist/fixtures/index.d.ts +28 -9
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +33 -14
- package/dist/fixtures/reserved-channel-authority/index.d.ts.map +1 -1
- package/dist/fixtures/reserved-channel-authority/index.js +17 -7
- package/dist/fixtures/reserved-channel-authority/props-update-roundtrip.json +4 -4
- package/dist/fixtures/schema-version-handshake/index.d.ts.map +1 -1
- package/dist/fixtures/schema-version-handshake/index.js +12 -4
- package/dist/fixtures/schema-version-handshake/version-match.json +7 -5
- package/dist/fixtures/schema-version-handshake/version-mismatch.json +9 -10
- package/dist/fixtures/subscribe-tenancy/absent-appid-defaults.json +18 -0
- package/dist/fixtures/subscribe-tenancy/app-mismatch.json +20 -0
- package/dist/fixtures/subscribe-tenancy/index.d.ts +6 -0
- package/dist/fixtures/subscribe-tenancy/index.d.ts.map +1 -0
- package/dist/fixtures/subscribe-tenancy/index.js +62 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/match-behavior.d.ts +19 -100
- package/dist/match-behavior.d.ts.map +1 -1
- package/dist/match-behavior.js +289 -246
- package/dist/registration-conformance/cases/gate-duplicate-hook.json +26 -0
- package/dist/registration-conformance/cases/gate-export-not-registered.json +1 -0
- package/dist/registration-conformance/cases/gate-multi-package-distinct-names-accept.json +26 -0
- package/dist/registration-conformance/cases/gate-no-gadgets-accept.json +1 -0
- package/dist/registration-conformance/cases/gate-package-mismatch.json +1 -0
- package/dist/registration-conformance/cases/gate-priority-not-registered-outranks-mismatch.json +1 -0
- package/dist/registration-conformance/cases/gate-priority-registry-outranks-public-env.json +22 -0
- package/dist/registration-conformance/cases/gate-public-env-missing.json +21 -0
- package/dist/registration-conformance/cases/gate-public-env-satisfied-accept.json +21 -0
- package/dist/registration-conformance/cases/gate-public-env-undeclared-wrapper-accept.json +26 -0
- package/dist/registration-conformance/cases/gate-registered-resolves-accept.json +1 -0
- package/dist/registration-conformance/cases/gate-stdlib-hook-accept.json +1 -0
- package/dist/registration-conformance/cases/gate-typo-classifies-as-not-registered.json +1 -0
- package/dist/registration-conformance/index.d.ts +64 -33
- package/dist/registration-conformance/index.d.ts.map +1 -1
- package/dist/registration-conformance/index.js +24 -8
- package/dist/run-conformance.d.ts +145 -6
- package/dist/run-conformance.d.ts.map +1 -1
- package/dist/run-conformance.js +531 -93
- package/dist/types.d.ts +319 -165
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +27 -23
- package/dist/ws-transport.d.ts.map +1 -1
- package/dist/ws-transport.js +3 -2
- package/package.json +3 -3
- package/dist/fixtures/observability-events/index.d.ts +0 -5
- package/dist/fixtures/observability-events/index.d.ts.map +0 -1
- package/dist/fixtures/observability-events/index.js +0 -22
- package/dist/fixtures/observability-events/observability-contract-error-emitted.json +0 -28
- package/dist/fixtures/observability-events/observability-wired-tool-invoked.json +0 -27
- package/dist/fixtures/refresh-semantics/index.d.ts +0 -5
- package/dist/fixtures/refresh-semantics/index.d.ts.map +0 -1
- package/dist/fixtures/refresh-semantics/index.js +0 -19
- package/dist/fixtures/refresh-semantics/stream-refresh-success.json +0 -39
- package/dist/fixtures/reserved-channel-authority/stream-schema-violation.json +0 -44
- package/dist/fixtures/wired-action-dispatch/index.d.ts +0 -5
- package/dist/fixtures/wired-action-dispatch/index.d.ts.map +0 -1
- package/dist/fixtures/wired-action-dispatch/index.js +0 -29
- package/dist/fixtures/wired-action-dispatch/wired-action-success.json +0 -35
- package/dist/fixtures/wired-action-dispatch/wired-action-tool-not-found.json +0 -30
- package/dist/fixtures/wired-action-dispatch/wired-action-tool-threw.json +0 -31
- 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
|
-
- `
|
|
20
|
-
- `
|
|
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
|
-
|
|
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
|
-
|
|
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)`
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":";
|
|
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
|
|
12
|
-
* ANY fixture failed.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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>
|
|
99
|
-
|
|
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 —
|
|
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
|
-
|
|
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
|
|
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.
|
|
15
|
-
*
|
|
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
|
-
* ##
|
|
19
|
+
* ## Closed vocabulary
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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 =
|
|
55
|
-
export interface
|
|
56
|
-
readonly kind: 'create-
|
|
57
|
-
readonly
|
|
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
|
|
79
|
+
readonly sessionId: string;
|
|
80
|
+
readonly override: unknown;
|
|
91
81
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
readonly
|
|
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
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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
|
|
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
|
|
119
|
-
*
|
|
120
|
-
*
|
|
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
|
|
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"}
|
package/dist/conformance-host.js
CHANGED
|
@@ -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-
|
|
6
|
+
{ "type": "create-session", "sessionId": "ck-bundle-fetch-failed-1" },
|
|
7
7
|
{
|
|
8
8
|
"type": "renderer-url-override",
|
|
9
|
-
"
|
|
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
|
-
"
|
|
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-
|
|
6
|
+
{ "type": "create-session", "sessionId": "ck-bootstrap-meta-missing-1" },
|
|
7
7
|
{
|
|
8
8
|
"type": "ui-initialize-response-override",
|
|
9
|
-
"
|
|
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
|
-
"
|
|
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,
|
|
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-
|
|
5
|
+
"setup": [{ "type": "create-session", "sessionId": "ck-bootstrap-success-1" }],
|
|
6
6
|
"inputEnvelope": {
|
|
7
7
|
"type": "render",
|
|
8
|
-
"
|
|
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
|
+
}
|