@ggui-ai/protocol-conformance 0.2.0-alpha.4 → 0.4.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 +4 -4
- 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/dist/types.d.ts
CHANGED
|
@@ -13,27 +13,31 @@
|
|
|
13
13
|
* {@link TeardownStep}, and {@link ExpectedBehavior} IS the public
|
|
14
14
|
* API. Renaming a field breaks every third-party consumer of the
|
|
15
15
|
* conformance kit. Treat this file the way `@ggui-ai/protocol`'s
|
|
16
|
-
* type declarations get treated: additive changes only
|
|
17
|
-
* via `(string & {})` tails
|
|
16
|
+
* type declarations get treated: additive changes only; where a
|
|
17
|
+
* union is extensible, it is via `(string & {})` tails (see the next
|
|
18
|
+
* section for which unions those are).
|
|
18
19
|
*
|
|
19
|
-
* ##
|
|
20
|
+
* ## Closed vs extensibly-closed unions
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
-
* see SPEC §extensibility and `ContractErrorCode` in
|
|
23
|
-
* `@ggui-ai/protocol`. Third-party conformance-host implementations
|
|
24
|
-
* MAY introduce setup / teardown / expectation kinds the reference
|
|
25
|
-
* kit doesn't recognize; the runner SHOULD skip such cases with a
|
|
26
|
-
* warning rather than failing loudly, exactly the way
|
|
27
|
-
* `ContractErrorCode`'s extensibility works.
|
|
22
|
+
* Two different evolution postures coexist in this module:
|
|
28
23
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
24
|
+
* - `SetupStep['type']` is **closed** — exactly the directive
|
|
25
|
+
* vocabulary the shipped fixture catalog authors. An unknown or
|
|
26
|
+
* malformed directive is a fixture-authoring error the runner
|
|
27
|
+
* rejects loudly (it can never be a legitimate runtime extension
|
|
28
|
+
* point, because the catalog and the vocabulary ship together).
|
|
29
|
+
* New directives land as new union arms in a kit version bump.
|
|
30
|
+
* `TeardownStep` is the same posture with a currently-empty
|
|
31
|
+
* vocabulary.
|
|
32
|
+
* - `ExpectedBehavior['kind']` and `TransportConfig['kind']` carry a
|
|
33
|
+
* `(string & {})` tail — see SPEC §11.4 (extensibly-closed
|
|
34
|
+
* unions, e.g. `SubmitActionKind` in `@ggui-ai/protocol`). Runners
|
|
35
|
+
* that
|
|
36
|
+
* receive a behavior kind they don't recognize SHOULD skip with a
|
|
37
|
+
* warning; an unrecognized transport kind is a configuration
|
|
38
|
+
* error.
|
|
35
39
|
*
|
|
36
|
-
* Everything else — tool names, envelope field names,
|
|
40
|
+
* Everything else — tool names, envelope field names, wire error
|
|
37
41
|
* codes — is passed verbatim and the implementation under test owns
|
|
38
42
|
* the recognition set.
|
|
39
43
|
*
|
|
@@ -51,8 +55,8 @@
|
|
|
51
55
|
* pinned here, deliberately decoupled from the live source tree. A
|
|
52
56
|
* drift check between these authored unions and the live protocol /
|
|
53
57
|
* renderer types belongs in the kit's own meta-tests, NOT in this
|
|
54
|
-
* file's import graph. If `@ggui-ai/protocol` adds a new
|
|
55
|
-
*
|
|
58
|
+
* file's import graph. If `@ggui-ai/protocol` adds a new canonical
|
|
59
|
+
* member to an extensibly-closed union tomorrow, conformance kit
|
|
56
60
|
* consumers MUST NOT be forced to recompile — their fixtures
|
|
57
61
|
* continue to reference the value they authored, and the runner's
|
|
58
62
|
* extensible `(string & {})` tail handles recognition. This mirrors
|
|
@@ -70,8 +74,9 @@
|
|
|
70
74
|
* unknown arms are accepted as opaque records so third-party runners
|
|
71
75
|
* can extend without a package bump.
|
|
72
76
|
*
|
|
73
|
-
* Pattern mirrored from
|
|
74
|
-
*
|
|
77
|
+
* Pattern mirrored from the protocol's extensibly-closed unions
|
|
78
|
+
* (e.g. `SubmitActionKind` in
|
|
79
|
+
* `@ggui-ai/protocol/integrations/mcp-apps`).
|
|
75
80
|
*/
|
|
76
81
|
export type TransportConfig = WebSocketTransportConfig | UnknownTransportConfig;
|
|
77
82
|
export interface WebSocketTransportConfig {
|
|
@@ -109,35 +114,112 @@ export type AuthConfig = {
|
|
|
109
114
|
* this module authors the declarative intent; the host owns the
|
|
110
115
|
* implementation.
|
|
111
116
|
*
|
|
112
|
-
* Discriminator is
|
|
113
|
-
* fixture
|
|
114
|
-
*
|
|
117
|
+
* Discriminator is CLOSED: exactly the directive vocabulary the
|
|
118
|
+
* shipped fixture catalog authors. The runner validates every step
|
|
119
|
+
* against this union before dispatching and throws a descriptive
|
|
120
|
+
* fixture-authoring error on an unknown or malformed directive — a
|
|
121
|
+
* typo'd `type` can never silently skip. Hosts that don't implement
|
|
122
|
+
* a (valid) directive signal it by throwing from `dispatchSetup`,
|
|
123
|
+
* which the runner records as a SKIP.
|
|
115
124
|
*/
|
|
116
|
-
export type SetupStep =
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
export type SetupStep = CreateGguiSessionStep | RendererUrlOverrideStep | ServerVersionOverrideStep | UiInitializeResponseOverrideStep | EmitEnvelopeStep;
|
|
126
|
+
/**
|
|
127
|
+
* Declared-action entry for {@link CreateGguiSessionStep.actionSpec}.
|
|
128
|
+
* An empty entry declares a void-payload action — fixtures over it
|
|
129
|
+
* exercise the name-membership half of the action contract. An entry
|
|
130
|
+
* carrying `schema` ALSO declares the action's payload contract: the
|
|
131
|
+
* implementation under test MUST validate a `data:submit` payload's
|
|
132
|
+
* `data` against it at receipt (SPEC §4.6) and reject non-conforming
|
|
133
|
+
* payloads with a `CONTRACT_VIOLATION` error frame — the same wire
|
|
134
|
+
* surface as an undeclared name. A host that cannot install
|
|
135
|
+
* per-action payload schemas MUST refuse the directive (throw, so the
|
|
136
|
+
* fixture skips honestly) rather than silently downgrading the
|
|
137
|
+
* declaration to name-membership.
|
|
138
|
+
*/
|
|
139
|
+
export interface ActionSpecEntryDecl {
|
|
140
|
+
readonly schema?: JsonSchemaDecl;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* JSON-Schema node authored on {@link ActionSpecEntryDecl.schema} —
|
|
144
|
+
* the kit's decoupled copy of the protocol's JSON Schema draft-07
|
|
145
|
+
* subset (`JsonSchema` in `@ggui-ai/protocol`), same authoring
|
|
146
|
+
* posture as {@link BootstrapFailureReason}: fixtures compile against
|
|
147
|
+
* the
|
|
148
|
+
* kit's frozen vocabulary, never the live protocol types. The typed
|
|
149
|
+
* core names exactly the keywords the shipped fixtures author and
|
|
150
|
+
* the runner's validating parse recognizes; the index-signature tail
|
|
151
|
+
* carries any further draft-07 keyword verbatim — the implementation
|
|
152
|
+
* under test owns their interpretation, exactly as with tool names
|
|
153
|
+
* and wire error codes.
|
|
154
|
+
*/
|
|
155
|
+
export interface JsonSchemaDecl {
|
|
156
|
+
readonly [keyword: string]: unknown;
|
|
157
|
+
readonly type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null';
|
|
158
|
+
readonly description?: string;
|
|
159
|
+
readonly enum?: readonly unknown[];
|
|
160
|
+
readonly items?: JsonSchemaDecl;
|
|
161
|
+
readonly properties?: Readonly<Record<string, JsonSchemaDecl>>;
|
|
162
|
+
readonly required?: readonly string[];
|
|
163
|
+
readonly additionalProperties?: JsonSchemaDecl | boolean;
|
|
164
|
+
}
|
|
165
|
+
export interface CreateGguiSessionStep {
|
|
166
|
+
readonly type: 'create-session';
|
|
119
167
|
/** Opaque render id the host allocates. Downstream steps + the
|
|
120
168
|
* fixture's `inputEnvelope` reference it by value. */
|
|
121
|
-
readonly
|
|
169
|
+
readonly sessionId: string;
|
|
122
170
|
/** Optional app (tenant) id the render scopes to. Defaults to the
|
|
123
171
|
* host's implementation-defined "default app". */
|
|
124
172
|
readonly appId?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Optional actionSpec declared on the GguiSession at creation —
|
|
175
|
+
* mirrors how a real ggui server derives the declared-action set
|
|
176
|
+
* from the render's data contract (actions are part of the render's
|
|
177
|
+
* identity, not separately registered). Servers MUST reject
|
|
178
|
+
* `data:submit` envelopes naming actions absent from this record —
|
|
179
|
+
* and, for entries that declare a `schema`, envelopes whose `data`
|
|
180
|
+
* does not conform to it (SPEC §4.6 receipt validation) — with an
|
|
181
|
+
* `error` frame, code `CONTRACT_VIOLATION`. Omitting the field
|
|
182
|
+
* declares no contract — all actions are accepted.
|
|
183
|
+
*/
|
|
184
|
+
readonly actionSpec?: Readonly<Record<string, ActionSpecEntryDecl>>;
|
|
125
185
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Point the render's bootstrap at a renderer-bundle URL the host
|
|
188
|
+
* controls — typically an unreachable one, to drive the
|
|
189
|
+
* `BUNDLE_FETCH_FAILED` bootstrap-failure path. A browser-host (Path-B)
|
|
190
|
+
* concern: WS-only hosts throw on it and the fixture skips.
|
|
191
|
+
*/
|
|
192
|
+
export interface RendererUrlOverrideStep {
|
|
193
|
+
readonly type: 'renderer-url-override';
|
|
194
|
+
/** GguiSession the override scopes to (a prior `create-session`). */
|
|
195
|
+
readonly sessionId: string;
|
|
196
|
+
/** Renderer-bundle URL the host substitutes for the real one. */
|
|
197
|
+
readonly url: string;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Advertise a server schema version other than the implementation's
|
|
201
|
+
* real one — drives the version-handshake rejection path
|
|
202
|
+
* (`UPGRADE_REQUIRED`).
|
|
203
|
+
*/
|
|
204
|
+
export interface ServerVersionOverrideStep {
|
|
205
|
+
readonly type: 'server-version-override';
|
|
206
|
+
/** GguiSession the override scopes to (a prior `create-session`). */
|
|
207
|
+
readonly sessionId: string;
|
|
208
|
+
/** Schema version the server advertises on the wire for this render. */
|
|
209
|
+
readonly advertiseVersion: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Substitute the MCP Apps `ui/initialize` response the host returns to
|
|
213
|
+
* the iframe — drives bootstrap-failure paths like
|
|
214
|
+
* `BOOTSTRAP_META_MISSING`. A browser-host (Path-B) concern: WS-only
|
|
215
|
+
* hosts throw on it and the fixture skips.
|
|
216
|
+
*/
|
|
217
|
+
export interface UiInitializeResponseOverrideStep {
|
|
218
|
+
readonly type: 'ui-initialize-response-override';
|
|
219
|
+
/** GguiSession the override scopes to (a prior `create-session`). */
|
|
220
|
+
readonly sessionId: string;
|
|
221
|
+
/** Response body the host returns from `ui/initialize` verbatim. */
|
|
222
|
+
readonly override: unknown;
|
|
141
223
|
}
|
|
142
224
|
export interface EmitEnvelopeStep {
|
|
143
225
|
readonly type: 'emit-envelope';
|
|
@@ -147,78 +229,88 @@ export interface EmitEnvelopeStep {
|
|
|
147
229
|
* format (sequence stamp, timestamp, etc.). */
|
|
148
230
|
readonly payload: unknown;
|
|
149
231
|
}
|
|
150
|
-
export interface SeedChannelStep {
|
|
151
|
-
readonly type: 'seed-channel';
|
|
152
|
-
readonly renderId: string;
|
|
153
|
-
readonly channel: string;
|
|
154
|
-
readonly value: unknown;
|
|
155
|
-
}
|
|
156
232
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
233
|
+
* Teardown directive — mirrors {@link SetupStep}'s closed posture,
|
|
234
|
+
* scoped to cleanup. The vocabulary is EMPTY in this kit version: no
|
|
235
|
+
* fixture authors a teardown directive (renders decay via TTL), so
|
|
236
|
+
* the union has no members and the runner rejects any authored
|
|
237
|
+
* teardown step as a fixture-authoring error. Future cleanup
|
|
238
|
+
* directives land additively as union arms.
|
|
159
239
|
*/
|
|
160
|
-
export
|
|
161
|
-
readonly type: string & {};
|
|
162
|
-
readonly [field: string]: unknown;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Teardown directive — mirrors {@link SetupStep}'s vocabulary, scoped
|
|
166
|
-
* to cleanup.
|
|
167
|
-
*/
|
|
168
|
-
export type TeardownStep = UnregisterToolStep | UnknownTeardownStep;
|
|
169
|
-
export interface UnregisterToolStep {
|
|
170
|
-
readonly type: 'unregister-tool';
|
|
171
|
-
readonly toolName: string;
|
|
172
|
-
}
|
|
173
|
-
export interface UnknownTeardownStep {
|
|
174
|
-
readonly type: string & {};
|
|
175
|
-
readonly [field: string]: unknown;
|
|
176
|
-
}
|
|
240
|
+
export type TeardownStep = never;
|
|
177
241
|
/**
|
|
178
242
|
* What the test asserts after the input envelope drives the host.
|
|
179
243
|
*
|
|
180
244
|
* Discriminator is extensibly closed via `(string & {})`. Each arm
|
|
181
245
|
* carries only the fields the assertion semantically requires — no
|
|
182
|
-
* optional-everything-bags.
|
|
183
|
-
* also expects a matching observability event) are authored as
|
|
184
|
-
* {@link TestCase.expectedBehavior} of one kind PLUS
|
|
185
|
-
* {@link TestCase.expectedObservability} alongside, not as nested
|
|
186
|
-
* unions.
|
|
246
|
+
* optional-everything-bags.
|
|
187
247
|
*/
|
|
188
|
-
export type ExpectedBehavior =
|
|
248
|
+
export type ExpectedBehavior = ActionAckBehavior | ErrorFrameBehavior | StreamUpdateBehavior | BootstrapFailureBehavior | BootstrapSuccessBehavior | VersionMismatchBehavior | PropsUpdateBehavior | SessionStateBehavior | NoOpBehavior | UnknownBehavior;
|
|
189
249
|
/**
|
|
190
|
-
* Expect
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
250
|
+
* Expect the action's `ack` frame to carry a numeric
|
|
251
|
+
* `payload.sequence` — the monotonic event sequence the server
|
|
252
|
+
* assigned when it appended the action to the GguiSession's consume
|
|
253
|
+
* buffer. The ack-carries-sequence contract is the wire-observable
|
|
254
|
+
* proof the event persisted; the retrieval half (the agent draining
|
|
255
|
+
* the buffer via `ggui_consume`) is an MCP tool call outside this WS
|
|
256
|
+
* kit's observation window.
|
|
194
257
|
*/
|
|
195
|
-
export interface
|
|
196
|
-
readonly kind: '
|
|
197
|
-
readonly code: ContractErrorCode;
|
|
198
|
-
readonly toolName: string;
|
|
199
|
-
readonly actionName?: string;
|
|
200
|
-
readonly sourceAction?: 'wired-action' | 'refresh-stream' | (string & {});
|
|
258
|
+
export interface ActionAckBehavior {
|
|
259
|
+
readonly kind: 'action-ack';
|
|
201
260
|
/**
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
261
|
+
* `requestId` the fixture's `inputEnvelope` stamps on the action
|
|
262
|
+
* frame. The matcher requires the ack to echo it — distinguishing
|
|
263
|
+
* the action's ack from the ack the runner's own subscribe frame
|
|
264
|
+
* produces.
|
|
206
265
|
*/
|
|
207
|
-
readonly
|
|
266
|
+
readonly requestId: string;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Expect an `error` frame whose `payload.code` matches. Generic
|
|
270
|
+
* error-frame vocabulary: `version-mismatch`'s `UPGRADE_REQUIRED`
|
|
271
|
+
* match is the same read narrowed to one code, and future rejection
|
|
272
|
+
* fixtures (`SESSION_NOT_FOUND`, `APP_MISMATCH`, …) author this arm
|
|
273
|
+
* with their expected code.
|
|
274
|
+
*/
|
|
275
|
+
export interface ErrorFrameBehavior {
|
|
276
|
+
readonly kind: 'error-frame';
|
|
277
|
+
/** Expected `payload.code` on the error frame. */
|
|
278
|
+
readonly code: string;
|
|
279
|
+
/** Optional `requestId` the error frame must echo — pins the error
|
|
280
|
+
* to the fixture's own dispatched frame rather than any error the
|
|
281
|
+
* observation window happens to catch. */
|
|
282
|
+
readonly requestId?: string;
|
|
208
283
|
}
|
|
209
|
-
/**
|
|
210
|
-
*
|
|
284
|
+
/**
|
|
285
|
+
* Expect a canonical channel-3 delivery frame (SPEC §12.2):
|
|
286
|
+
* `{type: 'data', payload: StreamEnvelope}` whose envelope names the
|
|
287
|
+
* declared `channel` and whose `payload.payload` body deep-equals the
|
|
288
|
+
* declared `value`. The `StreamEnvelope` shape is defined by
|
|
289
|
+
* `@ggui-ai/protocol` (`types/live-channel`). Frames of any other
|
|
290
|
+
* `type` never satisfy this expectation.
|
|
291
|
+
*/
|
|
211
292
|
export interface StreamUpdateBehavior {
|
|
212
293
|
readonly kind: 'stream-update';
|
|
213
294
|
readonly channel: string;
|
|
214
295
|
readonly value: unknown;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
296
|
+
/**
|
|
297
|
+
* How {@link value} is compared against the observed envelope's
|
|
298
|
+
* `payload` body.
|
|
299
|
+
*
|
|
300
|
+
* - `'exact'` (default) — deep-equal. The observed body MUST have
|
|
301
|
+
* exactly the declared keys, no more.
|
|
302
|
+
* - `'subset'` — every key declared in {@link value} MUST be
|
|
303
|
+
* present and matching in the observed body; extra observed keys
|
|
304
|
+
* are ignored. Subset semantics relax object key sets only —
|
|
305
|
+
* primitives and arrays still compare exact (an array with a
|
|
306
|
+
* missing element is a different array, not a subset).
|
|
307
|
+
*
|
|
308
|
+
* Use `'subset'` when the real payload carries non-deterministic
|
|
309
|
+
* fields the fixture cannot pin (generated ids, timestamps). Pinning
|
|
310
|
+
* only the deterministic keys keeps the assertion honest without
|
|
311
|
+
* wrongly rejecting a correct server over a random id.
|
|
312
|
+
*/
|
|
313
|
+
readonly valueMatch?: 'exact' | 'subset';
|
|
222
314
|
}
|
|
223
315
|
/**
|
|
224
316
|
* Expect a bootstrap-failure postMessage envelope + the renderer's
|
|
@@ -237,24 +329,51 @@ export interface BootstrapFailureBehavior {
|
|
|
237
329
|
}
|
|
238
330
|
/**
|
|
239
331
|
* Expect the renderer to reach the "ready to accept actions" state —
|
|
240
|
-
* no bootstrap failure, no version rejection, the
|
|
241
|
-
*
|
|
332
|
+
* no bootstrap failure, no version rejection, the render reaching
|
|
333
|
+
* `data-ggui-code-ready="true"` (or the host's
|
|
242
334
|
* equivalent signal).
|
|
243
335
|
*/
|
|
244
336
|
export interface BootstrapSuccessBehavior {
|
|
245
337
|
readonly kind: 'bootstrap-success';
|
|
246
|
-
/**
|
|
247
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Optional ack-field assertion — the server half of SPEC §12.2.2's
|
|
340
|
+
* version handshake. When authored, the subscribe `ack`'s
|
|
341
|
+
* `payload.serverVersion` MUST be present and equal the expected
|
|
342
|
+
* version; an ack that omits the field (legacy-pass-through) or
|
|
343
|
+
* advertises a different version FAILS the fixture.
|
|
344
|
+
*
|
|
345
|
+
* The only authored value is the `'current'` sentinel: the matcher
|
|
346
|
+
* resolves it to `PROTOCOL_SCHEMA_VERSION`, the canonical schema
|
|
347
|
+
* version this kit release was compiled against
|
|
348
|
+
* (`@ggui-ai/protocol`) — the same resolution
|
|
349
|
+
* {@link SubscribeFrameShaping.supportedVersions} uses, so the
|
|
350
|
+
* declared set and the asserted advertisement can never drift.
|
|
351
|
+
* Evergreen by construction; a fixture pinning a version literal
|
|
352
|
+
* would go stale at the next protocol bump. An explicit-literal arm
|
|
353
|
+
* can land additively if a fixture ever needs one.
|
|
354
|
+
*
|
|
355
|
+
* Absent → the assertion is ack presence alone (plus no error
|
|
356
|
+
* frame), the pre-handshake bootstrap claim.
|
|
357
|
+
*/
|
|
358
|
+
readonly serverVersion?: 'current';
|
|
248
359
|
}
|
|
249
360
|
/**
|
|
250
|
-
* Expect a version-handshake rejection
|
|
251
|
-
* `
|
|
252
|
-
*
|
|
361
|
+
* Expect a version-handshake rejection — an `error` frame with
|
|
362
|
+
* `payload.code === 'UPGRADE_REQUIRED'` (SPEC §12.2.2) after the
|
|
363
|
+
* runner's subscribe. The client-side declaration that PROVOKES the
|
|
364
|
+
* rejection is NOT authored here — it lives on the fixture's
|
|
365
|
+
* {@link SubscribeFrameShaping.supportedVersions} knob, the single
|
|
366
|
+
* place the runner sources its subscribe declaration from.
|
|
253
367
|
*/
|
|
254
368
|
export interface VersionMismatchBehavior {
|
|
255
369
|
readonly kind: 'version-mismatch';
|
|
370
|
+
/**
|
|
371
|
+
* Version the server is expected to be advertising when it rejects
|
|
372
|
+
* — for the shipped fixture, the value its `server-version-override`
|
|
373
|
+
* setup directive installed. Surfaced in failure diagnostics so a
|
|
374
|
+
* miss names the version the override should have advertised.
|
|
375
|
+
*/
|
|
256
376
|
readonly serverVersion: string;
|
|
257
|
-
readonly clientAccepts: readonly string[];
|
|
258
377
|
}
|
|
259
378
|
/**
|
|
260
379
|
* Expect a `props_update` round-trip — hosts deliver new props via
|
|
@@ -272,6 +391,45 @@ export interface PropsUpdateBehavior {
|
|
|
272
391
|
readonly expected: string;
|
|
273
392
|
};
|
|
274
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Expect a render-state mutation — the input message produced no wire
|
|
396
|
+
* response, but it MUST have changed observable GguiSession state.
|
|
397
|
+
*
|
|
398
|
+
* This is the kit's THIRD grading mechanism, alongside the Path-A WS
|
|
399
|
+
* fixtures (wire-observable) and the pure-function catalogs
|
|
400
|
+
* (deterministic validation): a *stateful* obligation. A
|
|
401
|
+
* Client→Server message like `host_context_observed` has no
|
|
402
|
+
* synchronous response envelope — its whole contract is the
|
|
403
|
+
* persistence onto the GguiSession. Asserting `no-op` (wire silence)
|
|
404
|
+
* would certify a server that drops the message entirely, so the
|
|
405
|
+
* honest grade reads the field back.
|
|
406
|
+
*
|
|
407
|
+
* The runner resolves this AFTER the observation window via
|
|
408
|
+
* {@link ConformanceHost.readSessionField} — it asks the host for the
|
|
409
|
+
* field's post-dispatch value and deep-equals it against
|
|
410
|
+
* {@link expected} (the same exact deep-equal the frame matchers
|
|
411
|
+
* use). A host that does not provide `readSessionField` makes the
|
|
412
|
+
* fixture SKIP (the kit cannot observe a mutation it has no
|
|
413
|
+
* introspection seam for) — never a silent pass. `matchBehavior`
|
|
414
|
+
* returns `unmatchable-on-ws` for this kind: frames cannot prove
|
|
415
|
+
* state.
|
|
416
|
+
*/
|
|
417
|
+
export interface SessionStateBehavior {
|
|
418
|
+
readonly kind: 'session-state';
|
|
419
|
+
/**
|
|
420
|
+
* The GguiSession field whose post-dispatch value the kit asserts
|
|
421
|
+
* (e.g. `hostContext`). Passed verbatim to
|
|
422
|
+
* {@link ConformanceHost.readSessionField}; the host owns the
|
|
423
|
+
* field-name recognition set.
|
|
424
|
+
*/
|
|
425
|
+
readonly field: string;
|
|
426
|
+
/**
|
|
427
|
+
* The value the field MUST hold after the input message has been
|
|
428
|
+
* processed by the implementation under test. Compared with exact
|
|
429
|
+
* deep equality.
|
|
430
|
+
*/
|
|
431
|
+
readonly expected: unknown;
|
|
432
|
+
}
|
|
275
433
|
/**
|
|
276
434
|
* No observable side-effect expected. Used for inputs the protocol
|
|
277
435
|
* intentionally drops (malformed envelopes, unrecognized JSON-RPC
|
|
@@ -289,16 +447,45 @@ export interface UnknownBehavior {
|
|
|
289
447
|
readonly [field: string]: unknown;
|
|
290
448
|
}
|
|
291
449
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
450
|
+
* Optional shaping of the RUNNER-OWNED subscribe frame.
|
|
451
|
+
*
|
|
452
|
+
* The subscribe frame is not part of a fixture's `inputEnvelope` — the
|
|
453
|
+
* runner always sends it first, with its conventional payload
|
|
454
|
+
* (`{sessionId, appId: 'conformance', role: 'user'}`). Some contracts
|
|
455
|
+
* are only drivable by varying that frame itself; this object is the
|
|
456
|
+
* fixture's declarative knob for those variations. Additive
|
|
457
|
+
* vocabulary: new shaping fields land as optional members, mirroring
|
|
458
|
+
* the kit's authored-shape evolution discipline.
|
|
296
459
|
*/
|
|
297
|
-
export interface
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
460
|
+
export interface SubscribeFrameShaping {
|
|
461
|
+
/**
|
|
462
|
+
* When `true`, the runner OMITS `appId` from its subscribe payload.
|
|
463
|
+
* Drives SPEC §12.2's identity-default resolution path: `appId` is
|
|
464
|
+
* optional on the wire, and absence means the server resolves the
|
|
465
|
+
* caller's identity-default app (token binding, identity mapping,
|
|
466
|
+
* or deployment default). Default `false` — the runner stamps its
|
|
467
|
+
* conventional `appId: 'conformance'`.
|
|
468
|
+
*/
|
|
469
|
+
readonly omitAppId?: boolean;
|
|
470
|
+
/**
|
|
471
|
+
* Protocol schema versions the runner DECLARES on its subscribe
|
|
472
|
+
* payload (`SubscribePayload.supportedVersions`) — the client half
|
|
473
|
+
* of SPEC §12.2.2's opt-in version handshake.
|
|
474
|
+
*
|
|
475
|
+
* - Absent → the runner declares nothing; the subscribe is
|
|
476
|
+
* version-agnostic (legacy-pass-through), exactly as before the
|
|
477
|
+
* handshake existed.
|
|
478
|
+
* - `'current'` (sentinel) → the runner resolves the declaration
|
|
479
|
+
* to `[PROTOCOL_SCHEMA_VERSION]`, the canonical schema version
|
|
480
|
+
* this kit release was compiled against (`@ggui-ai/protocol`).
|
|
481
|
+
* The sentinel keeps fixtures evergreen across protocol version
|
|
482
|
+
* bumps — no fixture pins a version literal that goes stale.
|
|
483
|
+
* - Explicit `string[]` → declared on the wire verbatim.
|
|
484
|
+
*
|
|
485
|
+
* The runner's validating parse rejects any other shape as a
|
|
486
|
+
* fixture-authoring error (closed vocabulary, like {@link SetupStep}).
|
|
487
|
+
*/
|
|
488
|
+
readonly supportedVersions?: 'current' | readonly string[];
|
|
302
489
|
}
|
|
303
490
|
/**
|
|
304
491
|
* One conformance-kit fixture. Authored as JSON under `./fixtures/**`,
|
|
@@ -324,14 +511,20 @@ export interface TestCase {
|
|
|
324
511
|
* be empty. See {@link SetupStep}.
|
|
325
512
|
*/
|
|
326
513
|
readonly setup: readonly SetupStep[];
|
|
514
|
+
/**
|
|
515
|
+
* Optional shaping of the runner-owned subscribe frame — see
|
|
516
|
+
* {@link SubscribeFrameShaping}. Absent means the runner's
|
|
517
|
+
* conventional subscribe payload is sent unmodified.
|
|
518
|
+
*/
|
|
519
|
+
readonly subscribe?: SubscribeFrameShaping;
|
|
327
520
|
/**
|
|
328
521
|
* The envelope the runner feeds to the system under test. Opaque
|
|
329
522
|
* shape — the runner passes it verbatim to the host's transport.
|
|
330
523
|
*
|
|
331
|
-
* Typically
|
|
332
|
-
* `{type: 'action',
|
|
333
|
-
*
|
|
334
|
-
* `{type: 'render',
|
|
524
|
+
* Typically a live-channel action message like
|
|
525
|
+
* `{type: 'action', requestId, payload: {sessionId, type:
|
|
526
|
+
* 'data:submit', payload: {action, data}}}` or
|
|
527
|
+
* `{type: 'render', sessionId: 'test-r1', resource: {…}}` for
|
|
335
528
|
* bootstrap-path fixtures.
|
|
336
529
|
*
|
|
337
530
|
* `unknown` rather than a typed union because the kit may drive
|
|
@@ -342,41 +535,19 @@ export interface TestCase {
|
|
|
342
535
|
/** What the runner asserts after the envelope is dispatched. */
|
|
343
536
|
readonly expectedBehavior: ExpectedBehavior;
|
|
344
537
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
* this field SHOULD be omitted — the nested one is canonical.
|
|
349
|
-
*/
|
|
350
|
-
readonly expectedObservability?: readonly ExpectedObservabilityEvent[];
|
|
351
|
-
/**
|
|
352
|
-
* Cleanup directives the host executes AFTER the assertion. Optional —
|
|
353
|
-
* most fixtures need no teardown post the `close-render` drop (renders
|
|
354
|
-
* decay via TTL); `unregister-tool` is the only remaining kind, used
|
|
355
|
-
* by fixtures that mutate the tool registry.
|
|
538
|
+
* Cleanup directives the host executes AFTER the assertion.
|
|
539
|
+
* Optional — no current fixture needs teardown (renders decay via
|
|
540
|
+
* TTL). The slot stays so future cleanup directives land additively.
|
|
356
541
|
*/
|
|
357
542
|
readonly teardown?: readonly TeardownStep[];
|
|
358
543
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Canonical contract-error codes. Mirrors `@ggui-ai/protocol`'s
|
|
361
|
-
* `ContractErrorCode` at the time this kit version was authored. The
|
|
362
|
-
* `(string & {})` tail lets fixtures reference codes introduced by
|
|
363
|
-
* later protocol revisions without a kit bump — the runner's
|
|
364
|
-
* recognition set is its own concern.
|
|
365
|
-
*
|
|
366
|
-
* Drift discipline: the kit ships a meta-test that walks
|
|
367
|
-
* `@ggui-ai/protocol`'s `ContractErrorCode` union against this
|
|
368
|
-
* declaration and warns when a new canonical member appears. The
|
|
369
|
-
* runner's behaviour on unknown codes (skip with warning) is
|
|
370
|
-
* additive-safe: adding a code here is a kit minor; removing one is
|
|
371
|
-
* a major.
|
|
372
|
-
*/
|
|
373
|
-
export type ContractErrorCode = 'TOOL_NOT_FOUND' | 'TOOL_THREW' | 'TOOL_TIMEOUT' | 'SCHEMA_VIOLATION' | 'SCHEMA_MISMATCH_ERROR' | 'SESSION_NOT_FOUND' | 'AUTH_REJECTED' | (string & {});
|
|
374
544
|
/**
|
|
375
545
|
* Boot-path failure reasons. Mirrors `@ggui-ai/iframe-runtime`'s
|
|
376
546
|
* `BootstrapFailureReason` — combines parse-time, post-parse
|
|
377
547
|
* orchestration, and pre-WS transport-observable failures. Authored
|
|
378
|
-
* copy
|
|
379
|
-
*
|
|
548
|
+
* copy per the drift-decoupling posture in the module docstring:
|
|
549
|
+
* fixtures compile against the kit's frozen vocabulary, never the
|
|
550
|
+
* live protocol or renderer types.
|
|
380
551
|
*/
|
|
381
552
|
export type BootstrapFailureReason = 'MISSING_TOOL_OUTPUT' | 'MISSING_META_GGUI_BOOTSTRAP' | 'BOOTSTRAP_META_MISSING' | 'MALFORMED_BOOTSTRAP' | 'EXPIRED_BOOTSTRAP' | 'UI_INITIALIZE_FAILED' | 'WS_HANDSHAKE_FAILED' | 'UPGRADE_REQUIRED' | 'BUNDLE_FETCH_FAILED' | 'CSP_VIOLATION' | 'SESSION_NOT_FOUND' | 'AUTH_REJECTED' | (string & {});
|
|
382
553
|
/**
|
|
@@ -395,30 +566,13 @@ export type ProtocolError = {
|
|
|
395
566
|
readonly message?: string;
|
|
396
567
|
} | {
|
|
397
568
|
readonly kind: 'auth';
|
|
398
|
-
readonly code: 'SESSION_NOT_FOUND' | '
|
|
569
|
+
readonly code: 'SESSION_NOT_FOUND' | 'BOOTSTRAP_EXPIRED' | 'BOOTSTRAP_INVALID' | 'BOOTSTRAP_SESSION_MISMATCH' | 'BOOTSTRAP_APP_MISMATCH' | 'UNAUTHENTICATED';
|
|
399
570
|
readonly message?: string;
|
|
400
571
|
} | {
|
|
401
572
|
readonly kind: 'protocol';
|
|
402
573
|
readonly code: 'SESSION_MISMATCH' | 'APP_MISMATCH' | 'MALFORMED_ENVELOPE' | (string & {});
|
|
403
574
|
readonly message?: string;
|
|
404
575
|
readonly details?: unknown;
|
|
405
|
-
} | {
|
|
406
|
-
readonly kind: 'contract';
|
|
407
|
-
readonly payload: {
|
|
408
|
-
readonly toolName: string;
|
|
409
|
-
readonly actionName?: string;
|
|
410
|
-
readonly sourceAction?: {
|
|
411
|
-
readonly type: 'wired-action' | 'refresh-stream' | (string & {});
|
|
412
|
-
readonly dispatchedAt: string;
|
|
413
|
-
};
|
|
414
|
-
readonly error: {
|
|
415
|
-
readonly code: ContractErrorCode;
|
|
416
|
-
readonly message: string;
|
|
417
|
-
readonly causedBy?: string;
|
|
418
|
-
};
|
|
419
|
-
readonly timestamp: string;
|
|
420
|
-
readonly schemaVersion?: string;
|
|
421
|
-
};
|
|
422
576
|
} | {
|
|
423
577
|
readonly kind: 'bootstrap';
|
|
424
578
|
readonly reason: BootstrapFailureReason;
|