@contractspec/lib.surface-runtime 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +164 -0
  2. package/dist/adapters/ai-sdk-stub.d.ts +5 -0
  3. package/dist/adapters/ai-sdk-stub.js +13 -0
  4. package/dist/adapters/blocknote-stub.d.ts +6 -0
  5. package/dist/adapters/blocknote-stub.js +31 -0
  6. package/dist/adapters/dnd-kit-adapter.d.ts +13 -0
  7. package/dist/adapters/dnd-kit-adapter.js +44 -0
  8. package/dist/adapters/dnd-kit-stub.d.ts +6 -0
  9. package/dist/adapters/dnd-kit-stub.js +8 -0
  10. package/dist/adapters/floating-ui-stub.d.ts +6 -0
  11. package/dist/adapters/floating-ui-stub.js +19 -0
  12. package/dist/adapters/index.d.ts +11 -0
  13. package/dist/adapters/index.js +176 -0
  14. package/dist/adapters/interfaces.d.ts +75 -0
  15. package/dist/adapters/interfaces.js +1 -0
  16. package/dist/adapters/motion-stub.d.ts +7 -0
  17. package/dist/adapters/motion-stub.js +27 -0
  18. package/dist/adapters/motion-stub.test.d.ts +1 -0
  19. package/dist/adapters/resizable-panels-stub.d.ts +6 -0
  20. package/dist/adapters/resizable-panels-stub.js +46 -0
  21. package/dist/adapters/resizable-panels-stub.test.d.ts +1 -0
  22. package/dist/browser/adapters/ai-sdk-stub.js +12 -0
  23. package/dist/browser/adapters/blocknote-stub.js +30 -0
  24. package/dist/browser/adapters/dnd-kit-adapter.js +43 -0
  25. package/dist/browser/adapters/dnd-kit-stub.js +7 -0
  26. package/dist/browser/adapters/floating-ui-stub.js +18 -0
  27. package/dist/browser/adapters/index.js +175 -0
  28. package/dist/browser/adapters/interfaces.js +0 -0
  29. package/dist/browser/adapters/motion-stub.js +26 -0
  30. package/dist/browser/adapters/resizable-panels-stub.js +45 -0
  31. package/dist/browser/evals/golden-context.js +0 -0
  32. package/dist/browser/evals/golden-harness.js +848 -0
  33. package/dist/browser/examples/pm-workbench.bundle.js +476 -0
  34. package/dist/browser/i18n/catalogs/en.js +71 -0
  35. package/dist/browser/i18n/catalogs/es.js +32 -0
  36. package/dist/browser/i18n/catalogs/fr.js +32 -0
  37. package/dist/browser/i18n/catalogs/index.js +133 -0
  38. package/dist/browser/i18n/index.js +173 -0
  39. package/dist/browser/i18n/keys.js +19 -0
  40. package/dist/browser/i18n/messages.js +143 -0
  41. package/dist/browser/index.js +2466 -0
  42. package/dist/browser/react/BundleProvider.js +47 -0
  43. package/dist/browser/react/BundleRenderer.js +726 -0
  44. package/dist/browser/react/OverlayConflictResolver.js +255 -0
  45. package/dist/browser/react/PatchProposalCard.js +255 -0
  46. package/dist/browser/react/RegionRenderer.js +128 -0
  47. package/dist/browser/react/SlotRenderer.js +118 -0
  48. package/dist/browser/react/WidgetPalette.js +59 -0
  49. package/dist/browser/react/index.js +792 -0
  50. package/dist/browser/runtime/apply-surface-patch.js +322 -0
  51. package/dist/browser/runtime/audit-events.js +137 -0
  52. package/dist/browser/runtime/build-context.js +55 -0
  53. package/dist/browser/runtime/extension-registry.js +58 -0
  54. package/dist/browser/runtime/field-renderer-registry.js +145 -0
  55. package/dist/browser/runtime/index.js +1496 -0
  56. package/dist/browser/runtime/overlay-alignment.js +83 -0
  57. package/dist/browser/runtime/overlay-signer.js +15 -0
  58. package/dist/browser/runtime/override-store.js +52 -0
  59. package/dist/browser/runtime/planner-prompt.js +67 -0
  60. package/dist/browser/runtime/planner-tools.js +77 -0
  61. package/dist/browser/runtime/policy-eval.js +155 -0
  62. package/dist/browser/runtime/preference-adapter.js +67 -0
  63. package/dist/browser/runtime/resolve-bundle.js +767 -0
  64. package/dist/browser/runtime/resolve-preferences.js +59 -0
  65. package/dist/browser/runtime/rollback.js +347 -0
  66. package/dist/browser/runtime/widget-registry.js +36 -0
  67. package/dist/browser/spec/define-module-bundle.js +113 -0
  68. package/dist/browser/spec/index.js +319 -0
  69. package/dist/browser/spec/types.js +0 -0
  70. package/dist/browser/spec/validate-bundle.js +65 -0
  71. package/dist/browser/spec/validate-surface-patch.js +206 -0
  72. package/dist/browser/spec/verification-snapshot-types.js +0 -0
  73. package/dist/browser/telemetry/index.js +20 -0
  74. package/dist/browser/telemetry/surface-metrics.js +20 -0
  75. package/dist/evals/golden-context.d.ts +24 -0
  76. package/dist/evals/golden-context.js +1 -0
  77. package/dist/evals/golden-harness.d.ts +29 -0
  78. package/dist/evals/golden-harness.js +849 -0
  79. package/dist/evals/golden-harness.test.d.ts +1 -0
  80. package/dist/examples/pm-workbench.bundle.d.ts +177 -0
  81. package/dist/examples/pm-workbench.bundle.js +477 -0
  82. package/dist/i18n/catalogs/en.d.ts +1 -0
  83. package/dist/i18n/catalogs/en.js +72 -0
  84. package/dist/i18n/catalogs/es.d.ts +1 -0
  85. package/dist/i18n/catalogs/es.js +33 -0
  86. package/dist/i18n/catalogs/fr.d.ts +1 -0
  87. package/dist/i18n/catalogs/fr.js +33 -0
  88. package/dist/i18n/catalogs/index.d.ts +3 -0
  89. package/dist/i18n/catalogs/index.js +134 -0
  90. package/dist/i18n/index.d.ts +5 -0
  91. package/dist/i18n/index.js +174 -0
  92. package/dist/i18n/keys.d.ts +20 -0
  93. package/dist/i18n/keys.js +20 -0
  94. package/dist/i18n/messages.d.ts +5 -0
  95. package/dist/i18n/messages.js +144 -0
  96. package/dist/index.d.ts +4 -0
  97. package/dist/index.js +2467 -0
  98. package/dist/node/adapters/ai-sdk-stub.js +12 -0
  99. package/dist/node/adapters/blocknote-stub.js +30 -0
  100. package/dist/node/adapters/dnd-kit-adapter.js +43 -0
  101. package/dist/node/adapters/dnd-kit-stub.js +7 -0
  102. package/dist/node/adapters/floating-ui-stub.js +18 -0
  103. package/dist/node/adapters/index.js +175 -0
  104. package/dist/node/adapters/interfaces.js +0 -0
  105. package/dist/node/adapters/motion-stub.js +26 -0
  106. package/dist/node/adapters/resizable-panels-stub.js +45 -0
  107. package/dist/node/evals/golden-context.js +0 -0
  108. package/dist/node/evals/golden-harness.js +848 -0
  109. package/dist/node/examples/pm-workbench.bundle.js +476 -0
  110. package/dist/node/i18n/catalogs/en.js +71 -0
  111. package/dist/node/i18n/catalogs/es.js +32 -0
  112. package/dist/node/i18n/catalogs/fr.js +32 -0
  113. package/dist/node/i18n/catalogs/index.js +133 -0
  114. package/dist/node/i18n/index.js +173 -0
  115. package/dist/node/i18n/keys.js +19 -0
  116. package/dist/node/i18n/messages.js +143 -0
  117. package/dist/node/index.js +2466 -0
  118. package/dist/node/react/BundleProvider.js +47 -0
  119. package/dist/node/react/BundleRenderer.js +726 -0
  120. package/dist/node/react/OverlayConflictResolver.js +255 -0
  121. package/dist/node/react/PatchProposalCard.js +255 -0
  122. package/dist/node/react/RegionRenderer.js +128 -0
  123. package/dist/node/react/SlotRenderer.js +118 -0
  124. package/dist/node/react/WidgetPalette.js +59 -0
  125. package/dist/node/react/index.js +792 -0
  126. package/dist/node/runtime/apply-surface-patch.js +322 -0
  127. package/dist/node/runtime/audit-events.js +137 -0
  128. package/dist/node/runtime/build-context.js +55 -0
  129. package/dist/node/runtime/extension-registry.js +58 -0
  130. package/dist/node/runtime/field-renderer-registry.js +145 -0
  131. package/dist/node/runtime/index.js +1496 -0
  132. package/dist/node/runtime/overlay-alignment.js +83 -0
  133. package/dist/node/runtime/overlay-signer.js +15 -0
  134. package/dist/node/runtime/override-store.js +52 -0
  135. package/dist/node/runtime/planner-prompt.js +67 -0
  136. package/dist/node/runtime/planner-tools.js +77 -0
  137. package/dist/node/runtime/policy-eval.js +155 -0
  138. package/dist/node/runtime/preference-adapter.js +67 -0
  139. package/dist/node/runtime/resolve-bundle.js +767 -0
  140. package/dist/node/runtime/resolve-preferences.js +59 -0
  141. package/dist/node/runtime/rollback.js +347 -0
  142. package/dist/node/runtime/widget-registry.js +36 -0
  143. package/dist/node/spec/define-module-bundle.js +113 -0
  144. package/dist/node/spec/index.js +319 -0
  145. package/dist/node/spec/types.js +0 -0
  146. package/dist/node/spec/validate-bundle.js +65 -0
  147. package/dist/node/spec/validate-surface-patch.js +206 -0
  148. package/dist/node/spec/verification-snapshot-types.js +0 -0
  149. package/dist/node/telemetry/index.js +20 -0
  150. package/dist/node/telemetry/surface-metrics.js +20 -0
  151. package/dist/react/BundleProvider.d.ts +13 -0
  152. package/dist/react/BundleProvider.js +48 -0
  153. package/dist/react/BundleRenderer.d.ts +22 -0
  154. package/dist/react/BundleRenderer.js +727 -0
  155. package/dist/react/OverlayConflictResolver.d.ts +15 -0
  156. package/dist/react/OverlayConflictResolver.js +256 -0
  157. package/dist/react/PatchProposalCard.d.ts +13 -0
  158. package/dist/react/PatchProposalCard.js +256 -0
  159. package/dist/react/RegionRenderer.d.ts +13 -0
  160. package/dist/react/RegionRenderer.js +129 -0
  161. package/dist/react/SlotRenderer.d.ts +13 -0
  162. package/dist/react/SlotRenderer.js +119 -0
  163. package/dist/react/WidgetPalette.d.ts +12 -0
  164. package/dist/react/WidgetPalette.js +60 -0
  165. package/dist/react/index.d.ts +7 -0
  166. package/dist/react/index.js +793 -0
  167. package/dist/runtime/apply-surface-patch.d.ts +15 -0
  168. package/dist/runtime/apply-surface-patch.js +323 -0
  169. package/dist/runtime/apply-surface-patch.test.d.ts +1 -0
  170. package/dist/runtime/audit-events.d.ts +70 -0
  171. package/dist/runtime/audit-events.js +138 -0
  172. package/dist/runtime/audit-events.test.d.ts +1 -0
  173. package/dist/runtime/build-context.d.ts +9 -0
  174. package/dist/runtime/build-context.js +56 -0
  175. package/dist/runtime/extension-registry.d.ts +39 -0
  176. package/dist/runtime/extension-registry.js +59 -0
  177. package/dist/runtime/field-renderer-registry.d.ts +23 -0
  178. package/dist/runtime/field-renderer-registry.js +146 -0
  179. package/dist/runtime/field-renderer-registry.test.d.ts +1 -0
  180. package/dist/runtime/index.d.ts +16 -0
  181. package/dist/runtime/index.js +1497 -0
  182. package/dist/runtime/overlay-alignment.d.ts +49 -0
  183. package/dist/runtime/overlay-alignment.js +84 -0
  184. package/dist/runtime/overlay-alignment.test.d.ts +1 -0
  185. package/dist/runtime/overlay-signer.d.ts +15 -0
  186. package/dist/runtime/overlay-signer.js +16 -0
  187. package/dist/runtime/override-store.d.ts +44 -0
  188. package/dist/runtime/override-store.js +53 -0
  189. package/dist/runtime/override-store.test.d.ts +1 -0
  190. package/dist/runtime/planner-prompt.d.ts +39 -0
  191. package/dist/runtime/planner-prompt.js +68 -0
  192. package/dist/runtime/planner-prompt.test.d.ts +1 -0
  193. package/dist/runtime/planner-tools.d.ts +106 -0
  194. package/dist/runtime/planner-tools.js +78 -0
  195. package/dist/runtime/planner-tools.test.d.ts +1 -0
  196. package/dist/runtime/policy-eval.d.ts +23 -0
  197. package/dist/runtime/policy-eval.js +156 -0
  198. package/dist/runtime/preference-adapter.d.ts +6 -0
  199. package/dist/runtime/preference-adapter.js +68 -0
  200. package/dist/runtime/resolve-bundle.d.ts +68 -0
  201. package/dist/runtime/resolve-bundle.js +768 -0
  202. package/dist/runtime/resolve-bundle.test.d.ts +1 -0
  203. package/dist/runtime/resolve-preferences.d.ts +9 -0
  204. package/dist/runtime/resolve-preferences.js +60 -0
  205. package/dist/runtime/resolve-preferences.test.d.ts +1 -0
  206. package/dist/runtime/rollback.d.ts +21 -0
  207. package/dist/runtime/rollback.js +348 -0
  208. package/dist/runtime/rollback.test.d.ts +1 -0
  209. package/dist/runtime/widget-registry.d.ts +26 -0
  210. package/dist/runtime/widget-registry.js +37 -0
  211. package/dist/runtime/widget-registry.test.d.ts +1 -0
  212. package/dist/spec/define-module-bundle.d.ts +17 -0
  213. package/dist/spec/define-module-bundle.js +114 -0
  214. package/dist/spec/define-module-bundle.test.d.ts +1 -0
  215. package/dist/spec/index.d.ts +5 -0
  216. package/dist/spec/index.js +320 -0
  217. package/dist/spec/types.d.ts +494 -0
  218. package/dist/spec/types.js +1 -0
  219. package/dist/spec/validate-bundle.d.ts +23 -0
  220. package/dist/spec/validate-bundle.js +66 -0
  221. package/dist/spec/validate-bundle.test.d.ts +1 -0
  222. package/dist/spec/validate-surface-patch.d.ts +39 -0
  223. package/dist/spec/validate-surface-patch.js +207 -0
  224. package/dist/spec/validate-surface-patch.test.d.ts +1 -0
  225. package/dist/spec/verification-snapshot-types.d.ts +23 -0
  226. package/dist/spec/verification-snapshot-types.js +1 -0
  227. package/dist/spec/verification-snapshot.test.d.ts +5 -0
  228. package/dist/telemetry/index.d.ts +5 -0
  229. package/dist/telemetry/index.js +21 -0
  230. package/dist/telemetry/surface-metrics.d.ts +17 -0
  231. package/dist/telemetry/surface-metrics.js +21 -0
  232. package/package.json +920 -0
