@ggui-ai/protocol-conformance 0.14.0 → 0.15.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 CHANGED
@@ -51,7 +51,7 @@ One declared grading gap on the action loop: `action-ack` proves the append half
51
51
 
52
52
  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.
53
53
 
54
- 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. Eight catalogs ship today:
54
+ 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. Nine catalogs ship today:
55
55
 
56
56
  - **`@ggui-ai/protocol-conformance/schema-conformance`** — which `DataContract.clientCapabilities` payloads a conformant parser MUST accept / reject (the gadget wire shape).
57
57
  - **`@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`).
@@ -61,6 +61,7 @@ Each catalog ships its cases as raw JSON so a non-TypeScript implementer can gra
61
61
  - **`@ggui-ai/protocol-conformance/host-helper-conformance`** — which render coordinates a HOST-HELPER library MUST extract from a `tools/call` result, and which malformed results it MUST decline rather than guess at.
62
62
  - **`@ggui-ai/protocol-conformance/refusal-envelope-conformance`** — the tool result a server MUST project for a PRE-GENERATION refusal (SPEC §7.1's refused arm): an in-result `isError` (never a throw), `content[0].text` leading with the code, `structuredContent` carrying `{outcome: 'refused', refusal}` and nothing else, no `_meta` — and no envelope at all for a registered code whose surfaces exclude the render gate.
63
63
  - **`@ggui-ai/protocol-conformance/registry-completeness`** — the structural obligations a deployment's closed refusal-code registry MUST satisfy: `surfaces` non-empty, `code === key`, every `after-fix` entry naming who acts (`fixBy`), and `retry` inside the closed four-value set. Grades DATA rather than a function — the caller passes their registry.
64
+ - **`@ggui-ai/protocol-conformance/transport-refusal-conformance`** — the JSON-RPC error a per-app MCP endpoint MUST answer with when it refuses a request for a typed reason (SPEC §7.1's endpoint-level refusal, ggui#825): HTTP 403 with `{ code: -32003, message: 'App not found', data: { refusal } }`, where `refusal` is the registry projection without the render-only fields (`code`, `message`, `fix`, `retry`) — and no envelope at all for a registered code whose surfaces exclude `mcp-endpoint` (an untyped authorization failure answers 403 with `-32001` and no `data`, by contract — ggui#836). Runner: `runTransportRefusalConformance(project)`; folded into `runConformance()` via `transportRefusalProjector`.
64
65
 
65
66
  ```ts
66
67
  import { runSchemaConformance } from "@ggui-ai/protocol-conformance/schema-conformance";
@@ -119,20 +120,21 @@ It deliberately does **not** grade: the order in which a substrate-less server a
119
120
 
120
121
  What is actually graded against shipping code today, and what is still awaiting a driver. This is an inventory, not a roadmap — a mechanism is listed as proven only if a test in the repo drives it against the real implementation on every push.
121
122
 
122
- Every test below is a plain `*.test.ts` inside a package's `src/`, so it runs in that package's ordinary vitest suite. The ggui repo's per-push CI runs `turbo run test` across all non-e2e packages, so **all ten rows below are gated on every push** — the kit needs no dedicated workflow, and adopters wiring it into their own CI need nothing more than their existing unit-test task.
123
-
124
- | Obligation | Graded against | Driver |
125
- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
126
- | Gadget wire schema (§7.7.2) | the shipping `@ggui-ai/protocol` `clientCapabilitiesSpecSchema` | this kit's own `src/schema-conformance/schema-conformance.test.ts` |
127
- | Registration gate (§7.7.3, §7.9) | the shipping gate trio in `@ggui-ai/mcp-server-handlers` | `mcp-server-handlers/src/renders/assert-gadgets.conformance.test.ts` |
128
- | Bundle-URL resolution (§7.7.2) | the shipping `resolveGadgetUrls` in `@ggui-ai/mcp-server-handlers` | `mcp-server-handlers/src/renders/resolve-gadget-urls.conformance.test.ts` |
129
- | Path-A WS fixtures — first-party | the real `@ggui-ai/mcp-server` GguiSession channel, booted in-process on an ephemeral port | `mcp-server/src/ggui-session-channel.conformance.test.ts` — **8 pass / 4 skip / 0 fail** |
130
- | Path-A WS fixtures — vendor-neutral | `@ggui-ai/protocol-reference-server` | `protocol-reference-server/src/conformance.test.ts` — **9 pass / 3 skip / 0 fail** |
131
- | `resources/read` typed failures + mount invariant | the shipping `registerGguiRenderResourceTemplate` in `@ggui-ai/mcp-server` | `mcp-server/src/mcp-apps-outbound.resource-read.conformance.test.ts` — **12 pass / 0 skip / 0 fail** |
132
- | Tool-binding resolution — declared wins, contract derivation (§7.7.4.1) | the shipping `resolveMcpToolBindings` in `@ggui-ai/artifact-manifest` | `artifact-manifest/src/mcp-tool-bindings.conformance.test.ts` |
133
- | Tool-binding search filters — `tool=` / `server=` semantics (§7.7.4.1) | the shipping `matchesMcpToolFilters` in `@ggui-ai/registry-core` | `registry-core/src/mcp-tool-filters.conformance.test.ts` |
134
- | Pre-generation refusal envelope (§7.1, refused arm) | the shipping `ggui_render` refusal projection in `@ggui-ai/mcp-server-handlers` | `mcp-server-handlers/src/renders/render-refusal-projection.conformance.test.ts` |
135
- | Refusal-registry completeness (ggui#786 ruling 5b) | the shipping `PRE_GENERATION_REFUSAL_CODES` in `@ggui-ai/protocol` | this kit's own `src/registry-completeness/registry-completeness.test.ts` |
123
+ Every test below is a plain `*.test.ts` inside a package's `src/`, so it runs in that package's ordinary vitest suite. The ggui repo's per-push CI runs `turbo run test` across all non-e2e packages, so **all twelve rows below are gated on every push** — the kit needs no dedicated workflow, and adopters wiring it into their own CI need nothing more than their existing unit-test task.
124
+
125
+ | Obligation | Graded against | Driver |
126
+ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
127
+ | Gadget wire schema (§7.7.2) | the shipping `@ggui-ai/protocol` `clientCapabilitiesSpecSchema` | this kit's own `src/schema-conformance/schema-conformance.test.ts` |
128
+ | Registration gate (§7.7.3, §7.9) | the shipping gate trio in `@ggui-ai/mcp-server-handlers` | `mcp-server-handlers/src/renders/assert-gadgets.conformance.test.ts` |
129
+ | Bundle-URL resolution (§7.7.2) | the shipping `resolveGadgetUrls` in `@ggui-ai/mcp-server-handlers` | `mcp-server-handlers/src/renders/resolve-gadget-urls.conformance.test.ts` |
130
+ | Path-A WS fixtures — first-party | the real `@ggui-ai/mcp-server` GguiSession channel, booted in-process on an ephemeral port | `mcp-server/src/ggui-session-channel.conformance.test.ts` — **24 pass / 12 skip / 0 fail** |
131
+ | Path-A WS fixtures — vendor-neutral | `@ggui-ai/protocol-reference-server` | `protocol-reference-server/src/conformance.test.ts` — **19 pass / 5 skip / 0 fail** |
132
+ | `resources/read` typed failures + mount invariant | the shipping `registerGguiRenderResourceTemplate` in `@ggui-ai/mcp-server` | `mcp-server/src/mcp-apps-outbound.resource-read.conformance.test.ts` — **12 pass / 0 skip / 0 fail** |
133
+ | Tool-binding resolution — declared wins, contract derivation (§7.7.4.1) | the shipping `resolveMcpToolBindings` in `@ggui-ai/artifact-manifest` | `artifact-manifest/src/mcp-tool-bindings.conformance.test.ts` |
134
+ | Tool-binding search filters — `tool=` / `server=` semantics (§7.7.4.1) | the shipping `matchesMcpToolFilters` in `@ggui-ai/registry-core` | `registry-core/src/mcp-tool-filters.conformance.test.ts` |
135
+ | Pre-generation refusal envelope (§7.1, refused arm) | `projectRenderRefusal` in `@ggui-ai/protocol` — the one source both the shipping `ggui_render` handler (`@ggui-ai/mcp-server-handlers`) and `@ggui-ai/protocol-reference-server` answer through | `mcp-server-handlers/src/renders/render-refusal-projection.conformance.test.ts` |
136
+ | Refusal-registry completeness (ggui#786 ruling 5b) | the shipping `PRE_GENERATION_REFUSAL_CODES` in `@ggui-ai/protocol` | this kit's own `src/registry-completeness/registry-completeness.test.ts` |
137
+ | Endpoint-level refusal (§7.1, `mcp-endpoint` surface, ggui#825) | the catalog's kit-local reference projector only — no shipping deployment projector is graded yet: the OSS route is generic (`errorMapper`), the first-party server and the reference server supply no projector (SKIPPED, named), and the pod's mapper lands after this slice | this kit's own `src/transport-refusal-conformance/transport-refusal-conformance.test.ts` (coherence + discrimination) |
136
138
 
137
139
  Both WS drivers pin their pass set _and_ their skip set as exact sets, and the resource-read driver pins its skip set as exactly empty. A fixture silently degrading to a skip fails the build, and so does a skipped fixture that quietly starts passing — a skip set that can grow unnoticed is a false gate, and re-pinning it is meant to be a deliberate act.
138
140
 
@@ -170,6 +172,13 @@ npx @ggui-ai/protocol-conformance --url ws://localhost:3000/ws --auth bearer:$TO
170
172
 
171
173
  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).
172
174
 
175
+ The three pure-function catalogs (`refusal-envelope`, `registry-completeness`, `transport-refusal`) grade an in-process function and a data table, not the wire, so a URL alone cannot reach them. The CLI hands them their inputs by flag — `--registry <file.json>` (the deployment's refusal registry, a JSON object keyed by code), `--projector <module>` (an ES module whose `project(refusal)` — named or default export — returns the SPEC §7.1 tool result, or `null` for a code with no render envelope) and `--transport-projector <module>` (same shape, returning the endpoint's `{ httpStatus, error }` or `null`). A flag left out leaves its catalog on the scorecard as SKIPPED with the flag named. Catalog rows count as executed fixtures, so a run given a catalog flag can exit 0 while every wire fixture is SKIPPED — read the scorecard for the wire signal. Minimal spec-correct samples of each input live in the repository at [`src/cli-samples/`](https://github.com/ggui-ai/ggui/tree/main/packages/protocol-conformance/src/cli-samples) (not in the npm tarball); the test suite pins that they pass their catalogs.
176
+
177
+ ```
178
+ npx @ggui-ai/protocol-conformance --url ws://localhost:3000/ws --auth bearer:$TOKEN \
179
+ --registry ./refusal-registry.json --projector ./project.mjs --transport-projector ./endpoint.mjs
180
+ ```
181
+
173
182
  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.
174
183
 
175
184
  ## License
package/dist/cli.d.ts CHANGED
@@ -1,16 +1,38 @@
1
1
  #!/usr/bin/env node
2
- import { type ConformanceResult } from './run-conformance.js';
2
+ import { type ConformanceResult, type RunConformanceConfig } from './run-conformance.js';
3
3
  import type { AuthConfig } from './types.js';
4
4
  interface ParsedArgs {
5
5
  readonly url?: string;
6
6
  readonly auth?: string;
7
7
  readonly only?: readonly string[];
8
8
  readonly timeoutMs?: number;
9
+ /** Path to a JSON file holding the deployment's refusal registry (code → row). */
10
+ readonly registry?: string;
11
+ /** Path to an ES module exporting the SPEC §7.1 refusal projector. */
12
+ readonly projector?: string;
13
+ /** Path to an ES module exporting the endpoint-level refusal projector. */
14
+ readonly transportProjector?: string;
9
15
  readonly verbose: boolean;
10
16
  readonly help: boolean;
11
17
  }
12
18
  declare function parseArgs(argv: readonly string[]): ParsedArgs;
13
19
  declare function parseAuth(spec: string): AuthConfig;
20
+ /** The three optional inputs a CLI run can hand the pure-function catalogs. */
21
+ export type CatalogInputs = Pick<RunConformanceConfig, 'refusalRegistry' | 'refusalProjector' | 'transportRefusalProjector'>;
22
+ /** The flag values that name where each catalog input comes from. */
23
+ export interface CatalogInputPaths {
24
+ readonly registry?: string | undefined;
25
+ readonly projector?: string | undefined;
26
+ readonly transportProjector?: string | undefined;
27
+ }
28
+ /**
29
+ * Resolve the `--registry` / `--projector` / `--transport-projector`
30
+ * flags into the runner's catalog inputs. A flag that is absent yields
31
+ * no input, and the runner reports that catalog SKIPPED with the flag
32
+ * named. Every failure names the flag and the path.
33
+ */
34
+ export declare function loadCatalogInputs(paths: CatalogInputPaths): Promise<CatalogInputs>;
35
+ export declare const USAGE: string;
14
36
  declare function main(argv: readonly string[]): Promise<number>;
15
37
  /**
16
38
  * Map a {@link ConformanceResult} to the CLI exit code:
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
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;AA6CD,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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAsDA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,sBAAsB,CAAC;AAK9B,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,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,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,CA0DtD;AAOD,iBAAS,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAY3C;AAMD,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,oBAAoB,EACpB,iBAAiB,GAAG,kBAAkB,GAAG,2BAA2B,CACrE,CAAC;AAEF,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClD;AA4ID;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC,CA6BxF;AAMD,eAAO,MAAM,KAAK,QAyDL,CAAC;AAEd,iBAAe,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoE5D;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
@@ -16,25 +16,42 @@
16
16
  * may legitimately not provide every `ConformanceHost` directive in
17
17
  * v1.0 — but a run that is ALL skips carries no signal.
18
18
  *
19
+ * The three PURE-FUNCTION catalogs (refusal-envelope,
20
+ * registry-completeness, transport-refusal) grade an in-process
21
+ * function and a data table, not the wire: `--registry <file.json>`,
22
+ * `--projector <module>` and `--transport-projector <module>` hand
23
+ * them their inputs (ggui#803 leg 3). A flag left out leaves its
24
+ * catalog SKIPPED with the flag named, never silently absent. Catalog
25
+ * rows count as executed fixtures, so a run given a catalog flag can
26
+ * exit 0 while every wire fixture is SKIPPED — read the scorecard,
27
+ * not only the exit code, for a wire signal.
28
+ *
19
29
  * Honest scope of the bundled CLI: no `host` wiring. The CLI runs
20
30
  * the runner with an absent host, meaning every fixture with non-empty
21
31
  * `setup` skips cleanly with the "no host provided" reason — and
22
32
  * 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.
33
+ * hostless CLI run with no catalog flag skips them all and exits 2 by
34
+ * design. To get real pass/fail signal against an implementation,
35
+ * consumers call `runConformance({ host: myImpl })` programmatically
36
+ * and apply the same guard:
37
+ * `result.failed.length > 0 || result.passed.length === 0` is a red
38
+ * build. The CLI remains the lowest-friction wire-level smoke check
39
+ * for setups where the server under test is provisioned out-of-band
40
+ * rather than through a `ConformanceHost` injection.
30
41
  */
42
+ import { readFile } from 'node:fs/promises';
43
+ import { resolve } from 'node:path';
44
+ import { pathToFileURL } from 'node:url';
31
45
  import { createDefaultReporter, formatFailures, formatSkips } from './reporter.js';
32
- import { runConformance } from './run-conformance.js';
46
+ import { runConformance, } from './run-conformance.js';
33
47
  function parseArgs(argv) {
34
48
  let url;
35
49
  let auth;
36
50
  const only = [];
37
51
  let timeoutMs;
52
+ let registry;
53
+ let projector;
54
+ let transportProjector;
38
55
  let verbose = false;
39
56
  let help = false;
40
57
  for (let i = 0; i < argv.length; i += 1) {
@@ -62,6 +79,15 @@ function parseArgs(argv) {
62
79
  if (Number.isNaN(timeoutMs))
63
80
  throw new Error('--timeout-ms must be an integer');
64
81
  break;
82
+ case '--registry':
83
+ registry = next();
84
+ break;
85
+ case '--projector':
86
+ projector = next();
87
+ break;
88
+ case '--transport-projector':
89
+ transportProjector = next();
90
+ break;
65
91
  case '--verbose':
66
92
  case '-v':
67
93
  verbose = true;
@@ -74,7 +100,11 @@ function parseArgs(argv) {
74
100
  throw new Error(`unknown flag '${flag}' — run with --help for usage`);
75
101
  }
76
102
  }
77
- return { url, auth, only, timeoutMs, verbose, help };
103
+ return { url, auth, only, timeoutMs, registry, projector, transportProjector, verbose, help };
104
+ }
105
+ /** The message of a thrown value, without pretending every throw is an Error. */
106
+ function errorMessage(err) {
107
+ return err instanceof Error ? err.message : String(err);
78
108
  }
79
109
  function parseAuth(spec) {
80
110
  const idx = spec.indexOf(':');
@@ -89,10 +119,150 @@ function parseAuth(spec) {
89
119
  return { kind: 'session-cookie', cookie: value };
90
120
  throw new Error(`--auth kind '${kind}' is not recognized; supported: 'bearer', 'session-cookie'`);
91
121
  }
122
+ function isRecord(value) {
123
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
124
+ }
125
+ function isStringArray(value) {
126
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
127
+ }
128
+ /**
129
+ * The runtime twin of {@link RefusalRegistryRow} — a JSON registry is
130
+ * held to exactly the shape a programmatic caller has to satisfy at
131
+ * compile time. Value-level rules (recognized retry class, `fixBy` on
132
+ * `after-fix`, code equals key, non-empty surfaces) are NOT checked
133
+ * here: those are the registry-completeness catalog's pins, and a
134
+ * deliberately broken registry must stay expressible so they can
135
+ * grade it.
136
+ */
137
+ function isRegistryRow(value) {
138
+ return (isRecord(value) &&
139
+ typeof value.code === 'string' &&
140
+ isStringArray(value.surfaces) &&
141
+ typeof value.retry === 'string' &&
142
+ (value.fixBy === undefined || typeof value.fixBy === 'string') &&
143
+ typeof value.emitter === 'string' &&
144
+ typeof value.description === 'string');
145
+ }
146
+ function parseRegistryView(text, path) {
147
+ let parsed;
148
+ try {
149
+ parsed = JSON.parse(text);
150
+ }
151
+ catch (err) {
152
+ throw new Error(`--registry ${path} is not JSON: ${errorMessage(err)}`);
153
+ }
154
+ if (!isRecord(parsed)) {
155
+ throw new Error(`--registry ${path} must be a JSON object of rows keyed by code`);
156
+ }
157
+ const rows = [];
158
+ for (const [code, row] of Object.entries(parsed)) {
159
+ if (!isRegistryRow(row)) {
160
+ throw new Error(`--registry ${path}: row '${code}' is not a registry row (needs string code, string[] surfaces, string retry, string emitter, string description, optional string fixBy)`);
161
+ }
162
+ rows.push([code, row]);
163
+ }
164
+ // fromEntries defines OWN properties — a row keyed `__proto__` stays a row.
165
+ return Object.fromEntries(rows);
166
+ }
167
+ function isRefusalInput(value) {
168
+ return (isRecord(value) &&
169
+ typeof value.code === 'string' &&
170
+ typeof value.message === 'string' &&
171
+ typeof value.fix === 'string' &&
172
+ typeof value.retry === 'string' &&
173
+ typeof value.handshake === 'string' &&
174
+ (value.balanceCentsAtCheck === undefined || typeof value.balanceCentsAtCheck === 'number'));
175
+ }
176
+ function isProjectedRefusalResult(value) {
177
+ return (isRecord(value) &&
178
+ typeof value.isError === 'boolean' &&
179
+ typeof value.text === 'string' &&
180
+ isRecord(value.structuredContent) &&
181
+ typeof value.structuredContent.outcome === 'string' &&
182
+ isRefusalInput(value.structuredContent.refusal) &&
183
+ typeof value.hasMeta === 'boolean' &&
184
+ isStringArray(value.identityFields));
185
+ }
186
+ function isTransportRefusalInput(value) {
187
+ return (isRecord(value) &&
188
+ typeof value.code === 'string' &&
189
+ typeof value.message === 'string' &&
190
+ typeof value.fix === 'string' &&
191
+ typeof value.retry === 'string');
192
+ }
193
+ function isProjectedTransportRefusal(value) {
194
+ return (isRecord(value) &&
195
+ typeof value.httpStatus === 'number' &&
196
+ isRecord(value.error) &&
197
+ typeof value.error.code === 'number' &&
198
+ typeof value.error.message === 'string' &&
199
+ isRecord(value.error.data) &&
200
+ isTransportRefusalInput(value.error.data.refusal));
201
+ }
202
+ /**
203
+ * Import an ES module at `path` (resolved against the working
204
+ * directory) and return the projector it exports —
205
+ * `export function project(...)` or the default export. The module is
206
+ * an external boundary: its export is checked to be a function here,
207
+ * and every value it returns is checked against the projection shape
208
+ * by `guard` before the catalog sees it, so a module that returns the
209
+ * wrong shape fails by flag name instead of typing its way into a
210
+ * grade. The catalogs grade that throw as a FAIL on the case in hand
211
+ * and keep grading the rest — never a crash of the run.
212
+ */
213
+ async function loadProjector(flag, path, guard) {
214
+ const href = pathToFileURL(resolve(path)).href;
215
+ let mod;
216
+ try {
217
+ mod = await import(/* @vite-ignore */ href);
218
+ }
219
+ catch (err) {
220
+ throw new Error(`${flag} ${path} could not be imported: ${errorMessage(err)}`);
221
+ }
222
+ const candidate = mod.project ?? mod.default;
223
+ if (typeof candidate !== 'function') {
224
+ throw new Error(`${flag} ${path} must export \`project\` (or a default export) as a function`);
225
+ }
226
+ return (input) => {
227
+ const out = candidate(input);
228
+ if (out === null)
229
+ return null;
230
+ if (!guard(out)) {
231
+ throw new Error(`${flag} ${path} returned a value that is not a projection: ${JSON.stringify(out)}`);
232
+ }
233
+ return out;
234
+ };
235
+ }
236
+ /**
237
+ * Resolve the `--registry` / `--projector` / `--transport-projector`
238
+ * flags into the runner's catalog inputs. A flag that is absent yields
239
+ * no input, and the runner reports that catalog SKIPPED with the flag
240
+ * named. Every failure names the flag and the path.
241
+ */
242
+ export async function loadCatalogInputs(paths) {
243
+ const inputs = {};
244
+ if (paths.registry !== undefined) {
245
+ let text;
246
+ try {
247
+ text = await readFile(resolve(paths.registry), 'utf8');
248
+ }
249
+ catch (err) {
250
+ throw new Error(`--registry ${paths.registry} could not be read: ${errorMessage(err)}`);
251
+ }
252
+ inputs.refusalRegistry = parseRegistryView(text, paths.registry);
253
+ }
254
+ if (paths.projector !== undefined) {
255
+ inputs.refusalProjector = await loadProjector('--projector', paths.projector, isProjectedRefusalResult);
256
+ }
257
+ if (paths.transportProjector !== undefined) {
258
+ inputs.transportRefusalProjector = await loadProjector('--transport-projector', paths.transportProjector, isProjectedTransportRefusal);
259
+ }
260
+ return inputs;
261
+ }
92
262
  // =============================================================================
