@ggui-ai/protocol-conformance 0.13.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.
Files changed (36) hide show
  1. package/README.md +28 -14
  2. package/dist/cli.d.ts +23 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +228 -19
  5. package/dist/fixtures/index.d.ts +9 -0
  6. package/dist/fixtures/index.d.ts.map +1 -1
  7. package/dist/fixtures/index.js +9 -0
  8. package/dist/index.d.ts +6 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +17 -0
  11. package/dist/json-equal.d.ts +6 -0
  12. package/dist/json-equal.d.ts.map +1 -0
  13. package/dist/json-equal.js +23 -0
  14. package/dist/refusal-envelope-conformance/cases/refuse-after-fix-caller.json +27 -0
  15. package/dist/refusal-envelope-conformance/cases/refuse-after-fix-owner-with-balance.json +29 -0
  16. package/dist/refusal-envelope-conformance/cases/refuse-later.json +27 -0
  17. package/dist/refusal-envelope-conformance/cases/refuse-never.json +27 -0
  18. package/dist/refusal-envelope-conformance/cases/refuse-next-period.json +27 -0
  19. package/dist/refusal-envelope-conformance/cases/refuse-non-render-surface.json +12 -0
  20. package/dist/refusal-envelope-conformance/index.d.ts +108 -0
  21. package/dist/refusal-envelope-conformance/index.d.ts.map +1 -0
  22. package/dist/refusal-envelope-conformance/index.js +119 -0
  23. package/dist/registry-completeness/index.d.ts +105 -0
  24. package/dist/registry-completeness/index.d.ts.map +1 -0
  25. package/dist/registry-completeness/index.js +110 -0
  26. package/dist/reporter.d.ts.map +1 -1
  27. package/dist/reporter.js +26 -9
  28. package/dist/run-conformance.d.ts +38 -0
  29. package/dist/run-conformance.d.ts.map +1 -1
  30. package/dist/run-conformance.js +125 -4
  31. package/dist/transport-refusal-conformance/cases/refuse-deprovisioned-endpoint.json +25 -0
  32. package/dist/transport-refusal-conformance/cases/refuse-render-only-code.json +11 -0
  33. package/dist/transport-refusal-conformance/index.d.ts +54 -0
  34. package/dist/transport-refusal-conformance/index.d.ts.map +1 -0
  35. package/dist/transport-refusal-conformance/index.js +52 -0
  36. package/package.json +20 -5
package/README.md CHANGED
@@ -51,13 +51,17 @@ 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. Five 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`).
58
58
  - **`@ggui-ai/protocol-conformance/resolution-conformance`** — which bundle + style URLs the server MUST compute for a gadget descriptor's transport fields (`bundleHost` precedence, default host, loopback `http` scheme).
59
59
  - **`@ggui-ai/protocol-conformance/binding-conformance`** — which MCP tool-binding set a manifest resolves to (a declared `mcpTools` list wins entirely; blueprints without it derive bare tool-name entries from contract tool names) and which artifacts the registry `tool=` / `server=` search filters MUST match (SPEC §7.7.4.1).
60
60
  - **`@ggui-ai/protocol-conformance/props-schema-conformance`** — the schema-precise render arbiter (SPEC §2.3.2): the enforced-props-schema builder (drift), its RFC 8785 sha256 hash, the grammar-safe profile verdict, and returned-schema AUTHORITY (sample validity under the case's own `propsSchema`, including the 2026-08-19 out-of-vocabulary enum incident pinned as a permanent sample).
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
+ - **`@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
+ - **`@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`.
61
65
 
62
66
  ```ts
63
67
  import { runSchemaConformance } from "@ggui-ai/protocol-conformance/schema-conformance";
@@ -77,7 +81,7 @@ if (schema.failed.length + gate.failed.length + urls.failed.length > 0) {
77
81
  }
78
82
  ```
79
83
 
