@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.
- package/README.md +28 -14
- package/dist/cli.d.ts +23 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +228 -19
- package/dist/fixtures/index.d.ts +9 -0
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +9 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/json-equal.d.ts +6 -0
- package/dist/json-equal.d.ts.map +1 -0
- package/dist/json-equal.js +23 -0
- package/dist/refusal-envelope-conformance/cases/refuse-after-fix-caller.json +27 -0
- package/dist/refusal-envelope-conformance/cases/refuse-after-fix-owner-with-balance.json +29 -0
- package/dist/refusal-envelope-conformance/cases/refuse-later.json +27 -0
- package/dist/refusal-envelope-conformance/cases/refuse-never.json +27 -0
- package/dist/refusal-envelope-conformance/cases/refuse-next-period.json +27 -0
- package/dist/refusal-envelope-conformance/cases/refuse-non-render-surface.json +12 -0
- package/dist/refusal-envelope-conformance/index.d.ts +108 -0
- package/dist/refusal-envelope-conformance/index.d.ts.map +1 -0
- package/dist/refusal-envelope-conformance/index.js +119 -0
- package/dist/registry-completeness/index.d.ts +105 -0
- package/dist/registry-completeness/index.d.ts.map +1 -0
- package/dist/registry-completeness/index.js +110 -0
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +26 -9
- package/dist/run-conformance.d.ts +38 -0
- package/dist/run-conformance.d.ts.map +1 -1
- package/dist/run-conformance.js +125 -4
- package/dist/transport-refusal-conformance/cases/refuse-deprovisioned-endpoint.json +25 -0
- package/dist/transport-refusal-conformance/cases/refuse-render-only-code.json +11 -0
- package/dist/transport-refusal-conformance/index.d.ts +54 -0
- package/dist/transport-refusal-conformance/index.d.ts.map +1 -0
- package/dist/transport-refusal-conformance/index.js +52 -0
- 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.
|
|
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
|
|
120
|
-
|
|
121
|
-
| Obligation | Graded against
|
|
122
|
-
| ----------------------------------------------------------------------- |
|
|
123
|
-
| Gadget wire schema (§7.7.2) | the shipping `@ggui-ai/protocol` `clientCapabilitiesSpecSchema`
|
|
124
|
-
| Registration gate (§7.7.3, §7.9) | the shipping gate trio in `@ggui-ai/mcp-server-handlers`
|
|
125
|
-
| Bundle-URL resolution (§7.7.2) | the shipping `resolveGadgetUrls` in `@ggui-ai/mcp-server-handlers`
|
|
126
|
-
| Path-A WS fixtures — first-party | the real `@ggui-ai/mcp-server` GguiSession channel, booted in-process on an ephemeral port
|
|
127
|
-
| Path-A WS fixtures — vendor-neutral | `@ggui-ai/protocol-reference-server`
|
|
128
|
-
| `resources/read` typed failures + mount invariant | the shipping `registerGguiRenderResourceTemplate` in `@ggui-ai/mcp-server`
|
|
129
|
-
| Tool-binding resolution — declared wins, contract derivation (§7.7.4.1) | the shipping `resolveMcpToolBindings` in `@ggui-ai/artifact-manifest`
|
|
130
|
-
| Tool-binding search filters — `tool=` / `server=` semantics (§7.7.4.1) | the shipping `matchesMcpToolFilters` in `@ggui-ai/registry-core`
|
|
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":";
|
|
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
|
|
24
|
-
* pass/fail signal against an implementation,
|
|
25
|
-
* `runConformance({ host: myImpl })` programmatically
|
|
26
|
-
* same guard:
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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,
|
|
121
|
-
|
|
122
|
-
proved nothing never reads
|
|
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: ${
|
|
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: ${
|
|
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` / `
|
|
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: ${
|
|
408
|
+
process.stderr.write(`fatal: ${errorMessage(err)}\n`);
|
|
200
409
|
process.exit(2);
|
|
201
410
|
});
|
|
202
411
|
}
|
package/dist/fixtures/index.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/fixtures/index.js
CHANGED
|
@@ -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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,YAAY,EAEV,eAAe,EACf,wBAAwB,EACxB,sBAAsB,EACtB,UAAU,EAEV,QAAQ,EACR,qBAAqB,EAErB,SAAS,EACT,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,uBAAuB,EACvB,yBAAyB,EACzB,gCAAgC,EAChC,gBAAgB,EAEhB,YAAY,EAEZ,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,eAAe,EAEf,aAAa,EACb,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAMpB,YAAY,EACV,eAAe,EAEf,SAAS,IAAI,aAAa,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,iCAAiC,EACjC,iBAAiB,EAEjB,YAAY,IAAI,gBAAgB,GACjC,MAAM,uBAAuB,CAAC;AAK/B,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,EACnB,gCAAgC,EAChC,8BAA8B,EAC9B,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AASxD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,+BAA+B,CAAC;AAOvC,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACV,0BAA0B,EAC1B,6BAA6B,EAC7B,4BAA4B,EAC5B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EACL,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,2BAA2B,EAC3B,+BAA+B,EAC/B,6BAA6B,GAC9B,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EACL,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,mCAAmC,CAAC;AAM3C,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,gCAAgC,CAAC;
|
|
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 @@
|
|
|
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
|
+
}
|