93
263
  // Entry point
94
264
  // =============================================================================
95
- const USAGE = `
265
+ export const USAGE = `
96
266
  ggui-protocol-conformance — run the protocol conformance kit against a live server
97
267
 
98
268
  Usage:
@@ -113,22 +283,42 @@ Options:
113
283
  filters the pure-function catalog rows, whose
114
284
  names are prefixed <catalog>/<case>.
115
285
  --timeout-ms <N> Per-fixture observation window. Default 2000.
286
+ --registry <file.json> The deployment's refusal-code registry, a JSON
287
+ object keyed by code (grades registry-completeness).
288
+ --projector <module> ES module whose \`project(refusal)\` — a named or
289
+ default export — returns the SPEC §7.1 tool result
290
+ for a refusal, or null when the code has no
291
+ envelope on the render-gate surface (grades
292
+ refusal-envelope).
293
+ --transport-projector <module>
294
+ ES module whose \`project(refusal)\` — named or
295
+ default export — returns the endpoint's
296
+ { httpStatus, error } for a refusal, or null when
297
+ the code has no transport envelope (grades
298
+ transport-refusal).
116
299
  --verbose, -v Print failure details + skip reasons at the end.
117
300
  --help, -h Show this help.
118
301
 
119
- Pure-function catalogs (refusal-envelope, registry-completeness):
302
+ Pure-function catalogs (refusal-envelope, registry-completeness, transport-refusal):
120
303
  These grade an in-process function and a data table, not the wire, so
121
- the CLI cannot supply them over a URL. They appear on every scorecard
122
- as SKIPPED with the reason — an ungraded obligation stays visible
123
- rather than vanishing. To GRADE them, call runConformance()
124
- programmatically with refusalProjector / refusalRegistry.
304
+ a URL alone cannot reach them: the three flags above hand them their
305
+ inputs. A flag left out leaves its catalog on the scorecard as SKIPPED
306
+ with the flag named — an ungraded obligation stays visible rather
307
+ than vanishing. File and module paths resolve against the working
308
+ directory. A projector must be synchronous; one that throws, returns
309
+ undefined, or returns a non-projection is graded as a FAIL on that
310
+ case (exit 1) and the remaining cases are still graded — never a
311
+ crash. Catalog rows count as executed fixtures: a run given a
312
+ catalog flag can exit 0 while every wire fixture is SKIPPED, so read
313
+ the scorecard, not only the exit code, for a wire signal.
125
314
 
126
315
  Exit codes:
127
316
  0 — at least one fixture executed and none failed.
128
317
  1 — at least one fixture failed.
129
- 2 — invocation error (bad args, cannot reach server, etc.), or the
130
- run executed zero fixtures (every fixture skipped) — a run that
131
- proved nothing never reads as success.
318
+ 2 — invocation error (bad args, unreadable registry or projector,
319
+ cannot reach server, etc.), or the run executed zero fixtures
320
+ (every fixture skipped) — a run that proved nothing never reads
321
+ as success.
132
322
  `.trimStart();
