@ggui-ai/protocol-conformance 0.7.0 → 0.9.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 +15 -12
- package/dist/binding-conformance/cases/bind-blueprint-no-contract-none.json +6 -0
- package/dist/binding-conformance/cases/bind-contract-without-tool-names-none.json +18 -0
- package/dist/binding-conformance/cases/bind-declared-gadget.json +13 -0
- package/dist/binding-conformance/cases/bind-declared-wins-over-contract.json +23 -0
- package/dist/binding-conformance/cases/bind-derived-caps-at-sixteen.json +54 -0
- package/dist/binding-conformance/cases/bind-derived-props-and-stream.json +36 -0
- package/dist/binding-conformance/cases/bind-derived-skips-invalid-names.json +40 -0
- package/dist/binding-conformance/cases/bind-derived-union-dedupes.json +38 -0
- package/dist/binding-conformance/cases/bind-gadget-undeclared-none.json +6 -0
- package/dist/binding-conformance/cases/bind-reject-charset.json +9 -0
- package/dist/binding-conformance/cases/bind-reject-duplicate-pair.json +12 -0
- package/dist/binding-conformance/cases/bind-reject-empty-list.json +9 -0
- package/dist/binding-conformance/cases/bind-reject-overlong-name.json +13 -0
- package/dist/binding-conformance/cases/bind-reject-server-charset.json +9 -0
- package/dist/binding-conformance/cases/bind-reject-too-many-entries.json +27 -0
- package/dist/binding-conformance/cases/filter-case-sensitive-server.json +7 -0
- package/dist/binding-conformance/cases/filter-case-sensitive-tool.json +7 -0
- package/dist/binding-conformance/cases/filter-none-passes-bound.json +7 -0
- package/dist/binding-conformance/cases/filter-none-passes-unbound.json +6 -0
- package/dist/binding-conformance/cases/filter-pair-bare-never-matches.json +7 -0
- package/dist/binding-conformance/cases/filter-pair-exact.json +10 -0
- package/dist/binding-conformance/cases/filter-pair-not-cross-entry.json +10 -0
- package/dist/binding-conformance/cases/filter-server-excludes-bare.json +7 -0
- package/dist/binding-conformance/cases/filter-server-match.json +7 -0
- package/dist/binding-conformance/cases/filter-tool-cross-server.json +7 -0
- package/dist/binding-conformance/cases/filter-tool-excludes-unbound.json +6 -0
- package/dist/binding-conformance/cases/filter-tool-matches-bare-entry.json +7 -0
- package/dist/binding-conformance/cases/filter-tool-no-match.json +7 -0
- package/dist/binding-conformance/index.d.ts +214 -0
- package/dist/binding-conformance/index.d.ts.map +1 -0
- package/dist/binding-conformance/index.js +153 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/resolution-conformance/cases/resolve-bundle-host-computed.json +3 -3
- package/dist/resolution-conformance/cases/resolve-default-host.json +2 -2
- package/dist/resolution-conformance/cases/resolve-loopback-host-http-scheme.json +2 -2
- package/dist/resource-read-conformance/cases/channel-less-read-mounts-inline.json +24 -0
- package/dist/resource-read-conformance/cases/pin-past-the-head-answers-not-found.json +29 -0
- package/dist/resource-read-conformance/cases/pinned-superseded-record-serves-its-reign-immutably.json +42 -0
- package/dist/resource-read-conformance/cases/read-without-a-delivery-channel-answers-not-mountable.json +15 -4
- package/dist/resource-read-conformance/index.d.ts +60 -4
- package/dist/resource-read-conformance/index.d.ts.map +1 -1
- package/dist/resource-read-conformance/index.js +144 -11
- package/package.json +8 -3
|
@@ -76,6 +76,15 @@ export type ResourceReadSeed = {
|
|
|
76
76
|
/** A live render row that has committed a component. */
|
|
77
77
|
readonly kind: 'committed-render';
|
|
78
78
|
readonly session: string;
|
|
79
|
+
/**
|
|
80
|
+
* Size class of the committed component relative to the inline
|
|
81
|
+
* `codeB64` cap. `'under-inline-cap'` (the default) seeds a
|
|
82
|
+
* component the server can deliver inline even with no wired
|
|
83
|
+
* channels; `'over-inline-cap'` seeds one it cannot — the shape
|
|
84
|
+
* that still exercises the no-delivery-channel failure arm on a
|
|
85
|
+
* channel-less server.
|
|
86
|
+
*/
|
|
87
|
+
readonly size: 'under-inline-cap' | 'over-inline-cap';
|
|
79
88
|
} | {
|
|
80
89
|
/** A live render row whose generation has not committed yet. */
|
|
81
90
|
readonly kind: 'uncommitted-render';
|
|
@@ -89,6 +98,19 @@ export type ResourceReadSeed = {
|
|
|
89
98
|
*/
|
|
90
99
|
readonly kind: 'registered-blueprint';
|
|
91
100
|
readonly as: string;
|
|
101
|
+
} | {
|
|
102
|
+
/**
|
|
103
|
+
* A session with epoch HISTORY (#483): `records[0]` is the mint
|
|
104
|
+
* (epoch 0), each later entry one `ggui_update` (epoch N). The
|
|
105
|
+
* driver seeds the ledger so every superseded record is
|
|
106
|
+
* reconstructable (an in-reign props event per record) and the
|
|
107
|
+
* row carries the head epoch.
|
|
108
|
+
*/
|
|
109
|
+
readonly kind: 'epoch-history';
|
|
110
|
+
readonly session: string;
|
|
111
|
+
readonly records: ReadonlyArray<{
|
|
112
|
+
readonly props: Readonly<Record<string, unknown>>;
|
|
113
|
+
}>;
|
|
92
114
|
};
|
|
93
115
|
/**
|
|
94
116
|
* The blueprint-key segment of a locator.
|
|
@@ -118,6 +140,14 @@ export type ResourceReadLocator = {
|
|
|
118
140
|
readonly kind: 'render';
|
|
119
141
|
readonly session: string;
|
|
120
142
|
readonly key?: ResourceReadKey;
|
|
143
|
+
/**
|
|
144
|
+
* Epoch pin (#483, SPEC §7.1.2.2): present ⇒ the kit reads the
|
|
145
|
+
* PINNED history record (`…#N`); absent ⇒ the live head. The
|
|
146
|
+
* fragment encoding is pinned HERE deliberately — the kit is
|
|
147
|
+
* the arbiter of the wire grammar, so an encoding change is a
|
|
148
|
+
* conscious, visible kit change.
|
|
149
|
+
*/
|
|
150
|
+
readonly epoch?: number;
|
|
121
151
|
} | {
|
|
122
152
|
readonly kind: 'raw';
|
|
123
153
|
readonly uri: string;
|
|
@@ -160,6 +190,15 @@ export type ResourceReadExpectation = {
|
|
|
160
190
|
} | {
|
|
161
191
|
/** A result whose contents declare a delivery channel. */
|
|
162
192
|
readonly kind: 'live-mount';
|
|
193
|
+
} | {
|
|
194
|
+
/**
|
|
195
|
+
* A mount serving a HISTORY record (#483): still a real mount
|
|
196
|
+
* (delivery channel required), graded additionally on its
|
|
197
|
+
* props — every `propsInclude` entry must appear in the parsed
|
|
198
|
+
* `propsJson` with the authored value.
|
|
199
|
+
*/
|
|
200
|
+
readonly kind: 'pinned-mount';
|
|
201
|
+
readonly propsInclude?: Readonly<Record<string, unknown>>;
|
|
163
202
|
};
|
|
164
203
|
/** One read within a case. */
|
|
165
204
|
export interface ResourceReadProbe {
|
|
@@ -200,6 +239,14 @@ export interface ResourceReadConformanceCase {
|
|
|
200
239
|
* not excused here.
|
|
201
240
|
*/
|
|
202
241
|
readonly indistinguishable?: readonly string[];
|
|
242
|
+
/**
|
|
243
|
+
* Probe names whose MOUNT outcomes must carry byte-identical
|
|
244
|
+
* `propsJson` (#483 — pinned-record immutability). Deliberately
|
|
245
|
+
* compares ONLY the props: live-channel tokens and expiries on the
|
|
246
|
+
* same meta legitimately differ per read; the RECORD is what may
|
|
247
|
+
* not.
|
|
248
|
+
*/
|
|
249
|
+
readonly identicalMounts?: readonly string[];
|
|
203
250
|
/**
|
|
204
251
|
* Literal substrings that MUST NOT appear anywhere in any error frame
|
|
205
252
|
* this case produces. The values a refusal would leak if a diagnostic
|
|
@@ -225,14 +272,22 @@ export interface ResourceReadScenario {
|
|
|
225
272
|
* markup that transports it is not.
|
|
226
273
|
*/
|
|
227
274
|
export interface ResourceReadRenderMeta {
|
|
228
|
-
/** A fetchable component URL — the static delivery channel. */
|
|
275
|
+
/** A fetchable component URL — the fetched static delivery channel. */
|
|
229
276
|
readonly codeUrl?: string;
|
|
277
|
+
/** Base64 component source — the inline static delivery channel. */
|
|
278
|
+
readonly codeB64?: string;
|
|
230
279
|
/** The live channel's endpoint. Only a channel WITH a token. */
|
|
231
280
|
readonly wsUrl?: string;
|
|
232
281
|
/** The live channel's token. Only a channel WITH an endpoint. */
|
|
233
282
|
readonly wsToken?: string;
|
|
234
283
|
/** A server-emitted system card, which carries its own content. */
|
|
235
284
|
readonly kind?: string;
|
|
285
|
+
/**
|
|
286
|
+
* The mount's props as a JSON string (#483). Graded by the
|
|
287
|
+
* `pinned-mount` expectation and the `identicalMounts` group;
|
|
288
|
+
* ungraded elsewhere.
|
|
289
|
+
*/
|
|
290
|
+
readonly propsJson?: string;
|
|
236
291
|
}
|
|
237
292
|
/** The raw JSON-RPC error body a server put on the wire. */
|
|
238
293
|
export interface JsonRpcErrorFrame {
|
|
@@ -280,7 +335,7 @@ export type ResourceReadScenarioDriver = (scenario: ResourceReadScenario) => Pro
|
|
|
280
335
|
/**
|
|
281
336
|
* Every resource-read case the kit ships, in deterministic order: the
|
|
282
337
|
* four typed classes, then the disclosure fusions, then the boundary of
|
|
283
|
-
* the classification, then the
|
|
338
|
+
* the classification, then the three mount cases.
|
|
284
339
|
*/
|
|
285
340
|
export declare const resourceReadCases: readonly ResourceReadConformanceCase[];
|
|
286
341
|
/** The resource URI every render locator hangs off. */
|
|
@@ -293,8 +348,9 @@ export declare const GGUI_RENDER_RESOURCE_URI = "ui://ggui/render";
|
|
|
293
348
|
export declare function renderLocatorUri(locator: ResourceReadLocator, registeredKeys?: Readonly<Record<string, string>>): string;
|
|
294
349
|
/**
|
|
295
350
|
* Does this render meta carry enough to paint something? One of the
|
|
296
|
-
*
|
|
297
|
-
* live channel, or a
|
|
351
|
+
* four delivery channels a runtime knows: a fetchable static component
|
|
352
|
+
* URL, inline base64 component source, a live channel, or a
|
|
353
|
+
* server-emitted system card.
|
|
298
354
|
*
|
|
299
355
|
* The live channel needs BOTH halves — an endpoint with no token cannot
|
|
300
356
|
* be opened, and a token with no endpoint has nowhere to go — so a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resource-read-conformance/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resource-read-conformance/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -110,13 +110,16 @@ import registryFusion from './cases/registry-match-fuses-on-not-mountable.json'
|
|
|
110
110
|
import malformedLocator from './cases/malformed-locator-stays-outside-the-typed-set.json' with { type: 'json' };
|
|
111
111
|
import liveRowMount from './cases/live-row-read-returns-mount-material.json' with { type: 'json' };
|
|
112
112
|
import remintMount from './cases/evicted-row-remints-from-the-durable-record.json' with { type: 'json' };
|
|
113
|
+
import inlineMount from './cases/channel-less-read-mounts-inline.json' with { type: 'json' };
|
|
114
|
+
import pinnedImmutable from './cases/pinned-superseded-record-serves-its-reign-immutably.json' with { type: 'json' };
|
|
115
|
+
import pinPastHead from './cases/pin-past-the-head-answers-not-found.json' with { type: 'json' };
|
|
113
116
|
// =============================================================================
|
|
114
117
|
// Catalog
|
|
115
118
|
// =============================================================================
|
|
116
119
|
/**
|
|
117
120
|
* Every resource-read case the kit ships, in deterministic order: the
|
|
118
121
|
* four typed classes, then the disclosure fusions, then the boundary of
|
|
119
|
-
* the classification, then the
|
|
122
|
+
* the classification, then the three mount cases.
|
|
120
123
|
*/
|
|
121
124
|
export const resourceReadCases = [
|
|
122
125
|
notFoundOnAMiss,
|
|
@@ -132,6 +135,9 @@ export const resourceReadCases = [
|
|
|
132
135
|
malformedLocator,
|
|
133
136
|
liveRowMount,
|
|
134
137
|
remintMount,
|
|
138
|
+
inlineMount,
|
|
139
|
+
pinnedImmutable,
|
|
140
|
+
pinPastHead,
|
|
135
141
|
];
|
|
136
142
|
// =============================================================================
|
|
137
143
|
// Locator grammar + the mount predicate
|
|
@@ -146,22 +152,24 @@ export const GGUI_RENDER_RESOURCE_URI = 'ui://ggui/render';
|
|
|
146
152
|
export function renderLocatorUri(locator, registeredKeys = {}) {
|
|
147
153
|
if (locator.kind === 'raw')
|
|
148
154
|
return locator.uri;
|
|
155
|
+
const pin = locator.epoch === undefined ? '' : `#${locator.epoch}`;
|
|
149
156
|
const base = `${GGUI_RENDER_RESOURCE_URI}/${locator.session}`;
|
|
150
157
|
const keyRef = locator.key;
|
|
151
158
|
if (keyRef === undefined)
|
|
152
|
-
return base
|
|
159
|
+
return `${base}${pin}`;
|
|
153
160
|
if (keyRef.kind === 'literal')
|
|
154
|
-
return `${base}/${keyRef.value}`;
|
|
161
|
+
return `${base}/${keyRef.value}${pin}`;
|
|
155
162
|
const key = registeredKeys[keyRef.seed];
|
|
156
163
|
if (key === undefined) {
|
|
157
164
|
throw new Error(`no key was reported for registry seed '${keyRef.seed}'`);
|
|
158
165
|
}
|
|
159
|
-
return `${base}/${key}`;
|
|
166
|
+
return `${base}/${key}${pin}`;
|
|
160
167
|
}
|
|
161
168
|
/**
|
|
162
169
|
* Does this render meta carry enough to paint something? One of the
|
|
163
|
-
*
|
|
164
|
-
* live channel, or a
|
|
170
|
+
* four delivery channels a runtime knows: a fetchable static component
|
|
171
|
+
* URL, inline base64 component source, a live channel, or a
|
|
172
|
+
* server-emitted system card.
|
|
165
173
|
*
|
|
166
174
|
* The live channel needs BOTH halves — an endpoint with no token cannot
|
|
167
175
|
* be opened, and a token with no endpoint has nowhere to go — so a
|
|
@@ -170,6 +178,8 @@ export function renderLocatorUri(locator, registeredKeys = {}) {
|
|
|
170
178
|
export function declaresDeliveryChannel(meta) {
|
|
171
179
|
if (typeof meta.codeUrl === 'string' && meta.codeUrl.length > 0)
|
|
172
180
|
return true;
|
|
181
|
+
if (typeof meta.codeB64 === 'string' && meta.codeB64.length > 0)
|
|
182
|
+
return true;
|
|
173
183
|
if (typeof meta.kind === 'string' && meta.kind.length > 0)
|
|
174
184
|
return true;
|
|
175
185
|
return (typeof meta.wsUrl === 'string' &&
|
|
@@ -284,6 +294,9 @@ async function gradeCase(testCase, prepared) {
|
|
|
284
294
|
const fusion = gradeIndistinguishable(testCase, outcomes);
|
|
285
295
|
if (fusion !== null)
|
|
286
296
|
return { kind: 'fail', failure: fusion };
|
|
297
|
+
const pinned = gradeIdenticalMounts(testCase, outcomes);
|
|
298
|
+
if (pinned !== null)
|
|
299
|
+
return { kind: 'fail', failure: pinned };
|
|
287
300
|
return { kind: 'pass' };
|
|
288
301
|
}
|
|
289
302
|
function gradeProbe(testCase, probe, uri, outcome) {
|
|
@@ -304,6 +317,38 @@ function gradeProbe(testCase, probe, uri, outcome) {
|
|
|
304
317
|
}
|
|
305
318
|
return null;
|
|
306
319
|
}
|
|
320
|
+
if (probe.expect.kind === 'pinned-mount') {
|
|
321
|
+
if (outcome.kind !== 'mount') {
|
|
322
|
+
return fail('a pinned history record within the servable window mounts (SPEC §7.1.2.2)', 'a result whose contents declare a delivery channel and carry the record props', `reading '${uri}' failed, but this pinned record resolves on this server.`);
|
|
323
|
+
}
|
|
324
|
+
if (!declaresDeliveryChannel(outcome.renderMeta)) {
|
|
325
|
+
return fail('a pinned mount is still a mount', 'render meta declaring a delivery channel', `reading '${uri}' succeeded with contents that declare no delivery channel.`);
|
|
326
|
+
}
|
|
327
|
+
const include = probe.expect.propsInclude;
|
|
328
|
+
if (include !== undefined) {
|
|
329
|
+
const raw = outcome.renderMeta.propsJson;
|
|
330
|
+
let parsed = null;
|
|
331
|
+
if (typeof raw === 'string') {
|
|
332
|
+
try {
|
|
333
|
+
const candidate = JSON.parse(raw);
|
|
334
|
+
if (isRecord(candidate))
|
|
335
|
+
parsed = candidate;
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
parsed = null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (parsed === null) {
|
|
342
|
+
return fail('a pinned mount carries its record props (SPEC §7.1.2.2)', 'parseable propsJson on the render meta', `reading '${uri}' mounted, but its render meta carries no parseable propsJson to grade the record against.`);
|
|
343
|
+
}
|
|
344
|
+
for (const [key, expected] of Object.entries(include)) {
|
|
345
|
+
if (JSON.stringify(parsed[key]) !== JSON.stringify(expected)) {
|
|
346
|
+
return fail('a pinned record serves the props of ITS epoch, never a neighbor\'s', `propsJson entry '${key}' = ${JSON.stringify(expected)}`, `reading '${uri}' served '${key}' = ${JSON.stringify(parsed[key])}.`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
307
352
|
if (outcome.kind !== 'error') {
|
|
308
353
|
return fail('a read that cannot be mounted answers with a typed error, never a success-shaped result', probe.expect, `reading '${uri}' returned a result, but this locator cannot be mounted on this server.`);
|
|
309
354
|
}
|
|
@@ -371,6 +416,40 @@ function gradeDisclosure(testCase, outcomes) {
|
|
|
371
416
|
}
|
|
372
417
|
return null;
|
|
373
418
|
}
|
|
419
|
+
function gradeIdenticalMounts(testCase, outcomes) {
|
|
420
|
+
const group = testCase.identicalMounts ?? [];
|
|
421
|
+
if (group.length < 2)
|
|
422
|
+
return null;
|
|
423
|
+
let reference = null;
|
|
424
|
+
for (const probeName of group) {
|
|
425
|
+
const outcome = outcomes.get(probeName);
|
|
426
|
+
if (outcome === undefined || outcome.kind !== 'mount' || outcome.renderMeta.propsJson === undefined) {
|
|
427
|
+
return {
|
|
428
|
+
name: testCase.name,
|
|
429
|
+
probe: probeName,
|
|
430
|
+
obligation: 'a pinned history record serves identical props on every read (SPEC §7.1.2.2)',
|
|
431
|
+
expected: 'a mount outcome carrying propsJson to compare',
|
|
432
|
+
actual: outcome ?? '(no outcome recorded)',
|
|
433
|
+
message: `probe '${probeName}' is named in this case's identicalMounts group but did not produce a props-carrying mount, so immutability cannot even be compared.`,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
if (reference === null) {
|
|
437
|
+
reference = { probe: probeName, propsJson: outcome.renderMeta.propsJson };
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (outcome.renderMeta.propsJson !== reference.propsJson) {
|
|
441
|
+
return {
|
|
442
|
+
name: testCase.name,
|
|
443
|
+
probe: probeName,
|
|
444
|
+
obligation: 'a pinned history record serves identical props on every read (SPEC §7.1.2.2)',
|
|
445
|
+
expected: reference.propsJson,
|
|
446
|
+
actual: outcome.renderMeta.propsJson,
|
|
447
|
+
message: `probes '${reference.probe}' and '${probeName}' read the same pinned record and got different props — the record mutated between reads.`,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
374
453
|
function gradeIndistinguishable(testCase, outcomes) {
|
|
375
454
|
const group = testCase.indistinguishable ?? [];
|
|
376
455
|
if (group.length < 2)
|
|
@@ -526,6 +605,14 @@ export function parseCase(input) {
|
|
|
526
605
|
probeNames.add(parsed.as);
|
|
527
606
|
return parsed;
|
|
528
607
|
});
|
|
608
|
+
const mountGroup = input['identicalMounts'];
|
|
609
|
+
if (mountGroup !== undefined) {
|
|
610
|
+
if (!Array.isArray(mountGroup))
|
|
611
|
+
throw bad("'identicalMounts' must be an array of probe names");
|
|
612
|
+
if (mountGroup.length < 2) {
|
|
613
|
+
throw bad("'identicalMounts' names a single probe — a group of one asserts nothing, which reads as an obligation being graded when it is not");
|
|
614
|
+
}
|
|
615
|
+
}
|
|
529
616
|
const group = input['indistinguishable'];
|
|
530
617
|
if (group !== undefined) {
|
|
531
618
|
if (!Array.isArray(group))
|
|
@@ -560,6 +647,7 @@ export function parseCase(input) {
|
|
|
560
647
|
seeds,
|
|
561
648
|
reads,
|
|
562
649
|
...(group !== undefined ? { indistinguishable: group.map(String) } : {}),
|
|
650
|
+
...(mountGroup !== undefined ? { identicalMounts: mountGroup.map(String) } : {}),
|
|
563
651
|
...(secrets !== undefined ? { disclosesNothing: secrets.map(String) } : {}),
|
|
564
652
|
};
|
|
565
653
|
}
|
|
@@ -588,6 +676,7 @@ const CASE_KEYS = [
|
|
|
588
676
|
'seeds',
|
|
589
677
|
'reads',
|
|
590
678
|
'indistinguishable',
|
|
679
|
+
'identicalMounts',
|
|
591
680
|
'disclosesNothing',
|
|
592
681
|
];
|
|
593
682
|
const SERVER_KEYS = [
|
|
@@ -600,12 +689,13 @@ const PROBE_KEYS = ['as', 'locator', 'expect'];
|
|
|
600
689
|
const SEED_KEYS = {
|
|
601
690
|
'identity-record': ['kind', 'session', 'key', 'blueprint'],
|
|
602
691
|
'durable-blueprint': ['kind', 'componentRef', 'body'],
|
|
603
|
-
'committed-render': ['kind', 'session'],
|
|
692
|
+
'committed-render': ['kind', 'session', 'size'],
|
|
693
|
+
'epoch-history': ['kind', 'session', 'records'],
|
|
604
694
|
'uncommitted-render': ['kind', 'session'],
|
|
605
695
|
'registered-blueprint': ['kind', 'as'],
|
|
606
696
|
};
|
|
607
697
|
const LOCATOR_KEYS = {
|
|
608
|
-
render: ['kind', 'session', 'key'],
|
|
698
|
+
render: ['kind', 'session', 'key', 'epoch'],
|
|
609
699
|
raw: ['kind', 'uri'],
|
|
610
700
|
};
|
|
611
701
|
const KEY_KEYS = {
|
|
@@ -616,6 +706,7 @@ const EXPECTATION_KEYS = {
|
|
|
616
706
|
'typed-error': ['kind', 'jsonRpcCode', 'dataCode', 'detailAbsent'],
|
|
617
707
|
'outside-typed-set': ['kind'],
|
|
618
708
|
'live-mount': ['kind'],
|
|
709
|
+
'pinned-mount': ['kind', 'propsInclude'],
|
|
619
710
|
};
|
|
620
711
|
function parseSubstrate(bad, value) {
|
|
621
712
|
switch (value) {
|
|
@@ -665,16 +756,37 @@ function parseSeed(bad, seed) {
|
|
|
665
756
|
}
|
|
666
757
|
return { kind: 'durable-blueprint', componentRef, body };
|
|
667
758
|
}
|
|
668
|
-
case 'committed-render':
|
|
759
|
+
case 'committed-render': {
|
|
760
|
+
const size = seed['size'] ?? 'under-inline-cap';
|
|
761
|
+
if (size !== 'under-inline-cap' && size !== 'over-inline-cap') {
|
|
762
|
+
throw bad("a committed-render seed's 'size' must be 'under-inline-cap' or 'over-inline-cap' (or omitted for the default)");
|
|
763
|
+
}
|
|
669
764
|
return {
|
|
670
765
|
kind: 'committed-render',
|
|
671
766
|
session: requireText(bad, seed['session'], "a committed-render seed's 'session'"),
|
|
767
|
+
size,
|
|
672
768
|
};
|
|
769
|
+
}
|
|
673
770
|
case 'uncommitted-render':
|
|
674
771
|
return {
|
|
675
772
|
kind: 'uncommitted-render',
|
|
676
773
|
session: requireText(bad, seed['session'], "an uncommitted-render seed's 'session'"),
|
|
677
774
|
};
|
|
775
|
+
case 'epoch-history': {
|
|
776
|
+
const session = requireText(bad, seed['session'], "epoch-history seed 'session'");
|
|
777
|
+
const rawRecords = seed['records'];
|
|
778
|
+
if (!Array.isArray(rawRecords) || rawRecords.length < 2) {
|
|
779
|
+
throw bad("epoch-history seed 'records' must be an array of at least TWO entries — one mint plus one update; a single-record history has nothing superseded to pin");
|
|
780
|
+
}
|
|
781
|
+
const records = rawRecords.map((entry, i) => {
|
|
782
|
+
if (!isRecord(entry) || !isRecord(entry['props'])) {
|
|
783
|
+
throw bad(`epoch-history seed record ${i} authors no 'props' object`);
|
|
784
|
+
}
|
|
785
|
+
rejectUnknownKeys(bad, entry, ['props'], `epoch-history seed record ${i}`);
|
|
786
|
+
return { props: entry['props'] };
|
|
787
|
+
});
|
|
788
|
+
return { kind: 'epoch-history', session, records };
|
|
789
|
+
}
|
|
678
790
|
case 'registered-blueprint':
|
|
679
791
|
return {
|
|
680
792
|
kind: 'registered-blueprint',
|
|
@@ -705,10 +817,19 @@ function parseLocator(bad, as, locator) {
|
|
|
705
817
|
switch (locator['kind']) {
|
|
706
818
|
case 'render': {
|
|
707
819
|
const session = requireText(bad, locator['session'], `probe '${as}' locator 'session'`);
|
|
820
|
+
const rawEpoch = locator['epoch'];
|
|
821
|
+
let epoch;
|
|
822
|
+
if (rawEpoch !== undefined) {
|
|
823
|
+
if (typeof rawEpoch !== 'number' || !Number.isInteger(rawEpoch) || rawEpoch < 0) {
|
|
824
|
+
throw bad(`probe '${as}' locator 'epoch' must be a non-negative integer`);
|
|
825
|
+
}
|
|
826
|
+
epoch = rawEpoch;
|
|
827
|
+
}
|
|
708
828
|
const key = locator['key'];
|
|
829
|
+
const base = { kind: 'render', session, ...(epoch !== undefined ? { epoch } : {}) };
|
|
709
830
|
if (key === undefined)
|
|
710
|
-
return
|
|
711
|
-
return {
|
|
831
|
+
return base;
|
|
832
|
+
return { ...base, key: parseKey(bad, as, key) };
|
|
712
833
|
}
|
|
713
834
|
case 'raw':
|
|
714
835
|
return { kind: 'raw', uri: requireText(bad, locator['uri'], `probe '${as}' locator 'uri'`) };
|
|
@@ -744,6 +865,18 @@ function parseExpectation(bad, as, expect) {
|
|
|
744
865
|
switch (expect['kind']) {
|
|
745
866
|
case 'outside-typed-set':
|
|
746
867
|
return { kind: 'outside-typed-set' };
|
|
868
|
+
case 'pinned-mount': {
|
|
869
|
+
const rawInclude = expect['propsInclude'];
|
|
870
|
+
if (rawInclude !== undefined && !isRecord(rawInclude)) {
|
|
871
|
+
throw bad(`probe '${as}' 'propsInclude' must be an object of expected prop entries`);
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
kind: 'pinned-mount',
|
|
875
|
+
...(rawInclude !== undefined
|
|
876
|
+
? { propsInclude: rawInclude }
|
|
877
|
+
: {}),
|
|
878
|
+
};
|
|
879
|
+
}
|
|
747
880
|
case 'live-mount':
|
|
748
881
|
return { kind: 'live-mount' };
|
|
749
882
|
case 'typed-error': {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/protocol-conformance",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Conformance test kit for the ggui protocol. Drives a behavioral fixture catalog against a live implementation over WebSocket, a resources/read catalog against a live MCP server, and
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Conformance test kit for the ggui protocol. Drives a behavioral fixture catalog against a live implementation over WebSocket, a resources/read catalog against a live MCP server, and four pure-function catalogs (schema, registration, resolution, binding) for the gadget and tool-binding obligations, reporting a scorecard-style pass / fail / skip. MCP builders adopt this kit to prove their implementation is protocol-conformant.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ggui",
|
|
@@ -50,11 +50,16 @@
|
|
|
50
50
|
"types": "./dist/resource-read-conformance/index.d.ts",
|
|
51
51
|
"import": "./dist/resource-read-conformance/index.js",
|
|
52
52
|
"default": "./dist/resource-read-conformance/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./binding-conformance": {
|
|
55
|
+
"types": "./dist/binding-conformance/index.d.ts",
|
|
56
|
+
"import": "./dist/binding-conformance/index.js",
|
|
57
|
+
"default": "./dist/binding-conformance/index.js"
|
|
53
58
|
}
|
|
54
59
|
},
|
|
55
60
|
"dependencies": {
|
|
56
61
|
"ws": "^8.20.1",
|
|
57
|
-
"@ggui-ai/protocol": "0.
|
|
62
|
+
"@ggui-ai/protocol": "0.9.0"
|
|
58
63
|
},
|
|
59
64
|
"devDependencies": {
|
|
60
65
|
"@types/node": "^22.0.0",
|