@@ -0,0 +1,849 @@
1
+ // @bun
2
+ // src/telemetry/surface-metrics.ts
3
+ import {
4
+ createCounter,
5
+ createHistogram
6
+ } from "@contractspec/lib.observability/metrics";
7
+ var METER_NAME = "@contractspec/lib.surface-runtime";
8
+ var resolutionDurationMs = createHistogram("bundle_surface_resolution_duration_ms", "Time to resolve bundle spec to surface plan in milliseconds", METER_NAME);
9
+ var patchAcceptanceCounter = createCounter("bundle_surface_patch_acceptance_total", "Total AI patch proposals accepted by user", METER_NAME);
10
+ var patchRejectionCounter = createCounter("bundle_surface_patch_rejection_total", "Total AI patch proposals rejected by user", METER_NAME);
11
+ var policyDenialCounter = createCounter("bundle_surface_policy_denial_total", "Total policy denials (actions denied by policy)", METER_NAME);
12
+ var surfaceFallbackCounter = createCounter("bundle_surface_fallback_total", "Total surface or layout fallbacks during resolution", METER_NAME);
13
+ var missingRendererCounter = createCounter("bundle_surface_renderer_missing_total", "Total slots with no renderer for requested node kind", METER_NAME);
14
+ // src/runtime/resolve-preferences.ts
15
+ var DIMENSION_KEYS = [
16
+ "guidance",
17
+ "density",
18
+ "dataDepth",
19
+ "control",
20
+ "media",
21
+ "pace",
22
+ "narrative"
23
+ ];
24
+ var SCOPE_ORDER = [
25
+ "user",
26
+ "workspace-user",
27
+ "bundle",
28
+ "surface",
29
+ "entity",
30
+ "session"
31
+ ];
32
+ function mergeByScope(layers, ctx) {
33
+ const merged = { ...ctx.preferences };
34
+ const sourceByDimension = {};
35
+ for (const scope of SCOPE_ORDER) {
36
+ const layer = layers[scope];
37
+ if (!layer)
38
+ continue;
39
+ for (const dim of DIMENSION_KEYS) {
40
+ const val = layer[dim];
41
+ if (val !== undefined) {
42
+ merged[dim] = val;
43
+ sourceByDimension[dim] = scope;
44
+ }
45
+ }
46
+ }
47
+ for (const dim of DIMENSION_KEYS) {
48
+ if (sourceByDimension[dim] === undefined) {
49
+ sourceByDimension[dim] = "session";
50
+ }
51
+ }
52
+ return { canonical: merged, sourceByDimension };
53
+ }
54
+ function resolveConstraints(_canonical, _ctx) {
55
+ return { constrained: {}, notes: [] };
56
+ }
57
+ function resolvePreferenceProfile(ctx) {
58
+ const layers = {
59
+ session: ctx.preferences
60
+ };
61
+ const { canonical, sourceByDimension } = mergeByScope(layers, ctx);
62
+ const { constrained, notes } = resolveConstraints(canonical, ctx);
63
+ return {
64
+ canonical,
65
+ sourceByDimension,
66
+ constrained,
67
+ notes
68
+ };
69
+ }
70
+
71
+ // src/spec/validate-surface-patch.ts
72
+ var VALID_OPS = [
73
+ "insert-node",
74
+ "replace-node",
75
+ "remove-node",
76
+ "move-node",
77
+ "resize-panel",
78
+ "set-layout",
79
+ "reveal-field",
80
+ "hide-field",
81
+ "promote-action",
82
+ "set-focus"
83
+ ];
84
+ var VALID_NODE_KINDS = [
85
+ "metric-strip",
86
+ "data-view",
87
+ "entity-card",
88
+ "entity-header",
89
+ "entity-summary",
90
+ "entity-section",
91
+ "entity-field",
92
+ "entity-activity",
93
+ "entity-relations",
94
+ "entity-timeline",
95
+ "entity-comments",
96
+ "entity-attachments",
97
+ "entity-view-switcher",
98
+ "entity-automation-panel",
99
+ "rich-doc",
100
+ "chat-thread",
101
+ "assistant-panel",
102
+ "action-bar",
103
+ "timeline",
104
+ "board",
105
+ "table",
106
+ "calendar",
107
+ "form",
108
+ "chart",
109
+ "relation-graph",
110
+ "custom-widget"
111
+ ];
112
+ function validateSurfaceNode(node, path) {
113
+ if (!node.nodeId || typeof node.nodeId !== "string") {
114
+ throw new Error(`${path}: nodeId must be a non-empty string`);
115
+ }
116
+ if (!node.kind || !VALID_NODE_KINDS.includes(node.kind)) {
117
+ throw new Error(`${path}: kind must be one of ${VALID_NODE_KINDS.join(", ")}`);
118
+ }
119
+ if (node.children) {
120
+ for (let i = 0;i < node.children.length; i++) {
121
+ const child = node.children[i];
122
+ if (child)
123
+ validateSurfaceNode(child, `${path}.children[${i}]`);
124
+ }
125
+ }
126
+ }
127
+ function validateSurfacePatchOp(op, index) {
128
+ const path = `ops[${index}]`;
129
+ if (!op || typeof op !== "object" || !("op" in op)) {
130
+ throw new Error(`${path}: must be an object with op field`);
131
+ }
132
+ const opType = op.op;
133
+ if (!VALID_OPS.includes(opType)) {
134
+ throw new Error(`${path}: op must be one of ${VALID_OPS.join(", ")}`);
135
+ }
136
+ switch (op.op) {
137
+ case "insert-node":
138
+ if (!op.slotId || typeof op.slotId !== "string") {
139
+ throw new Error(`${path}: insert-node requires slotId string`);
140
+ }
141
+ if (!op.node) {
142
+ throw new Error(`${path}: insert-node requires node`);
143
+ }
144
+ validateSurfaceNode(op.node, `${path}.node`);
145
+ if (op.index !== undefined && typeof op.index !== "number") {
146
+ throw new Error(`${path}: insert-node index must be number if present`);
147
+ }
148
+ break;
149
+ case "replace-node":
150
+ if (!op.nodeId || typeof op.nodeId !== "string") {
151
+ throw new Error(`${path}: replace-node requires nodeId string`);
152
+ }
153
+ if (!op.node) {
154
+ throw new Error(`${path}: replace-node requires node`);
155
+ }
156
+ validateSurfaceNode(op.node, `${path}.node`);
157
+ break;
158
+ case "remove-node":
159
+ if (!op.nodeId || typeof op.nodeId !== "string") {
160
+ throw new Error(`${path}: remove-node requires nodeId string`);
161
+ }
162
+ break;
163
+ case "move-node":
164
+ if (!op.nodeId || typeof op.nodeId !== "string") {
165
+ throw new Error(`${path}: move-node requires nodeId string`);
166
+ }
167
+ if (!op.toSlotId || typeof op.toSlotId !== "string") {
168
+ throw new Error(`${path}: move-node requires toSlotId string`);
169
+ }
170
+ if (op.index !== undefined && typeof op.index !== "number") {
171
+ throw new Error(`${path}: move-node index must be number if present`);
172
+ }
173
+ break;
174
+ case "resize-panel":
175
+ if (!op.persistKey || typeof op.persistKey !== "string") {
176
+ throw new Error(`${path}: resize-panel requires persistKey string`);
177
+ }
178
+ if (!Array.isArray(op.sizes) || op.sizes.some((s) => typeof s !== "number")) {
179
+ throw new Error(`${path}: resize-panel requires sizes number[]`);
180
+ }
181
+ break;
182
+ case "set-layout":
183
+ if (!op.layoutId || typeof op.layoutId !== "string") {
184
+ throw new Error(`${path}: set-layout requires layoutId string`);
185
+ }
186
+ break;
187
+ case "reveal-field":
188
+ case "hide-field":
189
+ if (!op.fieldId || typeof op.fieldId !== "string") {
190
+ throw new Error(`${path}: ${op.op} requires fieldId string`);
191
+ }
192
+ break;
193
+ case "promote-action": {
194
+ if (!op.actionId || typeof op.actionId !== "string") {
195
+ throw new Error(`${path}: promote-action requires actionId string`);
196
+ }
197
+ const validPlacements = ["header", "inline", "context", "assistant"];
198
+ if (!op.placement || !validPlacements.includes(op.placement)) {
199
+ throw new Error(`${path}: promote-action placement must be one of ${validPlacements.join(", ")}`);
200
+ }
201
+ break;
202
+ }
203
+ case "set-focus":
204
+ if (!op.targetId || typeof op.targetId !== "string") {
205
+ throw new Error(`${path}: set-focus requires targetId string`);
206
+ }
207
+ break;
208
+ default:
209
+ throw new Error(`${path}: unknown op "${opType}"`);
210
+ }
211
+ }
212
+ function validateSurfacePatch(ops) {
213
+ if (!Array.isArray(ops)) {
214
+ throw new Error("Patch ops must be an array");
215
+ }
216
+ for (let i = 0;i < ops.length; i++) {
217
+ const op = ops[i];
218
+ if (op)
219
+ validateSurfacePatchOp(op, i);
220
+ }
221
+ }
222
+ function validateSurfaceNodeAgainstKinds(node, allowedKinds, path) {
223
+ if (!allowedKinds.includes(node.kind)) {
224
+ throw new Error(`${path}: kind "${node.kind}" not in allowed list [${allowedKinds.join(", ")}]`);
225
+ }
226
+ if (node.children) {
227
+ for (let i = 0;i < node.children.length; i++) {
228
+ const child = node.children[i];
229
+ if (child)
230
+ validateSurfaceNodeAgainstKinds(child, allowedKinds, `${path}.children[${i}]`);
231
+ }
232
+ }
233
+ }
234
+ function validatePatchProposal(ops, constraints) {
235
+ if (!Array.isArray(ops)) {
236
+ throw new Error("Patch ops must be an array");
237
+ }
238
+ const { allowedOps, allowedSlots, allowedNodeKinds } = constraints;
239
+ for (let i = 0;i < ops.length; i++) {
240
+ const op = ops[i];
241
+ if (!op)
242
+ continue;
243
+ const path = `ops[${i}]`;
244
+ if (!allowedOps.includes(op.op)) {
245
+ throw new Error(`${path}: op "${op.op}" not in allowed list [${allowedOps.join(", ")}]`);
246
+ }
247
+ switch (op.op) {
248
+ case "insert-node":
249
+ if (!allowedSlots.includes(op.slotId)) {
250
+ throw new Error(`${path}: slotId "${op.slotId}" not in allowed slots [${allowedSlots.join(", ")}]`);
251
+ }
252
+ if (op.node) {
253
+ validateSurfaceNodeAgainstKinds(op.node, allowedNodeKinds, `${path}.node`);
254
+ }
255
+ break;
256
+ case "move-node":
257
+ if (!allowedSlots.includes(op.toSlotId)) {
258
+ throw new Error(`${path}: toSlotId "${op.toSlotId}" not in allowed slots [${allowedSlots.join(", ")}]`);
259
+ }
260
+ break;
261
+ case "replace-node":
262
+ if (op.node) {
263
+ validateSurfaceNodeAgainstKinds(op.node, allowedNodeKinds, `${path}.node`);
264
+ }
265
+ break;
266
+ default:
267
+ break;
268
+ }
269
+ }
270
+ validateSurfacePatch(ops);
271
+ }
272
+
273
+ // src/runtime/apply-surface-patch.ts
274
+ import { Logger } from "@contractspec/lib.observability";
275
+ var logger = new Logger("@contractspec/lib.surface-runtime");
276
+ function findNode(nodes, nodeId) {
277
+ for (const n of nodes) {
278
+ if (n.nodeId === nodeId)
279
+ return n;
280
+ if (n.children) {
281
+ const found = findNode(n.children, nodeId);
282
+ if (found)
283
+ return found;
284
+ }
285
+ }
286
+ return;
287
+ }
288
+ function collectNodeIds(nodes) {
289
+ const ids = new Set;
290
+ for (const n of nodes) {
291
+ ids.add(n.nodeId);
292
+ if (n.children)
293
+ collectNodeIds(n.children).forEach((id) => ids.add(id));
294
+ }
295
+ return ids;
296
+ }
297
+ function validateOp(op, nodeIds) {
298
+ if (op.op === "remove-node" || op.op === "replace-node" || op.op === "move-node") {
299
+ if (!nodeIds.has(op.nodeId)) {
300
+ throw new Error(`Patch op references unknown nodeId: ${op.nodeId}`);
301
+ }
302
+ }
303
+ if (op.op === "insert-node" && !op.node?.nodeId) {
304
+ throw new Error("insert-node requires node with nodeId");
305
+ }
306
+ }
307
+ function produceInverse(op, plan) {
308
+ switch (op.op) {
309
+ case "insert-node":
310
+ return op.node ? { op: "remove-node", nodeId: op.node.nodeId } : null;
311
+ case "remove-node": {
312
+ const node = findNode(plan.nodes, op.nodeId);
313
+ return node ? { op: "insert-node", slotId: "primary", node } : null;
314
+ }
315
+ case "replace-node": {
316
+ const prev = findNode(plan.nodes, op.nodeId);
317
+ return prev ? { op: "replace-node", nodeId: op.nodeId, node: prev } : null;
318
+ }
319
+ case "set-layout":
320
+ return { op: "set-layout", layoutId: plan.layoutId };
321
+ case "reveal-field":
322
+ return { op: "hide-field", fieldId: op.fieldId };
323
+ case "hide-field":
324
+ return { op: "reveal-field", fieldId: op.fieldId };
325
+ case "move-node":
326
+ case "resize-panel":
327
+ case "set-focus":
328
+ case "promote-action":
329
+ return null;
330
+ default:
331
+ return null;
332
+ }
333
+ }
334
+ function applySurfacePatch(plan, ops) {
335
+ if (ops.length === 0)
336
+ return { plan, inverseOps: [] };
337
+ validateSurfacePatch(ops);
338
+ const nodeIds = collectNodeIds(plan.nodes);
339
+ for (const op of ops) {
340
+ validateOp(op, nodeIds);
341
+ }
342
+ let nextNodes = [...plan.nodes];
343
+ let nextLayoutId = plan.layoutId;
344
+ const inverseOps = [];
345
+ for (const op of ops) {
346
+ const inv = produceInverse(op, {
347
+ ...plan,
348
+ nodes: nextNodes,
349
+ layoutId: nextLayoutId
350
+ });
351
+ if (inv)
352
+ inverseOps.unshift(inv);
353
+ switch (op.op) {
354
+ case "insert-node":
355
+ if (op.node)
356
+ nextNodes = [...nextNodes, op.node];
357
+ break;
358
+ case "remove-node":
359
+ nextNodes = nextNodes.filter((n) => n.nodeId !== op.nodeId);
360
+ break;
361
+ case "replace-node": {
362
+ const replace = (nodes) => nodes.map((n) => n.nodeId === op.nodeId ? op.node : { ...n, children: n.children ? replace(n.children) : undefined });
363
+ nextNodes = replace(nextNodes);
364
+ break;
365
+ }
366
+ case "set-layout":
367
+ nextLayoutId = op.layoutId;
368
+ break;
369
+ case "move-node":
370
+ case "resize-panel":
371
+ case "set-focus":
372
+ case "reveal-field":
373
+ case "hide-field":
374
+ case "promote-action":
375
+ break;
376
+ }
377
+ }
378
+ const result = {
379
+ plan: { ...plan, nodes: nextNodes, layoutId: nextLayoutId },
380
+ inverseOps
381
+ };
382
+ logger.info("bundle.surface.patch.applied", {
383
+ bundleKey: plan.bundleKey,
384
+ surfaceId: plan.surfaceId,
385
+ opCount: ops.length,
386
+ opTypes: [...new Set(ops.map((o) => o.op))]
387
+ });
388
+ return result;
389
+ }
390
+
391
+ // src/runtime/override-store.ts
392
+ function buildOverrideTargetKey(bundleKey, surfaceId, routeId) {
393
+ return routeId ? `${bundleKey}:${routeId}:${surfaceId}` : `${bundleKey}:${surfaceId}`;
394
+ }
395
+ function createOverrideStoreWithApprovalGate(store, options) {
396
+ const { requireApprovalForWorkspacePatches = false, requestApproval } = options;
397
+ return {
398
+ list: store.list.bind(store),
399
+ async save(scope, targetKey, patch, saveOptions) {
400
+ if (requireApprovalForWorkspacePatches && scope === "workspace" && requestApproval) {
401
+ const approved = await requestApproval({ scope, targetKey, patch });
402
+ if (!approved) {
403
+ throw new Error("Workspace overlay save rejected: approval required and not granted");
404
+ }
405
+ }
406
+ return store.save(scope, targetKey, patch, saveOptions);
407
+ },
408
+ remove: store.remove.bind(store)
409
+ };
410
+ }
411
+ function createInMemoryOverrideStore() {
412
+ const overrides = new Map;
413
+ function nextId() {
414
+ return `ov_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
415
+ }
416
+ return {
417
+ async list(scope, targetKey) {
418
+ return Array.from(overrides.values()).filter((o) => o.scope === scope && o.targetKey === targetKey);
419
+ },
420
+ async save(scope, targetKey, patch, options) {
421
+ const overrideId = options?.overrideId ?? nextId();
422
+ const stored = {
423
+ overrideId,
424
+ scope,
425
+ targetKey,
426
+ patch,
427
+ createdAt: new Date().toISOString(),
428
+ createdBy: options?.createdBy
429
+ };
430
+ overrides.set(overrideId, stored);
431
+ return overrideId;
432
+ },
433
+ async remove(overrideId) {
434
+ overrides.delete(overrideId);
435
+ }
436
+ };
437
+ }
438
+
439
+ // src/runtime/resolve-bundle.ts
440
+ import { traceAsync } from "@contractspec/lib.observability/tracing";
441
+ import { Logger as Logger2 } from "@contractspec/lib.observability";
442
+ var logger2 = new Logger2("@contractspec/lib.surface-runtime");
443
+ function getOpTarget(op) {
444
+ switch (op.op) {
445
+ case "hide-field":
446
+ case "reveal-field":
447
+ return `field:${op.fieldId}`;
448
+ case "remove-node":
449
+ case "replace-node":
450
+ case "move-node":
451
+ return `node:${op.nodeId}`;
452
+ case "insert-node":
453
+ return op.slotId ? `slot:${op.slotId}` : null;
454
+ default:
455
+ return null;
456
+ }
457
+ }
458
+ function matchRoute(pathPattern, route) {
459
+ if (pathPattern === route)
460
+ return true;
461
+ const patternParts = pathPattern.split("/");
462
+ const routeParts = route.split("/");
463
+ if (patternParts.length !== routeParts.length)
464
+ return false;
465
+ for (let i = 0;i < patternParts.length; i++) {
466
+ const p = patternParts[i];
467
+ const r = routeParts[i];
468
+ if (p?.startsWith(":"))
469
+ continue;
470
+ if (p !== r)
471
+ return false;
472
+ }
473
+ return true;
474
+ }
475
+ function selectSurface(spec, ctx) {
476
+ const route = spec.routes.find((r) => matchRoute(r.path, ctx.route)) ?? spec.routes[0];
477
+ if (!route) {
478
+ throw new Error("No routes declared in bundle spec.");
479
+ }
480
+ const fallbackRoute = !spec.routes.find((r) => matchRoute(r.path, ctx.route));
481
+ let surface = spec.surfaces[route.defaultSurface];
482
+ let fallback;
483
+ if (!surface && route.candidateSurfaces?.length) {
484
+ for (const sid of route.candidateSurfaces) {
485
+ surface = spec.surfaces[sid];
486
+ if (surface) {
487
+ fallback = `surface=${sid}`;
488
+ break;
489
+ }
490
+ }
491
+ }
492
+ if (!surface) {
493
+ const overview = Object.values(spec.surfaces).find((s) => s.kind === "overview");
494
+ if (overview) {
495
+ surface = overview;
496
+ fallback = "surface=overview";
497
+ }
498
+ }
499
+ if (!surface) {
500
+ throw new Error(`Default surface "${route.defaultSurface}" was not found.`);
501
+ }
502
+ return {
503
+ surface,
504
+ routeId: route.routeId,
505
+ fallback: fallback ?? (fallbackRoute ? "route" : undefined)
506
+ };
507
+ }
508
+ function selectLayout(surface, ctx, spec) {
509
+ let preferredLayoutId;
510
+ if (ctx.activeViewId && ctx.entity?.type && spec?.entities?.viewKinds) {
511
+ const viewKind = spec.entities.viewKinds[ctx.activeViewId];
512
+ const entityType = spec.entities.entityTypes[ctx.entity.type];
513
+ if (viewKind?.defaultLayoutId && entityType?.supportedViews?.includes(ctx.activeViewId)) {
514
+ preferredLayoutId = viewKind.defaultLayoutId;
515
+ }
516
+ }
517
+ const matching = surface.layouts.filter((candidate) => candidate.when?.(ctx) ?? true);
518
+ let layout;
519
+ if (preferredLayoutId) {
520
+ layout = matching.find((l) => l.layoutId === preferredLayoutId) ?? surface.layouts.find((l) => l.layoutId === preferredLayoutId) ?? matching[matching.length - 1] ?? surface.layouts[0];
521
+ } else {
522
+ layout = matching[matching.length - 1] ?? surface.layouts[0];
523
+ }
524
+ if (!layout) {
525
+ throw new Error(`Surface "${surface.surfaceId}" has no layouts.`);
526
+ }
527
+ const layoutFallback = matching.length === 0 && surface.layouts.length > 0;
528
+ return { layout, layoutFallback: layoutFallback || undefined };
529
+ }
530
+ function resolveDataRecipes(recipes, ctx) {
531
+ const bindings = {};
532
+ for (const r of recipes) {
533
+ if (r.when?.(ctx) ?? true) {
534
+ const key = r.hydrateInto ?? r.recipeId;
535
+ bindings[key] = { recipeId: r.recipeId, source: r.source, _stub: true };
536
+ }
537
+ }
538
+ return bindings;
539
+ }
540
+ function allowFromDecision(decision) {
541
+ if (!decision)
542
+ return true;
543
+ return decision.effect === "allow" || decision.effect === "allow-session-only";
544
+ }
545
+ function generateResolutionId() {
546
+ return `res_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
547
+ }
548
+ var OVERLAY_SCOPE_ORDER = [
549
+ "system",
550
+ "workspace",
551
+ "team",
552
+ "user",
553
+ "session"
554
+ ];
555
+ function buildAdaptation(ctx) {
556
+ const profile = resolvePreferenceProfile(ctx);
557
+ const notes = [...profile.notes];
558
+ const constrainedEntries = Object.entries(profile.constrained);
559
+ if (constrainedEntries.length > 0) {
560
+ notes.push(`constrained: ${constrainedEntries.map(([k, v]) => `${k}=${v}`).join(", ")}`);
561
+ }
562
+ return {
563
+ appliedDimensions: profile.canonical,
564
+ notes
565
+ };
566
+ }
567
+ async function resolveBundle(spec, ctx, options) {
568
+ return traceAsync("surface.resolveBundle", async (span) => {
569
+ const start = performance.now();
570
+ try {
571
+ const plan = await resolveBundleInternal(spec, ctx, options);
572
+ const durationMs = performance.now() - start;
573
+ resolutionDurationMs.record(durationMs, {
574
+ bundleKey: spec.meta.key,
575
+ surfaceId: plan.surfaceId,
576
+ fallback: plan.surfaceId === "_error" ? "error" : plan.audit.reasons.some((r) => r.startsWith("fallback=")) ? "yes" : "no"
577
+ });
578
+ span.setAttribute("bundle.key", spec.meta.key);
579
+ span.setAttribute("surface.id", plan.surfaceId);
580
+ span.setAttribute("resolution.duration_ms", durationMs);
581
+ logger2.info("bundle.surface.resolved", {
582
+ bundleKey: spec.meta.key,
583
+ surfaceId: plan.surfaceId,
584
+ layoutId: plan.layoutId,
585
+ resolutionMs: Math.round(durationMs),
586
+ fallback: plan.audit.reasons.some((r) => r.startsWith("fallback=")),
587
+ nodeCount: plan.nodes.length
588
+ });
589
+ return plan;
590
+ } catch (err) {
591
+ const durationMs = performance.now() - start;
592
+ resolutionDurationMs.record(durationMs, {
593
+ bundleKey: spec.meta.key,
594
+ surfaceId: "_error",
595
+ fallback: "error"
596
+ });
597
+ return createErrorPlan(spec, ctx, err);
598
+ }
599
+ }, "@contractspec/lib.surface-runtime");
600
+ }
601
+ async function resolveBundleInternal(spec, ctx, options) {
602
+ const {
603
+ surface,
604
+ routeId,
605
+ fallback: surfaceFallback
606
+ } = selectSurface(spec, ctx);
607
+ const { layout, layoutFallback } = selectLayout(surface, ctx, spec);
608
+ let bindings = resolveDataRecipes(surface.data, ctx);
609
+ const redactBinding = options?.policy?.redactBinding;
610
+ if (redactBinding) {
611
+ const redacted = {};
612
+ for (const [k, v] of Object.entries(bindings)) {
613
+ redacted[k] = redactBinding(k, ctx) ?? v;
614
+ }
615
+ bindings = redacted;
616
+ }
617
+ let nodes = [];
618
+ const evaluateNode = options?.policy?.evaluateNode;
619
+ if (evaluateNode) {
620
+ nodes = nodes.filter((n) => allowFromDecision(evaluateNode(n, ctx)));
621
+ }
622
+ const reasons = [
623
+ `surface=${surface.surfaceId}`,
624
+ `layout=${layout.layoutId}`,
625
+ `density=${ctx.preferences.density}`,
626
+ `narrative=${ctx.preferences.narrative}`
627
+ ];
628
+ if (surfaceFallback) {
629
+ reasons.push(`fallback=${surfaceFallback}`);
630
+ surfaceFallbackCounter.add(1, {
631
+ bundleKey: spec.meta.key,
632
+ type: "surface"
633
+ });
634
+ }
635
+ if (layoutFallback) {
636
+ reasons.push("fallback=layout");
637
+ surfaceFallbackCounter.add(1, { bundleKey: spec.meta.key, type: "layout" });
638
+ }
639
+ const resolutionId = generateResolutionId();
640
+ const plan = {
641
+ bundleKey: spec.meta.key,
642
+ surfaceId: surface.surfaceId,
643
+ layoutId: layout.layoutId,
644
+ layoutRoot: layout.root,
645
+ nodes,
646
+ actions: surface.actions ?? [],
647
+ commands: surface.commands ?? [],
648
+ bindings,
649
+ adaptation: buildAdaptation(ctx),
650
+ overlays: [],
651
+ ai: {
652
+ plannerId: spec.ai?.plannerId
653
+ },
654
+ audit: {
655
+ resolutionId,
656
+ createdAt: new Date().toISOString(),
657
+ reasons
658
+ },
659
+ locale: ctx.locale
660
+ };
661
+ options?.audit?.emit({
662
+ eventId: `evt_${resolutionId}`,
663
+ at: new Date().toISOString(),
664
+ actorId: ctx.actorId,
665
+ source: "system",
666
+ bundleKey: spec.meta.key,
667
+ surfaceId: surface.surfaceId,
668
+ eventType: "surface.resolved",
669
+ payload: {
670
+ resolutionId,
671
+ layoutId: layout.layoutId,
672
+ reasons,
673
+ nodeCount: nodes.length
674
+ }
675
+ });
676
+ let finalPlan = plan;
677
+ if (options?.overlayMerge) {
678
+ const { overrideStore, scopes = OVERLAY_SCOPE_ORDER } = options.overlayMerge;
679
+ const targetKey = buildOverrideTargetKey(spec.meta.key, surface.surfaceId, routeId);
680
+ const appliedOverlays = [];
681
+ const conflicts = [];
682
+ const seenTargets = new Map;
683
+ const conflictKeys = new Set;
684
+ for (const scope of scopes) {
685
+ if (scope === "session")
686
+ continue;
687
+ const stored = await overrideStore.list(scope, targetKey);
688
+ for (const ov of stored) {
689
+ if (ov.patch.length === 0)
690
+ continue;
691
+ for (const op of ov.patch) {
692
+ const opTarget = getOpTarget(op);
693
+ if (opTarget) {
694
+ const prev = seenTargets.get(opTarget);
695
+ if (prev && prev.scope !== scope) {
696
+ const ck = [opTarget, prev.scope, scope].sort().join(":");
697
+ if (!conflictKeys.has(ck)) {
698
+ conflictKeys.add(ck);
699
+ conflicts.push({
700
+ targetKey: opTarget,
701
+ scopeA: prev.scope,
702
+ scopeB: scope,
703
+ overlayIdA: prev.overlayId,
704
+ overlayIdB: ov.overrideId
705
+ });
706
+ }
707
+ }
708
+ seenTargets.set(opTarget, { scope, overlayId: ov.overrideId });
709
+ }
710
+ }
711
+ try {
712
+ const { plan: next } = applySurfacePatch(finalPlan, ov.patch);
713
+ finalPlan = next;
714
+ appliedOverlays.push({
715
+ overlayId: ov.overrideId,
716
+ scope,
717
+ appliedOps: ov.patch.length
718
+ });
719
+ } catch (err) {
720
+ logger2.warn("bundle.overlay.apply.failed", {
721
+ overlayId: ov.overrideId,
722
+ scope,
723
+ error: err instanceof Error ? err.message : String(err)
724
+ });
725
+ }
726
+ }
727
+ }
728
+ finalPlan = {
729
+ ...finalPlan,
730
+ overlays: appliedOverlays,
731
+ overlayConflicts: conflicts.length > 0 ? conflicts : undefined
732
+ };
733
+ }
734
+ return finalPlan;
735
+ }
736
+ function createErrorPlan(spec, ctx, err) {
737
+ const message = err instanceof Error ? err.message : String(err);
738
+ const errorLayoutRoot = {
739
+ type: "stack",
740
+ direction: "vertical",
741
+ children: [{ type: "slot", slotId: "primary" }]
742
+ };
743
+ return {
744
+ bundleKey: spec.meta.key,
745
+ surfaceId: "_error",
746
+ layoutId: "_error",
747
+ layoutRoot: errorLayoutRoot,
748
+ nodes: [],
749
+ actions: [],
750
+ commands: [],
751
+ bindings: {},
752
+ adaptation: {
753
+ appliedDimensions: ctx.preferences,
754
+ notes: [`resolution failed: ${message}`]
755
+ },
756
+ overlays: [],
757
+ ai: {},
758
+ audit: {
759
+ resolutionId: generateResolutionId(),
760
+ createdAt: new Date().toISOString(),
761
+ reasons: ["fallback=error", `error=${message}`]
762
+ },
763
+ locale: ctx.locale
764
+ };
765
+ }
766
+
767
+ // src/runtime/build-context.ts
768
+ var DEFAULT_PREFERENCES = {
769
+ guidance: "hints",
770
+ density: "standard",
771
+ dataDepth: "detailed",
772
+ control: "standard",
773
+ media: "text",
774
+ pace: "balanced",
775
+ narrative: "top-down"
776
+ };
777
+ function buildContext(params) {
778
+ const {
779
+ route,
780
+ params: routeParams = {},
781
+ query = {},
782
+ tenantId = "default",
783
+ workspaceId,
784
+ actorId,
785
+ device = "desktop",
786
+ preferences: partialPrefs = {},
787
+ capabilities = [],
788
+ featureFlags = [],
789
+ mode,
790
+ locale,
791
+ timezone,
792
+ entity,
793
+ conversation,
794
+ activeViewId
795
+ } = params;
796
+ const preferences = {
797
+ ...DEFAULT_PREFERENCES,
798
+ ...partialPrefs
799
+ };
800
+ return {
801
+ tenantId,
802
+ workspaceId,
803
+ actorId,
804
+ route,
805
+ params: routeParams,
806
+ query,
807
+ device,
808
+ mode,
809
+ locale,
810
+ timezone,
811
+ entity,
812
+ conversation,
813
+ activeViewId,
814
+ preferences,
815
+ capabilities,
816
+ featureFlags
817
+ };
818
+ }
819
+
820
+ // src/evals/golden-harness.ts
821
+ function toSnapshotPlan(plan) {
822
+ return {
823
+ bundleKey: plan.bundleKey,
824
+ surfaceId: plan.surfaceId,
825
+ layoutId: plan.layoutId,
826
+ layoutRoot: plan.layoutRoot,
827
+ nodeCount: plan.nodes.length,
828
+ actionCount: plan.actions.length,
829
+ commandCount: plan.commands.length,
830
+ bindingKeys: Object.keys(plan.bindings).sort(),
831
+ reasons: [...plan.audit.reasons].sort(),
832
+ fallback: plan.audit.reasons.some((r) => r.startsWith("fallback="))
833
+ };
834
+ }
835
+ async function runGoldenResolve(spec, ctx) {
836
+ const fullCtx = buildContext({
837
+ route: ctx.route,
838
+ params: ctx.params ?? {},
839
+ device: ctx.device ?? "desktop",
840
+ preferences: ctx.preferences,
841
+ capabilities: ctx.capabilities ?? []
842
+ });
843
+ const plan = await resolveBundle(spec, fullCtx);
844
+ return toSnapshotPlan(plan);
845
+ }
846
+ export {
847
+ toSnapshotPlan,
848
+ runGoldenResolve
849
+ };