@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,1459 @@
1
+ /**
2
+ * Current protocol version. Used for cache invalidation and capability discovery.
3
+ *
4
+ * The block comment below is a chronological changelog of protocol
5
+ * wire-shape changes. Each dated entry describes a wire-shape or
6
+ * schema change; the most recent change anchors {@link PROTOCOL_VERSION}.
7
+ *
8
+ * --------------------------------------------------------------------
9
+ * `GguiBootstrapMeta.compiledValidators` — precompiled eval-free
10
+ * runtime validators (ADDITIVE, non-breaking):
11
+ *
12
+ * - `GguiBootstrapMeta` (and the server's `StackItemBootstrapView`
13
+ * projection) gain an optional `compiledValidators` field —
14
+ * `CompiledContractValidators`: ESM validator-module source
15
+ * strings, one per `propsSpec` / `actionSpec` entry / `streamSpec`
16
+ * channel / `contextSpec` slot, compiled server-side by
17
+ * `compileValidatorModule`.
18
+ * - The renderer iframe runs under a strict CSP with no
19
+ * `'unsafe-eval'`, so it cannot run `ajv.compile()` (which builds
20
+ * validators via `new Function`). Validators are now compiled at
21
+ * push time — where `eval` is legal and the contract schema is
22
+ * fixed — and shipped; the iframe loads them via `blob:` dynamic
23
+ * import and only ever RUNS them.
24
+ * - Additive + optional: a bootstrap without the field is valid;
25
+ * a consumer that ignores it falls back to the server as the sole
26
+ * contract authority.
27
+ *
28
+ * --------------------------------------------------------------------
29
+ * Wire `GadgetRef` retired — `clientCapabilities.gadgets` package-keyed,
30
+ * `version` dropped from the wire (BREAKING, pre-launch):
31
+ *
32
+ * w1. **`clientCapabilities.gadgets` is package-keyed.** The wire map
33
+ * flipped from `Record<bindingName, GadgetRef>` to
34
+ * `Record<packageName, GadgetPackageUse>`, where
35
+ * `GadgetPackageUse = Record<exportName, GadgetExportUse>` and
36
+ * `GadgetExportUse = { description?, usage? }`. A package entry IS
37
+ * its export map — the wire has no package-level field, so there
38
+ * is no `exports` wrapper. The export NAME is the inner map key;
39
+ * its grammar (`use`-prefixed hook vs PascalCase component)
40
+ * discriminates kind — there is no `hook` / `component` field and
41
+ * no arbitrary `binding` name.
42
+ *
43
+ * w2. **`version` is OFF the wire.** A wire gadget reference carries
44
+ * identity only — `(package, export name)`. `version` is the
45
+ * operator's deployment pin in `App.gadgets`, not the agent's to
46
+ * author: the ggui server resolves the full `GadgetDescriptor`
47
+ * (version + transport metadata) from the catalog at push time.
48
+ * The catalog lint enforces ≤1 descriptor per package, so
49
+ * `(name, package)` resolves to exactly one registered export.
50
+ *
51
+ * w3. **`GadgetRef` / `GadgetHookRef` / `GadgetComponentRef` /
52
+ * `GadgetRefBase` + schema `gadgetRefSchema` + helper
53
+ * `gadgetPackageKey` deleted.** New: `GadgetPackageUse`,
54
+ * `GadgetExportUse`, `GadgetUse` (a flattened `{ package, name,
55
+ * description?, usage? }` view) + `listContractGadgets(contract)`
56
+ * flatten helper. `gadgetIdentityKey` re-keyed to `(name, package)`.
57
+ *
58
+ * w4. **Push reject `gadget_version_not_registered` removed.** With no
59
+ * wire `version` there is nothing to mismatch;
60
+ * `assertGadgetsRegistered` keeps `gadget_not_registered` +
61
+ * `gadget_package_mismatch`. New fatal catalog lint
62
+ * `LINT_GADGET_DUPLICATE_PACKAGE` enforces ≤1 descriptor per
63
+ * package in an app's `App.gadgets`.
64
+ *
65
+ * --------------------------------------------------------------------
66
+ * `loadGadgets()` retired — gadgets direct-imported, runtime registry
67
+ * per-package (BREAKING, pre-launch):
68
+ *
69
+ * v1. **`GguiBootstrapMeta.gadgets` is per-package.** The channel
70
+ * flipped from one entry per hook (`{hook, package?, bundleUrl?,
71
+ * bundleSri?}`) to one entry per registered gadget PACKAGE
72
+ * (`{package, bundleUrl?, bundleSri?}` — `package` REQUIRED). The
73
+ * iframe-runtime loads each package's module namespace once and
74
+ * stores it under `globalThis.__ggui__.gadgets[package]`, so every
75
+ * hook AND component export the package ships is reachable. The
76
+ * runtime `gadgets` slot is correspondingly keyed by package name
77
+ * (`Record<package, ModuleNamespace>`), not flat by hook name.
78
+ *
79
+ * v2. **`loadGadgets()` removed from `@ggui-ai/gadgets`.** Generated
80
+ * component code now direct-imports gadget exports
81
+ * (`import { useLeafletMap } from '@my-org/leaflet'`) — one idiom
82
+ * with the design-system primitives. The `loadGadgets()` accessor
83
+ * + its `GadgetsCatalog` Proxy are gone; the 7 STDLIB hooks stay
84
+ * as plain named exports of `@ggui-ai/gadgets`. The iframe-runtime
85
+ * rewriter resolves every gadget package specifier to a
86
+ * per-package data-URL shim (hook exports → lazy thunks, component
87
+ * exports → error-boundary-wrapped components).
88
+ *
89
+ * --------------------------------------------------------------------
90
+ * Component gadgets — `GadgetDescriptor` normalized to a package
91
+ * (BREAKING, pre-launch):
92
+ *
93
+ * u1. **`GadgetDescriptor` is now a PACKAGE.** A descriptor carries
94
+ * package identity (`package`, `version`) + transport metadata
95
+ * (`bundleUrl`, `bundleHost`, `bundleSri`, `styleUrl`, `connect`,
96
+ * `requires`, `typesUrl`, `typesSri`) once at the package level,
97
+ * plus an `exports: GadgetExport[]` array (≥1, enforced by the
98
+ * schema). The per-export fields that used to sit flat on the
99
+ * descriptor — `hook`, `description`, `usage`, `example`,
100
+ * `gotchas`, `permission`, `required` — MOVED onto each
101
+ * `exports[*]`. A gadget package can now ship more than one
102
+ * export behind a single npm identity + bundle.
103
+ *
104
+ * u2. **`GadgetExport` is a union discriminated by field presence
105
+ * (`hook` vs `component`) — no `kind` field.**
106
+ * `GadgetExport = GadgetHookExport | GadgetComponentExport`,
107
+ * told apart by which identifier field is present: `{hook, …}` is
108
+ * a `use`-prefixed React hook the generated component calls;
109
+ * `{component, …}` is a PascalCase React component the generated
110
+ * code renders as JSX. Both share the per-export teaching text
111
+ * (`description` / `usage` / `example` / `gotchas`) + runtime
112
+ * gates (`permission` / `required`) via `GadgetExportBase`. New
113
+ * schemas `gadgetExportSchema` (wire-permissive) +
114
+ * `strictGadgetExportSchema` (registry — teaching text required,
115
+ * `permission` enum-tight).
116
+ *
117
+ * u3. **`GadgetRef` (wire) is a union discriminated by field
118
+ * presence (`hook` vs `component`) — no `kind` field.**
119
+ * `GadgetRef = GadgetHookRef | GadgetComponentRef` — the wire
120
+ * ref selects ONE export of a registered package:
121
+ * `{hook, package, version, description?, usage?}`
122
+ * or `{component, package, version, description?, usage?}`.
123
+ * `gadgetRefSchema` is a `z.union([…])` of two `.strict()`
124
+ * members — transport fields + per-export registry metadata stay
125
+ * off the wire.
126
+ *
127
+ * u4. **`descriptorToJSDoc` → `exportToJSDoc`.** The codegen helper
128
+ * now takes a `GadgetExport` (`exportToJSDoc(entry: GadgetExport)`)
129
+ * and projects its per-export teaching text. `catalogToAugmentationDts`
130
+ * walks `descriptor.exports`, emitting one `typeof import(...)`
131
+ * declaration per HOOK export (component exports are skipped —
132
+ * `GadgetsCatalog` is the `loadGadgets()` hook surface).
133
+ * (`catalogToAugmentationDts` retired GG.8.3 — the descriptor-
134
+ * derived TS augmentation supersedes it.)
135
+ *
136
+ * u5. **New helpers `gadgetExportName` / `gadgetPackageKey`.**
137
+ * `gadgetExportName(x: GadgetRef | GadgetExport)` returns the
138
+ * `hook` | `component` name — the single accessor every site
139
+ * that used to read `.hook` now calls. `gadgetPackageKey({package,
140
+ * version})` is the canonical `(package, version)` dedup key the
141
+ * resolver keys on; `gadgetIdentityKey(ref)` is the full
142
+ * `(name, package, version)` export-identity key the push-time
143
+ * gates agree on (the hook / component name grammars are
144
+ * disjoint, so the name itself is kind-disambiguating).
145
+ * (`gadgetPackageKey` + `GadgetRef` retired in GG.8.8 — the wire
146
+ * went package-keyed, so `version` left the wire and the
147
+ * `(package, version)` key collapsed to package-only; see the
148
+ * w-block. `gadgetIdentityKey` now keys `(name, package)`.)
149
+ *
150
+ * u6. **New name grammars `HOOK_NAME_RE` + `COMPONENT_NAME_RE`.**
151
+ * `HOOK_NAME_RE` (`/^use[A-Z][A-Za-z0-9]*$/`) pins the
152
+ * `use`-prefixed hook grammar; `COMPONENT_NAME_RE`
153
+ * (`/^[A-Z][A-Za-z0-9]*$/`) pins the PascalCase component
154
+ * grammar. Both the wire `gadgetRefSchema` and the registry
155
+ * `gadget(Strict)ExportSchema` enforce them, so a malformed
156
+ * export name fails loudly at parse time.
157
+ * (`gadgetRefSchema` retired GG.8.8 with the wire shift to the
158
+ * package-keyed `clientCapabilities.gadgets` map; the grammars
159
+ * themselves stay live — see the w-block.)
160
+ *
161
+ * `STDLIB_GADGETS` collapses to a 1-element array — the
162
+ * `@ggui-ai/gadgets` package descriptor whose `exports` are the
163
+ * seven stdlib hook exports. `loadGadgets()` + component-gadget
164
+ * rendering are GG.8.2+ work; GG.8.1 is the type/schema layer only.
165
+ *
166
+ * --------------------------------------------------------------------
167
+ * Client-library plugin SDK + registry-membership gate (additive, no
168
+ * wire breakage):
169
+ *
170
+ * p1. **`createGguiGadget` SDK** in `@ggui-ai/gadgets`.
171
+ * Authors a single wrapper that publishes a stable React hook
172
+ * contract atop a 3rd-party library (Leaflet, Mapbox, Stripe,
173
+ * Chart.js, …) so the LLM never sees raw library APIs. Every
174
+ * wrapper carries `{description, usage, example}` (required) +
175
+ * optional `{gotchas, version, package, bundleUrl, styleUrl,
176
+ * connect[]}`. The factory zod-validates the spec at module load
177
+ * and throws `WrapperConformanceError` with field-path violations
178
+ * on shape misses. Returns a callable hook whose immutable
179
+ * `.descriptor` is what operators register on
180
+ * `App.gadgets`.
181
+ *
182
+ * p2. **Two-schema strictness pattern on `GadgetDescriptor`.** A
183
+ * single TS type drives both the registry (strict —
184
+ * `strictGadgetDescriptorSchema` requires `description` /
185
+ * `usage` / `example` and at least one of `package` /
186
+ * `bundleUrl`) and the contract refs (permissive —
187
+ * `gadgetDescriptorSchema` for wire/contract use). Strictness
188
+ * lives in the validators, not the type, so consumers don't
189
+ * duplicate type defs.
190
+ *
191
+ * p3. **Registry-membership gate at push validation.**
192
+ * `assertGadgetsRegistered(contract, appGadgets)`
193
+ * walks `contract.clientCapabilities.gadgets[*].hook` and
194
+ * throws `GadgetNotRegisteredError` on any reference not
195
+ * present in `App.gadgets`. Carries did-you-mean
196
+ * suggestions via `findClosestRegisteredHook` (Levenshtein < 3
197
+ * cutoff). Push falls back to `STDLIB_GADGETS` when the
198
+ * `App` row exists but doesn't carry an explicit catalog —
199
+ * symmetric with handshake.ts and list-gadgets.ts so
200
+ * every default-configured server enforces the gate without
201
+ * explicit operator config.
202
+ *
203
+ * p4. **Push-time enrichment of `StackItem.clientCapabilities`.**
204
+ * Thin contract Refs are merged with the canonical registry
205
+ * descriptor so the persisted StackItem carries the FULL entry
206
+ * (teaching text + bundleUrl + styleUrl + connect[]).
207
+ * Contract-side overrides win on conflict so agents may author
208
+ * intent-specific description/usage at the mount site.
209
+ *
210
+ * p5. **CSP derivation from registered gadget origins.** New
211
+ * `deriveBundleOrigins(item)` reads `bundleUrl` / `styleUrl` /
212
+ * `connect[]` off the enriched StackItem and emits per-directive
213
+ * origin buckets. `composeContentSecurityPolicy(origins)` formats
214
+ * them into a `Content-Security-Policy` header value
215
+ * (`script-src 'self' 'unsafe-inline' <origins>` so the inline
216
+ * `__GGUI_BOOTSTRAP__` survives, `style-src 'self' 'unsafe-inline'
217
+ * <origins>`, `connect-src 'self' <origins>`, `img-src 'self'
218
+ * data: <connect-origins>` so map tiles load). The renderer route
219
+ * attaches the header on `/r/<shortCode>` ONLY when libraries
220
+ * declare external origins — pre-plugin scenarios stay
221
+ * header-clean.
222
+ *
223
+ * p6. **Boilerplate generator prefers `bundleUrl` over `package`.**
224
+ * Imports group by `bundleUrl ?? package` so a gadget with a
225
+ * hosted bundle emits
226
+ * `import { useLeafletMap } from 'https://registry.ggui.ai/leaflet@0.0.1/bundle.js'`,
227
+ * while npm-packaged-only libraries fall through to the existing
228
+ * bare-specifier path.
229
+ *
230
+ * p7. **Teaching-text plumb into BOTH LLM paths.** The same
231
+ * `composeAvailableGadgetsSection(libraries)` helper feeds the
232
+ * synth prompt (`synthesize-contract.ts`) AND the decision-LLM
233
+ * prompt (`decision.ts`'s `buildDecisionUserMessage`). Both paths
234
+ * now see `description` + `usage` for every registered gadget
235
+ * with bounded per-entry (300 chars) + total (3 KB) budgets, so
236
+ * `App.gadgets`-registered plugins instruct the LLM
237
+ * uniformly without per-gadget prompt engineering.
238
+ *
239
+ * p8. **CLI seed via `ggui.json#app.gadgets`.** Manifest
240
+ * schema gains an optional array of full registry descriptors;
241
+ * the CLI threads them into `InMemoryAppMetadataStore` so the
242
+ * same in-process singleton powers `ggui_list_gadgets`,
243
+ * handshake, push validation + enrichment, and CSP derivation.
244
+ *
245
+ * p9. **Reference plugin + e2e gate.** `@ggui-samples/gadget-leaflet`
246
+ * ships as the canonical wrapper-author example;
247
+ * `@ggui-samples/ggui-leaflet-demo` is a sample server with the
248
+ * Leaflet plugin pre-registered. e2e/scenarios/19 pins the
249
+ * registry-membership gate end-to-end on the wire (registered
250
+ * hook accepts, unregistered hook rejects with
251
+ * `gadget_not_registered`, typo gets did-you-mean).
252
+ * Live-verified against `ggui-default` with a real LLM-backed
253
+ * negotiator.
254
+ *
255
+ * --------------------------------------------------------------------
256
+ * Ajv layered validation (single source of truth for inner JSON
257
+ * Schema + runtime data validation):
258
+ *
259
+ * v1. **All four runtime validators use Ajv + closed-shape.** The
260
+ * hand-rolled JSON-Schema-subset validator is retired.
261
+ * `validatePropsData` / `validateActionData` / `validateStreamData`
262
+ * / `validateContextData` now share one seam — `compileForValidation()`
263
+ * from `@ggui-ai/protocol/validation/ajv-runtime` — which injects
264
+ * `additionalProperties: false` at every object node before Ajv
265
+ * compiles. Closed-shape applies uniformly at any depth (arrays
266
+ * of objects, oneOf branches, additionalProperties-as-schema).
267
+ * The `done`-vs-declared-`completed` class of bug — and any
268
+ * similar field-name divergence — surfaces as a wire-time
269
+ * `ContractViolationError` with the exact path (`todos[0].done`)
270
+ * instead of rendering silently as `undefined`.
271
+ * Authors who genuinely want an opaque-object escape hatch set
272
+ * `additionalProperties: true` explicitly on the schema; the
273
+ * injector preserves author intent (boolean kept, schema-form
274
+ * recursed into).
275
+ * Tolerated keywords: `example` (OpenAPI metadata) and
276
+ * `nullable` (OpenAPI 3.0 shorthand) registered as no-ops so
277
+ * Ajv strict mode doesn't reject schemas that carry them.
278
+ * Pre-launch no-backcompat: stream / action / context schemas
279
+ * authored as `{type: 'object'}` with no `properties` now mean
280
+ * "empty object only" (not "any object"). Migrate to explicit
281
+ * `properties` declarations OR set `additionalProperties: true`.
282
+ *
283
+ * v2. **Layer-B meta-validation at handshake + push.**
284
+ * `assertContractSchemasValid(contract)` walks the six inner
285
+ * JSON Schema fields (`propsSpec.properties[*].schema`,
286
+ * `actionSpec[*].schema`, `streamSpec[*].schema`,
287
+ * `contextSpec[*].schema`, `agentCapabilities.tools[*].inputSchema`,
288
+ * `agentCapabilities.tools[*].outputSchema`) and runs
289
+ * `compileForValidation()` on each. Ajv strict mode throws at
290
+ * compile-time on malformed schemas (unknown keywords, properties
291
+ * values that aren't schemas, array schemas with non-schema
292
+ * items). Every offender collects into one
293
+ * `ContractSchemaMetaError` so the agent fixes them all in one
294
+ * round rather than retry-per-field. Called BEFORE the
295
+ * negotiator runs (handshake) and BEFORE any state mutation
296
+ * (push). Same fail-fast posture as the cross-reference +
297
+ * name-invariant + schema-compat assertions already in place.
298
+ *
299
+ * v3. **Wrapper zod schemas are `.strict()`.** Seven entry/spec
300
+ * wrappers in `@ggui-ai/protocol/schemas/data-contract` flipped
301
+ * from `.passthrough()` to `.strict()`:
302
+ * `propEntrySchema`, `propsSpecSchema`, `actionEntrySchema`,
303
+ * `streamChannelEntrySchema` (+ its inner `source` schema),
304
+ * `contextEntrySchema`, `agentToolEntrySchema` (+ its inner
305
+ * `example` schema), `gadgetDescriptorSchema`. Extras at the
306
+ * wrapper layer now reject — symmetric with the closed-shape
307
+ * rigor at the data layer. `jsonSchemaSchema` and the outer
308
+ * `dataContractSchema` envelope stay permissive (vendor JSON
309
+ * Schema extensions + forward-compatibility for future top-
310
+ * level fields).
311
+ * Pre-launch no-backcompat: agents that put unknown fields at
312
+ * wrapper layers MUST migrate. Common offender: `required: []`
313
+ * on the `propsSpec` wrapper (required lives per-entry, not at
314
+ * the spec level).
315
+ *
316
+ * --------------------------------------------------------------------
317
+ * propsSpec closed-shape (strict mode) + wire-schema trim:
318
+ *
319
+ * u3. **`pushOutputSchema` + `updateOutputSchema` + `handshakeOutputSchema`
320
+ * trimmed to match handler reality.** Three response schemas in
321
+ * `@ggui-ai/protocol/schemas/mcp` were carrying retired fields
322
+ * that handlers had already stopped emitting (zod strips them
323
+ * before serialization). Public schema now mirrors what flows on
324
+ * the wire — third-party importers see the same shape the
325
+ * structuredContent carries:
326
+ * - `handshakeOutputSchema`: dropped `reason`, `target`,
327
+ * `alternatives`, `contractHash`, `serverCapabilities`.
328
+ * `serverCapabilities` flows via `_meta.ggui.bootstrap` instead.
329
+ * - `pushOutputSchema`: dropped `sessionId`, `shortCode`,
330
+ * `codeReady`, `handshakeId`, `decision`, `contract`,
331
+ * `interaction`, `contractHash`, `cache.*`, `codeUrl`,
332
+ * `codeHash`. Wire shape is now `{stackItemId, url, action,
333
+ * nextStep?}` — 90% byte reduction at the trim layer.
334
+ * - `updateOutputSchema`: dropped `sessionId`, `decision`,
335
+ * `contract`, `interaction`, `contractHash`. Wire shape is
336
+ * now `{stackItemId, updated}`.
337
+ * Pre-launch no-backcompat: consumers that read any of the dropped
338
+ * fields off the wire response MUST migrate. Internal telemetry
339
+ * threading via TS-only `HandshakeOutput` / `PushOutput` /
340
+ * `UpdateOutput` shapes is preserved for handler-side callers.
341
+ *
342
+ * u2. **`validatePropsData` is closed-shape (strict mode).** Keys
343
+ * not declared on `propsSpec.properties` are now rejected with
344
+ * `ContractViolationError{tool:'ggui_update'}` (or `'ggui_push'`
345
+ * depending on call site). Load-bearing for `ggui_update kind:'merge'`
346
+ * — without it, a typo'd patch field would silently land on the
347
+ * stack item with no propsSpec coverage. Symmetric with
348
+ * `validateActionData`'s allowlist enforcement; intentionally
349
+ * asymmetric with `actionSpec.data` / `streamSpec.payload` /
350
+ * `contextSpec.value` (those validate type-only, forward-compatible).
351
+ * Pre-launch no-backcompat: agents that sent extra metadata fields
352
+ * alongside declared props MUST refine the contract's `propsSpec`
353
+ * to declare them, or drop them from the wire payload.
354
+ *
355
+ * --------------------------------------------------------------------
356
+ * `ggui_update` replace + merge modes:
357
+ *
358
+ * u1. **`kind: 'replace' | 'merge'` discriminator on `ggui_update`.**
359
+ * Wire input reshaped from `{stackItemId, props}` to a
360
+ * discriminated union:
361
+ * - `{stackItemId, kind:'replace', props}` — full props
362
+ * replacement; the map IS the new state. Same semantics as
363
+ * the pre-discriminator wire.
364
+ * - `{stackItemId, kind:'merge', patch}` — RFC 7396 JSON Merge
365
+ * Patch (top-level shallow merge; nested objects recurse;
366
+ * `null` deletes the key; arrays fully replace). Use when
367
+ * most props stay the same and the agent only needs to send
368
+ * a delta — typical after a single domain-tool mutation.
369
+ * Both modes validate the FINAL props (post-merge for `merge`)
370
+ * against the stack item's `propsSpec` and reject on violation.
371
+ * Missing the required field for a mode (e.g. `kind:'merge'`
372
+ * without `patch`) throws `ContractViolationError{tool:
373
+ * 'ggui_update'}` pre-mutation.
374
+ * Pre-launch no-backcompat: agents that issued the old
375
+ * `{stackItemId, props}` shape MUST migrate to
376
+ * `{stackItemId, kind:'replace', props}`. The OSS handler's
377
+ * handshakeId arm was retired in the same pass; protocol schema
378
+ * now mirrors the handler's direct-only surface.
379
+ *
380
+ * --------------------------------------------------------------------
381
+ * Pipe-as-single-source-of-truth pivot:
382
+ *
383
+ * t1. **`ggui_runtime_claim_pending` retired.** The iframe-side rescue
384
+ * drain + its 10s claim timer + per-action `pendingActions` map
385
+ * are gone. The pipe is now the single source of truth: every
386
+ * `submit_action` either succeeds (event lands on the pipe; agent
387
+ * drains via `ggui_consume`) or fails (`PIPE_NOT_FOUND` / transport
388
+ * error; iframe emits the action inline via `ui/message`). No
389
+ * timer, no rescue, no race between two atomic-pop callers.
390
+ *
391
+ * t2. **`_meta.ggui.userAction` unifies `fallback` + `nudge`.** New
392
+ * single discriminator on `ui/message` envelopes replacing the
393
+ * separate `_meta.ggui.fallback` (reason: pipe_not_found | timeout)
394
+ * + `_meta.ggui.nudge` (no_active_consumer) split:
395
+ *
396
+ * - `kind: 'queued'` — pipe HAS the event; agent SHOULD dispatch
397
+ * the prepared `{tool: 'ggui_consume', args: {stackItemId}}`
398
+ * nextStep to drain. Emitted when `submit_action` returned
399
+ * `{ok:true, consumerPresent:false}`.
400
+ * - `kind: 'inline'` — pipe is GONE; action data + uiContext
401
+ * delivered inline in `payload`. Agent MUST act directly on
402
+ * `payload.actionData`; calling `ggui_consume` would return
403
+ * empty. Emitted when `submit_action` returned PIPE_NOT_FOUND,
404
+ * INVALID_ACTION_KIND, or any transport/relay error. Optional
405
+ * `nextStep: string` hint surfaces the contract's bound agent
406
+ * tool when present.
407
+ *
408
+ * Type guard `isGguiUserActionMeta` lives on
409
+ * `@ggui-ai/protocol/integrations/mcp-apps`.
410
+ *
411
+ * t3. **Per-event `uiContext` on the pipe.** `submit_action`'s
412
+ * `dispatch` payload reshaped from `{intent, data}` to `{intent,
413
+ * actionData, uiContext}`. The iframe captures the contract's
414
+ * `contextSpec` snapshot at gesture time and stores it on the
415
+ * pipe entry alongside the action data — `consume`'s output
416
+ * events now carry `{intent, actionData, uiContext, actionId,
417
+ * firedAt}` per event.
418
+ *
419
+ * t4. **`GguiConsumeOutput.contextSnapshot` retired.** The top-level
420
+ * contextSpec snapshot on consume's output is gone. Per-event
421
+ * `uiContext` (t3) replaces it. Agents read state AS OF the
422
+ * moment the user acted — not the post-action state that might
423
+ * have already mutated by the time consume returns.
424
+ *
425
+ * --------------------------------------------------------------------
426
+ * No-active-consumer fast-path:
427
+ *
428
+ * s1. **`consumerPresent` on `ggui_runtime_submit_action` output.** New
429
+ * optional `consumerPresent?: boolean` field on the dispatch
430
+ * success branch. When `true`, at least one `ggui_consume`
431
+ * long-poll is currently registered against the targeted stack
432
+ * item — iframe takes today's path (10s claim timer + drain_ack
433
+ * race). When `false`, no consumer is registered: the action IS
434
+ * on the pipe, but the agent won't wake on its own. Iframe
435
+ * SHOULD immediately emit a `ui/message` nudge instead of
436
+ * waiting on the 10s timer. When `undefined`, the server doesn't
437
+ * have an active-consumer registry wired (graceful degradation;
438
+ * iframe falls back to the timer). Additive — agnostic consumers
439
+ * ignore the field.
440
+ *
441
+ * s2. **`_meta.ggui.nudge` discriminator on `ui/message`.** New
442
+ * structured fingerprint paired with the free-form text nudge:
443
+ * `{reason: 'no_active_consumer', stackItemId, actionId,
444
+ * submittedAt}`. Sibling of `_meta.ggui.fallback` but
445
+ * semantically distinct — the nudge carries NO action payload
446
+ * (the pipe holds the data); it's a pure wake-up signal telling
447
+ * the agent to `ggui_consume({stackItemId})`. Type guard
448
+ * `isGguiNudgeMeta` mirrors `isGguiFallbackMeta` so ggui-aware
449
+ * SDKs route deterministically. Additive — agnostic hosts ignore
450
+ * the field; free-form text alone is enough to act on.
451
+ *
452
+ * s3. **`ActiveConsumerRegistry` seam (`@ggui-ai/mcp-server-core`).**
453
+ * Optional in-process reference-count seam tracking which stack
454
+ * items currently have an in-flight `ggui_consume` long-poll.
455
+ * `consume.ts` wraps its long-poll in `enter`/`exit`;
456
+ * `submit-action.ts` queries `hasActive` after a successful pipe
457
+ * append. Reference impl `InMemoryActiveConsumerRegistry` ships;
458
+ * cloud/multi-pod deployments wire a Redis-backed adapter
459
+ * against the same interface. Mirrors the optional-seam pattern
460
+ * of `DrainAckNotifier` / `ObserverNotifier` / `ConsumeLogger`.
461
+ *
462
+ * --------------------------------------------------------------------
463
+ * Action drain guarantee:
464
+ *
465
+ * r1. **Drain-guarantee envelope + WS channel.** New optional
466
+ * `_meta.ggui.fallback` discriminator on `ui/message` envelopes
467
+ * (`reason: 'pipe_not_found' | 'timeout'`) so ggui-aware SDKs
468
+ * can route fallback gestures through their tool-result loop
469
+ * instead of injecting as a synthetic user prompt. New
470
+ * server→client `drain_ack` WS frame so the iframe-runtime can
471
+ * cancel its per-action 10s claim timer + dismiss the toast as
472
+ * `consumed` when an event drains via `ggui_consume`.
473
+ * Additive — agnostic hosts ignore the `_meta.ggui.fallback`
474
+ * field and the unrecognized WS frame (no protocol break).
475
+ *
476
+ * r2. **`ggui_runtime_claim_pending` wire.** New `audience: 'runtime'`
477
+ * tool the iframe calls from its per-action 10s timer to
478
+ * atomically pop a stale pipe entry when the host agent isn't
479
+ * draining. Same `consumeAndClear` primitive `ggui_consume` uses
480
+ * (one caller wins; race resolves at the server-side lock). The
481
+ * tool isn't agent-addressable; it surfaces on `/mcp` only and
482
+ * gates through the postMessage relay.
483
+ *
484
+ * r3. **Drain-guarantee telemetry.** New `action_consume_slow`
485
+ * info-event (submit → drain latency >2s) and
486
+ * `action_claim_timeout` warn-event (claim_pending fired because
487
+ * nobody drained for 10s). Operators derive the fallback-ratio
488
+ * protocol-adherence metric from these.
489
+ *
490
+ * --------------------------------------------------------------------
491
+ * Bootstrap-meta cleanup:
492
+ *
493
+ * q1. **`_meta.ggui.bootstrap.componentCode` retired.** The
494
+ * inline base64 ESM channel on the bootstrap envelope is deleted.
495
+ * Static-component bootstraps now travel exclusively via the
496
+ * content-addressable `codeUrl` channel composed by the push
497
+ * handler from its `codeStore` + `codeBaseUrl` deps. The
498
+ * `hasPushBootstrapMeta` discriminator collapses from
499
+ * `{wsUrl-with-token, componentCode, codeUrl, kind}` to
500
+ * `{wsUrl-with-token, codeUrl, kind}`. `StackItemBootstrapView`
501
+ * drops the field on the projection layer. `buildSelfContainedShell`
502
+ * accepts `{codeUrl, codeHash}` (or `systemKind`, or live-mode
503
+ * trio); throws when none are set. `/r/<shortCode>` mints
504
+ * codeUrl via codeStore.hashOf + put; falls through to live-mode
505
+ * when codeStore isn't wired. Iframe-runtime parser drops
506
+ * `componentCode` read + the inline-base64 `decodeBase64Utf8`
507
+ * helper. Saves 5-50KB per push.
508
+ *
509
+ * q2. **`_meta.ggui.bootstrap.adapters` retired.** The
510
+ * dormant dynamic-import-at-boot adapter loader is deleted from
511
+ * the wire surface. `GguiBootstrapMeta.adapters?` field removed;
512
+ * `parseAdapterSpecs` + `installAdapters` + the
513
+ * `globalThis.__ggui__.adapters` registry slot retired from
514
+ * iframe-runtime. An earlier change had already moved capability
515
+ * hooks to `@ggui-ai/gadgets`; this cleanup closes the
516
+ * dead surface. Native shells (`@ggui-ai/camera`,
517
+ * `@ggui-ai/ggui-react-native`) unchanged — they register
518
+ * adapters via their own `<GguiProvider>` React Context, never
519
+ * via bootstrap-meta.
520
+ *
521
+ * --------------------------------------------------------------------
522
+ * MCP Apps compliance & update fan-out:
523
+ *
524
+ * p1. **`ggui_stream` → `ggui_emit` rename (wire name + symbol prefix).**
525
+ * The send-from-agent-to-iframe tool was named `ggui_stream`, which
526
+ * reads as a noun referring to the stream object rather than the
527
+ * imperative act of emitting. `ggui_emit` reads correctly as an
528
+ * action and disambiguates from the wire field `streamSpec` (which
529
+ * keeps its name — it describes channels, not the act of emitting).
530
+ * Mechanical sweep across `@ggui-ai/protocol`, `@ggui-ai/mcp-server-handlers`,
531
+ * `@ggui-ai/mcp-server`, `@ggui-ai/ggui-cli`, `@ggui-ai/ggui-react-native`,
532
+ * `cloud/ggui-protocol-pod`, `cloud/generation-runtime`, and `cloud/cdk`:
533
+ *
534
+ * - Wire-name literal `'ggui_stream'` → `'ggui_emit'`
535
+ * - Pascal symbol prefix `GguiStream` → `GguiEmit` (covers
536
+ * `GguiEmitInput`, `GguiEmitOutput`, `GguiEmitHandlerDeps`,
537
+ * `createGguiEmitHandler`)
538
+ * - Test-d file rename `ggui-stream.test-d.ts` → `ggui-emit.test-d.ts`
539
+ *
540
+ * Untouched (NOT the tool name): `streamSpec`, `StreamChannelEntry`,
541
+ * `StreamEnvelope`, `SessionChannelServer`, `streamReplayOps`, and
542
+ * file paths like `session-mutations/stream.ts`. These describe the
543
+ * channel data plane, not the imperative emit action.
544
+ * Pre-launch breaking rename; no compatibility shim.
545
+ *
546
+ * p2. **`ggui_update` emits `_meta.ggui.bootstrap` on tool result.**
547
+ * Previously, the canonical re-apply path
548
+ * (MCP Apps `ui/notifications/tool-result` → host postMessage → iframe
549
+ * `_meta.ggui.bootstrap` consumer in `@ggui-ai/iframe-runtime`) had
550
+ * no envelope to deliver: `ggui_update`'s `resultMeta` was empty.
551
+ * Hosts that forwarded tool results couldn't trigger the spec-compliant
552
+ * live-update path. `createGguiUpdateHandler` now accepts the same
553
+ * bootstrap-emitting deps as `ggui_push` (`mintBootstrap`, `runtimeUrl`,
554
+ * `themeId`, `themeMode`, `themeProvider`, `appCallableTools`,
555
+ * `streamWebSocketLocalTools`) and emits `_meta.ggui.bootstrap`
556
+ * derived from the just-patched stack item via the shared
557
+ * `deriveStackItemBootstrapView` projection — byte-identical to
558
+ * `ggui_push`'s bootstrap envelope at the projection boundary.
559
+ * Strictly additive: prior consumers that ignored `resultMeta` are
560
+ * unaffected; hosts that DO forward `_meta.ggui.bootstrap` now
561
+ * receive the new envelope and can re-render without losing client
562
+ * state. Follows the MCP Apps tool-result forwarding rule.
563
+ * No wire-shape change; behavior change on `ggui_update`'s response
564
+ * `_meta` only.
565
+ *
566
+ * p3. **`/r/<shortCode>` mints the live trio (`wsUrl + token + expiresAt`)
567
+ * and inlines it in `__GGUI_BOOTSTRAP__`.** Previously the public
568
+ * render route minted no bootstrap token, so iframe-runtime's
569
+ * `subscribe.ts` rejected the envelope as "live-mode required" and
570
+ * never opened a WS — `props_update` and stream frames never reached
571
+ * the iframe even when the server fan-out fired. The route now calls
572
+ * `mintBootstrap(sessionId, appId)` when the minter is wired,
573
+ * rewrites localhost wsUrl to the request host (mirroring
574
+ * `/api/bootstrap/<shortCode>`), and threads the three fields into
575
+ * `buildSelfContainedShell`. `SelfContainedShellInputs` gains the
576
+ * three optional live-trio fields. Strictly additive — pre-existing
577
+ * static-only renders behave identically when no minter is wired.
578
+ *
579
+ * --------------------------------------------------------------------
580
+ * Multi-variant Blueprints — three-step handshake protocol:
581
+ *
582
+ * o. **Three-step handshake protocol.** `match` / `plan` /
583
+ * `hint` / `provisional` / `contractHash` (the previous
584
+ * top-level shape) DELETED from `handshakeOutputSchema`; replaced
585
+ * by a
586
+ * single `suggestion: HandshakeSuggestion` carrying a
587
+ * `origin: 'cache' | 'agent' | 'synth'` enum that routes the
588
+ * agent's next decision. `blueprintMeta` is ALWAYS present.
589
+ * `handshakeInputSchema` reshape: `contract?` + `hint?` ⇒ single
590
+ * `blueprintDraft: {contract, variance?, generator?}` (top-level
591
+ * `hint` field deleted in this slice). `handshakeOutputSchema`
592
+ * gains optional `alternatives: Blueprint[]` (top-N search misses
593
+ * below threshold so the agent can override into one).
594
+ * `pushInputSchema` reshape: `contract?` + `contractHash?` triad
595
+ * DELETED; replaced by a `decision` discriminator
596
+ * (`{kind: 'accept'} | {kind: 'override', blueprintDraft: {...}}`).
597
+ * `accept` reuses the provisional `blueprintId` from the
598
+ * handshake's `suggestion.blueprintMeta`; `override` mints a
599
+ * fresh `blueprintId` against a NEW draft. New types
600
+ * `BlueprintDraft`, `BlueprintMeta`, `SuggestionOrigin`,
601
+ * `HandshakeSuggestion`, `PushDecision`, `JsonPatch` ship in
602
+ * `@ggui-ai/protocol/types/handshake-suggestion` with zod mirrors
603
+ * in `@ggui-ai/protocol/schemas/handshake-suggestion`. A breaking
604
+ * reshape.
605
+ *
606
+ * --------------------------------------------------------------------
607
+ * Multi-variant Blueprints — operator-class blueprint tools:
608
+ *
609
+ * o. **Operator-class blueprint tool schemas** added to
610
+ * `@ggui-ai/protocol/schemas/ops-blueprint`. Four input/output
611
+ * pairs for the operator MCP tools:
612
+ *
613
+ * - `opsGenerateBlueprintInputSchema` /
614
+ * `opsGenerateBlueprintOutputSchema` — `ggui_ops_generate_blueprint`.
615
+ * Operator-authored blueprint generation, persona-tagged,
616
+ * optional `setAsOperatorDefault` flag.
617
+ * - `opsListBlueprintsInputSchema` /
618
+ * `opsListBlueprintsOutputSchema` — `ggui_ops_list_blueprints`.
619
+ * Indexed `(appId, contractHash)` list OR semantic search via
620
+ * `BlueprintSearch` when `intentKeywords` / `persona` is set.
621
+ * - `opsUpdateBlueprintInputSchema` /
622
+ * `opsUpdateBlueprintOutputSchema` — `ggui_ops_update_blueprint`.
623
+ * Mutable-field patch — `isOperatorDefault?: true` + partial
624
+ * `variance?`. Immutable fields (contractHash, appId,
625
+ * codeS3Url, codeHash, generator, createdAt, createdBy) are
626
+ * absent from the schema.
627
+ * - `opsDeleteBlueprintInputSchema` /
628
+ * `opsDeleteBlueprintOutputSchema` — `ggui_ops_delete_blueprint`.
629
+ * Idempotent — second delete returns `{deleted: true}`.
630
+ *
631
+ * All four tools tag `audience: ['ops']`; agents on `/mcp` do
632
+ * not see them. Strictly additive — existing handshake / push /
633
+ * list paths unchanged. No agent companion tool — agent
634
+ * blueprint authoring stays through the normal handshake → push
635
+ * flow.
636
+ *
637
+ * --------------------------------------------------------------------
638
+ * Multi-variant Blueprints — multi-axis blueprint search:
639
+ *
640
+ * n. **`Blueprint.contractEmbedding?: readonly number[]`** added —
641
+ * cached embedding vector written by `BlueprintStore.put` when an
642
+ * `EmbeddingProvider` is wired; read by `BlueprintSearch` on the
643
+ * embed axis (cosine similarity). Strictly additive — every
644
+ * existing `Blueprint` row deserializes with `contractEmbedding:
645
+ * undefined`, and the search layer's embed axis contributes zero
646
+ * in that case. Other axes (hash, structural, variance, intent)
647
+ * still carry the decision.
648
+ * nn. **`BlueprintSearchWeights` + `AppBlueprintSearchConfig`** types
649
+ * + zod schemas added. Per-app blueprint-search configuration
650
+ * lives on the `App` record in `@ggui-ai/mcp-server-core` as
651
+ * `App.blueprintSearchConfig?`; the wire shape stays in protocol
652
+ * so cloud-DDB + OSS in-memory adapters share one source of
653
+ * truth. Defaults applied at the impl layer
654
+ * (`DEFAULT_BLUEPRINT_SEARCH_WEIGHTS` / `THRESHOLD` / `TOP_K`
655
+ * in `@ggui-ai/mcp-server-core/blueprint-search`).
656
+ *
657
+ * --------------------------------------------------------------------
658
+ * Multi-variant Blueprints — the `Blueprint` record:
659
+ *
660
+ * m. **`Blueprint` type + zod schema** added to `@ggui-ai/protocol`.
661
+ * Represents the variant-unit between a `DataContract` and the
662
+ * generated UI code that renders it. Multiple `Blueprint` rows
663
+ * MAY share `(appId, contractHash)`; they differ on `generator`
664
+ * and/or `variance`. Fields: `blueprintId`, `contractHash`,
665
+ * `appId`, optional `codeS3Url + codeHash` (cached code pointer),
666
+ * `generator` (slug), optional `validatorScore`, `variance`
667
+ * (`{persona?, context?, seedPrompt?}`), optional
668
+ * `isOperatorDefault: true`, `createdAt`, `createdBy`
669
+ * ('agent' | 'operator'), and a read-cache copy of `contract`.
670
+ * Strictly additive — no fields removed; no consumer sees a
671
+ * schema-incompatible change. Consumers using `BlueprintProvider`
672
+ * see no break.
673
+ *
674
+ * --------------------------------------------------------------------
675
+ * Wire-shape v2 — contract vocabulary reshape:
676
+ *
677
+ * 0. `broadcast` field deleted; channel data sources move inline as
678
+ * `streamSpec[ch].source = { tool, args? }`. Server-side
679
+ * `runBroadcastLoop` removed entirely (no replacement on server;
680
+ * transport now runtime-negotiated by `@ggui-ai/wire`).
681
+ *
682
+ * 00. `DataContract.props` → `DataContract.propsSpec` rename. Aligns the
683
+ * contract-level declaration with the other three typed surfaces
684
+ * (`actionSpec` / `streamSpec` / `contextSpec`). Wire-side `props`
685
+ * field on `ggui_push.input` / `ggui_update.input` stays as `props`
686
+ * — those carry values, not the spec.
687
+ *
688
+ * 000. `DataContract.wiredTools` → `DataContract.agentTools` rename +
689
+ * entry restructure: drop `label`, rename `requestSchema`/`responseSchema`
690
+ * → `inputSchema`/`outputSchema` (MCP alignment), rename
691
+ * `example: {request, response}` → `example: {input, output}`, add
692
+ * `usage?: string` field. All `WiredTool*` types rename to `AgentTool*`.
693
+ * Hook name `useWiredTool` retired in a follow-up commit (paired
694
+ * with kind removal). `PushStory.wiredTools?: string[]` shorthand
695
+ * input ALSO renamed to `agentTools` in the same follow-up.
696
+ *
697
+ * 000a. `DataContract.agentTools` → `DataContract.agentCapabilities`
698
+ * rename. The catalog parent is renamed for symmetry with
699
+ * `clientCapabilities` — both are capability declarations grouped
700
+ * under a `*Capabilities` parent so the protocol's capability
701
+ * namespace reads as `{agent,client}Capabilities`. Inner map stays
702
+ * as `.tools` (e.g., `agentCapabilities.tools.fetch_quote`). Type
703
+ * alias `AgentToolSpec` → `AgentCapabilitiesSpec`; `AgentToolEntry`
704
+ * unchanged. Catalog-level `description?` field dropped (vestigial,
705
+ * no consumer). Linter rule messages + paths updated. `PushStory`
706
+ * shorthand input `agentTools: string[]` is left under its current
707
+ * name (retired later alongside PushStory itself).
708
+ *
709
+ * 000b. `DataContract.clientCapabilities.capabilities` →
710
+ * `DataContract.clientCapabilities.gadgets` rename + reshape.
711
+ * Inner map renamed for vocabulary parity with the agent side
712
+ * (`agentCapabilities.tools` vs `clientCapabilities.gadgets`).
713
+ * Per-entry type renamed: `ClientCapabilityEntry` →
714
+ * `GadgetDescriptor`; spec renamed: `ClientCapabilitySpec` →
715
+ * `ClientCapabilitiesSpec`. Hook generic renamed:
716
+ * `ClientCapabilityHook` → `GadgetHook`; lifecycle types
717
+ * `CapabilityStatus` → `GadgetStatus`, `CapabilityError` →
718
+ * `GadgetError`. Catalog-level `description?` dropped (vestigial,
719
+ * no consumer). New optional `example?: JsonValue` on
720
+ * `GadgetDescriptor` parallels `AgentToolEntry.example` so the
721
+ * agent has a concrete shape to reference for unfamiliar libraries.
722
+ * Hygiene rule codes renamed: `LINT_CAP_*` → `LINT_LIB_*`.
723
+ * Helper `getClientCapabilityNames` → `getGadgetNames` in
724
+ * `@ggui-ai/ui-gen/evaluation/axis-checks`. The
725
+ * `@ggui-ai/client-tools` package literal is renamed separately
726
+ * (see entry 000c) alongside the workspace directory move.
727
+ *
728
+ * 000e. Per-app library discovery. Three changes ship together
729
+ * (the data-plane half of the per-app library story; UX +
730
+ * renderer follow up):
731
+ *
732
+ * a. **App model** — the OSS `@ggui-ai/mcp-server-core` adds an
733
+ * `App` type with `gadgets: readonly GadgetDescriptor[]`
734
+ * and an `AppMetadataStore` seam. Reference `InMemoryAppMetadataStore` seeds
735
+ * every registered app with `STDLIB_GADGETS`.
736
+ * b. **Cloud DDB adapter** — `AppRecord.gadgets` added.
737
+ * `getApp` applies the **default-on-read** pattern at the row
738
+ * projection site so existing rows without the column survive.
739
+ * c. **New tool `ggui_list_gadgets`** — audience `['agent']`,
740
+ * bare `ggui_*` prefix (NOT `ggui_protocol_*` — fetches runtime,
741
+ * per-app data, not static spec). Input `{appId?: string}` —
742
+ * defaults to `ctx.appId`; explicit mismatch throws
743
+ * `AppAccessDeniedError` (code `app_access_denied`). App-not-found
744
+ * falls back to `STDLIB_GADGETS` (sandbox-app permitted-
745
+ * error path).
746
+ *
747
+ * Operator console UX to mutate per-app lists and renderer support for
748
+ * serving operator-added custom libraries are deferred to later
749
+ * work; initially every app's `gadgets` == stdlib by default.
750
+ *
751
+ * 000g. Generation triad vocabulary sweep. `@ggui-ai/ui-gen`'s
752
+ * HOW / WHAT / CHECK surfaces all speak the renamed contract
753
+ * paths (`agentCapabilities.tools`, `clientCapabilities.gadgets`).
754
+ *
755
+ * a. **HOW prompts** (`harness/prompts.ts`) — every reference to
756
+ * the catalog now uses `agentCapabilities.tools` /
757
+ * `clientCapabilities.gadgets`; pre-rename hook identifiers
758
+ * (`useWiredTool` / `useAgentTool` / `useClientTool`) are
759
+ * described as retired rather than mentioned by name.
760
+ * b. **WHAT classifier** (`classifier/inspect.ts`) — the inner
761
+ * `clientCapabilities.capabilities` read path was stale; now
762
+ * reads `clientCapabilities.gadgets` to match the current
763
+ * wire shape.
764
+ * c. **CHECK Tier 0** (`check/run-tier0.ts`) — `wire_undeclared`
765
+ * remediation messages no longer reference the retired
766
+ * `story.contract`; they name the flat `contract` field on
767
+ * `ggui_push`.
768
+ * d. **CHECK evaluator** (`evaluation/llm-evaluator.ts`,
769
+ * `evaluation/types-public.ts`) — eval criteria descriptions
770
+ * use `agentCapabilities.tools` and `clientCapabilities.gadgets`.
771
+ * e. **Anti-pattern grep gate** (`evaluation/axis-checks/checks/
772
+ * tooling.ts`) — `RETIRED_IDENTIFIERS` extended with
773
+ * `useAgentTool`, `callWiredTool`, `agentTools` (top-level
774
+ * field), `clientCapabilities.capabilities`,
775
+ * `@ggui-ai/client-tools` (package), `PushStory`,
776
+ * `pushStorySchema`, `story.adapters`, `declaredAdapters`,
777
+ * `assertAdaptersDeclared`, `HandshakeStoredStory`, and
778
+ * `record.story`. Each emits one issue per detection so the
779
+ * LLM rewrites toward the current shape before evaluation
780
+ * completes.
781
+ *
782
+ * 000h. Bench corpus rebuild. The static bench corpora carried
783
+ * example contracts and prose docstrings on the old catalog
784
+ * shape; this sweep updates them so every fixture and comment
785
+ * reads as a valid contract under the new wire shape.
786
+ *
787
+ * a. **Negotiator synth corpus** (`packages/negotiator/src/
788
+ * synth-bench/corpus.ts`) — docstring path `agentTools[*]` /
789
+ * `agentTools.tools[*]` → `agentCapabilities.tools[*]`.
790
+ * Fixture data already carried the new shape; only the prose
791
+ * drifted.
792
+ * b. **Multi-SDK gen fixtures** (`packages/benchmark/src/multi-
793
+ * sdk/commits.ts` + `fixtures/{activity-feed,inbox-triage,
794
+ * place-search,uber-ride}.fixture.ts`) — `agentCapabilities.
795
+ * tools` entries reshaped: drop `label`,
796
+ * `requestSchema`/`responseSchema` → `inputSchema`/
797
+ * `outputSchema`, `example: {request, response}` →
798
+ * `example: {input, output}`. `clientCapabilities.
799
+ * capabilities` → `clientCapabilities.gadgets`.
800
+ * c. **Floor-test contract** (`packages/benchmark/src/multi-sdk/
801
+ * floor.test.ts`) — sample `{ props: { properties: {} } }`
802
+ * → `{ propsSpec: { properties: {} } }` per the
803
+ * DataContract.props → propsSpec rename.
804
+ *
805
+ * 000f. Permissions-Policy derivation from
806
+ * `DataContract.clientCapabilities.gadgets[*].permission`.
807
+ * Replaces the previous App-level `declaredAdapters` runtime
808
+ * gate with per-contract derivation. `StackItem` gains an
809
+ * optional `clientCapabilities?: ClientCapabilitiesSpec` field
810
+ * so push commit-time persists the catalog onto the active stack
811
+ * item. `StackItemBootstrapView.permissionsPolicy?: readonly
812
+ * string[]` projects the union-deduplicated directive list every
813
+ * transport reads — public-render `/r/<shortCode>` emits a
814
+ * `Permissions-Policy` HTTP response header (`<directive>=(self)`
815
+ * per W3C Permissions Policy), MCP-Apps `_meta.ui.permissions`
816
+ * forwards into the iframe host's `allow=""` attribute, and the
817
+ * inline bootstrap mirrors `permissionsPolicy` so the
818
+ * iframe-runtime can surface the gate set to in-iframe consumers.
819
+ * Browser-enforced gates flow from the parent transport; the
820
+ * iframe-runtime itself cannot mutate Permissions-Policy
821
+ * post-load. `GguiBootstrapMeta` gains an optional matching
822
+ * `permissionsPolicy?: readonly string[]` field;
823
+ * `validateGguiBootstrapMeta` validates the array shape.
824
+ * Boilerplate generator (`@ggui-ai/ui-gen`) reads
825
+ * `clientCapabilities.gadgets` and emits
826
+ * one combined `import { hookA, hookB } from '<pkg>'` per
827
+ * declared package, alphabetically sorted within each group for
828
+ * stable diffs. The intermediate inner key
829
+ * `clientCapabilities.capabilities` is fully retired in the
830
+ * generator surface.
831
+ *
832
+ * 000d. Handshake input redesigned to flat fields + hint group.
833
+ * `handshakeInputSchema` was `{sessionId, story: {intent, data?, sourceTools?,
834
+ * agentTools?, prompt?, context?, contract?}, forceCreate?}`. The story
835
+ * nesting is dismantled: `intent` and `contract` are promoted to top-level
836
+ * on the input; `data`/`prompt`/`context`/`availableAgentTools`/`sourceTools`
837
+ * move under a labeled `hint?: HandshakeHint` group (cold-path synth signal).
838
+ * `agentTools` rename → `availableAgentTools` clarifies role (catalog seed,
839
+ * not authority). `PushStory` interface + `pushStorySchema` Zod retired
840
+ * entirely (no consumer left). Vestigial sub-schemas `pushSessionSchema`/
841
+ * `pushRenderingSchema`/`pushInfraSchema`/`pushShortcutsSchema` + their
842
+ * TS interfaces (`PushSession`/`PushRendering`/`PushInfra`/`PushShortcuts`)
843
+ * also dropped — dead code (no consumer outside sync-check).
844
+ * OSS handler's internal `HandshakeStoredStory` → `HandshakeStoredInput`
845
+ * reshape: `{intent, contract?, forceCreate?, hint?}` (drops legacy
846
+ * `context`/`schema`/`adapters` passthrough plumbing — schema lives in
847
+ * `contract.propsSpec`; adapters retire alongside the
848
+ * `declaredAdapters` deployment-policy field, replaced by per-app
849
+ * `gadgets` permissions). Negotiator
850
+ * `decide({story, sessionId, ctx})` → `decide({intent, contract?, hint?,
851
+ * sessionId, ctx})`. Push consumers (`push.ts`, `update.ts`) read
852
+ * `record.input.*` not `record.story.*`. `provisional-preview.ts` field
853
+ * `story` → `input` to match.
854
+ *
855
+ * 000g. Negotiator synth refactor. Synth now reads + writes the
856
+ * renamed catalog paths and threads per-app library context
857
+ * through.
858
+ *
859
+ * a. **Synth prompt + DECISION_TOOL JSON-schema** flipped from
860
+ * `clientCapabilities.capabilities` → `clientCapabilities.gadgets`.
861
+ * Inner field shape unchanged ({hook, package?, permission?,
862
+ * usage?}); only the parent key renamed. Worked examples in the
863
+ * synth system prompt match.
864
+ * b. **`parseToolInput` + `buildContract`** now extract
865
+ * `agentCapabilities.tools` AND `clientCapabilities.gadgets`
866
+ * from the LLM tool output and populate them on the assembled
867
+ * contract. Previously both catalogs were dropped silently —
868
+ * the LLM authored entries the contract never carried.
869
+ * c. **`NegotiatorInput.agent.gadgets?: readonly GadgetDescriptor[]`**
870
+ * + **`NegotiatorDecisionInput.gadgets?`**. The
871
+ * handshake handler reads `app.gadgets` via the bound
872
+ * `AppMetadataStore` and threads the catalog to the
873
+ * negotiator. The decision-prompt user message gains a
874
+ * "Client-side libraries available" section listing each
875
+ * hook + permission so the LLM authors valid bindings.
876
+ * d. **`mergeAgentTools` → `mergeAgentCapabilities`** rename.
877
+ * Behavior unchanged.
878
+ * e. **`mergeGadgets(contract, appGadgets)`** new
879
+ * function — enriches partial LLM-emitted gadget entries
880
+ * (missing package/permission) from the app's canonical
881
+ * catalog. Bindings whose `hook` doesn't match any app entry
882
+ * are preserved verbatim (third-party packages outside the
883
+ * seed). Wires into all three return paths (structured-output
884
+ * success, regex JSON fallback, buildFallbackDecision).
885
+ * f. **HandshakeNegotiator.decide** interface gains optional
886
+ * `gadgets`. OSS llm-backed-negotiator + cloud pod's
887
+ * Bedrock negotiator forward to `negotiate(input.agent.gadgets)`.
888
+ * g. **GguiHandshakeHandlerDeps.appMetadataStore** new optional field.
889
+ * OSS `createGguiServer` resolves it from `deps.handshake.appMetadataStore`
890
+ * or top-level `deps.appMetadataStore` (same store the
891
+ * `ggui_list_gadgets` tool uses).
892
+ * h. **Anti-pattern list** extended in synth + decision prompts
893
+ * with retired identifiers — `clientCapabilities.capabilities`,
894
+ * `PushStory`/`story.*`, `@ggui-ai/client-tools`,
895
+ * `story.adapters`/`declaredAdapters`, `useAgentTool`. Prevents
896
+ * LLM regression on older training data.
897
+ *
898
+ * 000c. `@ggui-ai/client-tools` → `@ggui-ai/gadgets` package
899
+ * rename. The workspace directory moves from `packages/client-tools`
900
+ * to `packages/gadgets`; the published package name
901
+ * changes from `@ggui-ai/client-tools` to `@ggui-ai/gadgets`.
902
+ * All import strings across the workspace flip simultaneously
903
+ * (negotiator synth prompt, ui-gen boilerplate + system prompt +
904
+ * eval axis checks, design rewrite-imports, ggui-react test
905
+ * fixtures, mcp-server-handlers blueprint docs, hygiene-rules
906
+ * `DEFAULT_GADGET_PACKAGE` literal). The exported hook surface
907
+ * (`useGeolocation`, `useClipboardWrite`, `useClipboardPaste`,
908
+ * `useNotifications`, `useFilePicker`, `useMicrophone`,
909
+ * `useCamera`) is unchanged. Package version stays at 0.1.0.
910
+ *
911
+ * 00000000. Protocol-level schema-compat invariant in
912
+ * `@ggui-ai/protocol/validation/schema-compat-invariants`:
913
+ * `CTR_SCHEMA_INCOMPAT` validates `actionSpec[*].schema` is a
914
+ * subset of the referenced `agentCapabilities.tools[*].inputSchema`,
915
+ * and `streamSpec[*].schema` is a superset of the referenced
916
+ * `agentCapabilities.tools[*].outputSchema`. Compares against the
917
+ * contract's OWN agentCapabilities catalog (no runtime tool registry,
918
+ * no zod conversion) — author-visible bug class. Distinct from
919
+ * and complementary to the server-level F4 check
920
+ * (`checkStackItemSchemaCompat` in `@ggui-ai/mcp-server`),
921
+ * which compares the same action/stream schemas against the
922
+ * live tool registry's zod schemas. Folded into
923
+ * `validateContractStructure` and wired into the push handler
924
+ * via `assertSchemaCompat(contract)`.
925
+ *
926
+ * 0000000. Name-invariant rules in
927
+ * `@ggui-ai/protocol/validation/name-invariants`:
928
+ * `CTR_DUP_NAME` (no name collision across `actionSpec` /
929
+ * `streamSpec` / `contextSpec` keys — boilerplate generator emits
930
+ * identifiers from these; a collision shadows or compiles
931
+ * ambiguously) + `CTR_RESERVED_NAME` (no `_ggui:`-prefixed keys on
932
+ * `actionSpec` or `contextSpec` — the streamSpec equivalent
933
+ * already fires in `validateContractStructure`; this extends the
934
+ * rule uniformly to the other two inbound spec maps). Both folded
935
+ * into `validateContractStructure` and into the push handler via
936
+ * `assertNameInvariants(contract)`, which throws
937
+ * `NameInvariantError` listing every offending name. Companion
938
+ * to the `CTR_REF_*` cross-reference rules in
939
+ * `@ggui-ai/protocol/validation/cross-references`.
940
+ *
941
+ * 000000. Cross-reference invariants in `@ggui-ai/protocol/validation/cross-references`:
942
+ * `CTR_REF_NEXT_STEP` (validates `actionSpec[*].nextStep` resolves
943
+ * to `agentCapabilities.tools[*]`) + `CTR_REF_STREAM_SOURCE` (validates
944
+ * `streamSpec[*].source.tool` resolves to `agentCapabilities.tools[*]`).
945
+ * Both wired into `validateContractStructure` (so blueprint-registry +
946
+ * future structural-validator callers get cross-refs free) and into
947
+ * the push handler via `assertCrossReferences(contract)`, which
948
+ * throws `CrossReferenceError` listing every dangling reference in
949
+ * one pass. Separate from `assertActionRoutingTargets` (which checks
950
+ * `nextStep` against the SERVER's `knownTools` registry); both run
951
+ * at push and surface author-recoverable failures before any state
952
+ * mutation.
953
+ * No migration doc — purely additive enforcement of invariants
954
+ * already documented in the type-system (`StreamChannelEntry.source`,
955
+ * `ActionEntry.nextStep` reference `CTR_REF_*` linter rules in their
956
+ * docstrings; this commit ships the linter behind those mentions).
957
+ *
958
+ * 00000. `ActionEntry.dispatch` discriminated-union collapsed to a
959
+ * single shape with an optional `nextStep?: string` hint. All
960
+ * actions are agent-routed by default; the pre-rename
961
+ * `kind: 'tool'` synchronous-tool dispatch path is retired (it
962
+ * silently bypassed the agent's reasoning loop, which conflicted
963
+ * with the actions-vs-context placement rule). Removed:
964
+ * `ActionDispatch` type, `dispatchTo` helper namespace,
965
+ * `actionDispatchSchema` zod variant. Renamed: `dispatch.tool` →
966
+ * `nextStep`, `dispatch.intendedTool` → `nextStep` (the two collapse
967
+ * into one optional advisory hint). The cross-ref linter
968
+ * (`CTR_REF_NEXT_STEP`) enforces that `nextStep` resolves to a
969
+ * declared `agentCapabilities.tools[*]` key on the same
970
+ * contract. `UnknownActionToolError` retained but reframed
971
+ * to fire on `nextStep` resolution rather than `dispatch.tool`.
972
+ *
973
+ * 0000. `DataContract.clientTools` → `DataContract.clientCapabilities`
974
+ * complete reframe. The pre-rename `ClientToolEntry` carried
975
+ * `argsSchema`/`responseSchema`/`example` as if the agent invoked
976
+ * the tool RPC-style; that was inverted — browser-capability hooks
977
+ * are owned by the UI, fire from the user side, and only become
978
+ * agent-observable when the UI threads their value into a
979
+ * `contextSpec` slot or an `actionSpec` payload. New entry shape:
980
+ * `{description?, usage?, hook, package?, permission?, required?}`
981
+ * — pure declaration, no input/output, no example. The catalog map
982
+ * key rename `tools` → `capabilities` mirrors the conceptual shift.
983
+ * `ClientCapabilityHook<TOutput, TOptions=void>` generic + `CapabilityStatus`
984
+ * + `CapabilityError` ship in `@ggui-ai/protocol`'s
985
+ * `types/gadget.ts` to lock the runtime contract every
986
+ * hook in the planned `@ggui-ai/gadgets` v1 catalog MUST
987
+ * satisfy. `useClientTool` React hook + `registerClientTool` /
988
+ * `WireClientToolArgs` / `WireClientToolResult` types + iframe
989
+ * `tool:<name>` channel RPC path are deleted — no RPC channel
990
+ * surface remains.
991
+ *
992
+ * --------------------------------------------------------------------
993
+ * Four wire-shape changes:
994
+ *
995
+ * 1. `ActionEntry` discriminated-union dispatch: collapsed
996
+ * `{tool, mode}` into `dispatch: {kind: 'tool', tool} | {kind:
997
+ * 'agent', intendedTool?}`. Renamed `mode: 'host-routed'` →
998
+ * `kind: 'agent'` for vendor-neutrality. Deleted
999
+ * `OrphanActionError` + `AmbiguousActionRoutingError`
1000
+ * (structurally impossible).
1001
+ *
1002
+ * 2. F4 schema-compat fail-fast at push validation: agent-recoverable
1003
+ * schema violations throw `SchemaCompatError` at push time instead
1004
+ * of silently committing a fallback error stack item. Eliminates
1005
+ * the "stuck on Generating UI…" trap.
1006
+ *
1007
+ * 3. `ggui_new_session` introduced + `sessionId` REQUIRED on
1008
+ * `ggui_handshake`: server-mints / agent-threads (SEP-2567 aligned).
1009
+ * Optional `seed` enables deterministic idempotent derivation.
1010
+ * Three-tool flow: `ggui_new_session` → `ggui_handshake` → `ggui_push`.
1011
+ *
1012
+ * 4. `pageId` → `stackItemId` rename: mechanical 1:1 sweep across
1013
+ * protocol/handlers/server/SDKs/cloud. `getSessionByPageId` →
1014
+ * `getSessionByStackItemId`; `PageNotFoundError` →
1015
+ * `StackItemNotFoundError`; `targetPageId` → `targetStackItemId`.
1016
+ *
1017
+ * --------------------------------------------------------------------
1018
+ * Marketplace registry:
1019
+ *
1020
+ * 1. `GadgetDescriptor.bundleSri?: string` — additive optional
1021
+ * field carrying the registry-emitted SHA-384 SRI hash of the
1022
+ * bundle bytes (`sha384-<base64>` format, validated via
1023
+ * `BUNDLE_SRI_RE`). When present, the iframe-runtime emits the
1024
+ * bundle import as a `<link rel="modulepreload" integrity>`
1025
+ * element so a CDN compromise can't silently swap the bundle.
1026
+ * Both `gadgetDescriptorSchema` (wire) and
1027
+ * `strictGadgetDescriptorSchema` (registry) carry the
1028
+ * same regex. Authors do NOT set this manually; registry
1029
+ * install writes it.
1030
+ *
1031
+ * 2. Marketplace registry HTTP API (`/search` / `/pkg` /
1032
+ * `/publish` / `/conformance/check`). Off the WS wire surface
1033
+ * — a distinct HTTP service, not a wire-protocol surface, and
1034
+ * therefore not a conformance-kit bump.
1035
+ *
1036
+ * --------------------------------------------------------------------
1037
+ * Canvas mode (additive):
1038
+ *
1039
+ * 1. `GguiBootstrapMeta.canvasMode?: boolean` — discriminator for
1040
+ * the iframe-runtime canvas-mount path. When `true`, the runtime
1041
+ * mounts a session-scoped `CanvasShell` (one iframe for the
1042
+ * whole session) instead of the legacy per-stack-item iframe.
1043
+ * Mutually exclusive with `stackItemId`. Producers SHOULD reject
1044
+ * bootstraps with both fields set; the protocol does not
1045
+ * require them to. Absent / `false` ⇒ existing inline behavior.
1046
+ *
1047
+ * 2. `_ggui:lifecycle` reserved channel + `CanvasLifecyclePayload`
1048
+ * discriminated union. Server publishes lifecycle envelopes
1049
+ * (`handshake_started` / `handshake_completed` /
1050
+ * `push_started` / `consume_polling`) on the reserved channel;
1051
+ * the canvas animator pill state machine advances on each kind.
1052
+ * Wire shape lives in `@ggui-ai/protocol/types/canvas-lifecycle`;
1053
+ * structural validator registered in `BUILTIN_RESERVED_VALIDATORS`.
1054
+ *
1055
+ * 3. `canvas_navigated` WS message type (Client → Server) with
1056
+ * `CanvasNavigatedPayload`. Emitted by the canvas iframe when
1057
+ * the user back-navigates; the server updates
1058
+ * `Session.activeStackItemId` and MAY abort in-flight cold-gen
1059
+ * for the popped item. Payload carries `sessionId` +
1060
+ * `previousActiveItemId` + `activeItemId` (no `appId` — the
1061
+ * subscriber binding is the authoritative scope).
1062
+ *
1063
+ * 4. `host_context_observed` WS message type (Client → Server) with
1064
+ * `HostContextObservedPayload`. The iframe echoes the
1065
+ * `McpUiHostContext` it received from `ui/initialize` so the
1066
+ * server can persist a `HostContextProjection` on the session.
1067
+ * Updates flow on every `host-context-changed` notification so
1068
+ * the projection stays current for canvas display-mode policy.
1069
+ *
1070
+ * --------------------------------------------------------------------
1071
+ * Schema hardening:
1072
+ *
1073
+ * 1. `StackItem.adapters?: AdapterType[]` — DELETED. Grant model now
1074
+ * lives entirely on `clientCapabilities.gadgets[*].permission`
1075
+ * which projects to the iframe's `Permissions-Policy` header.
1076
+ * `AdapterType` / `ADAPTER_TYPES` / `AdapterTypeSchema` removed
1077
+ * from `@ggui-ai/protocol`. SDK + cloud-pod call sites that
1078
+ * threaded `adapters: []` now drop the field entirely. The
1079
+ * runtime `AdapterPermissions` permission-state interface +
1080
+ * `AdapterRegistry` impl slot stay (orthogonal to the grant model
1081
+ * retired here).
1082
+ *
1083
+ * 2. `ggui.json#registryAuth` — DELETED. Source-controlled config no
1084
+ * longer carries Cognito pool ids. The publish CLI reads from
1085
+ * env exclusively: canonical `GGUI_REGISTRY_COGNITO_POOL_ID` /
1086
+ * `GGUI_REGISTRY_COGNITO_APP_CLIENT_ID`, legacy-fallback
1087
+ * `GGUI_COGNITO_POOL_ID` / `GGUI_COGNITO_APP_CLIENT_ID`. No
1088
+ * filesystem walk-up for ggui.json#registryAuth. Per-project
1089
+ * pinning still possible via `.envrc` / shell wrapper.
1090
+ *
1091
+ * 3. Blueprint name regex unified under gadget rules.
1092
+ * `BLUEPRINT_NAME_RE` is retired; both gadgets and blueprints now
1093
+ * share `GADGET_NAME_RE` (`/^[a-z][a-z0-9-]{1,62}[a-z0-9]$/` —
1094
+ * kebab-case, 2–64 chars, no underscores, no single-char). Names
1095
+ * that previously passed the looser blueprint regex (e.g.
1096
+ * `weather_card`, single-char `a`) now reject at publish + at
1097
+ * `register_blueprint`. The sample fixture `weather_card` was
1098
+ * renamed to `weather-card` to satisfy the unified rule.
1099
+ *
1100
+ * 4. `pluginId` → `artifactId` wire-side rename in
1101
+ * `@ggui-ai/registry-core` and the cloud Lambda env. The
1102
+ * registry stores both gadgets and blueprints under one row
1103
+ * family keyed by `kind: 'gadget' | 'blueprint'`; `artifactId`
1104
+ * matches what the field actually carries. Renames:
1105
+ * `PLUGINS_METADATA_SK → ARTIFACTS_METADATA_SK`,
1106
+ * `Plugins/Plugin*Row → Artifacts/Artifact*Row`,
1107
+ * `Row.pluginId → Row.artifactId`,
1108
+ * `SearchResultEntry.pluginId → .artifactId`,
1109
+ * `PublishResponseBody.pluginId → .artifactId`,
1110
+ * RegistryStorage methods (`getPluginMetadata` etc.) → `…Artifact…`,
1111
+ * CDK tables `<env>-Plugins` / `<env>-PluginVersions` →
1112
+ * `…-Artifacts` / `…-ArtifactVersions`, Lambda env vars
1113
+ * `PLUGINS_TABLE` / `PLUGIN_VERSIONS_TABLE` → `ARTIFACTS_TABLE` /
1114
+ * `ARTIFACT_VERSIONS_TABLE`.
1115
+ *
1116
+ * 5. Schema tighten-ups — the protocol+handler boundaries now
1117
+ * hard-reject misconfigured input that previously silently rode
1118
+ * through:
1119
+ *
1120
+ * - `clientCapabilitiesSpecSchema` is `.strict()` — the retired
1121
+ * `libraries` field name fails parse instead of being
1122
+ * silently dropped.
1123
+ * - `LINT_CONTRACT_RETIRED_FIELD` / `RETIRED_CONTRACT_FIELDS` /
1124
+ * `assertContractNoRetiredFields` — push + handshake reject
1125
+ * contracts that carry `libraries`, `dispatch`, `wiredTools`,
1126
+ * `clientTools`, `broadcast`, `capabilities`.
1127
+ * - `strictGadgetDescriptorSchema.permission` is
1128
+ * `z.enum(KNOWN_PERMISSION_NAMES)` — typos
1129
+ * (`geolocaiton`) fail at parse.
1130
+ * - `bundleUrl` / `styleUrl` / `connect[]` use `z.url()` not
1131
+ * `z.string().min(1)`.
1132
+ * - `tags` capped: ≤20 entries, each ≤64 chars, charset
1133
+ * `[a-z0-9-]`.
1134
+ * - `InMemoryAppMetadataStore` + cloud `dynamoAppMetadataStore`
1135
+ * re-validate every gadget through `strictGadgetDescriptorSchema`
1136
+ * on register + on read — strict-schema posture at the store
1137
+ * seam.
1138
+ * - `resolveGadgetUrls` uses `bundleHostScheme()` — `http://`
1139
+ * for loopback, `https://` elsewhere.
1140
+ * - `enrichContractGadgets` hoisted to `@ggui-ai/protocol` —
1141
+ * one canonical site shared by push (handlers) and
1142
+ * generation-dispatch (ui-gen).
1143
+ * - `assertGeneratorRegistered` extracted — handshake + push
1144
+ * share one allow-list check.
1145
+ * - `gadgetRequiresSchema` extracted — single source for the
1146
+ * `requires[]` field across wire / registry / artifact-manifest
1147
+ * schemas.
1148
+ * - `baseGadgetFieldsShape` extracted — both gadgetEntry
1149
+ * schemas spread from one shape constant.
1150
+ * - `manifestToRegistryEntry` helper hoisted from CLI to
1151
+ * `@ggui-ai/artifact-manifest`; install CLI imports the
1152
+ * canonical helper.
1153
+ * - `ReadErrorCode` adds `'yanked'` for the 410-Gone path;
1154
+ * `SearchErrorCode` defined as a closed enum; both are
1155
+ * closed unions.
1156
+ * - `conformanceFailureCode` sub-discriminator added on the
1157
+ * publish conformance-failed response.
1158
+ * - `GadgetGateErrorCode` closed enum union — single-source
1159
+ * for push-gate error codes (`gadget_not_registered`,
1160
+ * `gadget_public_env_missing`, `unknown_generator`).
1161
+ * - `handshakeOutputSchema.reason?: string (≤280 chars)` —
1162
+ * optional truncated diagnostic.
1163
+ * - `assertNoDuplicateGadgetHooks` (slug:
1164
+ * `duplicate_gadget_hook`) — hard reject when two bindings
1165
+ * declare the same `(package, hook)` pair.
1166
+ * - `resolveGadgetUrls` memoized — WeakMap-keyed cache on
1167
+ * entry object identity.
1168
+ *
1169
+ * --------------------------------------------------------------------
1170
+ * Blueprint conformance gate:
1171
+ *
1172
+ * 1. `ConformanceErrorCode` (closed union, exported from
1173
+ * `@ggui-ai/registry-core`) gains six new entries. The blueprint
1174
+ * branch of `checkConformance()` was previously a no-op
1175
+ * short-circuit; it is now five mandatory static gates plus one
1176
+ * opt-in runtime probe:
1177
+ *
1178
+ * - `blueprint_source_too_large` — `manifest.source` exceeds
1179
+ * {@link MAX_BLUEPRINT_SOURCE_BYTES} (5 MiB, symmetric with
1180
+ * the gadget bundle ceiling).
1181
+ * - `blueprint_compile_error` — `esbuild.transformSync({
1182
+ * loader: 'tsx' })` rejects the TSX source.
1183
+ * - `blueprint_disallowed_import` — `oxc-parser` walks the
1184
+ * source TSX (not compiled JS — esbuild tree-shakes unused
1185
+ * imports even with `treeShaking: false`) and rejects any
1186
+ * import outside `{ react, react/jsx-runtime, react-dom,
1187
+ * @ggui-ai/gadgets }`. Blueprints have no `peerDeps` channel.
1188
+ * - `blueprint_missing_default_export` — iframe runtime mounts
1189
+ * the default export as the root component.
1190
+ * - `fixture_props_shape_mismatch` — when both
1191
+ * `manifest.fixtureProps` and `manifest.contract.propsSpec`
1192
+ * are present, every key marked `required: true` on
1193
+ * `propsSpec.properties` must appear on the fixture.
1194
+ * - `blueprint_runtime_probe_failed` — opt-in via
1195
+ * `PublishArtifactDeps.blueprintProbe`. Probe compiles TSX →
1196
+ * CJS, evaluates in Node `vm.runInContext` with a `require`
1197
+ * shim, mounts the default export via
1198
+ * `react-dom/server.renderToString` with the manifest's
1199
+ * fixtureProps; any thrown error during compile / module
1200
+ * load / mount / render surfaces this code.
1201
+ *
1202
+ * Consumers with an exhaustive `switch` on
1203
+ * `ConformanceErrorCode` MUST add the six new branches. Closed-
1204
+ * union semantics mean prior-version exhaustive consumers
1205
+ * silently fall through on the new codes.
1206
+ *
1207
+ * 2. New constant `MAX_BLUEPRINT_SOURCE_BYTES` (= `5 * 1024 * 1024`)
1208
+ * exported from `@ggui-ai/registry-core`. Symmetric with the
1209
+ * gadget bundle ceiling so publishers can pre-check source size
1210
+ * before invoking `ggui blueprint publish`.
1211
+ *
1212
+ * 3. New optional `PublishArtifactDeps.blueprintProbe?:
1213
+ * BlueprintProbeRunner` deps slot on the publish op. Implementa-
1214
+ * tions wiring this slot get the 6th conformance gate (the
1215
+ * runtime probe); leaving it `undefined` preserves the
1216
+ * static-only behavior. Reference implementation:
1217
+ * `@ggui-ai/blueprint-probe`.
1218
+ *
1219
+ * 4. `POST /conformance` runs the 5 static gates only. `POST
1220
+ * /publish` MAY additionally run the runtime probe when the
1221
+ * deploy wires `blueprintProbe`. A blueprint passing
1222
+ * `/conformance` MAY still be rejected by `/publish` with
1223
+ * `blueprint_runtime_probe_failed`. Publishers SHOULD run the
1224
+ * local probe before publishing.
1225
+ *
1226
+ * Security posture for the runtime probe (read before wiring it).
1227
+ * `vm.runInContext` is NOT a security sandbox — it's a JavaScript-
1228
+ * isolation primitive, not an adversarial-code-execution boundary.
1229
+ * A blueprint that passes all five static gates can climb the
1230
+ * prototype chain of the injected `react` module to reach the parent
1231
+ * process's `Function` constructor and execute arbitrary code in the
1232
+ * parent context.
1233
+ *
1234
+ * Because of this, **the cloud-hosted publish Lambda's default deps
1235
+ * do NOT wire `blueprintProbe`.** The static gates still run on every
1236
+ * publish. The runtime probe is local-trust-boundary-only by default
1237
+ * — CLI on the publisher's own machine, self-hosted registries with
1238
+ * a closed publisher pool. A fully isolated probe (isolated-vm,
1239
+ * separate process, execution timeout) is future work.
1240
+ *
1241
+ * --------------------------------------------------------------------
1242
+ * Sigstore signing for public-visibility artifacts. The trust chain
1243
+ * bifurcates: private artifacts → Ed25519 (existing); public
1244
+ * artifacts → sigstore (Fulcio short-lived cert + Rekor transparency
1245
+ * log + cosign bundle).
1246
+ *
1247
+ * 1. **`SigstoreSignature` widened** to embed a serialized cosign
1248
+ * bundle (per `@sigstore/bundle` v0.3 spec) — `{ algorithm:
1249
+ * 'sigstore-cosign', bundleSha384, bundle: <serialized-json>,
1250
+ * signedAt }`. Old narrow shape (`{ uuid, logIndex }`) carried
1251
+ * only Rekor coordinates and was insufficient for offline
1252
+ * verification. The widened shape ships everything a verifier
1253
+ * needs: cert chain, inclusion proof, signed entry timestamp.
1254
+ *
1255
+ * 2. **`PublishArtifactInput.signature`** now typed
1256
+ * `GadgetSignature = Ed25519Signature | SigstoreSignature`
1257
+ * (discriminated union over `algorithm`). The publish op
1258
+ * dispatches on the discriminator after a single-shape guard
1259
+ * (`isGadgetSignature` from `@ggui-ai/gadget-signing`):
1260
+ * - `'ed25519'` → AuthorKeys-rooted flow (unchanged).
1261
+ * - `'sigstore-cosign'` → `verifyBundleSigstore` flow; on
1262
+ * verify-OK the leaf cert PEM is extracted from
1263
+ * `bundle.verificationMaterial.x509CertificateChain.certificates[0].rawBytes`
1264
+ * and pinned as `ArtifactVersionRow.authorPublicKey`.
1265
+ *
1266
+ * 3. **`@ggui-ai/gadget-signing` exports** new canonical type
1267
+ * guards `isEd25519Signature` / `isSigstoreSignature` /
1268
+ * `isGadgetSignature` (collapsing 3 duplicated inline guards)
1269
+ * and new error class `SigstoreSigningError` with discriminated
1270
+ * `code: 'oidc_invalid' | 'fulcio_error' | 'rekor_error' |
1271
+ * 'unknown'`. The `SigstoreNotImplementedError` stub class is
1272
+ * DELETED (pre-launch posture — no shims).
1273
+ *
1274
+ * 4. **`PublishError.error` gains** `oidc_resolution_failed`
1275
+ * (sub-discriminated by `oidcCode`) for CLI publish-side failure
1276
+ * to acquire an OIDC token. Server-side never returns this code
1277
+ * — it's CLI-internal — but the wire shape allows for it so the
1278
+ * same error envelope flows through the CLI's error printer.
1279
+ *
1280
+ * 5. **CLI publish dispatches on `manifest.visibility`**: `'public'`
1281
+ * → sigstore flow (calls `resolveOidcToken` then
1282
+ * `signBundleSigstore`); `'private'` → Ed25519 flow (unchanged).
1283
+ * New `--identity-token <jwt>` flag + `GGUI_OIDC_TOKEN` env var.
1284
+ * Resolution order: flag → env → GitHub Actions ambient (via
1285
+ * `ACTIONS_ID_TOKEN_REQUEST_URL`) → interactive PKCE browser
1286
+ * flow on a TTY.
1287
+ *
1288
+ * 6. **CLI install dispatches on `signature.algorithm`**:
1289
+ * `'sigstore-cosign'` → `verifyBundleSigstore` with optional
1290
+ * `--verify-identity <pattern>` (literal or `/regex/[flags]`
1291
+ * form). The flag enforces that the bundle's Fulcio leaf cert
1292
+ * SAN matches the supplied identity. Ed25519 unchanged.
1293
+ *
1294
+ * No exhaustive-switch concerns — `GadgetSignature` was already a
1295
+ * discriminated union; the changes widen the SigstoreSignature
1296
+ * variant in-place. Consumers that always hit the Ed25519 branch
1297
+ * keep working without modification.
1298
+ *
1299
+ * Trust chain table:
1300
+ *
1301
+ * | visibility | trust chain |
1302
+ * | ---------- | ---------------------------------------------- |
1303
+ * | `private` | Ed25519 author key pinned in registry's |
1304
+ * | | AuthorKeys table, base-rooted at the |
1305
+ * | | publisher's Cognito subject. |
1306
+ * | `public` | Fulcio short-lived X.509 cert (OIDC-backed, |
1307
+ * | | Sigstore-keyless) + Rekor inclusion proof, |
1308
+ * | | offline-verifiable via the embedded cosign |
1309
+ * | | bundle. |
1310
+ *
1311
+ * --------------------------------------------------------------------
1312
+ * Two-layer storage for blueprint compiled bytes (TSX → JS compile
1313
+ * boundary):
1314
+ *
1315
+ * - Blueprint version rows now carry a `compiledDigest` pointer
1316
+ * (lowercase hex SHA-256 of the compiled bytes) into a new
1317
+ * `<envName>-CompiledBlobs` DDB table; the previous
1318
+ * `blueprintSource` column is deleted (raw TSX stays on
1319
+ * `manifest.source` for audit / future-recompile).
1320
+ * - `ReadPkgResponse` exposes `compiledDigest` + `compiledBytes`
1321
+ * (base64); install consumers MUST read compiledBytes — the
1322
+ * registry is now the trust boundary for the compile step.
1323
+ * - `CompiledBlobRow` reserves `manifestSig` + `compiledSig`
1324
+ * columns for a future signing wave; this change writes neither.
1325
+ * - esbuild pinned to 0.25.12 (was `^0.25.0`) for digest stability.
1326
+ *
1327
+ * `compiledDigest` is the shareable cache key for cross-app cache
1328
+ * sharing and cross-registry federation; the reserved signature
1329
+ * columns ride on the blob row for a later signing wave.
1330
+ *
1331
+ * --------------------------------------------------------------------
1332
+ * `package.json`-style gadget refs + `GadgetRef` / `GadgetDescriptor`
1333
+ * split. The wire shape
1334
+ * `DataContract.clientCapabilities.gadgets[*]` carries the identity
1335
+ * tuple `{ hook, package, version, description?, usage? }`. Transport
1336
+ * metadata (`bundleUrl` / `bundleHost` / `bundleSri` / `typesUrl` /
1337
+ * `typesSri` / `permission` / `connect` / `requires` / `styleUrl` /
1338
+ * `required` / `gotchas` / `example`) stays on the registered
1339
+ * `GadgetDescriptor` resolved server-side from `App.gadgets`. The
1340
+ * resolved descriptor subset rides alongside the wire on
1341
+ * `SessionStackEntry.gadgetDescriptors` as a sidecar — no enrichment
1342
+ * overlay on the contract surface.
1343
+ *
1344
+ * 1. **`GadgetRef = { hook, package, version, description?, usage? }`**
1345
+ * Wire-side identity matches `package.json` dependency pins:
1346
+ * bare npm package name + exact semver pin (no ranges). Schema
1347
+ * `gadgetRefSchema` is `.strict()` and rejects transport fields
1348
+ * + range syntax at parse time.
1349
+ *
1350
+ * 2. **`GadgetEntry` → `GadgetDescriptor`** (TS interface rename).
1351
+ * Sibling renames: `gadgetEntrySchema` →
1352
+ * `gadgetDescriptorSchema`; `registryGadgetEntrySchema` →
1353
+ * `strictGadgetDescriptorSchema`; `gadgetManifestToGadgetEntry`
1354
+ * → `gadgetManifestToGadgetDescriptor`. No back-compat aliases.
1355
+ *
1356
+ * 3. **`ClientCapabilitiesSpec` is non-generic** — values are wire-side
1357
+ * `GadgetRef` only. Post-resolution descriptors live on the
1358
+ * `SessionStackEntry.gadgetDescriptors` sidecar (filtered subset
1359
+ * of `App.gadgets`). `filterDescriptorsToContract(contract,
1360
+ * appGadgets) → readonly GadgetDescriptor[]` replaces the prior
1361
+ * `enrichContractGadgets` overlay.
1362
+ *
1363
+ * 4. **`assertNoDuplicateGadgetHooks` keys on `hook` alone.** Hook
1364
+ * unique-per-app at registration time (`lintGadgetCatalog`);
1365
+ * LLM-generated component code wouldn't disambiguate two
1366
+ * destructure bindings with the same name. Operators pre-alias
1367
+ * at registration if they want both.
1368
+ *
1369
+ * 5. **New `@ggui-ai/gadgets` adapter port**:
1370
+ * `GadgetCatalogAdapter { list(appId): Promise<readonly
1371
+ * GadgetDescriptor[]> }` + `InMemoryGadgetCatalog` (static map +
1372
+ * `withDefault()` factory) + `CachingGadgetCatalog` (per-appId
1373
+ * TTL + single-flight dedup + `invalidate(appId?)`). One batch
1374
+ * method by design — never N+1 per-hook. Per-environment adapter
1375
+ * implementations (JSON / DynamoDB) land separately.
1376
+ *
1377
+ * 6. **`NPM_PACKAGE_NAME_RE` + `SEMVER_PIN_RE`** schema constants
1378
+ * published from `@ggui-ai/protocol`. Loose `z.string()` checks
1379
+ * retired; agent prompts show exact pinned identity. Cache key
1380
+ * `hashContract(wire, intent)` is a pure function of the wire
1381
+ * bytes (no canonicalize step) — version bumps invalidate caches
1382
+ * automatically.
1383
+ *
1384
+ * No exhaustive-switch concerns. Agents that previously omitted
1385
+ * `package` / `version` on a wire ref now fail at parse — the loud
1386
+ * failure is the design intent (a caller bug, not a forward-compat
1387
+ * hedge).
1388
+ */
1389
+ export const PROTOCOL_VERSION = 'draft-2026-05-24';
1390
+ /**
1391
+ * Schema version stamped onto wire envelopes that opt into the
1392
+ * `schemaVersion` forward-compat field (see {@link ActionEnvelope},
1393
+ * {@link StreamEnvelope}, {@link ContractErrorPayload}).
1394
+ *
1395
+ * Pre-launch semantics (current): producers SHOULD stamp; consumers
1396
+ * SHOULD NOT reject on mismatch — the field is advisory and lets old
1397
+ * clients recognize the protocol generation their server emits.
1398
+ *
1399
+ * Launch-cutover semantics (future): a later change promotes this to
1400
+ * required on producers and tightens client-side policy (e.g.,
1401
+ * reject-with-UPGRADE-REQUIRED when the received version's major bumps
1402
+ * past the client's known major).
1403
+ *
1404
+ * Kept as a string so SemVer-like extensions (`'1.0-rc.2'`,
1405
+ * `'1.1'`) don't require retyping consumers. Value equals
1406
+ * {@link PROTOCOL_VERSION} today — the alias exists so envelope-layer
1407
+ * consumers can reference schema-versioning specifically without
1408
+ * coupling to the broader cache-invalidation constant.
1409
+ */
1410
+ export const PROTOCOL_SCHEMA_VERSION = PROTOCOL_VERSION;
1411
+ /**
1412
+ * Canonical live-channel error code emitted when a peer's declared
1413
+ * {@link SubscribePayload.supportedVersions} /
1414
+ * {@link AckPayload.serverVersion} does not overlap the receiver's
1415
+ * known compatible set.
1416
+ *
1417
+ * `ErrorPayload.code` is typed as `string` (open) rather than a closed
1418
+ * union — this constant anchors the canonical literal so consumers can
1419
+ * pattern-match against a typed reference instead of string-sniffing.
1420
+ *
1421
+ * Policy posture:
1422
+ *
1423
+ * - OSS `createSessionChannelServer` default
1424
+ * `versionPolicy: 'reject'`: server emits `UPGRADE_REQUIRED` AND
1425
+ * closes the connection so the caller cannot proceed against a
1426
+ * version-mismatched session. Canonical first-party posture.
1427
+ * - `versionPolicy: 'advisory'` (legacy opt-out): server emits an
1428
+ * `UPGRADE_REQUIRED` error envelope on mismatch but keeps the
1429
+ * connection open. Use only for controlled migration windows
1430
+ * during which legacy-version clients must remain attached.
1431
+ *
1432
+ * Consumers (clients and servers alike) MUST handle the code as a
1433
+ * string — the constant exists for authoring ergonomics, not to imply
1434
+ * a closed union on `ErrorPayload.code`.
1435
+ */
1436
+ export const UPGRADE_REQUIRED = 'UPGRADE_REQUIRED';
1437
+ /**
1438
+ * Versions of the ggui protocol this client library accepts on the
1439
+ * wire.
1440
+ *
1441
+ * The first-party client declares this set on every subscribe via
1442
+ * {@link SubscribePayload.supportedVersions}. A server whose
1443
+ * {@link PROTOCOL_SCHEMA_VERSION} is NOT a member of this list is a
1444
+ * version mismatch — the server replies with an `UPGRADE_REQUIRED`
1445
+ * error envelope (see {@link UPGRADE_REQUIRED}). Symmetrically, if a
1446
+ * server returns {@link AckPayload.serverVersion} not in this set,
1447
+ * the client surfaces `UPGRADE_REQUIRED` to the caller.
1448
+ *
1449
+ * Seeded with {@link PROTOCOL_SCHEMA_VERSION}. Future minor-compatible
1450
+ * versions are added here as the protocol evolves — a client that
1451
+ * accepts both `"1.0"` and `"1.1"` ships with `['1.0', '1.1']`, and
1452
+ * `PROTOCOL_SCHEMA_VERSION` advances independently.
1453
+ *
1454
+ * Frozen so runtime consumers can't mutate the module-level array
1455
+ * (would be a cross-session leak).
1456
+ */
1457
+ export const CLIENT_SUPPORTED_VERSIONS = Object.freeze([
1458
+ PROTOCOL_SCHEMA_VERSION,
1459
+ ]);