@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 @@
1
+ {"version":3,"file":"data-contract.d.ts","sourceRoot":"","sources":["../../src/schemas/data-contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,663 @@
1
+ /**
2
+ * Zod schema for {@link DataContract} — the canonical wire shape for
3
+ * agent-authored contract declarations.
4
+ *
5
+ * ## Why this file exists
6
+ *
7
+ * Output-side seams that type-narrow contract on the wire —
8
+ * `pushOutputSchema.contract` and the various `decision` echoes — use
9
+ * `z.custom<DataContract>()` because they trust the shape (it
10
+ * originates from internal pod state).
11
+ *
12
+ * The input seam is different: agents author contract on
13
+ * `story.contract` and the handler MUST plumb them to the generator.
14
+ * `z.custom<DataContract>()` does NOT work on input schemas — the
15
+ * MCP SDK serializes input schemas as JSON Schema for `tools/list`,
16
+ * and `z.custom<T>()` has no JSON-Schema representation (it's a
17
+ * TypeScript-only escape hatch). The narrow alternative
18
+ * (`z.record(z.string(), z.unknown())`) erases the type and forces
19
+ * `as DataContract` casts downstream, which violates the project's
20
+ * Zero Workarounds Policy + Strict Typing First principle.
21
+ *
22
+ * The fundamental fix is this file: a real zod schema mirroring the
23
+ * `DataContract` interface field-for-field. Both the protocol's
24
+ * `handshakeInputSchema` and the OSS handler's `inputSchema` build
25
+ * their `contract?` field from `dataContractSchema`, the type derives
26
+ * via `z.infer`, and JSON-Schema serialization for MCP `tools/list`
27
+ * advertises the contract surface to agents.
28
+ *
29
+ * ## Shape source of truth
30
+ *
31
+ * The TS interface in `../types/data-contract.ts` remains the
32
+ * declared source of truth (consumers import the type). This file's
33
+ * schemas are a structural mirror — `dataContractSchema` is typed
34
+ * `z.ZodType<DataContract>` so any drift between schema and
35
+ * interface fails compile. A future cleanup can flip the relationship
36
+ * (TS via `z.infer`), but that's a broader refactor.
37
+ *
38
+ * ## JsonSchema posture
39
+ *
40
+ * Every nested `schema: JsonSchema` field on contract entries
41
+ * (PropEntry, ActionEntry, StreamChannelEntry, ContextEntry, ...)
42
+ * accepts `jsonSchemaSchema` — a permissive `z.object` over the
43
+ * known JSON Schema draft-07 subset {@link JsonSchema} declares,
44
+ * with `passthrough()` for fields the shape doesn't enumerate. We
45
+ * deliberately do NOT enforce JSON Schema's full grammar at this
46
+ * layer — that work belongs in
47
+ * `@ggui-ai/protocol/validation/schema-subset` and runs at
48
+ * push-time + blueprint-registration-time as the F4 schema
49
+ * compatibility checker. Agents authoring malformed schemas surface
50
+ * at that pass with a named violation reason; this layer's job is
51
+ * just to accept the contract and pass it to the generator.
52
+ */
53
+ import { z } from 'zod';
54
+ import { KNOWN_PERMISSION_NAMES } from '../validation/hygiene-rules.js';
55
+ import { STDLIB_GADGETS_PACKAGE } from '../gadgets/stdlib-gadgets.js';
56
+ import { COMPONENT_NAME_RE, HOOK_NAME_RE } from './gadget-name-grammar.js';
57
+ /**
58
+ * Recursive {@link JsonValue} — string | number | boolean | null |
59
+ * array | object. All fields on contract entries that carry default
60
+ * values, examples, or arbitrary JSON payloads use this.
61
+ */
62
+ export const jsonValueSchema = z.lazy(() => z.union([
63
+ z.string(),
64
+ z.number(),
65
+ z.boolean(),
66
+ z.null(),
67
+ z.array(jsonValueSchema),
68
+ // JsonObject is `{[key: string]: JsonValue | undefined}`. zod's
69
+ // `record` on the value side accepts `JsonValue`; missing keys
70
+ // surface as `undefined` at runtime which JSON.stringify drops.
71
+ z.record(z.string(), jsonValueSchema),
72
+ ]));
73
+ /**
74
+ * {@link JsonSchema} — JSON Schema draft-07 subset. Mirrors the
75
+ * fields the TS interface enumerates. `additionalProperties` and
76
+ * `items` are recursive — kept loose (`z.unknown()`) to avoid
77
+ * deep `z.lazy` chains; the F4 schema-subset checker validates
78
+ * full structural correctness at push time.
79
+ */
80
+ export const jsonSchemaSchema = z.lazy(() => z
81
+ .object({
82
+ type: z
83
+ .enum([
84
+ 'string',
85
+ 'number',
86
+ 'integer',
87
+ 'boolean',
88
+ 'array',
89
+ 'object',
90
+ 'null',
91
+ ])
92
+ .optional(),
93
+ description: z.string().optional(),
94
+ enum: z.array(jsonValueSchema).optional(),
95
+ default: jsonValueSchema.optional(),
96
+ example: jsonValueSchema.optional(),
97
+ items: jsonSchemaSchema.optional(),
98
+ properties: z.record(z.string(), jsonSchemaSchema).optional(),
99
+ required: z.array(z.string()).optional(),
100
+ additionalProperties: z
101
+ .union([jsonSchemaSchema, z.boolean()])
102
+ .optional(),
103
+ format: z.string().optional(),
104
+ })
105
+ .passthrough());
106
+ /**
107
+ * {@link PropEntry} — per-prop metadata in a {@link PropsSpec}.
108
+ *
109
+ * Shape: `{schema: {type:'string', ...}, required?, default?, ...}`.
110
+ * The JSON Schema NEVER sits flat at the entry level — every entry's
111
+ * schema lives in `.schema`. Authors writing `{type:'string'}` instead
112
+ * of `{schema: {type:'string'}}` will hit a shape error at push time.
113
+ */
114
+ export const propEntrySchema = z
115
+ .object({
116
+ description: z.string().optional(),
117
+ schema: jsonSchemaSchema,
118
+ required: z.boolean().optional(),
119
+ default: jsonValueSchema.optional(),
120
+ example: jsonValueSchema.optional(),
121
+ sourceTool: z.string().optional(),
122
+ })
123
+ .strict();
124
+ /** {@link PropsSpec} — wrapper `{description?, properties}` over the per-prop map. */
125
+ export const propsSpecSchema = z
126
+ .object({
127
+ description: z.string().optional(),
128
+ properties: z.record(z.string(), propEntrySchema),
129
+ })
130
+ .strict();
131
+ /**
132
+ * {@link ActionEntry} — per-action metadata in an {@link ActionSpec}.
133
+ *
134
+ * Actions are agent-routed gestures; no dispatch discriminator. Optional
135
+ * `nextStep` hints at the agent's intended next tool call (must resolve
136
+ * to an `agentCapabilities.tools[*]` key on the same contract —
137
+ * cross-ref enforced by the `CTR_REF_NEXT_STEP` linter).
138
+ *
139
+ * Anti-pattern: do NOT write `dispatch: {kind: 'tool', tool: '...'}` —
140
+ * that vocabulary is retired. Use a flat optional `nextStep: '<toolName>'`.
141
+ */
142
+ export const actionEntrySchema = z
143
+ .object({
144
+ description: z.string().optional(),
145
+ label: z.string(),
146
+ schema: jsonSchemaSchema.optional(),
147
+ example: jsonValueSchema.optional(),
148
+ icon: z.string().optional(),
149
+ confirm: z.boolean().optional(),
150
+ nextStep: z.string().min(1).optional(),
151
+ })
152
+ .strict();
153
+ /** {@link ActionSpec} — flat `Record<name, ActionEntry>`. */
154
+ export const actionSpecSchema = z.record(z.string(), actionEntrySchema);
155
+ /** {@link StreamChannelEntry} — per-channel metadata in a {@link StreamSpec}. */
156
+ export const streamChannelEntrySchema = z
157
+ .object({
158
+ description: z.string().optional(),
159
+ schema: jsonSchemaSchema,
160
+ example: jsonValueSchema.optional(),
161
+ mode: z.enum(['append', 'replace']).optional(),
162
+ replay: z.enum(['latest', 'all', 'none']).optional(),
163
+ complete: z.boolean().optional(),
164
+ tool: z.string().optional(),
165
+ source: z
166
+ .object({
167
+ tool: z.string(),
168
+ args: z.record(z.string(), jsonValueSchema).optional(),
169
+ })
170
+ .strict()
171
+ .optional(),
172
+ })
173
+ .strict();
174
+ /** {@link StreamSpec} — flat `Record<channel, StreamChannelEntry>`. */
175
+ export const streamSpecSchema = z.record(z.string(), streamChannelEntrySchema);
176
+ /** {@link ContextEntry} — per-slot metadata in a {@link ContextSpec}. */
177
+ export const contextEntrySchema = z
178
+ .object({
179
+ description: z.string().optional(),
180
+ schema: jsonSchemaSchema,
181
+ default: jsonValueSchema.optional(),
182
+ debounceMs: z.number().int().nonnegative().optional(),
183
+ example: jsonValueSchema.optional(),
184
+ })
185
+ .strict();
186
+ /** {@link ContextSpec} — flat `Record<slot, ContextEntry>`. */
187
+ export const contextSpecSchema = z.record(z.string(), contextEntrySchema);
188
+ /** {@link AgentToolEntry} — per-tool metadata in an {@link AgentCapabilitiesSpec}. */
189
+ export const agentToolEntrySchema = z
190
+ .object({
191
+ description: z.string().optional(),
192
+ usage: z.string().optional(),
193
+ inputSchema: jsonSchemaSchema.optional(),
194
+ outputSchema: jsonSchemaSchema.optional(),
195
+ required: z.boolean().optional(),
196
+ example: z
197
+ .object({
198
+ input: jsonValueSchema,
199
+ output: jsonValueSchema,
200
+ })
201
+ .strict()
202
+ .optional(),
203
+ })
204
+ .strict();
205
+ /** {@link AgentCapabilitiesSpec} — wrapper over the per-tool map. */
206
+ export const agentCapabilitiesSpecSchema = z
207
+ .object({
208
+ tools: z.record(z.string(), agentToolEntrySchema),
209
+ })
210
+ .passthrough();
211
+ /**
212
+ * `App.publicEnv` key regex.
213
+ *
214
+ * Each key in `App.publicEnv` MUST match this pattern. The prefix is
215
+ * the **security boundary** — operators can't accidentally stash
216
+ * sensitive credentials under arbitrary names, and downstream consumers
217
+ * (push gate, bootstrap projection, iframe shim) can rely on the
218
+ * naming convention to mean "public-by-design".
219
+ *
220
+ * Rule: `GGUI_PUBLIC_APP_` prefix, then uppercase letters / digits /
221
+ * underscores, at least one char after the prefix.
222
+ *
223
+ * `GGUI_PUBLIC_USER_*` keys are RESERVED for a future per-user
224
+ * channel. The current regex rejects them so App-side config can't
225
+ * pre-emptively use the namespace.
226
+ *
227
+ * Hoisted above `gadgetDescriptorSchema` so the wrapper's `requires`
228
+ * array can reference it at schema-construction time (TDZ-safe).
229
+ */
230
+ export const PUBLIC_ENV_APP_KEY_RE = /^GGUI_PUBLIC_APP_[A-Z0-9_]+$/;
231
+ /**
232
+ * Single source of truth for the `requires[]` field shape on gadget
233
+ * descriptors. The wire-permissive `gadgetDescriptorSchema`, the
234
+ * strict `strictGadgetDescriptorSchema`, AND the author-facing
235
+ * `@ggui-ai/artifact-manifest#gadgetManifestSchema` all need an
236
+ * identical `z.array(z.string().regex(PUBLIC_ENV_APP_KEY_RE))`.
237
+ * Exported here so any future tightening (e.g., cap count, dedupe
238
+ * refinement) lives in one place.
239
+ *
240
+ * Entries are App.publicEnv key names — `GGUI_PUBLIC_APP_*`. Wrappers
241
+ * that declare a `requires` key must have a corresponding App-side
242
+ * publicEnv value at push time (gate: `assertPublicEnvSatisfied`).
243
+ */
244
+ export const gadgetRequiresSchema = z
245
+ .array(z.string().regex(PUBLIC_ENV_APP_KEY_RE))
246
+ .readonly();
247
+ /**
248
+ * SRI hash format for gadget bundles. Registry install writes
249
+ * `bundleSri` in this shape; iframe-runtime emits it verbatim into
250
+ * the `<script integrity>` attribute. Only `sha384` is accepted on
251
+ * purpose:
252
+ *
253
+ * - SHA-384 is the strongest hash routinely allowed by browsers
254
+ * for SRI without compatibility caveats (SHA-512 is allowed but
255
+ * adds no real security over -384 here).
256
+ * - Pinning a single algorithm makes the publish Lambda's hash
257
+ * computation and the iframe's verification trivially aligned —
258
+ * no algorithm negotiation, no ambiguity at audit time.
259
+ *
260
+ * Base64 body is the standard SRI body (RFC 3548 `+/=`, NOT
261
+ * url-safe). The regex permits zero or two `=` pad chars (SHA-384
262
+ * digest is 48 bytes = base64 length 64 with no padding).
263
+ */
264
+ export const BUNDLE_SRI_RE = /^sha384-[A-Za-z0-9+/]+=*$/;
265
+ /**
266
+ * Bare npm package name. Either an unscoped name (`leaflet`) or a
267
+ * scoped name (`@my-org/leaflet`). Mirrors the
268
+ * `validate-npm-package-name` subset that nearly every modern
269
+ * registry accepts:
270
+ *
271
+ * - Lowercase alphanumerics + `.` + `_` + `-`.
272
+ * - First char of name (and scope, if present) MUST be alphanumeric
273
+ * — leading `.`/`_`/`-` rejected to match historical npm bans.
274
+ * - At most one optional `@scope/` prefix.
275
+ *
276
+ * Examples that pass:
277
+ * - `leaflet`, `react-router`, `@ggui-ai/gadgets`, `@my-org/foo.bar`
278
+ *
279
+ * Examples that fail:
280
+ * - `@scope/@other/name` (multi-scope)
281
+ * - `https://registry.ggui.ai/foo` (URL — registry choice lives on
282
+ * `bundleUrl` / `typesUrl`, not `package`)
283
+ * - `Leaflet` (uppercase)
284
+ * - `.foo` / `_foo` / `-foo` (leading non-alphanumeric)
285
+ */
286
+ export const NPM_PACKAGE_NAME_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
287
+ /**
288
+ * Exact semver pin (e.g., `0.0.1`, `1.2.3-beta.1`, `2.0.0+build.7`).
289
+ * No ranges (`^`, `~`, `>=`), no leading `v`, no wildcards.
290
+ *
291
+ * Why pin-only: the cache key for a generated UI is
292
+ * `hashContract(wire, intent)` — making the wire carry an exact
293
+ * version means cache invalidation is a pure function of the wire
294
+ * bytes (no canonicalize step). Version bumps produce new wire →
295
+ * fresh generation; forensics are observable from storage alone.
296
+ *
297
+ * Grammar mirrors semver 2.0 spec:
298
+ * `MAJOR.MINOR.PATCH(-PRERELEASE)?(+BUILD)?`
299
+ * Pre-release + build identifiers match `[\w.]+` (semver's
300
+ * dot-separated alphanumeric identifiers).
301
+ */
302
+ export const SEMVER_PIN_RE = /^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/;
303
+ /**
304
+ * Hostname-only regex for `bundleHost` — the registry hostname (no
305
+ * scheme, no path) that the server prepends `https://` to and
306
+ * appends the canonical `/bundles/<scope>/<name>/<version>/{bundle.js,style.css}`
307
+ * suffix to when resolving a gadget's URLs at push time.
308
+ *
309
+ * Examples that pass:
310
+ * - `registry.ggui.ai` (spec default)
311
+ * - `dev.registry.sandbox.ggui.ai`
312
+ * - `sandbox-ggui-main.registry.sandbox.ggui.ai`
313
+ * - `localhost:8787` (port permitted for local registries)
314
+ *
315
+ * Examples that fail (caught at register-time):
316
+ * - `https://registry.ggui.ai` (scheme not allowed; use `bundleUrl`
317
+ * full-URL escape hatch if you need non-HTTPS or a non-standard path)
318
+ * - `/leaflet@0.0.1/bundle.js` (path not allowed)
319
+ * - `Registry.Ggui.Ai` (must be lowercase; DNS names are
320
+ * case-insensitive but we pin lower for canonicalization)
321
+ */
322
+ export const BUNDLE_HOST_RE = /^[a-z0-9.-]+(:\d+)?$/;
323
+ /**
324
+ * Spec-default registry hostname applied when neither operator
325
+ * (`app.gadgets[*].bundleHost`) nor gadget manifest
326
+ * (`ggui.gadget.json#bundleHost`) declares one. The server's URL
327
+ * resolver falls through here last, so first-party + hosted-registry
328
+ * publishes "just work" without explicit operator config.
329
+ */
330
+ export const DEFAULT_BUNDLE_HOST = 'registry.ggui.ai';
331
+ /**
332
+ * Loopback `bundleHost` predicate — anchored regex matching the three
333
+ * IP/hostname pairs that resolve to localhost: `localhost`, `127.0.0.1`,
334
+ * `0.0.0.0` (each optionally suffixed with a port). Anchoring at both
335
+ * ends prevents `localhost-evil.com` from being treated as loopback.
336
+ *
337
+ * Used symmetrically by:
338
+ * - `buildInstallCommand` (publish CLI) to emit `http://localhost:PORT`
339
+ * in the printed `ggui gadget install ...` line.
340
+ * - `resolveGadgetUrls` (push-time bootstrap derivation) to compute
341
+ * `http://localhost:PORT/bundles/...` so iframe fetches a reachable
342
+ * URL during local-dev / sandbox-registry workflows.
343
+ *
344
+ * If install + render disagreed on scheme, an operator running a local
345
+ * registry would publish via `http://` then have render emit `https://`
346
+ * and silently fail in the iframe (mixed-content block in the host's
347
+ * sandboxed context).
348
+ */
349
+ export const LOOPBACK_HOST_RE = /^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?$/;
350
+ /**
351
+ * Compute the scheme (`http` / `https`) for a given `bundleHost`.
352
+ * Loopback hosts get `http://`; everything else gets `https://`.
353
+ *
354
+ * Centralized so any future scheme-policy change (e.g., allowing
355
+ * `https://` on a self-signed local cert) updates one site.
356
+ */
357
+ export function bundleHostScheme(host) {
358
+ return LOOPBACK_HOST_RE.test(host) ? 'http' : 'https';
359
+ }
360
+ /**
361
+ * `HOOK_NAME_RE` + `COMPONENT_NAME_RE` are defined in the
362
+ * dependency-free leaf {@link module:schemas/gadget-name-grammar} and
363
+ * re-exported here so the package barrel surface is unchanged. The
364
+ * leaf placement breaks the import cycle that would otherwise form
365
+ * between this module and `validation/hygiene-rules.ts` (which also
366
+ * needs the grammars and which this module imports
367
+ * `KNOWN_PERMISSION_NAMES` from).
368
+ */
369
+ export { HOOK_NAME_RE, COMPONENT_NAME_RE } from './gadget-name-grammar.js';
370
+ /**
371
+ * Per-export field shape shared by both export-schema postures —
372
+ * `gotchas` + `required`. Teaching text (`description` / `usage` /
373
+ * `example`) and `permission` differ between wire-permissive and
374
+ * registry-strict, so they are spread per-posture below.
375
+ */
376
+ const baseExportFieldsShape = {
377
+ gotchas: z.string().optional(),
378
+ required: z.boolean().optional(),
379
+ };
380
+ /**
381
+ * Wire-permissive per-export metadata: teaching text optional,
382
+ * `permission` free-form.
383
+ */
384
+ const permissiveExportMetaShape = {
385
+ ...baseExportFieldsShape,
386
+ description: z.string().optional(),
387
+ usage: z.string().optional(),
388
+ example: jsonValueSchema.optional(),
389
+ permission: z.string().optional(),
390
+ };
391
+ /**
392
+ * Registry-strict per-export metadata: teaching text REQUIRED,
393
+ * `permission` enum-tight ({@link KNOWN_PERMISSION_NAMES}).
394
+ */
395
+ const strictExportMetaShape = {
396
+ ...baseExportFieldsShape,
397
+ description: z.string().min(1),
398
+ usage: z.string().min(1),
399
+ example: jsonValueSchema,
400
+ permission: z.enum(KNOWN_PERMISSION_NAMES).optional(),
401
+ };
402
+ /**
403
+ * Package-level field shape shared by `gadgetDescriptorSchema` and
404
+ * `strictGadgetDescriptorSchema`. Identity (`package` + `version`) +
405
+ * transport metadata (`bundleUrl` / `bundleHost` / `bundleSri` /
406
+ * `styleUrl` / `connect` / `requires` / `typesUrl` / `typesSri`) —
407
+ * all per-PACKAGE. The two descriptor schemas differ only in their
408
+ * `exports` element schema (permissive vs strict).
409
+ */
410
+ const basePackageFieldsShape = {
411
+ // Identity tuple `(package, version)`. Pin-only semver per
412
+ // {@link SEMVER_PIN_RE}.
413
+ version: z.string().regex(SEMVER_PIN_RE),
414
+ package: z.string().regex(NPM_PACKAGE_NAME_RE),
415
+ // Full URL shape — non-URL strings would crash the iframe's
416
+ // `import(<bundleUrl>)` at runtime.
417
+ bundleUrl: z.url().optional(),
418
+ // Hostname-only constraint (see {@link BUNDLE_HOST_RE}).
419
+ bundleHost: z.string().regex(BUNDLE_HOST_RE).optional(),
420
+ // SHA-384 SRI hash; registry-emitted, hand-authored refs may omit.
421
+ bundleSri: z.string().regex(BUNDLE_SRI_RE).optional(),
422
+ styleUrl: z.url().optional(),
423
+ // CSP `connect-src` feed — full URL shape on every entry.
424
+ connect: z.array(z.url()).readonly().optional(),
425
+ // Shared `gadgetRequiresSchema`.
426
+ requires: gadgetRequiresSchema.optional(),
427
+ // HTTPS URL of the package's `.d.ts`. The handler fetches it at
428
+ // push time, SRI-verifies against `typesSri`, and loads it into the
429
+ // code-gen sandbox VFS. Optional at the base shape;
430
+ // `registeredGadgetDescriptorSchema` refines it to REQUIRED for
431
+ // non-stdlib packages.
432
+ typesUrl: z.url().optional(),
433
+ // SHA-384 SRI over the `.d.ts` bytes; registry-emitted. Reuses
434
+ // `BUNDLE_SRI_RE` — same `sha384-<base64>` shape as `bundleSri`.
435
+ typesSri: z.string().regex(BUNDLE_SRI_RE).optional(),
436
+ };
437
+ /**
438
+ * Wire-permissive {@link GadgetExport} schema — a union of a
439
+ * hook-export shape (carries `hook`) and a component-export shape
440
+ * (carries `component`); the identifier field present is the natural
441
+ * discriminator. Each member is `.strict()`, so an entry carrying
442
+ * BOTH `hook` and `component` is rejected. Teaching text is optional
443
+ * so contract authoring stays cheap; the registry-side
444
+ * {@link strictGadgetExportSchema} requires it.
445
+ */
446
+ export const gadgetExportSchema = z.union([
447
+ z
448
+ .object({
449
+ hook: z.string().regex(HOOK_NAME_RE),
450
+ ...permissiveExportMetaShape,
451
+ })
452
+ .strict(),
453
+ z
454
+ .object({
455
+ component: z.string().regex(COMPONENT_NAME_RE),
456
+ ...permissiveExportMetaShape,
457
+ })
458
+ .strict(),
459
+ ]);
460
+ /**
461
+ * Registry-strict {@link GadgetExport} schema — teaching text
462
+ * REQUIRED, `permission` enum-tight. Used as the `exports` element
463
+ * schema inside {@link strictGadgetDescriptorSchema}.
464
+ */
465
+ export const strictGadgetExportSchema = z.union([
466
+ z
467
+ .object({
468
+ hook: z.string().regex(HOOK_NAME_RE),
469
+ ...strictExportMetaShape,
470
+ })
471
+ .strict(),
472
+ z
473
+ .object({
474
+ component: z.string().regex(COMPONENT_NAME_RE),
475
+ ...strictExportMetaShape,
476
+ })
477
+ .strict(),
478
+ ]);
479
+ /**
480
+ * {@link GadgetDescriptor} — a gadget PACKAGE: identity + transport
481
+ * metadata + an `exports` array (≥1). Permissive shape: per-export
482
+ * teaching text is optional so contract authoring stays cheap.
483
+ * Registry-side registration uses the stricter
484
+ * {@link strictGadgetDescriptorSchema}.
485
+ */
486
+ export const gadgetDescriptorSchema = z
487
+ .object({
488
+ ...basePackageFieldsShape,
489
+ exports: z.array(gadgetExportSchema).min(1),
490
+ })
491
+ .strict();
492
+ /**
493
+ * Registry-side {@link GadgetDescriptor} validator. Stricter than the
494
+ * wire schema: every export's `description` / `usage` / `example` is
495
+ * REQUIRED and `permission` is enum-tight — via
496
+ * {@link strictGadgetExportSchema} as the `exports` element schema.
497
+ *
498
+ * Used by:
499
+ * - `createGguiGadget` SDK factory (validates wrapper specs).
500
+ * - `App.gadgets` registration handlers (ggui.json seed,
501
+ * ops_register_gadget, etc.).
502
+ *
503
+ * Same TS interface as the wire schema — the strictness lives in zod
504
+ * refinements, not the type system.
505
+ */
506
+ export const strictGadgetDescriptorSchema = z
507
+ .object({
508
+ ...basePackageFieldsShape,
509
+ exports: z.array(strictGadgetExportSchema).min(1),
510
+ })
511
+ .strict();
512
+ /**
513
+ * Registration-ready descriptor validator.
514
+ * {@link strictGadgetDescriptorSchema} plus the refinement that
515
+ * every non-stdlib package MUST carry a `typesUrl`.
516
+ *
517
+ * Two boundaries, two schemas:
518
+ *
519
+ * - `strictGadgetDescriptorSchema` — **author time**. The
520
+ * `createGguiGadget` SDK factory validates a wrapper spec at
521
+ * module load, BEFORE the build emits a `.d.ts` — `typesUrl`
522
+ * doesn't exist yet, so it can't be required here.
523
+ * - `registeredGadgetDescriptorSchema` — **registration time**.
524
+ * The build helper (`writeDescriptorJson`) and the `App.gadgets`
525
+ * registration handlers validate here, AFTER the build has
526
+ * emitted the `.d.ts`, computed its SRI, and stamped
527
+ * `typesUrl` + `typesSri` on the descriptor.
528
+ *
529
+ * The code-gen sandbox loads the `.d.ts` the URL points at to
530
+ * typecheck generated component code against the package's real
531
+ * export signatures. Stdlib (`@ggui-ai/gadgets`) is exempt — the
532
+ * sandbox loads its types directly. No permissive fallback:
533
+ * pre-launch posture forces strict typing across the board.
534
+ */
535
+ export const registeredGadgetDescriptorSchema = strictGadgetDescriptorSchema.refine((entry) => entry.package === STDLIB_GADGETS_PACKAGE ||
536
+ typeof entry.typesUrl === 'string', {
537
+ message: 'registered GadgetDescriptor MUST declare a `typesUrl` (HTTPS URL to the package\'s .d.ts) — the code-gen sandbox loads it to typecheck generated component code against the export signatures. Run `tsup --dts` (or `tsc --declaration`) in the wrapper build and publish the emitted .d.ts. Only the first-party `@ggui-ai/gadgets` stdlib is exempt.',
538
+ path: ['typesUrl'],
539
+ });
540
+ // `package` + `version` are required on every descriptor (mirroring
541
+ // the wire's `(hook, package, version)` identity tuple), so there is
542
+ // no "at least one of package / bundleUrl / bundleHost" refinement
543
+ // and no "bundleHost requires package + version" refinement.
544
+ /**
545
+ * Wire-side per-export USE entry on `clientCapabilities.gadgets`.
546
+ *
547
+ * The export NAME is the map key (see {@link gadgetPackageUseSchema});
548
+ * its grammar discriminates kind — a `use`-prefixed key is a hook, a
549
+ * PascalCase key is a component. Kind is therefore never a field.
550
+ *
551
+ * The only wire-authored payload is optional intent-specific prose:
552
+ *
553
+ * - `description?` / `usage?` — when present the agent's prose wins
554
+ * over the registered export's text; when omitted, push-time
555
+ * resolution inherits the registered text verbatim.
556
+ *
557
+ * Everything else — `version`, transport metadata, `permission`,
558
+ * `example`, `gotchas` — is registry-side and resolves from the
559
+ * `App.gadgets` catalog. `.strict()` so a hallucinated registry field
560
+ * fails loudly instead of being silently dropped.
561
+ */
562
+ export const gadgetExportUseSchema = z
563
+ .object({
564
+ description: z.string().optional(),
565
+ usage: z.string().optional(),
566
+ })
567
+ .strict();
568
+ /**
569
+ * Wire-side per-PACKAGE gadget use — the value type of
570
+ * `clientCapabilities.gadgets` (which is keyed by npm package name).
571
+ *
572
+ * A map of export name → {@link gadgetExportUseSchema}; at least one
573
+ * entry. The export name key is a `use`-prefixed hook
574
+ * ({@link HOOK_NAME_RE}) or a PascalCase component
575
+ * ({@link COMPONENT_NAME_RE}). The wire carries identity only —
576
+ * `(package, export name)` — never `version` or transport metadata;
577
+ * there is no package-level wire field, so the package entry IS its
578
+ * export map (no `exports` wrapper).
579
+ */
580
+ export const gadgetPackageUseSchema = z
581
+ .record(z
582
+ .string()
583
+ .refine((s) => HOOK_NAME_RE.test(s) || COMPONENT_NAME_RE.test(s), {
584
+ message: 'gadget export name must be a `use`-prefixed hook or a PascalCase component identifier',
585
+ }), gadgetExportUseSchema)
586
+ .refine((r) => Object.keys(r).length > 0, {
587
+ message: 'a `clientCapabilities.gadgets` package entry must declare at least one export',
588
+ });
589
+ /**
590
+ * {@link ClientCapabilitiesSpec} — wrapper over the package-keyed
591
+ * gadget map.
592
+ *
593
+ * `gadgets` is keyed by npm PACKAGE name; each value is a
594
+ * {@link gadgetPackageUseSchema} listing the exports of that package
595
+ * the UI uses. The wire carries identity only — `(package, export
596
+ * name)` — never `version` or transport metadata: the ggui server
597
+ * resolves the full {@link GadgetDescriptor} from the `App.gadgets`
598
+ * catalog at push time onto the `SessionStackEntry.gadgetDescriptors`
599
+ * sidecar.
600
+ *
601
+ * `.strict()` (not `.passthrough()`): the retired `libraries` field
602
+ * (renamed to `gadgets`) and any other stale sibling field MUST fail
603
+ * loudly at parse time.
604
+ */
605
+ export const clientCapabilitiesSpecSchema = z
606
+ .object({
607
+ gadgets: z.record(z.string().regex(NPM_PACKAGE_NAME_RE), gadgetPackageUseSchema),
608
+ })
609
+ .strict();
610
+ /**
611
+ * Zod schema for `App.publicEnv`.
612
+ *
613
+ * Flat `Record<string, string>` with key-regex enforcement
614
+ * ({@link PUBLIC_ENV_APP_KEY_RE}). Empty-string values are allowed
615
+ * (operator may want "intentionally absent" without dropping the key).
616
+ *
617
+ * Consumed by:
618
+ * - OSS `ggui.json#app.publicEnv` boot-time parse.
619
+ * - Cloud AppRecord persistence layer.
620
+ * - Defensive re-validation in `parseBootstrap` (iframe-runtime).
621
+ */
622
+ export const appPublicEnvSchema = z
623
+ .record(z.string().regex(PUBLIC_ENV_APP_KEY_RE), z.string())
624
+ .readonly();
625
+ /**
626
+ * {@link DataContract} — the unified four-spec contract surface
627
+ * agents author on `story.contract` and that the generator
628
+ * constrains the LLM to honor.
629
+ *
630
+ * Fields per the TS interface: `propsSpec` / `actionSpec` / `streamSpec` /
631
+ * `contextSpec` (the four typed surfaces), `agentCapabilities` (MCP
632
+ * tool catalog) + `clientCapabilities` (browser-capability hook
633
+ * catalog). All fields are optional — agents declare only what their UI
634
+ * uses. `intent` is NOT a contract field; internal consumers (prompt
635
+ * rendering, contract hash, cache scope) receive intent from the outer
636
+ * pipeline (the flat `intent` field on `ggui_handshake`, the operator
637
+ * prompt for harness benchmarks).
638
+ *
639
+ * No `interaction` mode field — the four specs describe the wire
640
+ * surface exhaustively, so a categorical mode label would be
641
+ * redundant. `passthrough()` below tolerates legacy payloads that
642
+ * still carry it — the field is silently dropped on round-trip.
643
+ *
644
+ * No `broadcast` field — channel data sources are declared per-channel
645
+ * via `streamSpec[ch].source` (flat `{tool, args?}` shape; transport
646
+ * auto-negotiated at runtime by `@ggui-ai/wire`).
647
+ *
648
+ * `passthrough()` lets unknown fields ride through without rejection
649
+ * — forward-compatible with future protocol additions, and matches
650
+ * the protocol's general "input-shape lax / validator-shape strict"
651
+ * posture (`handshakeInputSchema`, `pushInputSchema`, etc. all
652
+ * `passthrough` for the same reason).
653
+ */
654
+ export const dataContractSchema = z
655
+ .object({
656
+ propsSpec: propsSpecSchema.optional(),
657
+ actionSpec: actionSpecSchema.optional(),
658
+ streamSpec: streamSpecSchema.optional(),
659
+ contextSpec: contextSpecSchema.optional(),
660
+ agentCapabilities: agentCapabilitiesSpecSchema.optional(),
661
+ clientCapabilities: clientCapabilitiesSpecSchema.optional(),
662
+ })
663
+ .passthrough();