80
- The `schema-conformance` meta-test binds its catalog to the live `@ggui-ai/protocol` `clientCapabilitiesSpecSchema` — a drift-catch if the wire schema diverges from the §7.7.2 obligations the catalog freezes. The `registration-conformance`, `resolution-conformance`, and `binding-conformance` meta-tests verify catalog coherence against faithful in-test implementations (the kit stays vendor-neutral — it does not depend on a server implementation); grading the _shipping_ gate / resolver is an implementation-side test that drives the corresponding runner.
84
+ The `schema-conformance` meta-test binds its catalog to the live `@ggui-ai/protocol` `clientCapabilitiesSpecSchema` — a drift-catch if the wire schema diverges from the §7.7.2 obligations the catalog freezes. The `registration-conformance`, `resolution-conformance`, and `binding-conformance` meta-tests verify catalog coherence against faithful in-test implementations (the kit stays vendor-neutral — it does not depend on a server implementation); grading the _shipping_ gate / resolver is an implementation-side test that drives the corresponding runner. The two ggui#786 catalogs split the same way, and for the same reason: `registry-completeness` binds the LIVE `@ggui-ai/protocol` `PRE_GENERATION_REFUSAL_CODES` (the obligation IS a protocol declaration, so the drift-catch belongs kit-side, like `schema-conformance`), while `refusal-envelope-conformance` grades a kit-local reference projector (the obligation is an implementation behaviour, like `resolution-conformance`). Two of that ruling's six registry checks read repo source — the no-auto-retry docstring rule and the grep for code literals minted outside the registry file — and are therefore NOT in the kit: it publishes only `dist`, so a source walk could never run for an adopter. They live in `@ggui-ai/protocol`'s own suite. That split is declared, not hidden.
81
85
 
82
86
  ## Resource-read conformance — the MCP binding
83
87
 
@@ -116,18 +120,21 @@ It deliberately does **not** grade: the order in which a substrate-less server a
116
120
 
117
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.
118
122
 
119
- 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 eight 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.
120
-
121
- | Obligation | Graded against | Driver |
122
- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
123
- | Gadget wire schema (§7.7.2) | the shipping `@ggui-ai/protocol` `clientCapabilitiesSpecSchema` | this kit's own `src/schema-conformance/schema-conformance.test.ts` |
124
- | 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` |
125
- | 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` |
126
- | 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** |
127
- | Path-A WS fixtures — vendor-neutral | `@ggui-ai/protocol-reference-server` | `protocol-reference-server/src/conformance.test.ts` — **9 pass / 3 skip / 0 fail** |
128
- | `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** |
129
- | 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` |
130
- | 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` |
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) |
131
138
 
132
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.
133
140
 
@@ -165,6 +172,13 @@ npx @ggui-ai/protocol-conformance --url ws://localhost:3000/ws --auth bearer:$TO
165
172
 
166
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).
167
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
+
168
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.
169
183
 
170
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;AAoCD,iBAAe,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D5D;AAED;;;;;;;GAOG;AACH,iBAAS,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAI/D;AA+BD,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC"}
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:
@@ -109,17 +279,46 @@ Required:
109
279
  session-cookie:COOKIE — Cookie header
110
280
 
111
281
  Options:
112
- --only <fixture-name> Run only this fixture. May be repeated.
282
+ --only <fixture-name> Run only this fixture. May be repeated. Also
283
+ filters the pure-function catalog rows, whose
284
+ names are prefixed <catalog>/<case>.
113
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).
114
299
  --verbose, -v Print failure details + skip reasons at the end.
115
300
  --help, -h Show this help.
116
301
 
302
+ Pure-function catalogs (refusal-envelope, registry-completeness, transport-refusal):
303
+ These grade an in-process function and a data table, not the wire, so
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.
314
+
117
315
  Exit codes:
118
316
  0 — at least one fixture executed and none failed.
119
317
  1 — at least one fixture failed.
120
- 2 — invocation error (bad args, cannot reach server, etc.), or the
121
- run executed zero fixtures (every fixture skipped) — a run that
122
- proved nothing never reads as success.
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.
123
322
  `.trimStart();
