@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 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { BundleContext, ResolvedPreferenceProfile } from '../spec/types';
2
+ /**
3
+ * Resolves preferences by scope order (user → workspace-user → bundle → surface → entity → session)
4
+ * and applies constraint resolution stub.
5
+ *
6
+ * @param ctx - Bundle context with preferences
7
+ * @returns Resolved preference profile with canonical values and source attribution
8
+ */
9
+ export declare function resolvePreferenceProfile<C extends BundleContext>(ctx: C): ResolvedPreferenceProfile;
@@ -0,0 +1,60 @@
1
+ // @bun
2
+ // src/runtime/resolve-preferences.ts
3
+ var DIMENSION_KEYS = [
4
+ "guidance",
5
+ "density",
6
+ "dataDepth",
7
+ "control",
8
+ "media",
9
+ "pace",
10
+ "narrative"
11
+ ];
12
+ var SCOPE_ORDER = [
13
+ "user",
14
+ "workspace-user",
15
+ "bundle",
16
+ "surface",
17
+ "entity",
18
+ "session"
19
+ ];
20
+ function mergeByScope(layers, ctx) {
21
+ const merged = { ...ctx.preferences };
22
+ const sourceByDimension = {};
23
+ for (const scope of SCOPE_ORDER) {
24
+ const layer = layers[scope];
25
+ if (!layer)
26
+ continue;
27
+ for (const dim of DIMENSION_KEYS) {
28
+ const val = layer[dim];
29
+ if (val !== undefined) {
30
+ merged[dim] = val;
31
+ sourceByDimension[dim] = scope;
32
+ }
33
+ }
34
+ }
35
+ for (const dim of DIMENSION_KEYS) {
36
+ if (sourceByDimension[dim] === undefined) {
37
+ sourceByDimension[dim] = "session";
38
+ }
39
+ }
40
+ return { canonical: merged, sourceByDimension };
41
+ }
42
+ function resolveConstraints(_canonical, _ctx) {
43
+ return { constrained: {}, notes: [] };
44
+ }
45
+ function resolvePreferenceProfile(ctx) {
46
+ const layers = {
47
+ session: ctx.preferences
48
+ };
49
+ const { canonical, sourceByDimension } = mergeByScope(layers, ctx);
50
+ const { constrained, notes } = resolveConstraints(canonical, ctx);
51
+ return {
52
+ canonical,
53
+ sourceByDimension,
54
+ constrained,
55
+ notes
56
+ };
57
+ }
58
+ export {
59
+ resolvePreferenceProfile
60
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Rollback API for surface patches.
3
+ * Reverts last N approved patches by applying their inverse ops.
4
+ */
5
+ import type { ResolvedSurfacePlan } from './resolve-bundle';
6
+ import type { OverlayApprovalMeta } from '../spec/types';
7
+ export interface RollbackResult {
8
+ plan: ResolvedSurfacePlan;
9
+ revertedCount: number;
10
+ remainingStack: OverlayApprovalMeta[];
11
+ }
12
+ /**
13
+ * Reverts the last N approved patches by applying their inverse ops in order.
14
+ * Each OverlayApprovalMeta must have inverseOps; forward ops are ignored.
15
+ *
16
+ * @param plan - Current resolved plan (after patches were applied)
17
+ * @param approvalStack - Stack of approval metadata (most recent last)
18
+ * @param count - Number of patches to revert (default 1)
19
+ * @returns Updated plan, reverted count, and remaining stack
20
+ */
21
+ export declare function rollbackSurfacePatches(plan: ResolvedSurfacePlan, approvalStack: OverlayApprovalMeta[], count?: number): RollbackResult;
@@ -0,0 +1,348 @@
1
+ // @bun
2
+ // src/spec/validate-surface-patch.ts
3
+ var VALID_OPS = [
4
+ "insert-node",
5
+ "replace-node",
6
+ "remove-node",
7
+ "move-node",
8
+ "resize-panel",
9
+ "set-layout",
10
+ "reveal-field",
11
+ "hide-field",
12
+ "promote-action",
13
+ "set-focus"
14
+ ];
15
+ var VALID_NODE_KINDS = [
16
+ "metric-strip",
17
+ "data-view",
18
+ "entity-card",
19
+ "entity-header",
20
+ "entity-summary",
21
+ "entity-section",
22
+ "entity-field",
23
+ "entity-activity",
24
+ "entity-relations",
25
+ "entity-timeline",
26
+ "entity-comments",
27
+ "entity-attachments",
28
+ "entity-view-switcher",
29
+ "entity-automation-panel",
30
+ "rich-doc",
31
+ "chat-thread",
32
+ "assistant-panel",
33
+ "action-bar",
34
+ "timeline",
35
+ "board",
36
+ "table",
37
+ "calendar",
38
+ "form",
39
+ "chart",
40
+ "relation-graph",
41
+ "custom-widget"
42
+ ];
43
+ function validateSurfaceNode(node, path) {
44
+ if (!node.nodeId || typeof node.nodeId !== "string") {
45
+ throw new Error(`${path}: nodeId must be a non-empty string`);
46
+ }
47
+ if (!node.kind || !VALID_NODE_KINDS.includes(node.kind)) {
48
+ throw new Error(`${path}: kind must be one of ${VALID_NODE_KINDS.join(", ")}`);
49
+ }
50
+ if (node.children) {
51
+ for (let i = 0;i < node.children.length; i++) {
52
+ const child = node.children[i];
53
+ if (child)
54
+ validateSurfaceNode(child, `${path}.children[${i}]`);
55
+ }
56
+ }
57
+ }
58
+ function validateSurfacePatchOp(op, index) {
59
+ const path = `ops[${index}]`;
60
+ if (!op || typeof op !== "object" || !("op" in op)) {
61
+ throw new Error(`${path}: must be an object with op field`);
62
+ }
63
+ const opType = op.op;
64
+ if (!VALID_OPS.includes(opType)) {
65
+ throw new Error(`${path}: op must be one of ${VALID_OPS.join(", ")}`);
66
+ }
67
+ switch (op.op) {
68
+ case "insert-node":
69
+ if (!op.slotId || typeof op.slotId !== "string") {
70
+ throw new Error(`${path}: insert-node requires slotId string`);
71
+ }
72
+ if (!op.node) {
73
+ throw new Error(`${path}: insert-node requires node`);
74
+ }
75
+ validateSurfaceNode(op.node, `${path}.node`);
76
+ if (op.index !== undefined && typeof op.index !== "number") {
77
+ throw new Error(`${path}: insert-node index must be number if present`);
78
+ }
79
+ break;
80
+ case "replace-node":
81
+ if (!op.nodeId || typeof op.nodeId !== "string") {
82
+ throw new Error(`${path}: replace-node requires nodeId string`);
83
+ }
84
+ if (!op.node) {
85
+ throw new Error(`${path}: replace-node requires node`);
86
+ }
87
+ validateSurfaceNode(op.node, `${path}.node`);
88
+ break;
89
+ case "remove-node":
90
+ if (!op.nodeId || typeof op.nodeId !== "string") {
91
+ throw new Error(`${path}: remove-node requires nodeId string`);
92
+ }
93
+ break;
94
+ case "move-node":
95
+ if (!op.nodeId || typeof op.nodeId !== "string") {
96
+ throw new Error(`${path}: move-node requires nodeId string`);
97
+ }
98
+ if (!op.toSlotId || typeof op.toSlotId !== "string") {
99
+ throw new Error(`${path}: move-node requires toSlotId string`);
100
+ }
101
+ if (op.index !== undefined && typeof op.index !== "number") {
102
+ throw new Error(`${path}: move-node index must be number if present`);
103
+ }
104
+ break;
105
+ case "resize-panel":
106
+ if (!op.persistKey || typeof op.persistKey !== "string") {
107
+ throw new Error(`${path}: resize-panel requires persistKey string`);
108
+ }
109
+ if (!Array.isArray(op.sizes) || op.sizes.some((s) => typeof s !== "number")) {
110
+ throw new Error(`${path}: resize-panel requires sizes number[]`);
111
+ }
112
+ break;
113
+ case "set-layout":
114
+ if (!op.layoutId || typeof op.layoutId !== "string") {
115
+ throw new Error(`${path}: set-layout requires layoutId string`);
116
+ }
117
+ break;
118
+ case "reveal-field":
119
+ case "hide-field":
120
+ if (!op.fieldId || typeof op.fieldId !== "string") {
121
+ throw new Error(`${path}: ${op.op} requires fieldId string`);
122
+ }
123
+ break;
124
+ case "promote-action": {
125
+ if (!op.actionId || typeof op.actionId !== "string") {
126
+ throw new Error(`${path}: promote-action requires actionId string`);
127
+ }
128
+ const validPlacements = ["header", "inline", "context", "assistant"];
129
+ if (!op.placement || !validPlacements.includes(op.placement)) {
130
+ throw new Error(`${path}: promote-action placement must be one of ${validPlacements.join(", ")}`);
131
+ }
132
+ break;
133
+ }
134
+ case "set-focus":
135
+ if (!op.targetId || typeof op.targetId !== "string") {
136
+ throw new Error(`${path}: set-focus requires targetId string`);
137
+ }
138
+ break;
139
+ default:
140
+ throw new Error(`${path}: unknown op "${opType}"`);
141
+ }
142
+ }
143
+ function validateSurfacePatch(ops) {
144
+ if (!Array.isArray(ops)) {
145
+ throw new Error("Patch ops must be an array");
146
+ }
147
+ for (let i = 0;i < ops.length; i++) {
148
+ const op = ops[i];
149
+ if (op)
150
+ validateSurfacePatchOp(op, i);
151
+ }
152
+ }
153
+ function validateSurfaceNodeAgainstKinds(node, allowedKinds, path) {
154
+ if (!allowedKinds.includes(node.kind)) {
155
+ throw new Error(`${path}: kind "${node.kind}" not in allowed list [${allowedKinds.join(", ")}]`);
156
+ }
157
+ if (node.children) {
158
+ for (let i = 0;i < node.children.length; i++) {
159
+ const child = node.children[i];
160
+ if (child)
161
+ validateSurfaceNodeAgainstKinds(child, allowedKinds, `${path}.children[${i}]`);
162
+ }
163
+ }
164
+ }
165
+ function validatePatchProposal(ops, constraints) {
166
+ if (!Array.isArray(ops)) {
167
+ throw new Error("Patch ops must be an array");
168
+ }
169
+ const { allowedOps, allowedSlots, allowedNodeKinds } = constraints;
170
+ for (let i = 0;i < ops.length; i++) {
171
+ const op = ops[i];
172
+ if (!op)
173
+ continue;
174
+ const path = `ops[${i}]`;
175
+ if (!allowedOps.includes(op.op)) {
176
+ throw new Error(`${path}: op "${op.op}" not in allowed list [${allowedOps.join(", ")}]`);
177
+ }
178
+ switch (op.op) {
179
+ case "insert-node":
180
+ if (!allowedSlots.includes(op.slotId)) {
181
+ throw new Error(`${path}: slotId "${op.slotId}" not in allowed slots [${allowedSlots.join(", ")}]`);
182
+ }
183
+ if (op.node) {
184
+ validateSurfaceNodeAgainstKinds(op.node, allowedNodeKinds, `${path}.node`);
185
+ }
186
+ break;
187
+ case "move-node":
188
+ if (!allowedSlots.includes(op.toSlotId)) {
189
+ throw new Error(`${path}: toSlotId "${op.toSlotId}" not in allowed slots [${allowedSlots.join(", ")}]`);
190
+ }
191
+ break;
192
+ case "replace-node":
193
+ if (op.node) {
194
+ validateSurfaceNodeAgainstKinds(op.node, allowedNodeKinds, `${path}.node`);
195
+ }
196
+ break;
197
+ default:
198
+ break;
199
+ }
200
+ }
201
+ validateSurfacePatch(ops);
202
+ }
203
+
204
+ // src/runtime/apply-surface-patch.ts
205
+ import { Logger } from "@contractspec/lib.observability";
206
+ var logger = new Logger("@contractspec/lib.surface-runtime");
207
+ function findNode(nodes, nodeId) {
208
+ for (const n of nodes) {
209
+ if (n.nodeId === nodeId)
210
+ return n;
211
+ if (n.children) {
212
+ const found = findNode(n.children, nodeId);
213
+ if (found)
214
+ return found;
215
+ }
216
+ }
217
+ return;
218
+ }
219
+ function collectNodeIds(nodes) {
220
+ const ids = new Set;
221
+ for (const n of nodes) {
222
+ ids.add(n.nodeId);
223
+ if (n.children)
224
+ collectNodeIds(n.children).forEach((id) => ids.add(id));
225
+ }
226
+ return ids;
227
+ }
228
+ function validateOp(op, nodeIds) {
229
+ if (op.op === "remove-node" || op.op === "replace-node" || op.op === "move-node") {
230
+ if (!nodeIds.has(op.nodeId)) {
231
+ throw new Error(`Patch op references unknown nodeId: ${op.nodeId}`);
232
+ }
233
+ }
234
+ if (op.op === "insert-node" && !op.node?.nodeId) {
235
+ throw new Error("insert-node requires node with nodeId");
236
+ }
237
+ }
238
+ function produceInverse(op, plan) {
239
+ switch (op.op) {
240
+ case "insert-node":
241
+ return op.node ? { op: "remove-node", nodeId: op.node.nodeId } : null;
242
+ case "remove-node": {
243
+ const node = findNode(plan.nodes, op.nodeId);
244
+ return node ? { op: "insert-node", slotId: "primary", node } : null;
245
+ }
246
+ case "replace-node": {
247
+ const prev = findNode(plan.nodes, op.nodeId);
248
+ return prev ? { op: "replace-node", nodeId: op.nodeId, node: prev } : null;
249
+ }
250
+ case "set-layout":
251
+ return { op: "set-layout", layoutId: plan.layoutId };
252
+ case "reveal-field":
253
+ return { op: "hide-field", fieldId: op.fieldId };
254
+ case "hide-field":
255
+ return { op: "reveal-field", fieldId: op.fieldId };
256
+ case "move-node":
257
+ case "resize-panel":
258
+ case "set-focus":
259
+ case "promote-action":
260
+ return null;
261
+ default:
262
+ return null;
263
+ }
264
+ }
265
+ function applySurfacePatch(plan, ops) {
266
+ if (ops.length === 0)
267
+ return { plan, inverseOps: [] };
268
+ validateSurfacePatch(ops);
269
+ const nodeIds = collectNodeIds(plan.nodes);
270
+ for (const op of ops) {
271
+ validateOp(op, nodeIds);
272
+ }
273
+ let nextNodes = [...plan.nodes];
274
+ let nextLayoutId = plan.layoutId;
275
+ const inverseOps = [];
276
+ for (const op of ops) {
277
+ const inv = produceInverse(op, {
278
+ ...plan,
279
+ nodes: nextNodes,
280
+ layoutId: nextLayoutId
281
+ });
282
+ if (inv)
283
+ inverseOps.unshift(inv);
284
+ switch (op.op) {
285
+ case "insert-node":
286
+ if (op.node)
287
+ nextNodes = [...nextNodes, op.node];
288
+ break;
289
+ case "remove-node":
290
+ nextNodes = nextNodes.filter((n) => n.nodeId !== op.nodeId);
291
+ break;
292
+ case "replace-node": {
293
+ const replace = (nodes) => nodes.map((n) => n.nodeId === op.nodeId ? op.node : { ...n, children: n.children ? replace(n.children) : undefined });
294
+ nextNodes = replace(nextNodes);
295
+ break;
296
+ }
297
+ case "set-layout":
298
+ nextLayoutId = op.layoutId;
299
+ break;
300
+ case "move-node":
301
+ case "resize-panel":
302
+ case "set-focus":
303
+ case "reveal-field":
304
+ case "hide-field":
305
+ case "promote-action":
306
+ break;
307
+ }
308
+ }
309
+ const result = {
310
+ plan: { ...plan, nodes: nextNodes, layoutId: nextLayoutId },
311
+ inverseOps
312
+ };
313
+ logger.info("bundle.surface.patch.applied", {
314
+ bundleKey: plan.bundleKey,
315
+ surfaceId: plan.surfaceId,
316
+ opCount: ops.length,
317
+ opTypes: [...new Set(ops.map((o) => o.op))]
318
+ });
319
+ return result;
320
+ }
321
+
322
+ // src/runtime/rollback.ts
323
+ function rollbackSurfacePatches(plan, approvalStack, count = 1) {
324
+ if (count <= 0 || approvalStack.length === 0) {
325
+ return { plan, revertedCount: 0, remainingStack: approvalStack };
326
+ }
327
+ const toRevert = approvalStack.slice(-count);
328
+ const remaining = approvalStack.slice(0, -count);
329
+ const currentPlan = plan;
330
+ const allInverseOps = [];
331
+ for (const meta of toRevert) {
332
+ if (meta.inverseOps.length > 0) {
333
+ allInverseOps.push(...meta.inverseOps);
334
+ }
335
+ }
336
+ if (allInverseOps.length === 0) {
337
+ return { plan, revertedCount: 0, remainingStack: approvalStack };
338
+ }
339
+ const result = applySurfacePatch(currentPlan, allInverseOps);
340
+ return {
341
+ plan: result.plan,
342
+ revertedCount: toRevert.length,
343
+ remainingStack: remaining
344
+ };
345
+ }
346
+ export {
347
+ rollbackSurfacePatches
348
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Widget registry for surface runtime.
3
+ * Aligns with 09_extension_and_override_model.md.
4
+ *
5
+ * Widgets are registered in code, never from AI prompts.
6
+ * Trust tiers: core | workspace | signed-plugin (never ephemeral-ai).
7
+ */
8
+ export type WidgetTrust = 'core' | 'workspace' | 'signed-plugin';
9
+ export interface WidgetRegistryEntry {
10
+ widgetKey: string;
11
+ title: string;
12
+ nodeKind: 'custom-widget';
13
+ render: string;
14
+ configure?: string;
15
+ trust: WidgetTrust;
16
+ requiresCapabilities?: string[];
17
+ supportedSlots?: string[];
18
+ }
19
+ export interface WidgetRegistry {
20
+ register(entry: WidgetRegistryEntry): void;
21
+ get(widgetKey: string): WidgetRegistryEntry | undefined;
22
+ has(widgetKey: string): boolean;
23
+ list(): WidgetRegistryEntry[];
24
+ listByTrust(trust: WidgetTrust): WidgetRegistryEntry[];
25
+ }
26
+ export declare function createWidgetRegistry(): WidgetRegistry;
@@ -0,0 +1,37 @@
1
+ // @bun
2
+ // src/runtime/widget-registry.ts
3
+ function validateTrust(trust) {
4
+ if (trust === "ephemeral-ai") {
5
+ throw new Error("Widgets cannot be registered with ephemeral-ai trust. Registration is a code/package concern.");
6
+ }
7
+ if (trust !== "core" && trust !== "workspace" && trust !== "signed-plugin") {
8
+ throw new Error(`Invalid widget trust: ${trust}`);
9
+ }
10
+ }
11
+ function createWidgetRegistry() {
12
+ const entries = new Map;
13
+ return {
14
+ register(entry) {
15
+ validateTrust(entry.trust);
16
+ if (entry.nodeKind !== "custom-widget") {
17
+ throw new Error(`Widget "${entry.widgetKey}" must have nodeKind "custom-widget"`);
18
+ }
19
+ entries.set(entry.widgetKey, entry);
20
+ },
21
+ get(widgetKey) {
22
+ return entries.get(widgetKey);
23
+ },
24
+ has(widgetKey) {
25
+ return entries.has(widgetKey);
26
+ },
27
+ list() {
28
+ return Array.from(entries.values());
29
+ },
30
+ listByTrust(trust) {
31
+ return Array.from(entries.values()).filter((e) => e.trust === trust);
32
+ }
33
+ };
34
+ }
35
+ export {
36
+ createWidgetRegistry
37
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { ModuleBundleSpec } from './types';
2
+ /**
3
+ * Defines a module bundle spec. Validates shape at runtime and returns the spec.
4
+ *
5
+ * @param spec - The bundle spec to define
6
+ * @returns The same spec (for type inference)
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const PmWorkbenchBundle = defineModuleBundle({
11
+ * meta: { key: "pm.workbench", version: "0.1.0", title: "PM Workbench" },
12
+ * routes: [...],
13
+ * surfaces: {...},
14
+ * });
15
+ * ```
16
+ */
17
+ export declare function defineModuleBundle<const T extends ModuleBundleSpec>(spec: T): T;
@@ -0,0 +1,114 @@
1
+ // @bun
2
+ // src/spec/validate-bundle.ts
3
+ var KNOWN_NODE_KIND_RENDERERS = new Set([
4
+ "entity-section",
5
+ "entity-field",
6
+ "action-bar",
7
+ "table",
8
+ "timeline",
9
+ "rich-doc",
10
+ "chat-thread",
11
+ "assistant-panel",
12
+ "entity-card",
13
+ "entity-header",
14
+ "entity-summary",
15
+ "entity-activity",
16
+ "entity-relations",
17
+ "relation-graph",
18
+ "custom-widget"
19
+ ]);
20
+ function collectSlotIdsFromRegion(node) {
21
+ const ids = [];
22
+ if (node.type === "slot") {
23
+ ids.push(node.slotId);
24
+ }
25
+ if (node.type === "panel-group" || node.type === "stack") {
26
+ for (const child of node.children) {
27
+ ids.push(...collectSlotIdsFromRegion(child));
28
+ }
29
+ }
30
+ if (node.type === "tabs") {
31
+ for (const tab of node.tabs) {
32
+ ids.push(...collectSlotIdsFromRegion(tab.child));
33
+ }
34
+ }
35
+ if (node.type === "floating") {
36
+ ids.push(node.anchorSlotId);
37
+ ids.push(...collectSlotIdsFromRegion(node.child));
38
+ }
39
+ return ids;
40
+ }
41
+ function validateLayoutSlots(surface) {
42
+ const declaredSlotIds = new Set(surface.slots.map((s) => s.slotId));
43
+ for (const layout of surface.layouts) {
44
+ const layoutSlotIds = collectSlotIdsFromRegion(layout.root);
45
+ for (const slotId of layoutSlotIds) {
46
+ if (!declaredSlotIds.has(slotId)) {
47
+ throw new Error(`Surface "${surface.surfaceId}" layout "${layout.layoutId}" references undeclared slot "${slotId}". Declared slots: ${[...declaredSlotIds].join(", ")}`);
48
+ }
49
+ }
50
+ }
51
+ }
52
+ function validateBundleNodeKinds(surface) {
53
+ const warnings = [];
54
+ for (const slot of surface.slots) {
55
+ for (const kind of slot.accepts) {
56
+ if (!KNOWN_NODE_KIND_RENDERERS.has(kind)) {
57
+ warnings.push(`Surface "${surface.surfaceId}" slot "${slot.slotId}" accepts "${kind}" which has no dedicated renderer (generic fallback used)`);
58
+ }
59
+ }
60
+ }
61
+ return { warnings };
62
+ }
63
+
64
+ // src/spec/define-module-bundle.ts
65
+ function defineModuleBundle(spec) {
66
+ if (!spec.meta?.key || !spec.meta?.version || !spec.meta?.title) {
67
+ throw new Error("ModuleBundleSpec must have meta.key, meta.version, and meta.title");
68
+ }
69
+ if (!spec.routes?.length) {
70
+ throw new Error("ModuleBundleSpec must have at least one route");
71
+ }
72
+ if (!spec.surfaces || Object.keys(spec.surfaces).length === 0) {
73
+ throw new Error("ModuleBundleSpec must have at least one surface");
74
+ }
75
+ if (spec.entities) {
76
+ if (!spec.entities.entityTypes || typeof spec.entities.entityTypes !== "object") {
77
+ throw new Error("ModuleBundleSpec.entities must have entityTypes object when present");
78
+ }
79
+ if (!spec.entities.fieldKinds || typeof spec.entities.fieldKinds !== "object") {
80
+ throw new Error("ModuleBundleSpec.entities must have fieldKinds object when present");
81
+ }
82
+ }
83
+ const REQUIRED_DIMENSIONS = [
84
+ "guidance",
85
+ "density",
86
+ "dataDepth",
87
+ "control",
88
+ "media",
89
+ "pace",
90
+ "narrative"
91
+ ];
92
+ const MIN_DESCRIPTION_LENGTH = 10;
93
+ for (const surface of Object.values(spec.surfaces)) {
94
+ if (!surface.verification?.dimensions) {
95
+ throw new Error(`Surface "${surface.surfaceId}" must have verification.dimensions for all 7 preference dimensions`);
96
+ }
97
+ const dims = surface.verification.dimensions;
98
+ for (const d of REQUIRED_DIMENSIONS) {
99
+ const val = dims[d];
100
+ if (!val || typeof val !== "string") {
101
+ throw new Error(`Surface "${surface.surfaceId}" verification.dimensions.${d} must be a non-empty string`);
102
+ }
103
+ const trimmed = val.trim();
104
+ if (trimmed.length < MIN_DESCRIPTION_LENGTH) {
105
+ throw new Error(`Surface "${surface.surfaceId}" verification.dimensions.${d} must be at least ${MIN_DESCRIPTION_LENGTH} characters (got ${trimmed.length})`);
106
+ }
107
+ }
108
+ validateLayoutSlots(surface);
109
+ }
110
+ return spec;
111
+ }
112
+ export {
113
+ defineModuleBundle
114
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export { defineModuleBundle } from './define-module-bundle';
3
+ export type { VerificationSnapshotInput, VerificationSnapshotSummary, } from './verification-snapshot-types';
4
+ export { validateLayoutSlots, validateBundleNodeKinds, type ValidateNodeKindsResult, } from './validate-bundle';
5
+ export { validateSurfacePatch, validateSurfacePatchOp, validatePatchProposal, type PatchProposalConstraints, } from './validate-surface-patch';