@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 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. Four catalogs ship today:
54
+ Each catalog ships its cases as raw JSON so a non-TypeScript implementer can grade their own implementation, and each runner takes the implementation as a callback — the kit never hard-binds a concrete one. 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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -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":"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"}
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 { kind: 'render', session };
722
- return { kind: 'render', session, key: parseKey(bad, as, key) };
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.8.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.8.0"
67
+ "@ggui-ai/protocol": "0.10.0"
63
68
  },
64
69
  "devDependencies": {
65
70
  "@types/node": "^22.0.0",