124
323
  async function main(argv) {
125
324
  let parsed;
@@ -127,7 +326,7 @@ async function main(argv) {
127
326
  parsed = parseArgs(argv);
128
327
  }
129
328
  catch (err) {
130
- process.stderr.write(`error: ${String(err.message ?? err)}\n\n`);
329
+ process.stderr.write(`error: ${errorMessage(err)}\n\n`);
131
330
  process.stderr.write(USAGE);
132
331
  return 2;
133
332
  }
@@ -150,7 +349,16 @@ async function main(argv) {
150
349
  auth = parseAuth(parsed.auth);
151
350
  }
152
351
  catch (err) {
153
- 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`);
154
362
  process.stderr.write(USAGE);
155
363
  return 2;
156
364
  }
@@ -161,6 +369,7 @@ async function main(argv) {
161
369
  only: parsed.only !== undefined && parsed.only.length > 0 ? parsed.only : undefined,
162
370
  observationTimeoutMs: parsed.timeoutMs,
163
371
  reporter,
372
+ ...catalogInputs,
164
373
  });
165
374
  if (parsed.verbose) {
166
375
  const failures = formatFailures(result.failed);
@@ -192,11 +401,11 @@ function exitCodeForResult(result) {
192
401
  return 0;
193
402
  }
194
403
  // Immediately-invoked — this module IS the bin entry. Tests import
195
- // `parseArgs` / `parseAuth` / `exitCodeForResult` / `main` via named
196
- // exports without triggering the run.
404
+ // `parseArgs` / `parseAuth` / `loadCatalogInputs` / `exitCodeForResult`
405
+ // / `main` via named exports without triggering the run.
197
406
  if (isEntryPoint()) {
198
407
  main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
199
- process.stderr.write(`fatal: ${String(err.message ?? err)}\n`);
408
+ process.stderr.write(`fatal: ${errorMessage(err)}\n`);
200
409
  process.exit(2);
201
410
  });
202
411
  }
@@ -36,6 +36,15 @@
36
36
  * frame to drive — so they do NOT belong in this behavioral fixture
37
37
  * catalog.
38
38
  *
39
+ * The same is true of SPEC §7.1's PRE-GENERATION refusal obligations
40
+ * (ggui#786): what a server projects for a refusal
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.
47
+ *
39
48
  * ## An MCP-binding driver EXISTS for `resources/read` — `../resource-read-conformance`
40
49
  *
41
50
  * This catalog used to declare the MCP-binding driver as missing. It is
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FG;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"}
@@ -36,6 +36,15 @@
36
36
  * frame to drive — so they do NOT belong in this behavioral fixture
37
37
  * catalog.
38
38
  *
39
+ * The same is true of SPEC §7.1's PRE-GENERATION refusal obligations
40
+ * (ggui#786): what a server projects for a refusal
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.
47
+ *
39
48
  * ## An MCP-binding driver EXISTS for `resources/read` — `../resource-read-conformance`
40
49
  *
41
50
  * This catalog used to declare the MCP-binding driver as missing. It is
package/dist/index.d.ts CHANGED
@@ -24,6 +24,12 @@ export { gadgetResolutionCases, runResolutionConformance, } from './resolution-c
24
24
  export type { GadgetUrlEntry, ResolvedGadgetUrls, ResolutionConformanceCase, ResolutionConformanceMismatch, ResolutionConformanceResult, } from './resolution-conformance/index.js';
25
25
  export { bindingFilterCases, bindingResolutionCases, runBindingFilterCases, runBindingResolutionCases, } from './binding-conformance/index.js';
26
26
  export type { BindingFilterCase, BindingFilterMismatch, BindingFilterResult, BindingManifestEntry, BindingRejectCode, BindingResolutionCase, BindingResolutionMismatch, BindingResolutionOutcome, BindingResolutionResult, McpToolBindingDecl, McpToolFilterDecl, } from './binding-conformance/index.js';
27
+ export { refusalEnvelopeCases, runRefusalEnvelopeConformance, } from './refusal-envelope-conformance/index.js';
28
+ export type { PreGenerationRefusalInput, ProjectedRefusalResult, RefusalEnvelopeConformanceCase, RefusalEnvelopeConformanceResult, RefusalEnvelopeMismatch, } from './refusal-envelope-conformance/index.js';
29
+ export { registryCompletenessPins, runRegistryCompletenessConformance, } from './registry-completeness/index.js';
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';
27
33
  export { declaresDeliveryChannel, renderLocatorUri, resourceReadCases, runResourceReadConformance, GGUI_RENDER_RESOURCE_URI, } from './resource-read-conformance/index.js';
28
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';
29
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
@@ -41,6 +41,23 @@ export { gadgetResolutionCases, runResolutionConformance, } from './resolution-c
41
41
  // filter semantics (`tool=` / `server=` case-sensitive exact,
42
42
  // AND-composed per entry).
43
43
  export { bindingFilterCases, bindingResolutionCases, runBindingFilterCases, runBindingResolutionCases, } from './binding-conformance/index.js';
44
+ // Refusal-envelope conformance — the tool result a server MUST emit for
45
+ // a PRE-GENERATION refusal (SPEC §7.1's refused arm): an in-result
46
+ // `isError`, the code leading the text, `{outcome, refusal}` and
47
+ // nothing else on structuredContent, no `_meta`, and no envelope at all
48
+ // for a code whose surfaces exclude the render gate.
49
+ export { refusalEnvelopeCases, runRefusalEnvelopeConformance, } from './refusal-envelope-conformance/index.js';
50
+ // Registry-completeness — the structural obligations a deployment's
51
+ // closed refusal-code registry MUST satisfy. Grades DATA rather than a
52
+ // function: `surfaces` non-empty, `code === key`, every `after-fix`
53
+ // entry naming who acts, and `retry` inside the closed four-value set.
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';
44
61
  // Resource-read conformance — the kit's first MCP-binding driver, and
45
62
  // it binds `resources/read` ONLY (`tools/call` has none). A read of a
46
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
+ }