@ggui-ai/protocol 0.1.0-rc.1

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.
Files changed (222) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +46 -0
  3. package/dist/bridge/invoke-agent.d.ts +65 -0
  4. package/dist/bridge/invoke-agent.d.ts.map +1 -0
  5. package/dist/bridge/invoke-agent.js +113 -0
  6. package/dist/envelope-adapters.d.ts +24 -0
  7. package/dist/envelope-adapters.d.ts.map +1 -0
  8. package/dist/envelope-adapters.js +14 -0
  9. package/dist/envelopes/builders.d.ts +145 -0
  10. package/dist/envelopes/builders.d.ts.map +1 -0
  11. package/dist/envelopes/builders.js +113 -0
  12. package/dist/errors/unknown-permission-name.d.ts +12 -0
  13. package/dist/errors/unknown-permission-name.d.ts.map +1 -0
  14. package/dist/errors/unknown-permission-name.js +29 -0
  15. package/dist/errors/version-mismatch.d.ts +55 -0
  16. package/dist/errors/version-mismatch.d.ts.map +1 -0
  17. package/dist/errors/version-mismatch.js +52 -0
  18. package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
  19. package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
  20. package/dist/gadgets/resolve-contract-gadgets.js +119 -0
  21. package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
  22. package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
  23. package/dist/gadgets/stdlib-gadgets.js +161 -0
  24. package/dist/iframe-bridge.d.ts +63 -0
  25. package/dist/iframe-bridge.d.ts.map +1 -0
  26. package/dist/iframe-bridge.js +166 -0
  27. package/dist/index.d.ts +62 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +79 -0
  30. package/dist/integrations/mcp-apps.d.ts +1218 -0
  31. package/dist/integrations/mcp-apps.d.ts.map +1 -0
  32. package/dist/integrations/mcp-apps.js +427 -0
  33. package/dist/navigation/index.d.ts +3 -0
  34. package/dist/navigation/index.d.ts.map +1 -0
  35. package/dist/navigation/index.js +1 -0
  36. package/dist/navigation/stack-navigation.d.ts +55 -0
  37. package/dist/navigation/stack-navigation.d.ts.map +1 -0
  38. package/dist/navigation/stack-navigation.js +80 -0
  39. package/dist/recommended-prompts.d.ts +56 -0
  40. package/dist/recommended-prompts.d.ts.map +1 -0
  41. package/dist/recommended-prompts.js +55 -0
  42. package/dist/registry/blueprint-key.d.ts +9 -0
  43. package/dist/registry/blueprint-key.d.ts.map +1 -0
  44. package/dist/registry/blueprint-key.js +28 -0
  45. package/dist/registry/canonicalize-contract.d.ts +35 -0
  46. package/dist/registry/canonicalize-contract.d.ts.map +1 -0
  47. package/dist/registry/canonicalize-contract.js +166 -0
  48. package/dist/registry/summarize-contract.d.ts +46 -0
  49. package/dist/registry/summarize-contract.d.ts.map +1 -0
  50. package/dist/registry/summarize-contract.js +63 -0
  51. package/dist/schema-learning/derive-contract.d.ts +67 -0
  52. package/dist/schema-learning/derive-contract.d.ts.map +1 -0
  53. package/dist/schema-learning/derive-contract.js +117 -0
  54. package/dist/schema-learning/merge.d.ts +32 -0
  55. package/dist/schema-learning/merge.d.ts.map +1 -0
  56. package/dist/schema-learning/merge.js +146 -0
  57. package/dist/schemas/blueprint.d.ts +32 -0
  58. package/dist/schemas/blueprint.d.ts.map +1 -0
  59. package/dist/schemas/blueprint.js +92 -0
  60. package/dist/schemas/data-contract.d.ts +750 -0
  61. package/dist/schemas/data-contract.d.ts.map +1 -0
  62. package/dist/schemas/data-contract.js +663 -0
  63. package/dist/schemas/gadget-name-grammar.d.ts +29 -0
  64. package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
  65. package/dist/schemas/gadget-name-grammar.js +28 -0
  66. package/dist/schemas/handshake-suggestion.d.ts +46 -0
  67. package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
  68. package/dist/schemas/handshake-suggestion.js +107 -0
  69. package/dist/schemas/invoke.d.ts +337 -0
  70. package/dist/schemas/invoke.d.ts.map +1 -0
  71. package/dist/schemas/invoke.js +169 -0
  72. package/dist/schemas/mcp.d.ts +301 -0
  73. package/dist/schemas/mcp.d.ts.map +1 -0
  74. package/dist/schemas/mcp.js +373 -0
  75. package/dist/schemas/ops-blueprint.d.ts +176 -0
  76. package/dist/schemas/ops-blueprint.d.ts.map +1 -0
  77. package/dist/schemas/ops-blueprint.js +259 -0
  78. package/dist/schemas/sync-check.d.ts +11 -0
  79. package/dist/schemas/sync-check.d.ts.map +1 -0
  80. package/dist/schemas/sync-check.js +60 -0
  81. package/dist/screen-blueprints/define.d.ts +22 -0
  82. package/dist/screen-blueprints/define.d.ts.map +1 -0
  83. package/dist/screen-blueprints/define.js +3 -0
  84. package/dist/screen-blueprints/index.d.ts +4 -0
  85. package/dist/screen-blueprints/index.d.ts.map +1 -0
  86. package/dist/screen-blueprints/index.js +3 -0
  87. package/dist/screen-blueprints/match.d.ts +35 -0
  88. package/dist/screen-blueprints/match.d.ts.map +1 -0
  89. package/dist/screen-blueprints/match.js +51 -0
  90. package/dist/screen-blueprints/types.d.ts +164 -0
  91. package/dist/screen-blueprints/types.d.ts.map +1 -0
  92. package/dist/screen-blueprints/types.js +1 -0
  93. package/dist/stream/stream-parser.d.ts +62 -0
  94. package/dist/stream/stream-parser.d.ts.map +1 -0
  95. package/dist/stream/stream-parser.js +199 -0
  96. package/dist/transport/websocket.d.ts +178 -0
  97. package/dist/transport/websocket.d.ts.map +1 -0
  98. package/dist/transport/websocket.js +1 -0
  99. package/dist/types/app-config.d.ts +61 -0
  100. package/dist/types/app-config.d.ts.map +1 -0
  101. package/dist/types/app-config.js +1 -0
  102. package/dist/types/auth.d.ts +61 -0
  103. package/dist/types/auth.d.ts.map +1 -0
  104. package/dist/types/auth.js +1 -0
  105. package/dist/types/blueprint.d.ts +206 -0
  106. package/dist/types/blueprint.d.ts.map +1 -0
  107. package/dist/types/blueprint.js +1 -0
  108. package/dist/types/canvas-lifecycle.d.ts +105 -0
  109. package/dist/types/canvas-lifecycle.d.ts.map +1 -0
  110. package/dist/types/canvas-lifecycle.js +38 -0
  111. package/dist/types/capabilities.d.ts +40 -0
  112. package/dist/types/capabilities.d.ts.map +1 -0
  113. package/dist/types/capabilities.js +19 -0
  114. package/dist/types/contract-inference.d.ts +401 -0
  115. package/dist/types/contract-inference.d.ts.map +1 -0
  116. package/dist/types/contract-inference.js +44 -0
  117. package/dist/types/credential.d.ts +41 -0
  118. package/dist/types/credential.d.ts.map +1 -0
  119. package/dist/types/credential.js +32 -0
  120. package/dist/types/data-bindings.d.ts +322 -0
  121. package/dist/types/data-bindings.d.ts.map +1 -0
  122. package/dist/types/data-bindings.js +29 -0
  123. package/dist/types/data-contract.d.ts +1296 -0
  124. package/dist/types/data-contract.d.ts.map +1 -0
  125. package/dist/types/data-contract.js +111 -0
  126. package/dist/types/events.d.ts +182 -0
  127. package/dist/types/events.d.ts.map +1 -0
  128. package/dist/types/events.js +8 -0
  129. package/dist/types/feedback.d.ts +24 -0
  130. package/dist/types/feedback.d.ts.map +1 -0
  131. package/dist/types/feedback.js +7 -0
  132. package/dist/types/gadget.d.ts +121 -0
  133. package/dist/types/gadget.d.ts.map +1 -0
  134. package/dist/types/gadget.js +24 -0
  135. package/dist/types/handshake-suggestion.d.ts +264 -0
  136. package/dist/types/handshake-suggestion.d.ts.map +1 -0
  137. package/dist/types/handshake-suggestion.js +70 -0
  138. package/dist/types/host-context.d.ts +163 -0
  139. package/dist/types/host-context.d.ts.map +1 -0
  140. package/dist/types/host-context.js +142 -0
  141. package/dist/types/interface-context.d.ts +105 -0
  142. package/dist/types/interface-context.d.ts.map +1 -0
  143. package/dist/types/interface-context.js +115 -0
  144. package/dist/types/invoke.d.ts +28 -0
  145. package/dist/types/invoke.d.ts.map +1 -0
  146. package/dist/types/invoke.js +7 -0
  147. package/dist/types/live-channel.d.ts +613 -0
  148. package/dist/types/live-channel.d.ts.map +1 -0
  149. package/dist/types/live-channel.js +1 -0
  150. package/dist/types/llm.d.ts +61 -0
  151. package/dist/types/llm.d.ts.map +1 -0
  152. package/dist/types/llm.js +186 -0
  153. package/dist/types/mcp-proxy.d.ts +67 -0
  154. package/dist/types/mcp-proxy.d.ts.map +1 -0
  155. package/dist/types/mcp-proxy.js +46 -0
  156. package/dist/types/mcp.d.ts +637 -0
  157. package/dist/types/mcp.d.ts.map +1 -0
  158. package/dist/types/mcp.js +30 -0
  159. package/dist/types/openrouter-models.d.ts +22 -0
  160. package/dist/types/openrouter-models.d.ts.map +1 -0
  161. package/dist/types/openrouter-models.js +4843 -0
  162. package/dist/types/region.d.ts +26 -0
  163. package/dist/types/region.d.ts.map +1 -0
  164. package/dist/types/region.js +36 -0
  165. package/dist/types/session.d.ts +419 -0
  166. package/dist/types/session.d.ts.map +1 -0
  167. package/dist/types/session.js +1 -0
  168. package/dist/types/thread.d.ts +207 -0
  169. package/dist/types/thread.d.ts.map +1 -0
  170. package/dist/types/thread.js +57 -0
  171. package/dist/types/ui-generator.d.ts +100 -0
  172. package/dist/types/ui-generator.d.ts.map +1 -0
  173. package/dist/types/ui-generator.js +53 -0
  174. package/dist/validation/ajv-runtime.d.ts +140 -0
  175. package/dist/validation/ajv-runtime.d.ts.map +1 -0
  176. package/dist/validation/ajv-runtime.js +452 -0
  177. package/dist/validation/content-hash.d.ts +3 -0
  178. package/dist/validation/content-hash.d.ts.map +1 -0
  179. package/dist/validation/content-hash.js +21 -0
  180. package/dist/validation/contract-validator.d.ts +244 -0
  181. package/dist/validation/contract-validator.d.ts.map +1 -0
  182. package/dist/validation/contract-validator.js +711 -0
  183. package/dist/validation/cross-references.d.ts +105 -0
  184. package/dist/validation/cross-references.d.ts.map +1 -0
  185. package/dist/validation/cross-references.js +164 -0
  186. package/dist/validation/hygiene-rules.d.ts +250 -0
  187. package/dist/validation/hygiene-rules.d.ts.map +1 -0
  188. package/dist/validation/hygiene-rules.js +564 -0
  189. package/dist/validation/lint-contract.d.ts +130 -0
  190. package/dist/validation/lint-contract.d.ts.map +1 -0
  191. package/dist/validation/lint-contract.js +225 -0
  192. package/dist/validation/name-invariants.d.ts +117 -0
  193. package/dist/validation/name-invariants.d.ts.map +1 -0
  194. package/dist/validation/name-invariants.js +172 -0
  195. package/dist/validation/reserved-channels.d.ts +156 -0
  196. package/dist/validation/reserved-channels.d.ts.map +1 -0
  197. package/dist/validation/reserved-channels.js +356 -0
  198. package/dist/validation/resolve-stream-channel.d.ts +78 -0
  199. package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
  200. package/dist/validation/resolve-stream-channel.js +64 -0
  201. package/dist/validation/sanitize-error.d.ts +46 -0
  202. package/dist/validation/sanitize-error.d.ts.map +1 -0
  203. package/dist/validation/sanitize-error.js +88 -0
  204. package/dist/validation/schema-compat-invariants.d.ts +140 -0
  205. package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
  206. package/dist/validation/schema-compat-invariants.js +220 -0
  207. package/dist/validation/schema-meta-validation.d.ts +60 -0
  208. package/dist/validation/schema-meta-validation.d.ts.map +1 -0
  209. package/dist/validation/schema-meta-validation.js +131 -0
  210. package/dist/validation/schema-subset.d.ts +165 -0
  211. package/dist/validation/schema-subset.d.ts.map +1 -0
  212. package/dist/validation/schema-subset.js +295 -0
  213. package/dist/validation/ui-security.d.ts +54 -0
  214. package/dist/validation/ui-security.d.ts.map +1 -0
  215. package/dist/validation/ui-security.js +138 -0
  216. package/dist/validation/zod-to-json-schema.d.ts +63 -0
  217. package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
  218. package/dist/validation/zod-to-json-schema.js +126 -0
  219. package/dist/version.d.ts +1458 -0
  220. package/dist/version.d.ts.map +1 -0
  221. package/dist/version.js +1459 -0
  222. package/package.json +113 -0
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Layer B (inner JSON Schema meta-validation) for a `DataContract`.
3
+ *
4
+ * Walks the six inner JSON Schema fields the agent authors:
5
+ * 1. `propsSpec.properties[*].schema`
6
+ * 2. `actionSpec[*].schema` (optional per entry)
7
+ * 3. `streamSpec[*].schema`
8
+ * 4. `contextSpec[*].schema`
9
+ * 5. `agentCapabilities.tools[*].inputSchema` (optional per entry)
10
+ * 6. `agentCapabilities.tools[*].outputSchema` (optional per entry)
11
+ *
12
+ * For each present schema, runs `compileForValidation()` from
13
+ * `ajv-runtime`. Ajv's `strict: true` mode throws on malformed JSON
14
+ * Schemas at compile-time — unknown keywords, missing `items` on
15
+ * array nodes, properties values that are not schemas, etc. This is
16
+ * the meta-validation pass: it asserts the contract's own type
17
+ * descriptions are well-formed BEFORE any runtime data flows.
18
+ *
19
+ * Failures collect into a single throw with every malformed field
20
+ * named — agents fix all of them in one round rather than retry-
21
+ * per-field.
22
+ *
23
+ * Designed to slot at:
24
+ * - `ggui_handshake` entry — validates `blueprintDraft.contract`
25
+ * before the negotiator runs.
26
+ * - `ggui_push` entry — validates the `effectiveContract` (either
27
+ * synth-amended or override) before any state mutation.
28
+ *
29
+ * Both call sites already run cross-reference and name-invariant
30
+ * assertions for the same author-recoverable failure class; this
31
+ * sits alongside them.
32
+ */
33
+ import { compileForValidation } from './ajv-runtime.js';
34
+ /**
35
+ * Typed error for contract schema meta-validation failures. Mirrors
36
+ * the shape of `CrossReferenceError` / `ContractViolationError` for
37
+ * symmetry — push/handshake catch and surface a structured
38
+ * `contract_schema_invalid` error to the agent.
39
+ */
40
+ export class ContractSchemaMetaError extends Error {
41
+ code = 'contract_schema_invalid';
42
+ violations;
43
+ hint;
44
+ constructor(violations) {
45
+ const summary = violations.map(v => ` - ${v.field}: ${v.message}`).join('\n');
46
+ super(`Contract has malformed JSON Schemas:\n${summary}`);
47
+ this.name = 'ContractSchemaMetaError';
48
+ this.violations = violations;
49
+ this.hint =
50
+ 'Fix the JSON Schema at the named field. Common causes: ' +
51
+ 'missing `items` on an array schema, a properties entry that ' +
52
+ 'is not itself a JSON Schema object, or an unknown keyword. ' +
53
+ 'Re-call ggui_push (or ggui_handshake) once corrected.';
54
+ }
55
+ }
56
+ function tryCompile(field, schema, violations) {
57
+ // Undefined schema is a structural error, not an Ajv-compile error.
58
+ // Author confusion shape: agent puts a JSON Schema flat at the
59
+ // PropEntry / ActionEntry / StreamChannelEntry / ContextEntry level
60
+ // instead of wrapping it in `schema:`. Surface the missing field
61
+ // explicitly so the agent's recovery loop names the correction
62
+ // (`add a "schema" field`) instead of the opaque
63
+ // "Cannot read properties of undefined (reading 'type')" crash that
64
+ // Ajv would otherwise throw from `injectClosedShape`.
65
+ if (schema === undefined || schema === null) {
66
+ violations.push({
67
+ field,
68
+ message: `Missing 'schema' field. Each entry in propsSpec.properties / actionSpec / streamSpec / contextSpec is a WRAPPER that contains a JSON Schema in its 'schema:' field — the JSON Schema does NOT sit flat at the entry level. Example: propsSpec.properties.todos = { schema: { type: 'array', items: { ... } }, required: true }.`,
69
+ });
70
+ return;
71
+ }
72
+ try {
73
+ compileForValidation(schema);
74
+ }
75
+ catch (err) {
76
+ const raw = err instanceof Error ? err.message : String(err);
77
+ violations.push({ field, message: truncate(raw, 240) });
78
+ }
79
+ }
80
+ function truncate(s, max) {
81
+ return s.length > max ? `${s.slice(0, max - 1)}…` : s;
82
+ }
83
+ /**
84
+ * Walks the contract's six inner JSON Schema fields and asserts
85
+ * each compiles cleanly under Ajv's strict mode. Throws
86
+ * {@link ContractSchemaMetaError} on the first push attempt that
87
+ * smuggles a malformed schema; collects every offender in one pass
88
+ * before throwing so the agent sees the full list.
89
+ */
90
+ export function assertContractSchemasValid(contract) {
91
+ const violations = [];
92
+ if (contract.propsSpec?.properties) {
93
+ for (const [name, entry] of Object.entries(contract.propsSpec.properties)) {
94
+ tryCompile(`propsSpec.properties.${name}.schema`, entry.schema, violations);
95
+ }
96
+ }
97
+ if (contract.actionSpec) {
98
+ for (const [name, entry] of Object.entries(contract.actionSpec)) {
99
+ if (entry.schema) {
100
+ tryCompile(`actionSpec.${name}.schema`, entry.schema, violations);
101
+ }
102
+ }
103
+ }
104
+ if (contract.streamSpec) {
105
+ for (const [name, entry] of Object.entries(contract.streamSpec)) {
106
+ if (entry.schema) {
107
+ tryCompile(`streamSpec.${name}.schema`, entry.schema, violations);
108
+ }
109
+ }
110
+ }
111
+ if (contract.contextSpec) {
112
+ for (const [name, entry] of Object.entries(contract.contextSpec)) {
113
+ if (entry.schema) {
114
+ tryCompile(`contextSpec.${name}.schema`, entry.schema, violations);
115
+ }
116
+ }
117
+ }
118
+ if (contract.agentCapabilities?.tools) {
119
+ for (const [name, entry] of Object.entries(contract.agentCapabilities.tools)) {
120
+ if (entry.inputSchema) {
121
+ tryCompile(`agentCapabilities.tools.${name}.inputSchema`, entry.inputSchema, violations);
122
+ }
123
+ if (entry.outputSchema) {
124
+ tryCompile(`agentCapabilities.tools.${name}.outputSchema`, entry.outputSchema, violations);
125
+ }
126
+ }
127
+ }
128
+ if (violations.length > 0) {
129
+ throw new ContractSchemaMetaError(violations);
130
+ }
131
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Schema-subset algorithm — answers "can every value the `subset`
3
+ * schema accepts also pass the `superset` schema?".
4
+ *
5
+ * **Why this lives in the protocol package.** The schema-alignment
6
+ * contract is enforced at push-time + blueprint-registration; the
7
+ * canonical failure is {@link ContractErrorCode}
8
+ * `'SCHEMA_MISMATCH_ERROR'` — same envelope shape + channel as every
9
+ * other named contract violation. See the schema-compat docstrings
10
+ * on {@link ActionEntry.schema} and {@link StreamChannelEntry.schema}
11
+ * for the author-invariant the check enforces.
12
+ *
13
+ * **Check points.**
14
+ *
15
+ * - Pre-commit of a `StackItem` with `actionSpec` / `streamSpec`
16
+ * entries that reference tools: each action's declared schema
17
+ * MUST be a subset of the tool's inputSchema (what the action
18
+ * payload is allowed to send ⊆ what the tool accepts). Each
19
+ * stream channel's declared schema MUST be a subset of the
20
+ * tool's return schema (what the channel emits ⊆ what the tool
21
+ * returns — inverted because the DIRECTION reverses).
22
+ * - Policy via {@link CreateGguiServerOptions.schemaCompatCheck}:
23
+ * `'reject'` (default) / `'warn'` / `'off'`.
24
+ *
25
+ * **Algorithm scope (P0).**
26
+ *
27
+ * - `type` match — primitive types must agree; only `undefined`
28
+ * on the superset side is a wildcard.
29
+ * - `required` — subset's required set MUST be a subset of
30
+ * superset's required set (tighter required on the subset side
31
+ * = strictly fewer values accepted ⇒ OK; tighter on the
32
+ * superset side would accept FEWER values than the subset ⇒
33
+ * violation).
34
+ * - `properties` — recursion: every subset property MUST be a
35
+ * subset of the matching superset property.
36
+ * - `additionalProperties` — semantics:
37
+ *
38
+ * - superset `true` (default when omitted) → subset is
39
+ * unconstrained on extra keys — OK.
40
+ * - superset `false` → subset MUST also be `false` (anything
41
+ * else widens).
42
+ * - superset JsonSchema → subset's additionalProperties MUST
43
+ * be a subset of the superset's (recurse), OR `false`
44
+ * (never emits extras, always fits).
45
+ * - `items` — arrays: subset's `items` MUST be a subset of
46
+ * superset's `items`. When either side omits `items`, the check
47
+ * is permissive in that direction.
48
+ *
49
+ * **P1 scope (deferred).**
50
+ *
51
+ * - `oneOf` / `anyOf` covering — subset union members must each
52
+ * be covered by at least one superset member.
53
+ * - `enum` — subset's enum values must all be in the superset's
54
+ * enum (or superset has no enum constraint).
55
+ * - `const` — subset's const must equal superset's const (or
56
+ * superset has no const constraint).
57
+ *
58
+ * **P2 scope (deferred — known limitations documented for
59
+ * third-party authors).**
60
+ *
61
+ * - `$ref` — no local or remote resolution; schemas with `$ref`
62
+ * are flagged as {@link SubsetViolationReason.unsupported}.
63
+ * - `allOf` — not merged before comparison.
64
+ * - String / number constraints — `minimum` / `maximum` /
65
+ * `minLength` / `maxLength` / `pattern` / `format` are NOT
66
+ * compared. A superset's narrower bound is not detected as a
67
+ * violation.
68
+ * - Tuple items (`items: JsonSchema[]`) — not in the current
69
+ * {@link JsonSchema} type, so not supported here.
70
+ *
71
+ * **Determinism contract.** No randomness, no IO, no thrown
72
+ * exceptions for normal violations. Every incompatibility is
73
+ * reported as a {@link SubsetViolation} with enough field-path
74
+ * context for the emitted `SCHEMA_MISMATCH_ERROR` to name the
75
+ * mismatch cleanly. Thrown errors are reserved for programmer-
76
+ * bug conditions (a caller passes `null` where a JsonSchema is
77
+ * expected).
78
+ *
79
+ * @see ./schema-compat-invariants.ts — the protocol-level invariants
80
+ * that call this algorithm at push-time.
81
+ */
82
+ import type { JsonSchema, JsonValue } from '../types/data-contract.js';
83
+ /**
84
+ * Category of subset violation. Narrow enough that a downstream
85
+ * consumer can pattern-match on it if it wants to render a
86
+ * specialized message; wide enough to admit future P1/P2 reasons
87
+ * without a protocol-level bump.
88
+ */
89
+ export type SubsetViolationReason = 'type-mismatch'
90
+ /** Subset declares a property the superset does not allow (via
91
+ * `properties` or `additionalProperties: false`). */
92
+ | 'extra-property'
93
+ /** Subset marks a property required that is not required on the
94
+ * superset — accepted, but only when the superset also allows the
95
+ * property at all. The combined check produces this reason only
96
+ * when the superset REJECTS the property entirely (missing from
97
+ * properties AND additionalProperties: false). */
98
+ | 'required-widens'
99
+ /** Superset marks a property required that the subset does not
100
+ * require. The subset may omit a value the superset would reject. */
101
+ | 'missing-required'
102
+ /** Array items schema mismatch. */
103
+ | 'items-mismatch'
104
+ /** `additionalProperties: false` on superset, non-false on subset. */
105
+ | 'additional-properties-widens'
106
+ /** Schema uses a construct this P0 implementation does not support
107
+ * (e.g. `$ref`, `allOf`, `oneOf`/`anyOf`, `enum`, `const`). The
108
+ * pair is flagged instead of silently passing. */
109
+ | 'unsupported';
110
+ /**
111
+ * A single point of incompatibility between `superset` and `subset`.
112
+ * Carries enough context for the caller to render a message that
113
+ * names the field path + both sides.
114
+ */
115
+ export interface SubsetViolation {
116
+ /**
117
+ * Dotted field path from the root of the compared schemas.
118
+ * `''` (empty) means the root schemas themselves mismatched.
119
+ * `'properties.foo.items'` means the `items` of the `foo` property
120
+ * mismatched. Uses `.items` for array element descent and `.<key>`
121
+ * for object property descent. No escaping — property names
122
+ * containing `.` will produce ambiguous paths but are valid JSON.
123
+ */
124
+ readonly path: string;
125
+ /** Category of violation. */
126
+ readonly reason: SubsetViolationReason;
127
+ /** The superset side's value at `path`, as a short JSON string
128
+ * (stringified, truncated at 120 chars). `undefined` when the
129
+ * superset has no explicit value at the path. */
130
+ readonly superset?: string;
131
+ /** The subset side's value at `path`, same formatting rules as
132
+ * {@link SubsetViolation.superset}. */
133
+ readonly subset?: string;
134
+ /** Human-readable summary suitable for inclusion in a
135
+ * `SCHEMA_MISMATCH_ERROR` envelope. Producers MAY ignore this
136
+ * and render their own message from `path` + `reason` if they
137
+ * prefer a consistent localized format. */
138
+ readonly message: string;
139
+ }
140
+ /**
141
+ * Result of {@link isSchemaSubset}. Wraps `compatible` with the
142
+ * violation list so callers that only need the boolean can check
143
+ * `result.compatible`, and callers that emit envelopes can project
144
+ * the violations into the error details.
145
+ */
146
+ export interface SchemaSubsetResult {
147
+ readonly compatible: boolean;
148
+ readonly violations: readonly SubsetViolation[];
149
+ }
150
+ /**
151
+ * Compare two JSON Schemas under the "subset acceptance" relation:
152
+ * returns `compatible: true` iff every JSON value that `subset`
153
+ * accepts would also be accepted by `superset` (under the P0 scope
154
+ * documented at the top of this file).
155
+ *
156
+ * Neither argument is mutated. Order matters: `isSchemaSubset(a, b)`
157
+ * checks "is b a subset of a", NOT "is a a subset of b".
158
+ *
159
+ * `null` / non-object inputs throw — they are programmer errors,
160
+ * not schema violations. Every legitimate incompatibility is
161
+ * reported via the returned {@link SubsetViolation} list.
162
+ */
163
+ export declare function isSchemaSubset(superset: JsonSchema, subset: JsonSchema): SchemaSubsetResult;
164
+ export type { JsonValue };
165
+ //# sourceMappingURL=schema-subset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-subset.d.ts","sourceRoot":"","sources":["../../src/validation/schema-subset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAC7B,eAAe;AACjB;sDACsD;GACpD,gBAAgB;AAClB;;;;mDAImD;GACjD,iBAAiB;AACnB;sEACsE;GACpE,kBAAkB;AACpB,mCAAmC;GACjC,gBAAgB;AAClB,sEAAsE;GACpE,8BAA8B;AAChC;;mDAEmD;GACjD,aAAa,CAAC;AAElB;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC;;sDAEkD;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;4CACwC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;gDAG4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,UAAU,EACpB,MAAM,EAAE,UAAU,GACjB,kBAAkB,CAiBpB;AAoTD,YAAY,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Compare two JSON Schemas under the "subset acceptance" relation:
3
+ * returns `compatible: true` iff every JSON value that `subset`
4
+ * accepts would also be accepted by `superset` (under the P0 scope
5
+ * documented at the top of this file).
6
+ *
7
+ * Neither argument is mutated. Order matters: `isSchemaSubset(a, b)`
8
+ * checks "is b a subset of a", NOT "is a a subset of b".
9
+ *
10
+ * `null` / non-object inputs throw — they are programmer errors,
11
+ * not schema violations. Every legitimate incompatibility is
12
+ * reported via the returned {@link SubsetViolation} list.
13
+ */
14
+ export function isSchemaSubset(superset, subset) {
15
+ if (superset === null || typeof superset !== 'object') {
16
+ throw new TypeError(`isSchemaSubset: superset must be a JsonSchema object (received ${typeof superset})`);
17
+ }
18
+ if (subset === null || typeof subset !== 'object') {
19
+ throw new TypeError(`isSchemaSubset: subset must be a JsonSchema object (received ${typeof subset})`);
20
+ }
21
+ const violations = [];
22
+ compare(superset, subset, '', violations);
23
+ return {
24
+ compatible: violations.length === 0,
25
+ violations,
26
+ };
27
+ }
28
+ // ── Internal ──────────────────────────────────────────────────────
29
+ /**
30
+ * Structural deep-equality for two JSON Schemas. A schema is always a
31
+ * subset of itself, so an equal pair short-circuits {@link compare} —
32
+ * this is what lets a pair that uses an otherwise-unsupported
33
+ * construct (`enum`, `oneOf`, `const`, …) pass when the two sides are
34
+ * identical (e.g. a source-fed `streamSpec` channel whose schema is
35
+ * the backing tool's `outputSchema` verbatim).
36
+ */
37
+ function schemasDeepEqual(a, b) {
38
+ if (a === b)
39
+ return true;
40
+ if (a === null || b === null)
41
+ return false;
42
+ if (typeof a !== 'object' || typeof b !== 'object')
43
+ return false;
44
+ const aArr = Array.isArray(a);
45
+ if (aArr !== Array.isArray(b))
46
+ return false;
47
+ if (aArr) {
48
+ const ab = a;
49
+ const bb = b;
50
+ if (ab.length !== bb.length)
51
+ return false;
52
+ return ab.every((v, i) => schemasDeepEqual(v, bb[i]));
53
+ }
54
+ const ao = a;
55
+ const bo = b;
56
+ const aKeys = Object.keys(ao);
57
+ if (aKeys.length !== Object.keys(bo).length)
58
+ return false;
59
+ return aKeys.every((k) => Object.prototype.hasOwnProperty.call(bo, k) &&
60
+ schemasDeepEqual(ao[k], bo[k]));
61
+ }
62
+ function compare(superset, subset, path, out) {
63
+ // Identical schemas: a schema is trivially a subset of itself, so
64
+ // skip the structural walk. This is the ONLY path by which a pair
65
+ // using a P1/P2-unsupported construct (`enum`, `oneOf`, `const`, …)
66
+ // can be proved compatible — and it is sound, because equal schemas
67
+ // accept exactly the same value set.
68
+ if (schemasDeepEqual(superset, subset))
69
+ return;
70
+ // P2 unsupported constructs — flag instead of silently passing.
71
+ // Presence on EITHER side is flagged because a recursive check on
72
+ // an unresolved `$ref` / un-merged `allOf` would produce false
73
+ // negatives. Subset-only presence is also flagged so a caller that
74
+ // authors a narrower-by-`$ref` schema knows the subset check can't
75
+ // prove it.
76
+ if (hasUnsupported(superset) || hasUnsupported(subset)) {
77
+ out.push({
78
+ path,
79
+ reason: 'unsupported',
80
+ superset: safeStringify(supersetUnsupportedField(superset)),
81
+ subset: safeStringify(supersetUnsupportedField(subset)),
82
+ message: `${pathLabel(path)}: schema uses an unsupported construct ` +
83
+ `($ref / allOf / oneOf / anyOf / enum / const) — the subset ` +
84
+ `algorithm cannot prove compatibility for these constructs.`,
85
+ });
86
+ return;
87
+ }
88
+ // Type match. Superset `undefined` is a wildcard (accepts any
89
+ // type). Subset `undefined` against a specific superset type is a
90
+ // violation — "no declared type" is wider than any specific type.
91
+ const sup = normalizeType(superset);
92
+ const sub = normalizeType(subset);
93
+ if (sup !== undefined) {
94
+ if (sub === undefined || sub !== sup) {
95
+ out.push({
96
+ path,
97
+ reason: 'type-mismatch',
98
+ superset: sup,
99
+ subset: sub ?? '(unspecified)',
100
+ message: `${pathLabel(path)}: type mismatch — superset accepts ` +
101
+ `'${sup}' but subset ${sub === undefined ? 'does not declare a type' : `declares '${sub}'`}.`,
102
+ });
103
+ // Type mismatch invalidates downstream object/array structural
104
+ // checks — if the types don't match, deeper comparison is noise.
105
+ return;
106
+ }
107
+ }
108
+ // Object structure.
109
+ if (sup === 'object' || sub === 'object') {
110
+ compareObject(superset, subset, path, out);
111
+ }
112
+ // Array items. We descend through items only when both sides have
113
+ // `type: 'array'` (or superset omitted type and subset declares
114
+ // array — but that case is caught by the type block above as a
115
+ // mismatch). Omission on either side is permissive.
116
+ if (sup === 'array' || sub === 'array') {
117
+ compareArray(superset, subset, path, out);
118
+ }
119
+ }
120
+ function compareObject(superset, subset, path, out) {
121
+ const supProps = superset.properties ?? {};
122
+ const subProps = subset.properties ?? {};
123
+ const supRequired = new Set(superset.required ?? []);
124
+ const subRequired = new Set(subset.required ?? []);
125
+ const supAdditional = resolveAdditional(superset.additionalProperties);
126
+ const subAdditional = resolveAdditional(subset.additionalProperties);
127
+ // Every subset property must either be in supProps (recurse) OR
128
+ // be allowed by supAdditional.
129
+ for (const key of Object.keys(subProps)) {
130
+ const subChild = subProps[key];
131
+ if (!subChild)
132
+ continue; // paranoia — Object.keys guarantees it exists
133
+ const supChild = supProps[key];
134
+ const childPath = path === '' ? `properties.${key}` : `${path}.properties.${key}`;
135
+ if (supChild) {
136
+ compare(supChild, subChild, childPath, out);
137
+ }
138
+ else if (supAdditional.kind === 'allow') {
139
+ // Allowed by `additionalProperties: true` on superset —
140
+ // structurally unconstrained, so subset's shape is fine.
141
+ }
142
+ else if (supAdditional.kind === 'schema') {
143
+ compare(supAdditional.schema, subChild, childPath, out);
144
+ }
145
+ else {
146
+ // superset: additionalProperties false AND key not in
147
+ // properties. Subset would allow a value the superset rejects.
148
+ out.push({
149
+ path: childPath,
150
+ reason: 'extra-property',
151
+ superset: '(not allowed)',
152
+ subset: safeStringify(subChild),
153
+ message: `${pathLabel(childPath)}: subset allows property '${key}' ` +
154
+ `that superset rejects (not in superset.properties and ` +
155
+ `superset.additionalProperties is false).`,
156
+ });
157
+ }
158
+ }
159
+ // Additional-properties compatibility. A subset that allows
160
+ // additional properties when the superset rejects them is wider
161
+ // in the "extra keys" dimension.
162
+ if (supAdditional.kind === 'reject') {
163
+ if (subAdditional.kind === 'allow') {
164
+ out.push({
165
+ path,
166
+ reason: 'additional-properties-widens',
167
+ superset: 'false',
168
+ subset: 'true',
169
+ message: `${pathLabel(path)}: subset.additionalProperties is true but ` +
170
+ `superset.additionalProperties is false — subset accepts ` +
171
+ `extra keys the superset rejects.`,
172
+ });
173
+ }
174
+ else if (subAdditional.kind === 'schema') {
175
+ out.push({
176
+ path,
177
+ reason: 'additional-properties-widens',
178
+ superset: 'false',
179
+ subset: safeStringify(subset.additionalProperties),
180
+ message: `${pathLabel(path)}: subset declares an additionalProperties ` +
181
+ `schema, but superset.additionalProperties is false.`,
182
+ });
183
+ }
184
+ // subAdditional === 'reject' → equal; no violation.
185
+ }
186
+ else if (supAdditional.kind === 'schema') {
187
+ if (subAdditional.kind === 'allow') {
188
+ out.push({
189
+ path,
190
+ reason: 'additional-properties-widens',
191
+ superset: safeStringify(superset.additionalProperties),
192
+ subset: 'true',
193
+ message: `${pathLabel(path)}: subset.additionalProperties is true ` +
194
+ `(unconstrained) but superset constrains additional ` +
195
+ `properties to a schema.`,
196
+ });
197
+ }
198
+ else if (subAdditional.kind === 'schema') {
199
+ const childPath = path === '' ? 'additionalProperties' : `${path}.additionalProperties`;
200
+ compare(supAdditional.schema, subAdditional.schema, childPath, out);
201
+ }
202
+ // subAdditional === 'reject' → subset never emits extras, fits inside.
203
+ }
204
+ // supAdditional.kind === 'allow' → subset's extras are all legal.
205
+ // Required-set checks. A required-on-subset-only property is fine
206
+ // (subset is stricter). A required-on-superset-only property
207
+ // means the subset can produce values missing that key, which the
208
+ // superset would reject.
209
+ for (const key of supRequired) {
210
+ if (!subRequired.has(key)) {
211
+ // Only a violation if the property is actually reachable on
212
+ // the subset — if the subset simply doesn't mention the key
213
+ // at all (and its additionalProperties rejects), the subset
214
+ // can't even emit a value with that key, so a missing-required
215
+ // on the superset is something the subset-produced value will
216
+ // flunk.
217
+ out.push({
218
+ path: path === '' ? `required.${key}` : `${path}.required.${key}`,
219
+ reason: 'missing-required',
220
+ superset: '(required)',
221
+ subset: subRequired.has(key) ? '(required)' : '(optional or absent)',
222
+ message: `${pathLabel(path)}: superset requires '${key}' but subset ` +
223
+ `does not — subset can emit values missing '${key}' that ` +
224
+ `the superset rejects.`,
225
+ });
226
+ }
227
+ }
228
+ }
229
+ function compareArray(superset, subset, path, out) {
230
+ const supItems = superset.items;
231
+ const subItems = subset.items;
232
+ // Permissive when either side omits items.
233
+ if (!supItems || !subItems)
234
+ return;
235
+ const childPath = path === '' ? 'items' : `${path}.items`;
236
+ compare(supItems, subItems, childPath, out);
237
+ }
238
+ function normalizeType(schema) {
239
+ return schema.type;
240
+ }
241
+ function resolveAdditional(value) {
242
+ // JSON Schema draft-07: omitted ⇒ additionalProperties `true`.
243
+ if (value === undefined || value === true)
244
+ return { kind: 'allow' };
245
+ if (value === false)
246
+ return { kind: 'reject' };
247
+ return { kind: 'schema', schema: value };
248
+ }
249
+ function hasUnsupported(schema) {
250
+ return (supersetUnsupportedField(schema) !== undefined);
251
+ }
252
+ /**
253
+ * Returns the first unsupported P2-scope field present on the
254
+ * schema, or `undefined` when none. Order is stable so violation
255
+ * messages are deterministic.
256
+ */
257
+ function supersetUnsupportedField(schema) {
258
+ // JsonSchema type does not declare `$ref` / `allOf`, but raw JSON
259
+ // can carry them — a zod-converted schema or a hand-authored
260
+ // fixture will. We check via property lookup without widening the
261
+ // type.
262
+ const bag = schema;
263
+ if (typeof bag['$ref'] === 'string')
264
+ return '$ref';
265
+ if (Array.isArray(bag['allOf']))
266
+ return 'allOf';
267
+ if (Array.isArray(schema.oneOf))
268
+ return 'oneOf';
269
+ if (Array.isArray(schema.anyOf))
270
+ return 'anyOf';
271
+ if (Array.isArray(schema.enum))
272
+ return 'enum';
273
+ if (schema.const !== undefined)
274
+ return 'const';
275
+ return undefined;
276
+ }
277
+ function safeStringify(value) {
278
+ if (value === undefined)
279
+ return '(undefined)';
280
+ let json;
281
+ try {
282
+ json = JSON.stringify(value);
283
+ }
284
+ catch {
285
+ json = String(value);
286
+ }
287
+ if (json === undefined)
288
+ return '(undefined)';
289
+ if (json.length > 120)
290
+ return json.slice(0, 117) + '...';
291
+ return json;
292
+ }
293
+ function pathLabel(path) {
294
+ return path === '' ? '(root)' : path;
295
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Classification of a UI component by its portability.
3
+ *
4
+ * - `sandboxed` — pure React + `@ggui-ai/design` primitives. Portable,
5
+ * publishable, runs in any ggui rendering context.
6
+ * - `fullstack` — uses adapters (`@ggui-ai/react` hooks, server
7
+ * connectors). Requires a client bundle, app-scoped.
8
+ *
9
+ * Colocated with the classifier (`classifyUi`) because that's the
10
+ * only runtime producer of this value — the `UiManifest` schema in
11
+ * `@ggui-ai/project-config` imports this vocabulary and validates
12
+ * against it.
13
+ */
14
+ export type UiClass = 'sandboxed' | 'fullstack';
15
+ export interface DangerousPattern {
16
+ /** Regex to match against source/compiled code. */
17
+ pattern: RegExp;
18
+ /** Human-readable name of the pattern. */
19
+ name: string;
20
+ /** Why this is blocked and what to do instead. */
21
+ suggestion: string;
22
+ }
23
+ /**
24
+ * Patterns that are NEVER allowed in sandboxed UI components.
25
+ * These are security-critical — changes here affect every validation consumer.
26
+ */
27
+ export declare const DANGEROUS_PATTERNS: DangerousPattern[];
28
+ /** Import prefixes that indicate a fullstack UI (requires client bundle). */
29
+ export declare const FULLSTACK_IMPORT_PREFIXES: readonly ["@ggui-ai/wire", "@ggui-ai/react", "@app/components"];
30
+ /**
31
+ * Classify a component as sandboxed or fullstack based on its imports.
32
+ *
33
+ * - **sandboxed**: Pure React + @ggui-ai/design primitives. Portable, publishable.
34
+ * - **fullstack**: Uses @ggui-ai/wire, @ggui-ai/react, or @app/components. Private.
35
+ *
36
+ * Works on both source (.tsx) and compiled (.js) code.
37
+ */
38
+ export declare function classifyUi(code: string): UiClass;
39
+ export interface SecurityCheckResult {
40
+ /** True if no dangerous patterns found. */
41
+ safe: boolean;
42
+ /** List of violations found. */
43
+ violations: Array<{
44
+ name: string;
45
+ suggestion: string;
46
+ line?: number;
47
+ }>;
48
+ }
49
+ /**
50
+ * Check code for dangerous patterns.
51
+ * Works on both source and compiled code.
52
+ */
53
+ export declare function checkSecurity(code: string): SecurityCheckResult;
54
+ //# sourceMappingURL=ui-security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-security.d.ts","sourceRoot":"","sources":["../../src/validation/ui-security.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;AAMhD,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,EAiFhD,CAAC;AAIF,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,iEAI5B,CAAC;AAEX;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUhD;AAUD,MAAM,WAAW,mBAAmB;IAClC,2CAA2C;IAC3C,IAAI,EAAE,OAAO,CAAC;IACd,gCAAgC;IAChC,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAc/D"}