133
323
  async function main(argv) {
134
324
  let parsed;
@@ -136,7 +326,7 @@ async function main(argv) {
136
326
  parsed = parseArgs(argv);
137
327
  }
138
328
  catch (err) {
139
- process.stderr.write(`error: ${String(err.message ?? err)}\n\n`);
329
+ process.stderr.write(`error: ${errorMessage(err)}\n\n`);
140
330
  process.stderr.write(USAGE);
141
331
  return 2;
142
332
  }
@@ -159,7 +349,16 @@ async function main(argv) {
159
349
  auth = parseAuth(parsed.auth);
160
350
  }
161
351
  catch (err) {
162
- process.stderr.write(`error: ${String(err.message ?? err)}\n\n`);
352
+ process.stderr.write(`error: ${errorMessage(err)}\n\n`);
353
+ process.stderr.write(USAGE);
354
+ return 2;
355
+ }
356
+ let catalogInputs;
357
+ try {
358
+ catalogInputs = await loadCatalogInputs(parsed);
359
+ }
360
+ catch (err) {
361
+ process.stderr.write(`error: ${errorMessage(err)}\n\n`);
163
362
  process.stderr.write(USAGE);
164
363
  return 2;
165
364
  }
@@ -170,6 +369,7 @@ async function main(argv) {
170
369
  only: parsed.only !== undefined && parsed.only.length > 0 ? parsed.only : undefined,
171
370
  observationTimeoutMs: parsed.timeoutMs,
172
371
  reporter,
372
+ ...catalogInputs,
173
373
  });
