@ggui-ai/protocol-conformance 0.24.0 → 0.25.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 +1 -1
- package/dist/host-helper-conformance/index.d.ts +53 -1
- package/dist/host-helper-conformance/index.d.ts.map +1 -1
- package/dist/host-helper-conformance/index.js +75 -2
- package/dist/n1-compat-conformance/cases/release-13-render-result.json +25 -0
- package/dist/n1-compat-conformance/index.d.ts +1 -1
- package/dist/n1-compat-conformance/index.d.ts.map +1 -1
- package/dist/n1-compat-conformance/index.js +15 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Each catalog ships its cases as raw JSON so a non-TypeScript implementer can gra
|
|
|
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`** — grades a HOST-HELPER library (what mounts a ggui view and answers the MCP Apps bridge) through a JSON-RPC port the implementer supplies: a well-formed `ui/initialize` (H1), truthful capability advertisement (H2: an advertised `serverTools` answers `tools/call`, an advertised `serverResources` answers `resources/read`), honest and bounded refusals (H3/H4), and an unmodified `tools/call` relay (R1/R2), plus optional chrome (C1)
|
|
61
|
+
- **`@ggui-ai/protocol-conformance/host-helper-conformance`** — grades a HOST-HELPER library (what mounts a ggui view and answers the MCP Apps bridge) through a JSON-RPC port the implementer supplies: a well-formed `ui/initialize` (H1), truthful capability advertisement (H2: an advertised `serverTools` answers `tools/call`, an advertised `serverResources` answers `resources/read`), honest and bounded refusals (H3/H4), and an unmodified `tools/call` relay (R1/R2), plus optional chrome (C1), theme-coverage (T1) and model-tool-set (M1 — the host's own visibility filter graded against `toolVisibleToModel`: withhold the app-only `ggui_runtime_*` tools, keep every model-visible one; ggui#1414) grades. The scorecard names a tier: `relaying`, `read-only` (a legal grade), or `nonconforming`. Runner: `runHostHelperConformance(port)`.
|
|
62
62
|
- **`@ggui-ai/protocol-conformance/refusal-envelope-conformance`** — the tool result a server MUST project for a PRE-GENERATION refusal (SPEC §7.1's refused arm): an in-result `isError` (never a throw), `content[0].text` leading with the code, `structuredContent` carrying `{outcome: 'refused', refusal}` and nothing else, no `_meta` — and no envelope at all for a registered code whose surfaces exclude the render gate.
|
|
63
63
|
- **`@ggui-ai/protocol-conformance/registry-completeness`** — the structural obligations a deployment's closed refusal-code registry MUST satisfy: `surfaces` non-empty, `code === key`, every `after-fix` entry naming who acts (`fixBy`), and `retry` inside the closed four-value set. Grades DATA rather than a function — the caller passes their registry.
|
|
64
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 `-32007` and no `data`, by contract — ggui#836). Runner: `runTransportRefusalConformance(project)`; folded into `runConformance()` via `transportRefusalProjector`.
|
|
@@ -60,10 +60,20 @@
|
|
|
60
60
|
* reference validate is `@ggui-ai/design`'s `validateThemeCoverage`
|
|
61
61
|
* bound to the shipped `consumed-tokens.manifest.json` tokens.
|
|
62
62
|
*
|
|
63
|
+
* - `M1-model-tool-set` — OPTIONAL like T1, graded when the host supplies
|
|
64
|
+
* the filter it uses to derive the MODEL's tool list from a served
|
|
65
|
+
* `tools/list` (ggui#1414): the result must withhold every tool whose
|
|
66
|
+
* `_meta.ui.visibility` lacks `'model'` (ggui's six `ggui_runtime_*`
|
|
67
|
+
* app-only tools) and keep every other tool. SEP-1865 assigns this door
|
|
68
|
+
* to the host, and the server cannot observe a violation — a
|
|
69
|
+
* view-issued and a model-issued call are indistinguishable on the wire
|
|
70
|
+
* — so the kit is where a host proves it. Absent ⇒ `skip`.
|
|
71
|
+
*
|
|
63
72
|
* A helper that refuses the relay honestly is graded **tier
|
|
64
73
|
* `read-only`** — a LEGAL grade, with the R cases skipped, never
|
|
65
74
|
* failed. `nonconforming` means a dishonesty case failed.
|
|
66
75
|
*/
|
|
76
|
+
import { type McpAppsToolVisibility } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
67
77
|
/** Minimal JSON-RPC request the driver sends through the port. */
|
|
68
78
|
export interface JsonRpcRequest {
|
|
69
79
|
readonly jsonrpc: '2.0';
|
|
@@ -144,6 +154,41 @@ export interface ThemeCoverageOptions {
|
|
|
144
154
|
readonly registration: unknown;
|
|
145
155
|
readonly validate: (registration: unknown) => ThemeCoverageValidationResult;
|
|
146
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* A tool as a host sees it on a served `tools/list` — the two fields the
|
|
159
|
+
* visibility door reads. Nothing else on a tool declaration decides who may
|
|
160
|
+
* call it.
|
|
161
|
+
*/
|
|
162
|
+
export interface ServedToolDeclaration {
|
|
163
|
+
readonly name: string;
|
|
164
|
+
readonly _meta?: {
|
|
165
|
+
readonly ui?: {
|
|
166
|
+
readonly visibility?: readonly McpAppsToolVisibility[];
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** The M1 grade's input: the host's own model-facing filter. */
|
|
171
|
+
export interface ModelToolSetOptions {
|
|
172
|
+
/**
|
|
173
|
+
* The function the host uses to derive the MODEL's tool list from a
|
|
174
|
+
* served `tools/list` (names only). The kit feeds
|
|
175
|
+
* {@link MODEL_TOOL_SET_FIXTURE} and grades the result against
|
|
176
|
+
* `toolVisibleToModel`.
|
|
177
|
+
*/
|
|
178
|
+
readonly offeredToModel: (served: readonly ServedToolDeclaration[]) => readonly string[];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* A served-equivalent shape the M1 grade feeds a host's filter (ggui#1414):
|
|
182
|
+
* agent tools as ggui declares them — `ggui_render` and `ggui_update` stamp
|
|
183
|
+
* `visibility: ['model']`, the others carry no marker (the spec's default)
|
|
184
|
+
* — and ggui's six app-only `ggui_runtime_*` tools. The list is static and
|
|
185
|
+
* the names are the protocol's own, so it grades the FILTER a host applies,
|
|
186
|
+
* never the host's live `tools/list`: a host that filters by name rather
|
|
187
|
+
* than by visibility passes only as long as this list matches what its
|
|
188
|
+
* server lists, which is why the grade wants the filter and the SPEC
|
|
189
|
+
* points at the predicate.
|
|
190
|
+
*/
|
|
191
|
+
export declare const MODEL_TOOL_SET_FIXTURE: readonly ServedToolDeclaration[];
|
|
147
192
|
export interface HostHelperConformanceOptions {
|
|
148
193
|
/**
|
|
149
194
|
* How long a refusal may take before it counts as a hang (H4 /
|
|
@@ -153,7 +198,8 @@ export interface HostHelperConformanceOptions {
|
|
|
153
198
|
readonly refusalTimeoutMs?: number;
|
|
154
199
|
/**
|
|
155
200
|
* Chrome audit for the C-grades. Absent ⇒ C cases report `skip`
|
|
156
|
-
* (self-certification pending) — the tier is decided by H/R
|
|
201
|
+
* (self-certification pending) — the tier is decided by H/R and by
|
|
202
|
+
* whichever optional grades (C1, T1, M1) were supplied.
|
|
157
203
|
*/
|
|
158
204
|
readonly chromeAudit?: ChromeAudit;
|
|
159
205
|
/**
|
|
@@ -162,6 +208,12 @@ export interface HostHelperConformanceOptions {
|
|
|
162
208
|
* unthemed helper surface has no coverage obligation.
|
|
163
209
|
*/
|
|
164
210
|
readonly themeCoverage?: ThemeCoverageOptions;
|
|
211
|
+
/**
|
|
212
|
+
* The host's model-facing tool filter for the M1 grade (ggui#1414).
|
|
213
|
+
* Absent ⇒ M1 reports `skip` — the door is still the host's obligation;
|
|
214
|
+
* it is just not graded here.
|
|
215
|
+
*/
|
|
216
|
+
readonly modelToolSet?: ModelToolSetOptions;
|
|
165
217
|
}
|
|
166
218
|
export type HostHelperCaseOutcome = 'pass' | 'fail' | 'skip' | 'warn';
|
|
167
219
|
export interface HostHelperCaseResult {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host-helper-conformance/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host-helper-conformance/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -60,11 +60,45 @@
|
|
|
60
60
|
* reference validate is `@ggui-ai/design`'s `validateThemeCoverage`
|
|
61
61
|
* bound to the shipped `consumed-tokens.manifest.json` tokens.
|
|
62
62
|
*
|
|
63
|
+
* - `M1-model-tool-set` — OPTIONAL like T1, graded when the host supplies
|
|
64
|
+
* the filter it uses to derive the MODEL's tool list from a served
|
|
65
|
+
* `tools/list` (ggui#1414): the result must withhold every tool whose
|
|
66
|
+
* `_meta.ui.visibility` lacks `'model'` (ggui's six `ggui_runtime_*`
|
|
67
|
+
* app-only tools) and keep every other tool. SEP-1865 assigns this door
|
|
68
|
+
* to the host, and the server cannot observe a violation — a
|
|
69
|
+
* view-issued and a model-issued call are indistinguishable on the wire
|
|
70
|
+
* — so the kit is where a host proves it. Absent ⇒ `skip`.
|
|
71
|
+
*
|
|
63
72
|
* A helper that refuses the relay honestly is graded **tier
|
|
64
73
|
* `read-only`** — a LEGAL grade, with the R cases skipped, never
|
|
65
74
|
* failed. `nonconforming` means a dishonesty case failed.
|
|
66
75
|
*/
|
|
76
|
+
import { toolVisibleToModel, } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
67
77
|
import { renderLocatorUri } from '../resource-read-conformance/index.js';
|
|
78
|
+
/**
|
|
79
|
+
* A served-equivalent shape the M1 grade feeds a host's filter (ggui#1414):
|
|
80
|
+
* agent tools as ggui declares them — `ggui_render` and `ggui_update` stamp
|
|
81
|
+
* `visibility: ['model']`, the others carry no marker (the spec's default)
|
|
82
|
+
* — and ggui's six app-only `ggui_runtime_*` tools. The list is static and
|
|
83
|
+
* the names are the protocol's own, so it grades the FILTER a host applies,
|
|
84
|
+
* never the host's live `tools/list`: a host that filters by name rather
|
|
85
|
+
* than by visibility passes only as long as this list matches what its
|
|
86
|
+
* server lists, which is why the grade wants the filter and the SPEC
|
|
87
|
+
* points at the predicate.
|
|
88
|
+
*/
|
|
89
|
+
export const MODEL_TOOL_SET_FIXTURE = [
|
|
90
|
+
{ name: 'ggui_handshake' },
|
|
91
|
+
{ name: 'ggui_render', _meta: { ui: { visibility: ['model'] } } },
|
|
92
|
+
{ name: 'ggui_update', _meta: { ui: { visibility: ['model'] } } },
|
|
93
|
+
{ name: 'ggui_consume' },
|
|
94
|
+
{ name: 'ggui_amend' },
|
|
95
|
+
{ name: 'ggui_runtime_submit_action', _meta: { ui: { visibility: ['app'] } } },
|
|
96
|
+
{ name: 'ggui_runtime_pull', _meta: { ui: { visibility: ['app'] } } },
|
|
97
|
+
{ name: 'ggui_runtime_sync_context', _meta: { ui: { visibility: ['app'] } } },
|
|
98
|
+
{ name: 'ggui_runtime_refresh_ws_token', _meta: { ui: { visibility: ['app'] } } },
|
|
99
|
+
{ name: 'ggui_runtime_telemetry', _meta: { ui: { visibility: ['app'] } } },
|
|
100
|
+
{ name: 'ggui_runtime_declare_tool_catalog', _meta: { ui: { visibility: ['app'] } } },
|
|
101
|
+
];
|
|
68
102
|
const METHOD_NOT_SUPPORTED = -32601;
|
|
69
103
|
const DEFAULT_REFUSAL_TIMEOUT_MS = 2_000;
|
|
70
104
|
/** A method no ggui helper answers — the honest-refusal probe. */
|
|
@@ -72,8 +106,7 @@ const UNSUPPORTED_PROBE_METHOD = 'ggui-conformance/unsupported-probe';
|
|
|
72
106
|
/**
|
|
73
107
|
* The `resources/read` probe's URI: a well-formed render locator naming
|
|
74
108
|
* a render that does not exist, built with the kit's own locator grammar
|
|
75
|
-
* (one owner, `resource-read-conformance`; the kit never
|
|
76
|
-
* live protocol types). It is a locator on purpose, so a helper that
|
|
109
|
+
* (one owner, `resource-read-conformance`; the kit compiles against the protocol's published integrations only where a predicate IS the contract (`toolVisibleToModel`), never against live wire types). It is a locator on purpose, so a helper that
|
|
77
110
|
* forwards only `ui://ggui/render/` reads still forwards it; a relaying
|
|
78
111
|
* helper then hands back the server's own classification of the miss,
|
|
79
112
|
* which is an answer (ggui#1304).
|
|
@@ -438,6 +471,46 @@ export async function runHostHelperConformance(port, options = {}) {
|
|
|
438
471
|
});
|
|
439
472
|
}
|
|
440
473
|
}
|
|
474
|
+
// ── M1: the model's tool set withholds app-only tools ─────────────
|
|
475
|
+
if (options.modelToolSet === undefined) {
|
|
476
|
+
cases.push({
|
|
477
|
+
id: 'M1-model-tool-set',
|
|
478
|
+
outcome: 'skip',
|
|
479
|
+
detail: 'no model-facing filter supplied — the visibility door is still the host\'s obligation (SEP-1865), just not graded here (supply modelToolSet to grade)',
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
const offered = new Set(options.modelToolSet.offeredToModel(MODEL_TOOL_SET_FIXTURE));
|
|
484
|
+
const leaked = MODEL_TOOL_SET_FIXTURE.filter((t) => !toolVisibleToModel(t._meta?.ui?.visibility) && offered.has(t.name)).map((t) => t.name);
|
|
485
|
+
const withheld = MODEL_TOOL_SET_FIXTURE.filter((t) => toolVisibleToModel(t._meta?.ui?.visibility) && !offered.has(t.name)).map((t) => t.name);
|
|
486
|
+
const served = new Set(MODEL_TOOL_SET_FIXTURE.map((t) => t.name));
|
|
487
|
+
const unknown = [...offered].filter((name) => !served.has(name));
|
|
488
|
+
if (leaked.length === 0 && withheld.length === 0 && unknown.length === 0) {
|
|
489
|
+
cases.push({
|
|
490
|
+
id: 'M1-model-tool-set',
|
|
491
|
+
outcome: 'pass',
|
|
492
|
+
detail: `the model's tool set withholds every app-only tool and keeps every model-visible one (${offered.size} offered)`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
const parts = [
|
|
497
|
+
leaked.length > 0
|
|
498
|
+
? `app-only tools offered to the model (${leaked.length}): ${leaked.join(', ')} — a model holding the app credential can call them`
|
|
499
|
+
: undefined,
|
|
500
|
+
withheld.length > 0
|
|
501
|
+
? `model-visible tools withheld (${withheld.length}): ${withheld.join(', ')} — the agent cannot run the loop without them`
|
|
502
|
+
: undefined,
|
|
503
|
+
unknown.length > 0
|
|
504
|
+
? `names offered that the server never listed (${unknown.length}): ${unknown.join(', ')} — the list is not derived from tools/list`
|
|
505
|
+
: undefined,
|
|
506
|
+
].filter((part) => part !== undefined);
|
|
507
|
+
cases.push({
|
|
508
|
+
id: 'M1-model-tool-set',
|
|
509
|
+
outcome: 'fail',
|
|
510
|
+
detail: parts.join('; '),
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
441
514
|
const failures = cases
|
|
442
515
|
.filter((c) => c.outcome === 'fail')
|
|
443
516
|
.map((c) => c.id);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "release-13-render-result",
|
|
3
|
+
"description": "A ggui_render result as ggui tag 13 (5903985f2, serving prod) emitted it on a cold generation — captured verbatim from dev serving that tag (`ggui-protocol-5903985f2f46`, 2026-09-27T04:36Z) on a fixture app: `outcome: rendered`, the six identity fields, and the cache marker, WITHOUT `effort`. Today's renderOutputSchema MUST accept it: `effort` (#1459) is optional, so a consumer holding this release's result keeps parsing (VERSION-POLICY §3.6).",
|
|
4
|
+
"release": {
|
|
5
|
+
"label": "ggui tag 13",
|
|
6
|
+
"sha": "5903985f2"
|
|
7
|
+
},
|
|
8
|
+
"wire": "render-result",
|
|
9
|
+
"payload": {
|
|
10
|
+
"outcome": "rendered",
|
|
11
|
+
"sessionId": "render_63607d1b-afbf-405b-aa0d-43e1c27fc474",
|
|
12
|
+
"resourceUri": "ui://ggui/render/render_63607d1b-afbf-405b-aa0d-43e1c27fc474/9a679f0634bac356",
|
|
13
|
+
"action": "create",
|
|
14
|
+
"contractHash": "9a679f0634bac356",
|
|
15
|
+
"blueprintId": "bp_62c8adae-6405-4093-8a35-15f827a6f703",
|
|
16
|
+
"variantKey": "44136fa355b3678a",
|
|
17
|
+
"cache": {
|
|
18
|
+
"hit": false,
|
|
19
|
+
"llmCallsAvoided": 0,
|
|
20
|
+
"kind": "cold",
|
|
21
|
+
"reason": "cold: generated fresh — no saved interface fit this render"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"expect": "accepted"
|
|
25
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The protocol-owned wires the catalog can grade. */
|
|
2
|
-
export declare const N1_COMPAT_WIRES: readonly ["app-theme", "app-theme-read", "app-theme-carry", "render-meta", "generation-profile", "ops-generate-blueprint", "handshake-suggestion"];
|
|
2
|
+
export declare const N1_COMPAT_WIRES: readonly ["app-theme", "app-theme-read", "app-theme-carry", "render-meta", "generation-profile", "ops-generate-blueprint", "handshake-suggestion", "render-result"];
|
|
3
3
|
/** `backward`: the previous release's payload against today's parser. `forward`: a later release's payload against today's READ door. */
|
|
4
4
|
export declare const N1_COMPAT_DIRECTIONS: readonly ["backward", "forward"];
|
|
5
5
|
export type N1CompatDirection = (typeof N1_COMPAT_DIRECTIONS)[number];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/n1-compat-conformance/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/n1-compat-conformance/index.ts"],"names":[],"mappings":"AAgCA,sDAAsD;AACtD,eAAO,MAAM,eAAe,qKAAsK,CAAC;AAEnM,yIAAyI;AACzI,eAAO,MAAM,oBAAoB,kCAAmC,CAAC;AACrE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iKAAiK;IACjK,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,gGAAgG;IAChG,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAgDD,eAAO,MAAM,eAAe,EAAE,SAAS,YAAY,EAUhC,CAAC;AA6GpB,4DAA4D;AAC5D,wBAAgB,sBAAsB,IAAI,SAAS,cAAc,EAAE,CAoBlE"}
|
|
@@ -14,18 +14,19 @@
|
|
|
14
14
|
* Pure-function catalog: no transport, no adopter input — graded on every
|
|
15
15
|
* `runConformance()` and by the kit's own unit lane.
|
|
16
16
|
*/
|
|
17
|
-
import { appGenerationProfileSchema, appThemeGetResponseSchema, appThemeSchema, handshakeSuggestionSchema, opsGenerateBlueprintInputSchema, parseAppThemeAtReadDoor } from '@ggui-ai/protocol';
|
|
17
|
+
import { appGenerationProfileSchema, appThemeGetResponseSchema, appThemeSchema, handshakeSuggestionSchema, opsGenerateBlueprintInputSchema, parseAppThemeAtReadDoor, renderOutputSchema } from '@ggui-ai/protocol';
|
|
18
18
|
import { MCP_APP_AI_GGUI_RENDER_META_KEY, parseMcpAppAiGguiRenderMeta, } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
19
19
|
import release2AppThemeV2 from './cases/release-2-app-theme-v2.json' with { type: 'json' };
|
|
20
20
|
import release2RenderMeta from './cases/release-2-render-meta.json' with { type: 'json' };
|
|
21
21
|
import release2GenerationProfile from './cases/release-2-generation-profile.json' with { type: 'json' };
|
|
22
22
|
import release2OpsGenerateBlueprint from './cases/release-2-ops-generate-blueprint.json' with { type: 'json' };
|
|
23
23
|
import release91HandshakeSuggestion from './cases/release-9-1-handshake-suggestion.json' with { type: 'json' };
|
|
24
|
+
import release13RenderResult from './cases/release-13-render-result.json' with { type: 'json' };
|
|
24
25
|
import forwardAppThemeUnknownMember from './cases/forward-app-theme-unknown-member.json' with { type: 'json' };
|
|
25
26
|
import forwardAppThemeCarryUnknownMember from './cases/forward-app-theme-carry-unknown-member.json' with { type: 'json' };
|
|
26
27
|
import forwardRenderMetaUnknownMember from './cases/forward-render-meta-unknown-member.json' with { type: 'json' };
|
|
27
28
|
/** The protocol-owned wires the catalog can grade. */
|
|
28
|
-
export const N1_COMPAT_WIRES = ['app-theme', 'app-theme-read', 'app-theme-carry', 'render-meta', 'generation-profile', 'ops-generate-blueprint', 'handshake-suggestion'];
|
|
29
|
+
export const N1_COMPAT_WIRES = ['app-theme', 'app-theme-read', 'app-theme-carry', 'render-meta', 'generation-profile', 'ops-generate-blueprint', 'handshake-suggestion', 'render-result'];
|
|
29
30
|
/** `backward`: the previous release's payload against today's parser. `forward`: a later release's payload against today's READ door. */
|
|
30
31
|
export const N1_COMPAT_DIRECTIONS = ['backward', 'forward'];
|
|
31
32
|
function isN1CompatWire(v) {
|
|
@@ -82,6 +83,7 @@ export const N1_COMPAT_CASES = [
|
|
|
82
83
|
release2GenerationProfile,
|
|
83
84
|
release2OpsGenerateBlueprint,
|
|
84
85
|
release91HandshakeSuggestion,
|
|
86
|
+
release13RenderResult,
|
|
85
87
|
forwardAppThemeUnknownMember,
|
|
86
88
|
forwardAppThemeCarryUnknownMember,
|
|
87
89
|
forwardRenderMetaUnknownMember,
|
|
@@ -186,6 +188,14 @@ function gradeHandshakeSuggestion(payload) {
|
|
|
186
188
|
? { pass: true, detail: 'handshakeSuggestionSchema: accepted' }
|
|
187
189
|
: { pass: false, detail: `handshakeSuggestionSchema refused the previous release's suggestion: ${r.error.issues.map((i) => i.message).join('; ')}` };
|
|
188
190
|
}
|
|
191
|
+
// ggui#1459 — the render result the served release emits must keep parsing when
|
|
192
|
+
// the result gains an optional member (`effort`).
|
|
193
|
+
function gradeRenderResult(payload) {
|
|
194
|
+
const r = renderOutputSchema.safeParse(payload);
|
|
195
|
+
return r.success
|
|
196
|
+
? { pass: true, detail: 'renderOutputSchema: accepted' }
|
|
197
|
+
: { pass: false, detail: `renderOutputSchema refused the previous release's render result: ${r.error.issues.map((i) => i.message).join('; ')}` };
|
|
198
|
+
}
|
|
189
199
|
/** Grade every N−1 case against the protocol as shipped. */
|
|
190
200
|
export function runN1CompatConformance() {
|
|
191
201
|
return N1_COMPAT_CASES.map((c) => {
|
|
@@ -201,7 +211,9 @@ export function runN1CompatConformance() {
|
|
|
201
211
|
? gradeGenerationProfile(c.payload)
|
|
202
212
|
: c.wire === 'handshake-suggestion'
|
|
203
213
|
? gradeHandshakeSuggestion(c.payload)
|
|
204
|
-
:
|
|
214
|
+
: c.wire === 'render-result'
|
|
215
|
+
? gradeRenderResult(c.payload)
|
|
216
|
+
: gradeOpsGenerateBlueprint(c.payload);
|
|
205
217
|
return { name: c.name, direction: c.direction, pass: graded.pass, detail: `${c.release.label} (${c.release.sha}) → ${graded.detail}` };
|
|
206
218
|
});
|
|
207
219
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/protocol-conformance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Conformance test kit for the ggui protocol. Drives a behavioral fixture catalog against a live implementation over WebSocket, a resources/read catalog against a live MCP server, and nine pure-function catalogs (schema, registration, resolution, binding, props-schema, host-helper, refusal-envelope, registry-completeness, transport-refusal) for the gadget, tool-binding, schema-precise-render, host-helper and pre-generation-refusal obligations, reporting a scorecard-style pass / fail / skip. MCP builders adopt this kit to prove their implementation is protocol-conformant.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
},
|
|
105
105
|
"dependencies": {
|
|
106
106
|
"ws": "^8.20.1",
|
|
107
|
-
"@ggui-ai/protocol": "0.
|
|
107
|
+
"@ggui-ai/protocol": "0.25.0"
|
|
108
108
|
},
|
|
109
109
|
"devDependencies": {
|
|
110
110
|
"@types/node": "^22.0.0",
|