@ggui-ai/protocol-conformance 0.8.0 → 0.10.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 +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/props-schema-conformance/cases/empty-closed-wrapper.json +17 -0
- package/dist/props-schema-conformance/cases/format-in-core-assertive.json +28 -0
- package/dist/props-schema-conformance/cases/format-out-of-list-full.json +21 -0
- package/dist/props-schema-conformance/cases/nullable-normalization.json +23 -0
- package/dist/props-schema-conformance/cases/nullable-object-closed.json +50 -0
- package/dist/props-schema-conformance/cases/out-of-core-pattern-full.json +21 -0
- package/dist/props-schema-conformance/cases/scheduler-enum-authority.json +36 -0
- package/dist/props-schema-conformance/index.d.ts +57 -0
- package/dist/props-schema-conformance/index.d.ts.map +1 -0
- package/dist/props-schema-conformance/index.js +95 -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/index.d.ts +44 -0
- package/dist/resource-read-conformance/index.d.ts.map +1 -1
- package/dist/resource-read-conformance/index.js +128 -6
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -51,12 +51,13 @@ 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. Five 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
|
+
- **`@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).
|
|
60
61
|
|
|
61
62
|
```ts
|
|
62
63
|
import { runSchemaConformance } from "@ggui-ai/protocol-conformance/schema-conformance";
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export { allFixtures, fixturesByContract, bootstrapProtocolFixtures, consumeBuff
|
|
|
16
16
|
export type { ContractSlug } from './fixtures/index.js';
|
|
17
17
|
export { gadgetWireSchemaCases, runSchemaConformance, } from './schema-conformance/index.js';
|
|
18
18
|
export type { SchemaConformanceCase, SchemaConformanceMismatch, SchemaConformanceResult, } from './schema-conformance/index.js';
|
|
19
|
+
export { propsSchemaConformanceCases, runPropsSchemaConformance, } from './props-schema-conformance/index.js';
|
|
20
|
+
export type { PropsSchemaConformanceCase, PropsSchemaConformanceFailure, PropsSchemaConformanceReport, PropsSchemaConformanceSample, PropsSchemaImplementation, } from './props-schema-conformance/index.js';
|
|
19
21
|
export { gadgetRegistrationCases, runRegistrationConformance, } from './registration-conformance/index.js';
|
|
20
22
|
export type { GadgetGateRejectCode, GateOutcome, RegistrationConformanceCase, RegistrationConformanceMismatch, RegistrationConformanceResult, } from './registration-conformance/index.js';
|
|
21
23
|
export { gadgetResolutionCases, runResolutionConformance, } from './resolution-conformance/index.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;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,12 @@ export { allFixtures, fixturesByContract, bootstrapProtocolFixtures, consumeBuff
|
|
|
22
22
|
// Schema-conformance — which `DataContract.clientCapabilities`
|
|
23
23
|
// payloads a conformant parser MUST accept / reject.
|
|
24
24
|
export { gadgetWireSchemaCases, runSchemaConformance, } from './schema-conformance/index.js';
|
|
25
|
+
// Props-schema conformance — the schema-precise render arbiter
|
|
26
|
+
// (frozen shape 2026-08-19): the enforced-schema builder, its RFC 8785
|
|
27
|
+
// hash, the grammar-safe profile, and returned-schema AUTHORITY, each
|
|
28
|
+
// graded against caller-supplied callbacks over a polyglot JSON
|
|
29
|
+
// catalog. Includes the live-incident enum sample.
|
|
30
|
+
export { propsSchemaConformanceCases, runPropsSchemaConformance, } from './props-schema-conformance/index.js';
|
|
25
31
|
// Registration-conformance — which `(contract, appGadgets, appPublicEnv)`
|
|
26
32
|
// triples the push-time gadget gate stack MUST accept / reject, with
|
|
27
33
|
// which precise SPEC §7.9 reject code.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "empty-closed-wrapper",
|
|
3
|
+
"description": "A propsSpec-less / empty contract emits the empty closed wrapper — never omitted for a non-declined handshake. Non-empty props are invalid under it, making the props-without-propsSpec rejection schema-derivable.",
|
|
4
|
+
"propsSpec": { "properties": {} },
|
|
5
|
+
"propsSchema": {
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {},
|
|
8
|
+
"required": [],
|
|
9
|
+
"type": "object"
|
|
10
|
+
},
|
|
11
|
+
"propsSchemaHash": "d746974fa9afd5e951f76f9af38954b0ad7f436f2120dc974da65e5ee39f856f",
|
|
12
|
+
"propsSchemaProfile": "grammar-safe",
|
|
13
|
+
"samples": [
|
|
14
|
+
{ "props": {}, "valid": true },
|
|
15
|
+
{ "props": { "x": 1 }, "valid": false }
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "format-in-core-assertive",
|
|
3
|
+
"description": "The dialect pin: format is ASSERTIVE (not annotation-only), and the ten-format list is in the grammar-safe core. A malformed date-time MUST reject.",
|
|
4
|
+
"propsSpec": {
|
|
5
|
+
"properties": {
|
|
6
|
+
"when": {
|
|
7
|
+
"schema": { "type": "string", "format": "date-time" },
|
|
8
|
+
"required": true
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"propsSchema": {
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": { "when": { "format": "date-time", "type": "string" } },
|
|
15
|
+
"required": ["when"],
|
|
16
|
+
"type": "object"
|
|
17
|
+
},
|
|
18
|
+
"propsSchemaHash": "4a9fa57c154a96bdb5b4ac2f3efc878eb07a41cb64e10ac5701a87bee75ad885",
|
|
19
|
+
"propsSchemaProfile": "grammar-safe",
|
|
20
|
+
"samples": [
|
|
21
|
+
{ "props": { "when": "2026-08-19T12:00:00Z" }, "valid": true },
|
|
22
|
+
{
|
|
23
|
+
"props": { "when": "yesterday" },
|
|
24
|
+
"valid": false,
|
|
25
|
+
"note": "format-assertive semantics — annotation-only readers would wrongly accept this"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "format-out-of-list-full",
|
|
3
|
+
"description": "A validator-known format OUTSIDE the enumerated ten (url) classifies 'full'. Server enforcement is unchanged; only grammar compilation is off the table.",
|
|
4
|
+
"propsSpec": {
|
|
5
|
+
"properties": {
|
|
6
|
+
"link": { "schema": { "type": "string", "format": "url" } }
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"propsSchema": {
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": { "link": { "format": "url", "type": "string" } },
|
|
12
|
+
"required": [],
|
|
13
|
+
"type": "object"
|
|
14
|
+
},
|
|
15
|
+
"propsSchemaHash": "fa2b9ed841ac15c8a5ef525c61dc007532b7a6e6eea37ce7c6177ea5abe15213",
|
|
16
|
+
"propsSchemaProfile": "full",
|
|
17
|
+
"samples": [
|
|
18
|
+
{ "props": { "link": "https://example.com/a" }, "valid": true },
|
|
19
|
+
{ "props": { "link": "not a url" }, "valid": false }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nullable-normalization",
|
|
3
|
+
"description": "OpenAPI nullable:true normalizes to the canonical type union in the emitted bytes; null acceptance is preserved (the validator's nullable widening, made explicit).",
|
|
4
|
+
"propsSpec": {
|
|
5
|
+
"properties": {
|
|
6
|
+
"note": { "schema": { "type": "string", "nullable": true } }
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"propsSchema": {
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": { "note": { "type": ["string", "null"] } },
|
|
12
|
+
"required": [],
|
|
13
|
+
"type": "object"
|
|
14
|
+
},
|
|
15
|
+
"propsSchemaHash": "e45869c6cabb21553f1ff784c8dfdbbb1cd9992aa516ae98d995b1385a23bee8",
|
|
16
|
+
"propsSchemaProfile": "grammar-safe",
|
|
17
|
+
"samples": [
|
|
18
|
+
{ "props": { "note": null }, "valid": true },
|
|
19
|
+
{ "props": { "note": "x" }, "valid": true },
|
|
20
|
+
{ "props": { "note": 1 }, "valid": false },
|
|
21
|
+
{ "props": {}, "valid": true, "note": "note is not required" }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nullable-object-closed",
|
|
3
|
+
"description": "Closed shape survives nullability: a nullable free-form object node closes (additionalProperties:false), and the items of a nullable array are descended. Pins the round-2 injectClosedShape residual (2026-08-19) — single-string type comparisons silently left these subtrees open.",
|
|
4
|
+
"propsSpec": {
|
|
5
|
+
"properties": {
|
|
6
|
+
"meta": { "schema": { "type": ["object", "null"] } },
|
|
7
|
+
"rows": {
|
|
8
|
+
"schema": {
|
|
9
|
+
"type": ["array", "null"],
|
|
10
|
+
"items": { "type": "object", "properties": { "id": { "type": "string" } } }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"propsSchema": {
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"properties": {
|
|
18
|
+
"meta": {
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"type": ["object", "null"]
|
|
21
|
+
},
|
|
22
|
+
"rows": {
|
|
23
|
+
"items": {
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": { "id": { "type": "string" } },
|
|
26
|
+
"type": "object"
|
|
27
|
+
},
|
|
28
|
+
"type": ["array", "null"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": [],
|
|
32
|
+
"type": "object"
|
|
33
|
+
},
|
|
34
|
+
"propsSchemaHash": "8fcc242455134dd13ef9a6dbea78ff2b8f623798383e0fa27eacc107856dbc5c",
|
|
35
|
+
"propsSchemaProfile": "grammar-safe",
|
|
36
|
+
"samples": [
|
|
37
|
+
{ "props": { "meta": null, "rows": null }, "valid": true },
|
|
38
|
+
{ "props": { "meta": {}, "rows": [{ "id": "a" }] }, "valid": true },
|
|
39
|
+
{
|
|
40
|
+
"props": { "meta": { "x": 1 } },
|
|
41
|
+
"valid": false,
|
|
42
|
+
"note": "the nullable free-form object is CLOSED — undeclared keys reject"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"props": { "rows": [{ "id": "a", "extra": 1 }] },
|
|
46
|
+
"valid": false,
|
|
47
|
+
"note": "items of a nullable array are closed too"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "out-of-core-pattern-full",
|
|
3
|
+
"description": "An out-of-core keyword (pattern) classifies 'full' — the server still enforces it (AUTHORITY covers the whole schema); 'full' only means a grammar-compiling runtime falls back to schema-as-context.",
|
|
4
|
+
"propsSpec": {
|
|
5
|
+
"properties": {
|
|
6
|
+
"code": { "schema": { "type": "string", "pattern": "^[A-Z]+$" } }
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"propsSchema": {
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": { "code": { "pattern": "^[A-Z]+$", "type": "string" } },
|
|
12
|
+
"required": [],
|
|
13
|
+
"type": "object"
|
|
14
|
+
},
|
|
15
|
+
"propsSchemaHash": "9e9fe92d5aa4dbbda98ef26874acbd732318344126776dc159c3fe9d8fd25b8d",
|
|
16
|
+
"propsSchemaProfile": "full",
|
|
17
|
+
"samples": [
|
|
18
|
+
{ "props": { "code": "ABC" }, "valid": true },
|
|
19
|
+
{ "props": { "code": "abc" }, "valid": false }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "scheduler-enum-authority",
|
|
3
|
+
"description": "The incident shape: a closed enum vocabulary. The returned schema IS the enforced schema — props valid under it MUST validate; the out-of-vocabulary value ('booked') MUST reject with the enum keyword.",
|
|
4
|
+
"propsSpec": {
|
|
5
|
+
"properties": {
|
|
6
|
+
"status": {
|
|
7
|
+
"schema": { "type": "string", "enum": ["open", "busy", "tentative"] },
|
|
8
|
+
"required": true
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"propsSchema": {
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {
|
|
15
|
+
"status": { "enum": ["open", "busy", "tentative"], "type": "string" }
|
|
16
|
+
},
|
|
17
|
+
"required": ["status"],
|
|
18
|
+
"type": "object"
|
|
19
|
+
},
|
|
20
|
+
"propsSchemaHash": "1479a6193c963a0d501f0ef80fbba1d2d12087f2da9fcf86b2bd65b54c7e16ee",
|
|
21
|
+
"propsSchemaProfile": "grammar-safe",
|
|
22
|
+
"samples": [
|
|
23
|
+
{ "props": { "status": "open" }, "valid": true },
|
|
24
|
+
{
|
|
25
|
+
"props": { "status": "booked" },
|
|
26
|
+
"valid": false,
|
|
27
|
+
"note": "the live-incident value — out-of-vocabulary enum member"
|
|
28
|
+
},
|
|
29
|
+
{ "props": {}, "valid": false, "note": "required member missing" },
|
|
30
|
+
{
|
|
31
|
+
"props": { "status": "open", "extra": 1 },
|
|
32
|
+
"valid": false,
|
|
33
|
+
"note": "closed shape — undeclared key"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authored vocabulary — pinned here, deliberately decoupled from the
|
|
3
|
+
* live `@ggui-ai/protocol` source tree (the same drift discipline the
|
|
4
|
+
* behavioral fixture surface applies). JSON-representable only.
|
|
5
|
+
*/
|
|
6
|
+
export interface PropsSchemaConformanceSample {
|
|
7
|
+
readonly props: Record<string, unknown>;
|
|
8
|
+
readonly valid: boolean;
|
|
9
|
+
readonly note?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PropsSchemaConformanceCase {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly description: string;
|
|
14
|
+
/** The authored PropsSpec (JSON form). */
|
|
15
|
+
readonly propsSpec: Record<string, unknown>;
|
|
16
|
+
/** The expected enforced-schema artifact, in canonical form. */
|
|
17
|
+
readonly propsSchema: Record<string, unknown>;
|
|
18
|
+
/** sha256 (lowercase hex) over the RFC 8785 bytes of `propsSchema`. */
|
|
19
|
+
readonly propsSchemaHash: string;
|
|
20
|
+
/** Reference profile verdict. */
|
|
21
|
+
readonly propsSchemaProfile: 'grammar-safe' | 'full';
|
|
22
|
+
readonly samples: readonly PropsSchemaConformanceSample[];
|
|
23
|
+
}
|
|
24
|
+
/** The published catalog, id-sorted for deterministic reporting. */
|
|
25
|
+
export declare const propsSchemaConformanceCases: readonly PropsSchemaConformanceCase[];
|
|
26
|
+
/** The implementation under test, as callbacks. */
|
|
27
|
+
export interface PropsSchemaImplementation {
|
|
28
|
+
/** The enforced-schema builder (reference: `buildEnforcedPropsSchema`). */
|
|
29
|
+
readonly build: (propsSpec: Record<string, unknown>) => Record<string, unknown>;
|
|
30
|
+
/** The canonical hash (reference: `computePropsSchemaHash`). */
|
|
31
|
+
readonly hash: (schema: Record<string, unknown>) => string;
|
|
32
|
+
/** The profile classifier (reference: `classifyPropsSchemaProfile`). */
|
|
33
|
+
readonly classify: (schema: Record<string, unknown>) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Validity of `props` under a validator compiled from `schema`,
|
|
36
|
+
* read per the pinned dialect (draft-07, format-assertive, closed
|
|
37
|
+
* shape already in the bytes). Reference:
|
|
38
|
+
* `validatePropsDataWithSchema(props, schema).valid`.
|
|
39
|
+
*/
|
|
40
|
+
readonly validate: (props: Record<string, unknown>, schema: Record<string, unknown>) => boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface PropsSchemaConformanceFailure {
|
|
43
|
+
readonly caseId: string;
|
|
44
|
+
readonly obligation: 'drift' | 'hash' | 'profile' | 'authority';
|
|
45
|
+
readonly detail: string;
|
|
46
|
+
}
|
|
47
|
+
export interface PropsSchemaConformanceReport {
|
|
48
|
+
readonly total: number;
|
|
49
|
+
readonly failures: readonly PropsSchemaConformanceFailure[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Grade an implementation against the catalog. Deterministic; every
|
|
53
|
+
* failure names the case, the obligation, and the observed value —
|
|
54
|
+
* the report is the observable-violation surface of this contract.
|
|
55
|
+
*/
|
|
56
|
+
export declare function runPropsSchemaConformance(impl: PropsSchemaImplementation): PropsSchemaConformanceReport;
|
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/props-schema-conformance/index.ts"],"names":[],"mappings":"AAyCA;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,uEAAuE;IACvE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iCAAiC;IACjC,QAAQ,CAAC,kBAAkB,EAAE,cAAc,GAAG,MAAM,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,SAAS,4BAA4B,EAAE,CAAC;CAC3D;AAED,oEAAoE;AACpE,eAAO,MAAM,2BAA2B,EAAE,SAAS,0BAA0B,EAS1C,CAAC;AAEpC,mDAAmD;AACnD,MAAM,WAAW,yBAAyB;IACxC,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,CACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC;IAC3D,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC;IAC/D;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC;CACd;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,6BAA6B,EAAE,CAAC;CAC7D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,yBAAyB,GAC9B,4BAA4B,CAuC9B"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props-schema conformance catalog — the arbiter fixtures for the
|
|
3
|
+
* schema-precise render contract (frozen shape 2026-08-19; the six
|
|
4
|
+
* pins in `docs/plans/2026-08-19-schema-precise-render.md` §2).
|
|
5
|
+
*
|
|
6
|
+
* Four obligations, one catalog:
|
|
7
|
+
*
|
|
8
|
+
* - **DRIFT** — `buildPropsSchema(propsSpec)` MUST produce the
|
|
9
|
+
* case's `propsSchema` exactly (canonical construction: sorted
|
|
10
|
+
* keys at every schema node, closed shape materialized,
|
|
11
|
+
* `nullable` rewritten, metadata keywords stripped).
|
|
12
|
+
* - **HASH** — sha256 over the RFC 8785 canonical bytes of that
|
|
13
|
+
* schema MUST equal the case's `propsSchemaHash` (lowercase hex,
|
|
14
|
+
* 64 chars). Portable: any JCS library + sha256 reproduces it.
|
|
15
|
+
* - **PROFILE** — the grammar-safe classification MUST equal the
|
|
16
|
+
* case's `propsSchemaProfile` (purely syntactic keyword
|
|
17
|
+
* membership; unknown wire values degrade to `'full'` on the
|
|
18
|
+
* consumer side, but a producer MUST emit the reference verdict).
|
|
19
|
+
* - **AUTHORITY** — every sample's validity under a validator
|
|
20
|
+
* compiled FROM THE CASE'S `propsSchema` (format-assertive
|
|
21
|
+
* dialect) MUST equal `valid`. The returned schema IS the
|
|
22
|
+
* enforced schema; the samples include the live-incident
|
|
23
|
+
* out-of-vocabulary enum value.
|
|
24
|
+
*
|
|
25
|
+
* Same modeling as `../schema-conformance`: structural obligations,
|
|
26
|
+
* no transport, no host. Cases ship as raw JSON under `./cases/` so a
|
|
27
|
+
* non-TypeScript implementer can grade their own builder/validator;
|
|
28
|
+
* {@link runPropsSchemaConformance} takes the implementation as
|
|
29
|
+
* callbacks — the kit never imports a concrete implementation here.
|
|
30
|
+
* The reference binding (the kit's own meta-test) wires
|
|
31
|
+
* `@ggui-ai/protocol`'s exports; see
|
|
32
|
+
* `./props-schema-conformance.test.ts`.
|
|
33
|
+
*/
|
|
34
|
+
import emptyClosedWrapper from './cases/empty-closed-wrapper.json' with { type: 'json' };
|
|
35
|
+
import formatInCoreAssertive from './cases/format-in-core-assertive.json' with { type: 'json' };
|
|
36
|
+
import formatOutOfListFull from './cases/format-out-of-list-full.json' with { type: 'json' };
|
|
37
|
+
import nullableNormalization from './cases/nullable-normalization.json' with { type: 'json' };
|
|
38
|
+
import nullableObjectClosed from './cases/nullable-object-closed.json' with { type: 'json' };
|
|
39
|
+
import outOfCorePatternFull from './cases/out-of-core-pattern-full.json' with { type: 'json' };
|
|
40
|
+
import schedulerEnumAuthority from './cases/scheduler-enum-authority.json' with { type: 'json' };
|
|
41
|
+
/** The published catalog, id-sorted for deterministic reporting. */
|
|
42
|
+
export const propsSchemaConformanceCases = [
|
|
43
|
+
emptyClosedWrapper,
|
|
44
|
+
formatInCoreAssertive,
|
|
45
|
+
formatOutOfListFull,
|
|
46
|
+
nullableNormalization,
|
|
47
|
+
nullableObjectClosed,
|
|
48
|
+
outOfCorePatternFull,
|
|
49
|
+
schedulerEnumAuthority,
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* Grade an implementation against the catalog. Deterministic; every
|
|
53
|
+
* failure names the case, the obligation, and the observed value —
|
|
54
|
+
* the report is the observable-violation surface of this contract.
|
|
55
|
+
*/
|
|
56
|
+
export function runPropsSchemaConformance(impl) {
|
|
57
|
+
const failures = [];
|
|
58
|
+
for (const testCase of propsSchemaConformanceCases) {
|
|
59
|
+
const built = impl.build(testCase.propsSpec);
|
|
60
|
+
if (JSON.stringify(built) !== JSON.stringify(testCase.propsSchema)) {
|
|
61
|
+
failures.push({
|
|
62
|
+
caseId: testCase.id,
|
|
63
|
+
obligation: 'drift',
|
|
64
|
+
detail: `built schema differs from the pinned artifact: ${JSON.stringify(built)}`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const hash = impl.hash(testCase.propsSchema);
|
|
68
|
+
if (hash !== testCase.propsSchemaHash) {
|
|
69
|
+
failures.push({
|
|
70
|
+
caseId: testCase.id,
|
|
71
|
+
obligation: 'hash',
|
|
72
|
+
detail: `expected ${testCase.propsSchemaHash}, got ${hash}`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const profile = impl.classify(testCase.propsSchema);
|
|
76
|
+
if (profile !== testCase.propsSchemaProfile) {
|
|
77
|
+
failures.push({
|
|
78
|
+
caseId: testCase.id,
|
|
79
|
+
obligation: 'profile',
|
|
80
|
+
detail: `expected '${testCase.propsSchemaProfile}', got '${profile}'`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
for (const sample of testCase.samples) {
|
|
84
|
+
const valid = impl.validate(sample.props, testCase.propsSchema);
|
|
85
|
+
if (valid !== sample.valid) {
|
|
86
|
+
failures.push({
|
|
87
|
+
caseId: testCase.id,
|
|
88
|
+
obligation: 'authority',
|
|
89
|
+
detail: `props ${JSON.stringify(sample.props)} expected valid=${sample.valid}, got ${valid}${sample.note ? ` (${sample.note})` : ''}`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { total: propsSchemaConformanceCases.length, failures };
|
|
95
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pin-past-the-head-answers-not-found",
|
|
3
|
+
"description": "Epoch history (#483, SPEC §7.1.2.2): a pinned locator whose epoch exceeds the session's head names a record that does not exist — the same NOT_FOUND on -32002, detail-free, that a missing session answers. Nothing about the head's value may leak through the error shape: a caller must not be able to binary-search the history depth off error variance.",
|
|
4
|
+
"server": { "durableSubstrate": "all", "liveChannel": true },
|
|
5
|
+
"caller": "owner",
|
|
6
|
+
"seeds": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "epoch-history",
|
|
9
|
+
"session": "7a4d9c15-8e2f-4b06-a3c9-5d1f8e0b2c47",
|
|
10
|
+
"records": [{ "props": { "count": 1 } }, { "props": { "count": 5 } }]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"reads": [
|
|
14
|
+
{
|
|
15
|
+
"as": "past-head",
|
|
16
|
+
"locator": {
|
|
17
|
+
"kind": "render",
|
|
18
|
+
"session": "7a4d9c15-8e2f-4b06-a3c9-5d1f8e0b2c47",
|
|
19
|
+
"epoch": 7
|
|
20
|
+
},
|
|
21
|
+
"expect": {
|
|
22
|
+
"kind": "typed-error",
|
|
23
|
+
"jsonRpcCode": -32002,
|
|
24
|
+
"dataCode": "NOT_FOUND",
|
|
25
|
+
"detailAbsent": true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pinned-superseded-record-serves-its-reign-immutably",
|
|
3
|
+
"description": "Epoch history (#483, SPEC §7.1.2.2): a session whose head advanced to epoch 1 still serves its superseded epoch-0 record on the pinned locator — with the props of ITS reign (the amended mint state), never the successor's — and serves them byte-identically on a second read. The bare locator meanwhile tracks the live head. Immutability is graded on propsJson only: live-channel tokens on the same meta legitimately differ per read; the RECORD is what may not.",
|
|
4
|
+
"server": { "durableSubstrate": "all", "liveChannel": true },
|
|
5
|
+
"caller": "owner",
|
|
6
|
+
"seeds": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "epoch-history",
|
|
9
|
+
"session": "5e2c7a90-4f1b-4c3d-8a67-9b0e2d4f6a81",
|
|
10
|
+
"records": [{ "props": { "count": 1 } }, { "props": { "count": 5 } }]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"reads": [
|
|
14
|
+
{
|
|
15
|
+
"as": "pin0-first",
|
|
16
|
+
"locator": {
|
|
17
|
+
"kind": "render",
|
|
18
|
+
"session": "5e2c7a90-4f1b-4c3d-8a67-9b0e2d4f6a81",
|
|
19
|
+
"epoch": 0
|
|
20
|
+
},
|
|
21
|
+
"expect": { "kind": "pinned-mount", "propsInclude": { "count": 1 } }
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"as": "pin0-second",
|
|
25
|
+
"locator": {
|
|
26
|
+
"kind": "render",
|
|
27
|
+
"session": "5e2c7a90-4f1b-4c3d-8a67-9b0e2d4f6a81",
|
|
28
|
+
"epoch": 0
|
|
29
|
+
},
|
|
30
|
+
"expect": { "kind": "pinned-mount", "propsInclude": { "count": 1 } }
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"as": "head",
|
|
34
|
+
"locator": {
|
|
35
|
+
"kind": "render",
|
|
36
|
+
"session": "5e2c7a90-4f1b-4c3d-8a67-9b0e2d4f6a81"
|
|
37
|
+
},
|
|
38
|
+
"expect": { "kind": "pinned-mount", "propsInclude": { "count": 5 } }
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"identicalMounts": ["pin0-first", "pin0-second"]
|
|
42
|
+
}
|
|
@@ -98,6 +98,19 @@ export type ResourceReadSeed = {
|
|
|
98
98
|
*/
|
|
99
99
|
readonly kind: 'registered-blueprint';
|
|
100
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
|
+
}>;
|
|
101
114
|
};
|
|
102
115
|
/**
|
|
103
116
|
* The blueprint-key segment of a locator.
|
|
@@ -127,6 +140,14 @@ export type ResourceReadLocator = {
|
|
|
127
140
|
readonly kind: 'render';
|
|
128
141
|
readonly session: string;
|
|
129
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;
|
|
130
151
|
} | {
|
|
131
152
|
readonly kind: 'raw';
|
|
132
153
|
readonly uri: string;
|
|
@@ -169,6 +190,15 @@ export type ResourceReadExpectation = {
|
|
|
169
190
|
} | {
|
|
170
191
|
/** A result whose contents declare a delivery channel. */
|
|
171
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>>;
|
|
172
202
|
};
|
|
173
203
|
/** One read within a case. */
|
|
174
204
|
export interface ResourceReadProbe {
|
|
@@ -209,6 +239,14 @@ export interface ResourceReadConformanceCase {
|
|
|
209
239
|
* not excused here.
|
|
210
240
|
*/
|
|
211
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[];
|
|
212
250
|
/**
|
|
213
251
|
* Literal substrings that MUST NOT appear anywhere in any error frame
|
|
214
252
|
* this case produces. The values a refusal would leak if a diagnostic
|
|
@@ -244,6 +282,12 @@ export interface ResourceReadRenderMeta {
|
|
|
244
282
|
readonly wsToken?: string;
|
|
245
283
|
/** A server-emitted system card, which carries its own content. */
|
|
246
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;
|
|
247
291
|
}
|
|
248
292
|
/** The raw JSON-RPC error body a server put on the wire. */
|
|
249
293
|
export interface JsonRpcErrorFrame {
|
|
@@ -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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"}
|
|
@@ -111,6 +111,8 @@ import malformedLocator from './cases/malformed-locator-stays-outside-the-typed-
|
|
|
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
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' };
|
|
114
116
|
// =============================================================================
|
|
115
117
|
// Catalog
|
|
116
118
|
// =============================================================================
|
|
@@ -134,6 +136,8 @@ export const resourceReadCases = [
|
|
|
134
136
|
liveRowMount,
|
|
135
137
|
remintMount,
|
|
136
138
|
inlineMount,
|
|
139
|
+
pinnedImmutable,
|
|
140
|
+
pinPastHead,
|
|
137
141
|
];
|
|
138
142
|
// =============================================================================
|
|
139
143
|
// Locator grammar + the mount predicate
|
|
@@ -148,17 +152,18 @@ export const GGUI_RENDER_RESOURCE_URI = 'ui://ggui/render';
|
|
|
148
152
|
export function renderLocatorUri(locator, registeredKeys = {}) {
|
|
149
153
|
if (locator.kind === 'raw')
|
|
150
154
|
return locator.uri;
|
|
155
|
+
const pin = locator.epoch === undefined ? '' : `#${locator.epoch}`;
|
|
151
156
|
const base = `${GGUI_RENDER_RESOURCE_URI}/${locator.session}`;
|
|
152
157
|
const keyRef = locator.key;
|
|
153
158
|
if (keyRef === undefined)
|
|
154
|
-
return base
|
|
159
|
+
return `${base}${pin}`;
|
|
155
160
|
if (keyRef.kind === 'literal')
|
|
156
|
-
return `${base}/${keyRef.value}`;
|
|
161
|
+
return `${base}/${keyRef.value}${pin}`;
|
|
157
162
|
const key = registeredKeys[keyRef.seed];
|
|
158
163
|
if (key === undefined) {
|
|
159
164
|
throw new Error(`no key was reported for registry seed '${keyRef.seed}'`);
|
|
160
165
|
}
|
|
161
|
-
return `${base}/${key}`;
|
|
166
|
+
return `${base}/${key}${pin}`;
|
|
162
167
|
}
|
|
163
168
|
/**
|
|
164
169
|
* Does this render meta carry enough to paint something? One of the
|
|
@@ -289,6 +294,9 @@ async function gradeCase(testCase, prepared) {
|
|
|
289
294
|
const fusion = gradeIndistinguishable(testCase, outcomes);
|
|
290
295
|
if (fusion !== null)
|
|
291
296
|
return { kind: 'fail', failure: fusion };
|
|
297
|
+
const pinned = gradeIdenticalMounts(testCase, outcomes);
|
|
298
|
+
if (pinned !== null)
|
|
299
|
+
return { kind: 'fail', failure: pinned };
|
|
292
300
|
return { kind: 'pass' };
|
|
293
301
|
}
|
|
294
302
|
function gradeProbe(testCase, probe, uri, outcome) {
|
|
@@ -309,6 +317,38 @@ function gradeProbe(testCase, probe, uri, outcome) {
|
|
|
309
317
|
}
|
|
310
318
|
return null;
|
|
311
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
|
+
}
|
|
312
352
|
if (outcome.kind !== 'error') {
|
|
313
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.`);
|
|
314
354
|
}
|
|
@@ -376,6 +416,40 @@ function gradeDisclosure(testCase, outcomes) {
|
|
|
376
416
|
}
|
|
377
417
|
return null;
|
|
378
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
|
+
}
|
|
379
453
|
function gradeIndistinguishable(testCase, outcomes) {
|
|
380
454
|
const group = testCase.indistinguishable ?? [];
|
|
381
455
|
if (group.length < 2)
|
|
@@ -531,6 +605,14 @@ export function parseCase(input) {
|
|
|
531
605
|
probeNames.add(parsed.as);
|
|
532
606
|
return parsed;
|
|
533
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
|
+
}
|
|
534
616
|
const group = input['indistinguishable'];
|
|
535
617
|
if (group !== undefined) {
|
|
536
618
|
if (!Array.isArray(group))
|
|
@@ -565,6 +647,7 @@ export function parseCase(input) {
|
|
|
565
647
|
seeds,
|
|
566
648
|
reads,
|
|
567
649
|
...(group !== undefined ? { indistinguishable: group.map(String) } : {}),
|
|
650
|
+
...(mountGroup !== undefined ? { identicalMounts: mountGroup.map(String) } : {}),
|
|
568
651
|
...(secrets !== undefined ? { disclosesNothing: secrets.map(String) } : {}),
|
|
569
652
|
};
|
|
570
653
|
}
|
|
@@ -593,6 +676,7 @@ const CASE_KEYS = [
|
|
|
593
676
|
'seeds',
|
|
594
677
|
'reads',
|
|
595
678
|
'indistinguishable',
|
|
679
|
+
'identicalMounts',
|
|
596
680
|
'disclosesNothing',
|
|
597
681
|
];
|
|
598
682
|
const SERVER_KEYS = [
|
|
@@ -606,11 +690,12 @@ const SEED_KEYS = {
|
|
|
606
690
|
'identity-record': ['kind', 'session', 'key', 'blueprint'],
|
|
607
691
|
'durable-blueprint': ['kind', 'componentRef', 'body'],
|
|
608
692
|
'committed-render': ['kind', 'session', 'size'],
|
|
693
|
+
'epoch-history': ['kind', 'session', 'records'],
|
|
609
694
|
'uncommitted-render': ['kind', 'session'],
|
|
610
695
|
'registered-blueprint': ['kind', 'as'],
|
|
611
696
|
};
|
|
612
697
|
const LOCATOR_KEYS = {
|
|
613
|
-
render: ['kind', 'session', 'key'],
|
|
698
|
+
render: ['kind', 'session', 'key', 'epoch'],
|
|
614
699
|
raw: ['kind', 'uri'],
|
|
615
700
|
};
|
|
616
701
|
const KEY_KEYS = {
|
|
@@ -621,6 +706,7 @@ const EXPECTATION_KEYS = {
|
|
|
621
706
|
'typed-error': ['kind', 'jsonRpcCode', 'dataCode', 'detailAbsent'],
|
|
622
707
|
'outside-typed-set': ['kind'],
|
|
623
708
|
'live-mount': ['kind'],
|
|
709
|
+
'pinned-mount': ['kind', 'propsInclude'],
|
|
624
710
|
};
|
|
625
711
|
function parseSubstrate(bad, value) {
|
|
626
712
|
switch (value) {
|
|
@@ -686,6 +772,21 @@ function parseSeed(bad, seed) {
|
|
|
686
772
|
kind: 'uncommitted-render',
|
|
687
773
|
session: requireText(bad, seed['session'], "an uncommitted-render seed's 'session'"),
|
|
688
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
|
+
}
|
|
689
790
|
case 'registered-blueprint':
|
|
690
791
|
return {
|
|
691
792
|
kind: 'registered-blueprint',
|
|
@@ -716,10 +817,19 @@ function parseLocator(bad, as, locator) {
|
|
|
716
817
|
switch (locator['kind']) {
|
|
717
818
|
case 'render': {
|
|
718
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
|
+
}
|
|
719
828
|
const key = locator['key'];
|
|
829
|
+
const base = { kind: 'render', session, ...(epoch !== undefined ? { epoch } : {}) };
|
|
720
830
|
if (key === undefined)
|
|
721
|
-
return
|
|
722
|
-
return {
|
|
831
|
+
return base;
|
|
832
|
+
return { ...base, key: parseKey(bad, as, key) };
|
|
723
833
|
}
|
|
724
834
|
case 'raw':
|
|
725
835
|
return { kind: 'raw', uri: requireText(bad, locator['uri'], `probe '${as}' locator 'uri'`) };
|
|
@@ -755,6 +865,18 @@ function parseExpectation(bad, as, expect) {
|
|
|
755
865
|
switch (expect['kind']) {
|
|
756
866
|
case 'outside-typed-set':
|
|
757
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
|
+
}
|
|
758
880
|
case 'live-mount':
|
|
759
881
|
return { kind: 'live-mount' };
|
|
760
882
|
case 'typed-error': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/protocol-conformance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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 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": [
|
|
@@ -55,11 +55,16 @@
|
|
|
55
55
|
"types": "./dist/binding-conformance/index.d.ts",
|
|
56
56
|
"import": "./dist/binding-conformance/index.js",
|
|
57
57
|
"default": "./dist/binding-conformance/index.js"
|
|
58
|
+
},
|
|
59
|
+
"./props-schema-conformance": {
|
|
60
|
+
"types": "./dist/props-schema-conformance/index.d.ts",
|
|
61
|
+
"import": "./dist/props-schema-conformance/index.js",
|
|
62
|
+
"default": "./dist/props-schema-conformance/index.js"
|
|
58
63
|
}
|
|
59
64
|
},
|
|
60
65
|
"dependencies": {
|
|
61
66
|
"ws": "^8.20.1",
|
|
62
|
-
"@ggui-ai/protocol": "0.
|
|
67
|
+
"@ggui-ai/protocol": "0.10.0"
|
|
63
68
|
},
|
|
64
69
|
"devDependencies": {
|
|
65
70
|
"@types/node": "^22.0.0",
|