174
374
  if (parsed.verbose) {
175
375
  const failures = formatFailures(result.failed);
@@ -201,11 +401,11 @@ function exitCodeForResult(result) {
201
401
  return 0;
202
402
  }
203
403
  // Immediately-invoked — this module IS the bin entry. Tests import
204
- // `parseArgs` / `parseAuth` / `exitCodeForResult` / `main` via named
205
- // exports without triggering the run.
404
+ // `parseArgs` / `parseAuth` / `loadCatalogInputs` / `exitCodeForResult`
405
+ // / `main` via named exports without triggering the run.
206
406
  if (isEntryPoint()) {
207
407
  main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
208
- process.stderr.write(`fatal: ${String(err.message ?? err)}\n`);
408
+ process.stderr.write(`fatal: ${errorMessage(err)}\n`);
209
409
  process.exit(2);
210
410
  });
211
411
  }
@@ -38,10 +38,12 @@
38
38
  *
39
39
  * The same is true of SPEC §7.1's PRE-GENERATION refusal obligations
40
40
  * (ggui#786): what a server projects for a refusal
41
- * (`../refusal-envelope-conformance`) and what a deployment's closed
42
- * refusal registry must satisfy (`../registry-completeness`) are a
43
- * projection and a data check. Neither is WebSocket-observable, so
44
- * neither is registered below.
41
+ * (`../refusal-envelope-conformance`), what a deployment's closed
42
+ * refusal registry must satisfy (`../registry-completeness`), and what
43
+ * a per-app endpoint answers for an endpoint-level refusal
44
+ * (`../transport-refusal-conformance`, ggui#825) are two projections
45
+ * and a data check. None is WebSocket-observable, so none is
46
+ * registered below.
45
47
  *
46
48
  * ## An MCP-binding driver EXISTS for `resources/read` — `../resource-read-conformance`
47
49
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsGG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AACzF,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AACrF,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQ5C;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB,oBAAoB,GACpB,gBAAgB,GAChB,cAAc,GACd,4BAA4B,GAC5B,0BAA0B,GAC1B,mBAAmB,GACnB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,QAAQ,EAAE,CAAC,CAOlF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,QAAQ,EAGwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwGG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,gCAAgC,EAAE,MAAM,uCAAuC,CAAC;AACzF,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AACrF,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAExE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQ5C;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB,oBAAoB,GACpB,gBAAgB,GAChB,cAAc,GACd,4BAA4B,GAC5B,0BAA0B,GAC1B,mBAAmB,GACnB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,QAAQ,EAAE,CAAC,CAOlF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,QAAQ,EAGwB,CAAC"}
@@ -38,10 +38,12 @@
38
38
  *
39
39
  * The same is true of SPEC §7.1's PRE-GENERATION refusal obligations
40
40
  * (ggui#786): what a server projects for a refusal
41
- * (`../refusal-envelope-conformance`) and what a deployment's closed
42
- * refusal registry must satisfy (`../registry-completeness`) are a
43
- * projection and a data check. Neither is WebSocket-observable, so
44
- * neither is registered below.
41
+ * (`../refusal-envelope-conformance`), what a deployment's closed
42
+ * refusal registry must satisfy (`../registry-completeness`), and what
43
+ * a per-app endpoint answers for an endpoint-level refusal
44
+ * (`../transport-refusal-conformance`, ggui#825) are two projections
45
+ * and a data check. None is WebSocket-observable, so none is
46
+ * registered below.
45
47
  *
46
48
  * ## An MCP-binding driver EXISTS for `resources/read` — `../resource-read-conformance`
47
49
  *
package/dist/index.d.ts CHANGED
@@ -28,6 +28,8 @@ export { refusalEnvelopeCases, runRefusalEnvelopeConformance, } from './refusal-
28
28
  export type { PreGenerationRefusalInput, ProjectedRefusalResult, RefusalEnvelopeConformanceCase, RefusalEnvelopeConformanceResult, RefusalEnvelopeMismatch, } from './refusal-envelope-conformance/index.js';
29
29
  export { registryCompletenessPins, runRegistryCompletenessConformance, } from './registry-completeness/index.js';
30
30
  export type { RefusalRegistryRow, RefusalRegistryView, RegistryCompletenessMismatch, RegistryCompletenessPin, RegistryCompletenessResult, } from './registry-completeness/index.js';
31
+ export { runTransportRefusalConformance, transportRefusalCases, } from './transport-refusal-conformance/index.js';
32
+ export type { ProjectedTransportRefusal, TransportRefusalConformanceCase, TransportRefusalConformanceResult, TransportRefusalInput, TransportRefusalMismatch, } from './transport-refusal-conformance/index.js';
31
33
  export { declaresDeliveryChannel, renderLocatorUri, resourceReadCases, runResourceReadConformance, GGUI_RENDER_RESOURCE_URI, } from './resource-read-conformance/index.js';
32
34
  export type { DurableSubstrateWiring, JsonRpcErrorFrame, PreparedResourceReadScenario, ResourceReadCaller, ResourceReadConformanceCase, ResourceReadConformanceFailure, ResourceReadConformanceResult, ResourceReadConformanceSkip, ResourceReadErrorCodeDecl, ResourceReadExpectation, ResourceReadKey, ResourceReadLocator, ResourceReadOutcome, ResourceReadProbe, ResourceReadRenderMeta, ResourceReadScenario, ResourceReadScenarioDriver, ResourceReadSeed, ResourceReadServerShape, RunResourceReadConformanceOptions, } from './resource-read-conformance/index.js';
33
35
  export { loadFixture, listFixtures, loadAllFixtures } from './loader.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -52,6 +52,12 @@ export { refusalEnvelopeCases, runRefusalEnvelopeConformance, } from './refusal-
52
52
  // function: `surfaces` non-empty, `code === key`, every `after-fix`
53
53
  // entry naming who acts, and `retry` inside the closed four-value set.
54
54
  export { registryCompletenessPins, runRegistryCompletenessConformance, } from './registry-completeness/index.js';
55
+ // Transport-refusal — the JSON-RPC error a per-app MCP endpoint answers
56
+ // with when it refuses a request for a typed reason (ggui#825): HTTP 403,
57
+ // `-32003` / `App not found` (ggui#836), `data.refusal` carrying the registry
58
+ // projection without the render-only fields; a code whose surfaces
59
+ // exclude `mcp-endpoint` projects to nothing.
60
+ export { runTransportRefusalConformance, transportRefusalCases, } from './transport-refusal-conformance/index.js';
55
61
  // Resource-read conformance — the kit's first MCP-binding driver, and
56
62
  // it binds `resources/read` ONLY (`tools/call` has none). A read of a
57
63
  // render locator has exactly two exits: a result whose contents
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Structural equality over plain JSON data — the one comparator every
3
+ * pure-function catalog grades with (key order ignored, arrays ordered).
4
+ */
5
+ export declare function jsonEqual(a: unknown, b: unknown): boolean;
6
+ //# sourceMappingURL=json-equal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-equal.d.ts","sourceRoot":"","sources":["../src/json-equal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAezD"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Structural equality over plain JSON data — the one comparator every
3
+ * pure-function catalog grades with (key order ignored, arrays ordered).
4
+ */
5
+ export function jsonEqual(a, b) {
6
+ if (a === b)
7
+ return true;
8
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
9
+ return false;
10
+ }
11
+ if (Array.isArray(a) || Array.isArray(b)) {
12
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
13
+ return false;
14
+ return a.every((item, index) => jsonEqual(item, b[index]));
15
+ }
16
+ const left = a;
17
+ const right = b;
18
+ const leftKeys = Object.keys(left).sort();
19
+ const rightKeys = Object.keys(right).sort();
20
+ if (!jsonEqual(leftKeys, rightKeys))
21
+ return false;
22
+ return leftKeys.every((key) => jsonEqual(left[key], right[key]));
23
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/refusal-envelope-conformance/index.ts"],"names":[],"mappings":"AA0DA;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2DAA2D;IAC3D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,QAAQ,CAAC,iBAAiB,EAAE;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;KAC7C,CAAC;IACF,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,8BAA8B;IAC7C,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wCAAwC;IACxC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,8BAA8B,EAOzE,CAAC;AAEF,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,GAAG,MAAM,CAAC;CACzD;AAED,0DAA0D;AAC1D,MAAM,WAAW,gCAAgC;IAC/C,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,SAAS,uBAAuB,EAAE,CAAC;CACrD;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,CACP,OAAO,EAAE,yBAAyB,KAC/B,sBAAsB,GAAG,IAAI,GACjC,gCAAgC,CAwBlC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/refusal-envelope-conformance/index.ts"],"names":[],"mappings":"AA4DA;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2DAA2D;IAC3D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,QAAQ,CAAC,iBAAiB,EAAE;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;KAC7C,CAAC;IACF,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,8BAA8B;IAC7C,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wCAAwC;IACxC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,8BAA8B,EAOzE,CAAC;AAEF,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACjD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,GAAG,MAAM,CAAC;CACzD;AAED,0DAA0D;AAC1D,MAAM,WAAW,gCAAgC;IAC/C,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,SAAS,uBAAuB,EAAE,CAAC;CACrD;AAgBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,CACP,OAAO,EAAE,yBAAyB,KAC/B,sBAAsB,GAAG,IAAI,GACjC,gCAAgC,CAwBlC"}
@@ -55,6 +55,7 @@ import refuseLater from './cases/refuse-later.json' with { type: 'json' };
55
55
  import refuseNever from './cases/refuse-never.json' with { type: 'json' };
56
56
  import refuseNextPeriod from './cases/refuse-next-period.json' with { type: 'json' };
57
57
  import refuseNonRenderSurface from './cases/refuse-non-render-surface.json' with { type: 'json' };
58
+ import { jsonEqual } from '../json-equal.js';
58
59
  /**
59
60
  * Every refusal-envelope case the kit ships, in deterministic order:
60
61
  * the projectable cases by retry class (after-fix, next-period, later,
@@ -68,26 +69,6 @@ export const refusalEnvelopeCases = [
68
69
  refuseNever,
69
70
  refuseNonRenderSurface,
70
71
  ];
71
- /** Structural equality over plain JSON data. */
72
- function jsonEqual(a, b) {
73
- if (a === b)
74
- return true;
75
- if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
76
- return false;
77
- }
78
- if (Array.isArray(a) || Array.isArray(b)) {
79
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
80
- return false;
81
- return a.every((item, index) => jsonEqual(item, b[index]));
82
- }
83
- const left = a;
84
- const right = b;
85
- const leftKeys = Object.keys(left).sort();
86
- const rightKeys = Object.keys(right).sort();
87
- if (!jsonEqual(leftKeys, rightKeys))
88
- return false;
89
- return leftKeys.every((key) => jsonEqual(left[key], right[key]));
90
- }
91
72
  function projectionEqual(a, b) {
92
73
  if (a === null || b === null)
93
74
  return a === b;
@@ -4,6 +4,7 @@ import { type MatchResult } from './match-behavior.js';
4
4
  import type { AuthConfig, SessionStateBehavior, StreamUpdateBehavior, VersionMismatchBehavior } from './types.js';
5
5
  import { type PreGenerationRefusalInput, type ProjectedRefusalResult } from './refusal-envelope-conformance/index.js';
6
6
  import { type RefusalRegistryView } from './registry-completeness/index.js';
7
+ import { type ProjectedTransportRefusal, type TransportRefusalInput } from './transport-refusal-conformance/index.js';
7
8
  export interface RunConformanceConfig {
8
9
  /**
9
10
  * URL of the implementation under test. `http://` / `https://`
@@ -47,6 +48,14 @@ export interface RunConformanceConfig {
47
48
  * this run; omitted ⇒ reported SKIPPED, same reasoning as above.
48
49
  */
49
50
  readonly refusalRegistry?: RefusalRegistryView;
51
+ /**
52
+ * The deployment's PER-APP ENDPOINT refusal projector (SPEC §7.1's
53
+ * endpoint-level refusal, ggui#825): given a registry refusal, return
54
+ * the HTTP status + JSON-RPC error object the endpoint answers with, or
55
+ * `null` when the code has no transport envelope. Supplied ⇒ the
56
+ * `transport-refusal` catalog is graded; omitted ⇒ SKIPPED, named.
57
+ */
58
+ readonly transportRefusalProjector?: (refusal: TransportRefusalInput) => ProjectedTransportRefusal | null;
50
59
  }
51
60
  /**
52
61
  * Row-name prefixes for the PURE-FUNCTION catalogs the runner folds
@@ -54,7 +63,7 @@ export interface RunConformanceConfig {
54
63
  * WebSocket fixture name, and so the reporter can group them without a
55
64
  * second registry of names.
56
65
  */
57
- export declare const PURE_FUNCTION_CATALOG_SLUGS: readonly ["refusal-envelope", "registry-completeness"];
66
+ export declare const PURE_FUNCTION_CATALOG_SLUGS: readonly ["refusal-envelope", "registry-completeness", "transport-refusal"];
58
67
  /** One member of {@link PURE_FUNCTION_CATALOG_SLUGS}. */
59
68
  export type PureFunctionCatalogSlug = (typeof PURE_FUNCTION_CATALOG_SLUGS)[number];
60
69
  export interface ConformanceResult {
@@ -1 +1 @@
1
- {"version":3,"file":"run-conformance.d.ts","sourceRoot":"","sources":["../src/run-conformance.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run-conformance.d.ts","sourceRoot":"","sources":["../src/run-conformance.ts"],"names":[],"mappings":"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"}
@@ -47,7 +47,7 @@
47
47
  * loop is empty today.)
48
48
  * l. Record the outcome via reporter + accumulate in result.
49
49
  * 3. Fold in the PURE-FUNCTION catalogs (ggui#786) —
50
- * `refusal-envelope` and `registry-completeness`. They grade a
50
+ * `refusal-envelope`, `registry-completeness` and `transport-refusal`. They grade a
51
51
  * caller-supplied projector / registry rather than the wire, so
52
52
  * they need no transport; when the caller supplies neither, their
53
53
  * rows are reported SKIPPED with that reason so an ungraded
@@ -65,6 +65,7 @@ import { behaviorIs, deepEqual, matchBehavior } from './match-behavior.js';
65
65
  import { openWsTransport } from './ws-transport.js';
66
66
  import { refusalEnvelopeCases, runRefusalEnvelopeConformance, } from './refusal-envelope-conformance/index.js';
67
67
  import { registryCompletenessPins, runRegistryCompletenessConformance, } from './registry-completeness/index.js';
68
+ import { runTransportRefusalConformance, transportRefusalCases, } from './transport-refusal-conformance/index.js';
68
69
  /**
69
70
  * Row-name prefixes for the PURE-FUNCTION catalogs the runner folds
70
71
  * into a run. Prefixed so a catalog row can never collide with a
@@ -74,6 +75,7 @@ import { registryCompletenessPins, runRegistryCompletenessConformance, } from '.
74
75
  export const PURE_FUNCTION_CATALOG_SLUGS = [
75
76
  'refusal-envelope',
76
77
  'registry-completeness',
78
+ 'transport-refusal',
77
79
  ];
78
80
  // =============================================================================
79
81
  // Runner
@@ -161,7 +163,7 @@ function runPureFunctionCatalogs(config, reporter, buckets) {
161
163
  const projector = config.refusalProjector;
162
164
  if (projector === undefined) {
163
165
  for (const testCase of refusalEnvelopeCases) {
164
- skip(`refusal-envelope/${testCase.name}`, 'no `refusalProjector` supplied — pass one to runConformance() to grade SPEC §7.1\u2019s refused arm');
166
+ skip(`refusal-envelope/${testCase.name}`, 'no `refusalProjector` supplied — pass one to runConformance(), or `--projector <module>` on the CLI, to grade SPEC §7.1\u2019s refused arm');
165
167
  }
166
168
  }
167
169
  else {
@@ -178,11 +180,32 @@ function runPureFunctionCatalogs(config, reporter, buckets) {
178
180
  });
179
181
  }
180
182
  }
183
+ // ── transport-refusal (ggui#825) ──
184
+ const endpointProjector = config.transportRefusalProjector;
185
+ if (endpointProjector === undefined) {
186
+ for (const testCase of transportRefusalCases) {
187
+ skip(`transport-refusal/${testCase.name}`, 'no `transportRefusalProjector` supplied — pass one to runConformance(), or `--transport-projector <module>` on the CLI, to grade the endpoint-level refusal (SPEC §7.1, mcp-endpoint surface)');
188
+ }
189
+ }
190
+ else {
191
+ const endpoint = runTransportRefusalConformance(endpointProjector);
192
+ for (const name of endpoint.passed)
193
+ pass(`transport-refusal/${name}`);
194
+ for (const mismatch of endpoint.failed) {
195
+ fail({
196
+ name: `transport-refusal/${mismatch.name}`,
197
+ criterion: 'endpoint-level refusal envelope (SPEC §7.1, mcp-endpoint surface)',
198
+ expected: mismatch.expected,
199
+ received: mismatch.actual,
200
+ message: 'the projected endpoint refusal does not match the catalog case',
201
+ });
202
+ }
203
+ }
181
204
  // ── registry-completeness ──
182
205
  const registry = config.refusalRegistry;
183
206
  if (registry === undefined) {
184
207
  for (const pin of registryCompletenessPins) {
185
- skip(`registry-completeness/${pin.name}`, 'no `refusalRegistry` supplied — pass the deployment\u2019s refusal-code registry to runConformance() to grade it');
208
+ skip(`registry-completeness/${pin.name}`, 'no `refusalRegistry` supplied — pass the deployment\u2019s refusal-code registry to runConformance(), or `--registry <file.json>` on the CLI, to grade it');
186
209
  }
187
210
  }
188
211
  else {
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "refuse-deprovisioned-endpoint",
3
+ "description": "A deprovisioned app answers EVERY JSON-RPC request on its per-app MCP endpoint (initialize included) with HTTP 403 and a JSON-RPC error whose data.refusal carries the registry projection \u2014 code, message, fix, retry \u2014 so the client reads app_deprovisioned instead of guessing between a deleted app and a bad credential. error.code is -32003 (APP_NOT_FOUND \u2014 the endpoint no longer serves this app) and error.message is App not found; an untyped authorization failure answers -32001 (UNAUTHORIZED) with no data, so a client can tell the two apart by code and can tell neither from a dropped socket by mistake \u2014 -32000 is the SDK client's ConnectionClosed and a first-party server never chooses it (ggui#836).",
4
+ "refusal": {
5
+ "code": "app_deprovisioned",
6
+ "message": "the app record has no owner claim any more \u2014 the app was deprovisioned",
7
+ "fix": "provision the app again; this app id never renders again",
8
+ "retry": "never"
9
+ },
10
+ "expect": {
11
+ "httpStatus": 403,
12
+ "error": {
13
+ "code": -32003,
14
+ "message": "App not found",
15
+ "data": {
16
+ "refusal": {
17
+ "code": "app_deprovisioned",
18
+ "message": "the app record has no owner claim any more \u2014 the app was deprovisioned",
19
+ "fix": "provision the app again; this app id never renders again",
20
+ "retry": "never"
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "refuse-render-only-code",
3
+ "description": "A REGISTERED code whose surfaces exclude mcp-endpoint has no transport envelope: the per-app endpoint's authorization never speaks it, and a projector that types it anyway would put a render-gate state on a surface the registry does not list. The three auth arms (no issuer, foreign issuer, denied subject) stay the bare 403 by the same rule \u2014 naming any of them would say which is true.",
4
+ "refusal": {
5
+ "code": "insufficient_credit",
6
+ "message": "the configured allowance for this app is exhausted",
7
+ "fix": "top up the allowance for this app, then retry",
8
+ "retry": "after-fix"
9
+ },
10
+ "expect": null
11
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The refusal handed to the projector — the registry projection on the
3
+ * transport surface. Authored here rather than imported from
4
+ * `@ggui-ai/protocol` for the same reason as the render catalog: the kit
5
+ * grades the WIRE, and a stringly input keeps a stale kit from typing
6
+ * its way past a registry change.
7
+ */
8
+ export interface TransportRefusalInput {
9
+ /** Registered refusal code. */
10
+ readonly code: string;
11
+ /** Precise diagnostic. */
12
+ readonly message: string;
13
+ /** The one recovery step. */
14
+ readonly fix: string;
15
+ /** Retry class (`after-fix` | `next-period` | `later` | `never`). */
16
+ readonly retry: string;
17
+ }
18
+ /** What the endpoint answers: the HTTP status and the JSON-RPC error object. */
19
+ export interface ProjectedTransportRefusal {
20
+ readonly httpStatus: number;
21
+ readonly error: {
22
+ readonly code: number;
23
+ readonly message: string;
24
+ readonly data: {
25
+ readonly refusal: TransportRefusalInput;
26
+ };
27
+ };
28
+ }
29
+ /** One transport-refusal case, authored as JSON under `./cases/`. */
30
+ export interface TransportRefusalConformanceCase {
31
+ readonly name: string;
32
+ readonly description: string;
33
+ readonly refusal: TransportRefusalInput;
34
+ /** `null` when the code has no transport envelope (surfaces exclude `mcp-endpoint`). */
35
+ readonly expect: ProjectedTransportRefusal | null;
36
+ }
37
+ export declare const transportRefusalCases: readonly TransportRefusalConformanceCase[];
38
+ export interface TransportRefusalMismatch {
39
+ readonly name: string;
40
+ readonly expected: ProjectedTransportRefusal | null;
41
+ /** What the projector produced — or the message of the error it threw. */
42
+ readonly actual: ProjectedTransportRefusal | null | string;
43
+ }
44
+ export interface TransportRefusalConformanceResult {
45
+ readonly passed: readonly string[];
46
+ readonly failed: readonly TransportRefusalMismatch[];
47
+ }
48
+ /**
49
+ * Grade a per-app endpoint refusal projector against the catalog. Pure
50
+ * and synchronous: no server, no transport — the deterministic
51
+ * projection is the whole obligation.
52
+ */
53
+ export declare function runTransportRefusalConformance(project: (refusal: TransportRefusalInput) => ProjectedTransportRefusal | null): TransportRefusalConformanceResult;
54
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transport-refusal-conformance/index.ts"],"names":[],"mappings":"AAyBA;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6BAA6B;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,gFAAgF;AAChF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,IAAI,EAAE;YAAE,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAA;SAAE,CAAC;KAC5D,CAAC;CACH;AAED,qEAAqE;AACrE,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,yBAAyB,GAAG,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,qBAAqB,EAAE,SAAS,+BAA+B,EAG3E,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACpD,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,yBAAyB,GAAG,IAAI,GAAG,MAAM,CAAC;CAC5D;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,CAAC,OAAO,EAAE,qBAAqB,KAAK,yBAAyB,GAAG,IAAI,GAC5E,iCAAiC,CAiBnC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Transport-refusal conformance catalog (ggui#825) — grades a deployment's
3
+ * PER-APP ENDPOINT refusal projector: the function that turns a registry
4
+ * refusal into the JSON-RPC error a per-app MCP endpoint answers with when
5
+ * it refuses a request for a typed reason, on ANY method (`initialize`
6
+ * included).
7
+ *
8
+ * The contract it makes observable: a deprovisioned app answers HTTP 403
9
+ * with `{ code: -32003, message: "App not found", data: { refusal } }` where
10
+ * `refusal` is the registry projection without the render-only fields
11
+ * (`code`, `message`, `fix`, `retry`); an authorization failure that is
12
+ * NOT a registry state answers 403 with `-32001` and no `data` — the untyped
13
+ * arms deliberately indistinguishable among themselves, so a client never
14
+ * learns which one it hit (codes ruled in ggui#836).
15
+ *
16
+ * Each case ships as raw JSON under `./cases/`. The kit never imports a
17
+ * concrete server: the adopter supplies the projector, the catalog grades
18
+ * it, and a code whose `surfaces` exclude `mcp-endpoint` must project to
19
+ * `null` (no transport envelope exists for it).
20
+ */
21
+ import refuseDeprovisionedEndpoint from './cases/refuse-deprovisioned-endpoint.json' with { type: 'json' };
22
+ import refuseRenderOnlyCode from './cases/refuse-render-only-code.json' with { type: 'json' };
23
+ import { jsonEqual } from '../json-equal.js';
24
+ export const transportRefusalCases = [
25
+ refuseDeprovisionedEndpoint,
26
+ refuseRenderOnlyCode,
27
+ ];
28
+ /**
29
+ * Grade a per-app endpoint refusal projector against the catalog. Pure
30
+ * and synchronous: no server, no transport — the deterministic
31
+ * projection is the whole obligation.
32
+ */
33
+ export function runTransportRefusalConformance(project) {
34
+ const passed = [];
35
+ const failed = [];
36
+ for (const testCase of transportRefusalCases) {
37
+ let actual;
38
+ try {
39
+ actual = project(testCase.refusal);
40
+ }
41
+ catch (error) {
42
+ actual = error instanceof Error ? error.message : String(error);
43
+ }
44
+ if (typeof actual !== 'string' && jsonEqual(actual, testCase.expect)) {
45
+ passed.push(testCase.name);
46
+ }
47
+ else {
48
+ failed.push({ name: testCase.name, expected: testCase.expect, actual });
49
+ }
50
+ }
51
+ return { passed, failed };
52
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ggui-ai/protocol-conformance",
3
- "version": "0.14.0",
4
- "description": "Conformance test kit for the ggui protocol. Drives a behavioral fixture catalog against a live implementation over WebSocket, a resources/read catalog against a live MCP server, and eight pure-function catalogs (schema, registration, resolution, binding, props-schema, host-helper, refusal-envelope, registry-completeness) for the gadget, tool-binding, schema-precise-render, host-helper and pre-generation-refusal obligations, reporting a scorecard-style pass / fail / skip. MCP builders adopt this kit to prove their implementation is protocol-conformant.",
3
+ "version": "0.15.0",
4
+ "description": "Conformance test kit for the ggui protocol. Drives a behavioral fixture catalog against a live implementation over WebSocket, a resources/read catalog against a live MCP server, and nine pure-function catalogs (schema, registration, resolution, binding, props-schema, host-helper, refusal-envelope, registry-completeness, transport-refusal) for the gadget, tool-binding, schema-precise-render, host-helper and pre-generation-refusal obligations, reporting a scorecard-style pass / fail / skip. MCP builders adopt this kit to prove their implementation is protocol-conformant.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
7
7
  "ggui",
@@ -75,11 +75,16 @@
75
75
  "types": "./dist/registry-completeness/index.d.ts",
76
76
  "import": "./dist/registry-completeness/index.js",
77
77
  "default": "./dist/registry-completeness/index.js"
78
+ },
79
+ "./transport-refusal-conformance": {
80
+ "types": "./dist/transport-refusal-conformance/index.d.ts",
81
+ "import": "./dist/transport-refusal-conformance/index.js",
82
+ "default": "./dist/transport-refusal-conformance/index.js"
78
83
  }
79
84
  },
80
85
  "dependencies": {
81
86
  "ws": "^8.20.1",
82
- "@ggui-ai/protocol": "0.14.0"
87
+ "@ggui-ai/protocol": "0.15.0"
83
88
  },
84
89
  "devDependencies": {
85
90
  "@types/node": "^22.0.0",