@xemahq/dsl 0.1.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 (288) hide show
  1. package/LICENSE +201 -0
  2. package/dist/deliverable-spec/index.d.ts +3 -0
  3. package/dist/deliverable-spec/index.d.ts.map +1 -0
  4. package/dist/deliverable-spec/index.js +19 -0
  5. package/dist/deliverable-spec/index.js.map +1 -0
  6. package/dist/deliverable-spec/lib/schema.d.ts +151 -0
  7. package/dist/deliverable-spec/lib/schema.d.ts.map +1 -0
  8. package/dist/deliverable-spec/lib/schema.js +139 -0
  9. package/dist/deliverable-spec/lib/schema.js.map +1 -0
  10. package/dist/deliverable-spec/lib/types.d.ts +8 -0
  11. package/dist/deliverable-spec/lib/types.d.ts.map +1 -0
  12. package/dist/deliverable-spec/lib/types.js +3 -0
  13. package/dist/deliverable-spec/lib/types.js.map +1 -0
  14. package/dist/payload-codec/index.d.ts +8 -0
  15. package/dist/payload-codec/index.d.ts.map +1 -0
  16. package/dist/payload-codec/index.js +27 -0
  17. package/dist/payload-codec/index.js.map +1 -0
  18. package/dist/payload-codec/lib/blob-store.d.ts +37 -0
  19. package/dist/payload-codec/lib/blob-store.d.ts.map +1 -0
  20. package/dist/payload-codec/lib/blob-store.js +0 -0
  21. package/dist/payload-codec/lib/blob-store.js.map +1 -0
  22. package/dist/payload-codec/lib/codec-context.d.ts +6 -0
  23. package/dist/payload-codec/lib/codec-context.d.ts.map +1 -0
  24. package/dist/payload-codec/lib/codec-context.js +16 -0
  25. package/dist/payload-codec/lib/codec-context.js.map +1 -0
  26. package/dist/payload-codec/lib/codec.d.ts +51 -0
  27. package/dist/payload-codec/lib/codec.d.ts.map +1 -0
  28. package/dist/payload-codec/lib/codec.js +330 -0
  29. package/dist/payload-codec/lib/codec.js.map +1 -0
  30. package/dist/payload-codec/lib/enums.d.ts +18 -0
  31. package/dist/payload-codec/lib/enums.d.ts.map +1 -0
  32. package/dist/payload-codec/lib/enums.js +23 -0
  33. package/dist/payload-codec/lib/enums.js.map +1 -0
  34. package/dist/payload-codec/lib/errors.d.ts +18 -0
  35. package/dist/payload-codec/lib/errors.d.ts.map +1 -0
  36. package/dist/payload-codec/lib/errors.js +39 -0
  37. package/dist/payload-codec/lib/errors.js.map +1 -0
  38. package/dist/payload-codec/lib/http-blob-store.d.ts +21 -0
  39. package/dist/payload-codec/lib/http-blob-store.d.ts.map +1 -0
  40. package/dist/payload-codec/lib/http-blob-store.js +139 -0
  41. package/dist/payload-codec/lib/http-blob-store.js.map +1 -0
  42. package/dist/payload-codec/lib/lru-cache.d.ts +12 -0
  43. package/dist/payload-codec/lib/lru-cache.d.ts.map +1 -0
  44. package/dist/payload-codec/lib/lru-cache.js +59 -0
  45. package/dist/payload-codec/lib/lru-cache.js.map +1 -0
  46. package/dist/schema/action.schema.json +181 -0
  47. package/dist/schema/reusable-workflow.schema.json +46 -0
  48. package/dist/schema/workflow.schema.json +373 -0
  49. package/dist/workflow/index.d.ts +14 -0
  50. package/dist/workflow/index.d.ts.map +1 -0
  51. package/dist/workflow/index.js +49 -0
  52. package/dist/workflow/index.js.map +1 -0
  53. package/dist/workflow/lib/action-input-validator.d.ts +10 -0
  54. package/dist/workflow/lib/action-input-validator.d.ts.map +1 -0
  55. package/dist/workflow/lib/action-input-validator.js +69 -0
  56. package/dist/workflow/lib/action-input-validator.js.map +1 -0
  57. package/dist/workflow/lib/compiler/action-shape.d.ts +5 -0
  58. package/dist/workflow/lib/compiler/action-shape.d.ts.map +1 -0
  59. package/dist/workflow/lib/compiler/action-shape.js +43 -0
  60. package/dist/workflow/lib/compiler/action-shape.js.map +1 -0
  61. package/dist/workflow/lib/compiler/canonical-json.d.ts +3 -0
  62. package/dist/workflow/lib/compiler/canonical-json.d.ts.map +1 -0
  63. package/dist/workflow/lib/compiler/canonical-json.js +45 -0
  64. package/dist/workflow/lib/compiler/canonical-json.js.map +1 -0
  65. package/dist/workflow/lib/compiler/compile.d.ts +4 -0
  66. package/dist/workflow/lib/compiler/compile.d.ts.map +1 -0
  67. package/dist/workflow/lib/compiler/compile.js +794 -0
  68. package/dist/workflow/lib/compiler/compile.js.map +1 -0
  69. package/dist/workflow/lib/compiler/concurrency.d.ts +5 -0
  70. package/dist/workflow/lib/compiler/concurrency.d.ts.map +1 -0
  71. package/dist/workflow/lib/compiler/concurrency.js +104 -0
  72. package/dist/workflow/lib/compiler/concurrency.js.map +1 -0
  73. package/dist/workflow/lib/compiler/dag.d.ts +10 -0
  74. package/dist/workflow/lib/compiler/dag.d.ts.map +1 -0
  75. package/dist/workflow/lib/compiler/dag.js +74 -0
  76. package/dist/workflow/lib/compiler/dag.js.map +1 -0
  77. package/dist/workflow/lib/compiler/index.d.ts +6 -0
  78. package/dist/workflow/lib/compiler/index.d.ts.map +1 -0
  79. package/dist/workflow/lib/compiler/index.js +14 -0
  80. package/dist/workflow/lib/compiler/index.js.map +1 -0
  81. package/dist/workflow/lib/compiler/inputs.d.ts +4 -0
  82. package/dist/workflow/lib/compiler/inputs.d.ts.map +1 -0
  83. package/dist/workflow/lib/compiler/inputs.js +108 -0
  84. package/dist/workflow/lib/compiler/inputs.js.map +1 -0
  85. package/dist/workflow/lib/compiler/installation-resource-validator.d.ts +9 -0
  86. package/dist/workflow/lib/compiler/installation-resource-validator.d.ts.map +1 -0
  87. package/dist/workflow/lib/compiler/installation-resource-validator.js +76 -0
  88. package/dist/workflow/lib/compiler/installation-resource-validator.js.map +1 -0
  89. package/dist/workflow/lib/compiler/manifest-source.d.ts +4 -0
  90. package/dist/workflow/lib/compiler/manifest-source.d.ts.map +1 -0
  91. package/dist/workflow/lib/compiler/manifest-source.js +100 -0
  92. package/dist/workflow/lib/compiler/manifest-source.js.map +1 -0
  93. package/dist/workflow/lib/compiler/matrix.d.ts +4 -0
  94. package/dist/workflow/lib/compiler/matrix.d.ts.map +1 -0
  95. package/dist/workflow/lib/compiler/matrix.js +76 -0
  96. package/dist/workflow/lib/compiler/matrix.js.map +1 -0
  97. package/dist/workflow/lib/compiler/mount-plan.d.ts +4 -0
  98. package/dist/workflow/lib/compiler/mount-plan.d.ts.map +1 -0
  99. package/dist/workflow/lib/compiler/mount-plan.js +96 -0
  100. package/dist/workflow/lib/compiler/mount-plan.js.map +1 -0
  101. package/dist/workflow/lib/compiler/payload-reach-in.d.ts +14 -0
  102. package/dist/workflow/lib/compiler/payload-reach-in.d.ts.map +1 -0
  103. package/dist/workflow/lib/compiler/payload-reach-in.js +273 -0
  104. package/dist/workflow/lib/compiler/payload-reach-in.js.map +1 -0
  105. package/dist/workflow/lib/compiler/permissions.d.ts +6 -0
  106. package/dist/workflow/lib/compiler/permissions.d.ts.map +1 -0
  107. package/dist/workflow/lib/compiler/permissions.js +43 -0
  108. package/dist/workflow/lib/compiler/permissions.js.map +1 -0
  109. package/dist/workflow/lib/compiler/retry-timeout.d.ts +6 -0
  110. package/dist/workflow/lib/compiler/retry-timeout.d.ts.map +1 -0
  111. package/dist/workflow/lib/compiler/retry-timeout.js +64 -0
  112. package/dist/workflow/lib/compiler/retry-timeout.js.map +1 -0
  113. package/dist/workflow/lib/compiler/review-step.d.ts +18 -0
  114. package/dist/workflow/lib/compiler/review-step.d.ts.map +1 -0
  115. package/dist/workflow/lib/compiler/review-step.js +247 -0
  116. package/dist/workflow/lib/compiler/review-step.js.map +1 -0
  117. package/dist/workflow/lib/compiler/types.d.ts +42 -0
  118. package/dist/workflow/lib/compiler/types.d.ts.map +1 -0
  119. package/dist/workflow/lib/compiler/types.js +3 -0
  120. package/dist/workflow/lib/compiler/types.js.map +1 -0
  121. package/dist/workflow/lib/compiler/variable-requirements.d.ts +5 -0
  122. package/dist/workflow/lib/compiler/variable-requirements.d.ts.map +1 -0
  123. package/dist/workflow/lib/compiler/variable-requirements.js +119 -0
  124. package/dist/workflow/lib/compiler/variable-requirements.js.map +1 -0
  125. package/dist/workflow/lib/deliverable-spec-keys.d.ts +3 -0
  126. package/dist/workflow/lib/deliverable-spec-keys.d.ts.map +1 -0
  127. package/dist/workflow/lib/deliverable-spec-keys.js +90 -0
  128. package/dist/workflow/lib/deliverable-spec-keys.js.map +1 -0
  129. package/dist/workflow/lib/dispatch-inputs/index.d.ts +23 -0
  130. package/dist/workflow/lib/dispatch-inputs/index.d.ts.map +1 -0
  131. package/dist/workflow/lib/dispatch-inputs/index.js +106 -0
  132. package/dist/workflow/lib/dispatch-inputs/index.js.map +1 -0
  133. package/dist/workflow/lib/dispatch-inputs/to-json-schema.d.ts +3 -0
  134. package/dist/workflow/lib/dispatch-inputs/to-json-schema.d.ts.map +1 -0
  135. package/dist/workflow/lib/dispatch-inputs/to-json-schema.js +43 -0
  136. package/dist/workflow/lib/dispatch-inputs/to-json-schema.js.map +1 -0
  137. package/dist/workflow/lib/duration.d.ts +2 -0
  138. package/dist/workflow/lib/duration.d.ts.map +1 -0
  139. package/dist/workflow/lib/duration.js +26 -0
  140. package/dist/workflow/lib/duration.js.map +1 -0
  141. package/dist/workflow/lib/errors.d.ts +9 -0
  142. package/dist/workflow/lib/errors.d.ts.map +1 -0
  143. package/dist/workflow/lib/errors.js +28 -0
  144. package/dist/workflow/lib/errors.js.map +1 -0
  145. package/dist/workflow/lib/expression/ast.d.ts +61 -0
  146. package/dist/workflow/lib/expression/ast.d.ts.map +1 -0
  147. package/dist/workflow/lib/expression/ast.js +34 -0
  148. package/dist/workflow/lib/expression/ast.js.map +1 -0
  149. package/dist/workflow/lib/expression/context.d.ts +63 -0
  150. package/dist/workflow/lib/expression/context.d.ts.map +1 -0
  151. package/dist/workflow/lib/expression/context.js +32 -0
  152. package/dist/workflow/lib/expression/context.js.map +1 -0
  153. package/dist/workflow/lib/expression/evaluator.d.ts +5 -0
  154. package/dist/workflow/lib/expression/evaluator.d.ts.map +1 -0
  155. package/dist/workflow/lib/expression/evaluator.js +291 -0
  156. package/dist/workflow/lib/expression/evaluator.js.map +1 -0
  157. package/dist/workflow/lib/expression/index.d.ts +9 -0
  158. package/dist/workflow/lib/expression/index.d.ts.map +1 -0
  159. package/dist/workflow/lib/expression/index.js +26 -0
  160. package/dist/workflow/lib/expression/index.js.map +1 -0
  161. package/dist/workflow/lib/expression/interpolation.d.ts +9 -0
  162. package/dist/workflow/lib/expression/interpolation.d.ts.map +1 -0
  163. package/dist/workflow/lib/expression/interpolation.js +51 -0
  164. package/dist/workflow/lib/expression/interpolation.js.map +1 -0
  165. package/dist/workflow/lib/expression/parser.d.ts +4 -0
  166. package/dist/workflow/lib/expression/parser.d.ts.map +1 -0
  167. package/dist/workflow/lib/expression/parser.js +203 -0
  168. package/dist/workflow/lib/expression/parser.js.map +1 -0
  169. package/dist/workflow/lib/expression/template.d.ts +18 -0
  170. package/dist/workflow/lib/expression/template.d.ts.map +1 -0
  171. package/dist/workflow/lib/expression/template.js +63 -0
  172. package/dist/workflow/lib/expression/template.js.map +1 -0
  173. package/dist/workflow/lib/expression/tokenizer.d.ts +3 -0
  174. package/dist/workflow/lib/expression/tokenizer.d.ts.map +1 -0
  175. package/dist/workflow/lib/expression/tokenizer.js +153 -0
  176. package/dist/workflow/lib/expression/tokenizer.js.map +1 -0
  177. package/dist/workflow/lib/expression/tokens.d.ts +25 -0
  178. package/dist/workflow/lib/expression/tokens.d.ts.map +1 -0
  179. package/dist/workflow/lib/expression/tokens.js +24 -0
  180. package/dist/workflow/lib/expression/tokens.js.map +1 -0
  181. package/dist/workflow/lib/expression/walk-artifact-refs.d.ts +5 -0
  182. package/dist/workflow/lib/expression/walk-artifact-refs.d.ts.map +1 -0
  183. package/dist/workflow/lib/expression/walk-artifact-refs.js +138 -0
  184. package/dist/workflow/lib/expression/walk-artifact-refs.js.map +1 -0
  185. package/dist/workflow/lib/installation-resource-kind.d.ts +14 -0
  186. package/dist/workflow/lib/installation-resource-kind.d.ts.map +1 -0
  187. package/dist/workflow/lib/installation-resource-kind.js +59 -0
  188. package/dist/workflow/lib/installation-resource-kind.js.map +1 -0
  189. package/dist/workflow/lib/schemas-loader.d.ts +4 -0
  190. package/dist/workflow/lib/schemas-loader.d.ts.map +1 -0
  191. package/dist/workflow/lib/schemas-loader.js +36 -0
  192. package/dist/workflow/lib/schemas-loader.js.map +1 -0
  193. package/dist/workflow/lib/serializer.d.ts +3 -0
  194. package/dist/workflow/lib/serializer.d.ts.map +1 -0
  195. package/dist/workflow/lib/serializer.js +15 -0
  196. package/dist/workflow/lib/serializer.js.map +1 -0
  197. package/dist/workflow/lib/types.d.ts +179 -0
  198. package/dist/workflow/lib/types.d.ts.map +1 -0
  199. package/dist/workflow/lib/types.js +3 -0
  200. package/dist/workflow/lib/types.js.map +1 -0
  201. package/dist/workflow/lib/validate.d.ts +8 -0
  202. package/dist/workflow/lib/validate.d.ts.map +1 -0
  203. package/dist/workflow/lib/validate.js +119 -0
  204. package/dist/workflow/lib/validate.js.map +1 -0
  205. package/dist/workspace-manifest/index.d.ts +6 -0
  206. package/dist/workspace-manifest/index.d.ts.map +1 -0
  207. package/dist/workspace-manifest/index.js +22 -0
  208. package/dist/workspace-manifest/index.js.map +1 -0
  209. package/dist/workspace-manifest/lib/compile.d.ts +8 -0
  210. package/dist/workspace-manifest/lib/compile.d.ts.map +1 -0
  211. package/dist/workspace-manifest/lib/compile.js +439 -0
  212. package/dist/workspace-manifest/lib/compile.js.map +1 -0
  213. package/dist/workspace-manifest/lib/interpolate.d.ts +12 -0
  214. package/dist/workspace-manifest/lib/interpolate.d.ts.map +1 -0
  215. package/dist/workspace-manifest/lib/interpolate.js +81 -0
  216. package/dist/workspace-manifest/lib/interpolate.js.map +1 -0
  217. package/dist/workspace-manifest/lib/resolve-extends.d.ts +10 -0
  218. package/dist/workspace-manifest/lib/resolve-extends.d.ts.map +1 -0
  219. package/dist/workspace-manifest/lib/resolve-extends.js +108 -0
  220. package/dist/workspace-manifest/lib/resolve-extends.js.map +1 -0
  221. package/dist/workspace-manifest/lib/schema.d.ts +710 -0
  222. package/dist/workspace-manifest/lib/schema.d.ts.map +1 -0
  223. package/dist/workspace-manifest/lib/schema.js +355 -0
  224. package/dist/workspace-manifest/lib/schema.js.map +1 -0
  225. package/dist/workspace-manifest/lib/types.d.ts +153 -0
  226. package/dist/workspace-manifest/lib/types.d.ts.map +1 -0
  227. package/dist/workspace-manifest/lib/types.js +10 -0
  228. package/dist/workspace-manifest/lib/types.js.map +1 -0
  229. package/package.json +79 -0
  230. package/schema/action.schema.json +181 -0
  231. package/schema/reusable-workflow.schema.json +46 -0
  232. package/schema/workflow.schema.json +373 -0
  233. package/src/deliverable-spec/index.ts +19 -0
  234. package/src/deliverable-spec/lib/schema.ts +248 -0
  235. package/src/deliverable-spec/lib/types.ts +26 -0
  236. package/src/payload-codec/index.ts +40 -0
  237. package/src/payload-codec/lib/blob-store.ts +0 -0
  238. package/src/payload-codec/lib/codec-context.ts +38 -0
  239. package/src/payload-codec/lib/codec.ts +593 -0
  240. package/src/payload-codec/lib/enums.ts +58 -0
  241. package/src/payload-codec/lib/errors.ts +54 -0
  242. package/src/payload-codec/lib/http-blob-store.ts +257 -0
  243. package/src/payload-codec/lib/lru-cache.ts +81 -0
  244. package/src/workflow/index.ts +98 -0
  245. package/src/workflow/lib/action-input-validator.ts +160 -0
  246. package/src/workflow/lib/compiler/action-shape.ts +71 -0
  247. package/src/workflow/lib/compiler/canonical-json.ts +53 -0
  248. package/src/workflow/lib/compiler/compile.ts +1518 -0
  249. package/src/workflow/lib/compiler/concurrency.ts +223 -0
  250. package/src/workflow/lib/compiler/dag.ts +108 -0
  251. package/src/workflow/lib/compiler/index.ts +10 -0
  252. package/src/workflow/lib/compiler/inputs.ts +199 -0
  253. package/src/workflow/lib/compiler/installation-resource-validator.ts +114 -0
  254. package/src/workflow/lib/compiler/manifest-source.ts +176 -0
  255. package/src/workflow/lib/compiler/matrix.ts +135 -0
  256. package/src/workflow/lib/compiler/mount-plan.ts +202 -0
  257. package/src/workflow/lib/compiler/payload-reach-in.ts +497 -0
  258. package/src/workflow/lib/compiler/permissions.ts +64 -0
  259. package/src/workflow/lib/compiler/retry-timeout.ts +105 -0
  260. package/src/workflow/lib/compiler/review-step.ts +517 -0
  261. package/src/workflow/lib/compiler/types.ts +170 -0
  262. package/src/workflow/lib/compiler/variable-requirements.ts +208 -0
  263. package/src/workflow/lib/deliverable-spec-keys.ts +109 -0
  264. package/src/workflow/lib/dispatch-inputs/index.ts +160 -0
  265. package/src/workflow/lib/dispatch-inputs/to-json-schema.ts +60 -0
  266. package/src/workflow/lib/duration.ts +48 -0
  267. package/src/workflow/lib/errors.ts +37 -0
  268. package/src/workflow/lib/expression/ast.ts +108 -0
  269. package/src/workflow/lib/expression/context.ts +148 -0
  270. package/src/workflow/lib/expression/evaluator.ts +492 -0
  271. package/src/workflow/lib/expression/index.ts +28 -0
  272. package/src/workflow/lib/expression/interpolation.ts +84 -0
  273. package/src/workflow/lib/expression/parser.ts +264 -0
  274. package/src/workflow/lib/expression/template.ts +117 -0
  275. package/src/workflow/lib/expression/tokenizer.ts +200 -0
  276. package/src/workflow/lib/expression/tokens.ts +30 -0
  277. package/src/workflow/lib/expression/walk-artifact-refs.ts +232 -0
  278. package/src/workflow/lib/installation-resource-kind.ts +107 -0
  279. package/src/workflow/lib/schemas-loader.ts +64 -0
  280. package/src/workflow/lib/serializer.ts +30 -0
  281. package/src/workflow/lib/types.ts +361 -0
  282. package/src/workflow/lib/validate.ts +199 -0
  283. package/src/workspace-manifest/index.ts +27 -0
  284. package/src/workspace-manifest/lib/compile.ts +608 -0
  285. package/src/workspace-manifest/lib/interpolate.ts +140 -0
  286. package/src/workspace-manifest/lib/resolve-extends.ts +260 -0
  287. package/src/workspace-manifest/lib/schema.ts +612 -0
  288. package/src/workspace-manifest/lib/types.ts +392 -0
@@ -0,0 +1,140 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // ── Variable interpolation engine ──
3
+ //
4
+ // Resolves `${input.x}` and `${input.x | default: 'foo'}` references in
5
+ // manifest values. Closed grammar — no arbitrary expressions, no
6
+ // arithmetic, no function calls. The Monaco editor renders matching
7
+ // inputs from `spec.inputs.*` for autocomplete; anything else is a
8
+ // validation error.
9
+ //
10
+ // Restricted vs the workflow DSL's full expression engine because
11
+ // manifests are simpler conceptually: they're parameterized templates,
12
+ // not control flow. Keeping the interpolator narrow lets us reason
13
+ // about manifest correctness at compile time.
14
+ // ═══════════════════════════════════════════════════════════════════════════
15
+
16
+ const INTERPOLATION_RE = /\$\{\s*([^}]+?)\s*\}/g;
17
+ const INPUT_PATH_RE = /^input\.([a-zA-Z_][a-zA-Z0-9_]*)(?:\s*\|\s*default:\s*(.+))?$/;
18
+
19
+ export interface InterpolationContext {
20
+ readonly inputs: Readonly<Record<string, unknown>>;
21
+ }
22
+
23
+ export interface InterpolationIssue {
24
+ readonly path: string;
25
+ readonly message: string;
26
+ }
27
+
28
+ /**
29
+ * Walks `value` recursively, resolving every `${input.x}` reference
30
+ * against `ctx.inputs`. Returns a fully-resolved deep clone with no
31
+ * remaining interpolation tokens.
32
+ *
33
+ * Issues are accumulated and returned alongside the partially-resolved
34
+ * tree so the compiler can report all problems together. When `issues`
35
+ * is non-empty, the returned tree may contain unresolved tokens — the
36
+ * caller MUST check `issues.length` before treating the result as
37
+ * authoritative.
38
+ */
39
+ export function interpolate(
40
+ value: unknown,
41
+ ctx: InterpolationContext,
42
+ basePath = '$',
43
+ ): { readonly resolved: unknown; readonly issues: readonly InterpolationIssue[] } {
44
+ const issues: InterpolationIssue[] = [];
45
+ const resolved = walk(value, ctx, basePath, issues);
46
+ return { resolved, issues };
47
+ }
48
+
49
+ function walk(
50
+ value: unknown,
51
+ ctx: InterpolationContext,
52
+ path: string,
53
+ issues: InterpolationIssue[],
54
+ ): unknown {
55
+ if (typeof value === 'string') return interpolateString(value, ctx, path, issues);
56
+ if (Array.isArray(value)) {
57
+ return value.map((item, idx) => walk(item, ctx, `${path}[${idx}]`, issues));
58
+ }
59
+ if (value !== null && typeof value === 'object') {
60
+ const out: Record<string, unknown> = {};
61
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
62
+ out[key] = walk(child, ctx, `${path}.${key}`, issues);
63
+ }
64
+ return out;
65
+ }
66
+ return value;
67
+ }
68
+
69
+ function interpolateString(
70
+ raw: string,
71
+ ctx: InterpolationContext,
72
+ path: string,
73
+ issues: InterpolationIssue[],
74
+ ): string | unknown {
75
+ // Whole-string single-token: preserve original type (e.g.
76
+ // `${input.kbSpaceIds}` resolves to an array, not its toString).
77
+ const wholeMatch = /^\$\{\s*([^}]+?)\s*\}$/.exec(raw);
78
+ if (wholeMatch) {
79
+ return resolveToken(wholeMatch[1]!, ctx, path, issues);
80
+ }
81
+
82
+ return raw.replace(INTERPOLATION_RE, (_full, token: string) => {
83
+ const resolved = resolveToken(token, ctx, path, issues);
84
+ if (resolved === undefined || resolved === null) return '';
85
+ return Array.isArray(resolved) ? resolved.join(',') : String(resolved);
86
+ });
87
+ }
88
+
89
+ function resolveToken(
90
+ token: string,
91
+ ctx: InterpolationContext,
92
+ path: string,
93
+ issues: InterpolationIssue[],
94
+ ): unknown {
95
+ const m = INPUT_PATH_RE.exec(token);
96
+ if (!m) {
97
+ issues.push({
98
+ path,
99
+ message: `unsupported interpolation token '\${${token}}' — only \${input.<name>} and \${input.<name> | default: <literal>} are allowed`,
100
+ });
101
+ return undefined;
102
+ }
103
+ const inputName = m[1]!;
104
+ const defaultLiteral = m[2];
105
+ const present = Object.prototype.hasOwnProperty.call(ctx.inputs, inputName);
106
+ if (present) {
107
+ return ctx.inputs[inputName];
108
+ }
109
+ if (defaultLiteral !== undefined) {
110
+ return parseLiteral(defaultLiteral, path, issues);
111
+ }
112
+ issues.push({
113
+ path,
114
+ message: `unresolved input '\${input.${inputName}}' — declare it in spec.inputs or supply a default with '| default: ...'`,
115
+ });
116
+ return undefined;
117
+ }
118
+
119
+ function parseLiteral(
120
+ raw: string,
121
+ path: string,
122
+ issues: InterpolationIssue[],
123
+ ): unknown {
124
+ const trimmed = raw.trim();
125
+ if (
126
+ (trimmed.startsWith("'") && trimmed.endsWith("'")) ||
127
+ (trimmed.startsWith('"') && trimmed.endsWith('"'))
128
+ ) {
129
+ return trimmed.slice(1, -1);
130
+ }
131
+ if (trimmed === 'true') return true;
132
+ if (trimmed === 'false') return false;
133
+ const num = Number(trimmed);
134
+ if (!Number.isNaN(num) && trimmed !== '') return num;
135
+ issues.push({
136
+ path,
137
+ message: `default literal '${raw}' is not a string ('foo'/"foo"), boolean, or number`,
138
+ });
139
+ return undefined;
140
+ }
@@ -0,0 +1,260 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // ── WorkspaceManifest `extends:` resolver ──
3
+ //
4
+ // Section 4.4 of the engineering plan. Provides a deterministic merge
5
+ // primitive plus a thin orchestrator the composer / preview endpoint /
6
+ // llm-registry calls to materialize the effective manifest.
7
+ //
8
+ // Design constraints:
9
+ // • Single-level extends only. The base MUST NOT carry its own
10
+ // `extends:` — keeps semantics deterministic, avoids resolution
11
+ // cycles, and lets every consumer reason about the merged shape
12
+ // without recursion. Multi-level inheritance can ship later if
13
+ // real demand surfaces.
14
+ // • Caller injects the `lookupBase(ref)` function. The kernel
15
+ // package stays runtime-dep-free; the llm-registry-api composition
16
+ // seeder and the composer each provide their own lookup
17
+ // implementation.
18
+ // • Failures are typed errors, never silent fallbacks. Missing ref,
19
+ // base-with-extends, or wrong shape — all surface as
20
+ // {@link ManifestExtendsError}.
21
+ // ═══════════════════════════════════════════════════════════════════════════
22
+
23
+ import type { ToolSelectionEntry } from '@xemahq/kernel-contracts/mcp-tool';
24
+
25
+ import type {
26
+ ManifestCredential,
27
+ ManifestEnvVar,
28
+ ManifestInputDeclaration,
29
+ ManifestMountDeclaration,
30
+ ManifestPermissions,
31
+ ManifestPersistence,
32
+ ManifestOutputSurface,
33
+ ManifestSeedFile,
34
+ ManifestSkillRef,
35
+ ManifestWorkingFile,
36
+ WorkspaceManifest,
37
+ WorkspaceManifestSpec,
38
+ } from './types';
39
+
40
+ /**
41
+ * Caller-supplied lookup function. Returns the parsed manifest for the
42
+ * given `xema://manifest/<slug>@<version>` reference, or `null` when no
43
+ * row exists at any scope tier. Implementations:
44
+ * • The composition seeder: in-memory map built over every manifest
45
+ * enumerated across the biome tree.
46
+ * • Tests: in-memory map.
47
+ */
48
+ export type ManifestBaseLookup = (
49
+ ref: string,
50
+ ) => Promise<WorkspaceManifest | null>;
51
+
52
+ export class ManifestExtendsError extends Error {
53
+ constructor(
54
+ message: string,
55
+ readonly code:
56
+ | 'BASE_NOT_FOUND'
57
+ | 'BASE_HAS_EXTENDS'
58
+ | 'BASE_MALFORMED'
59
+ | 'INVALID_REF',
60
+ readonly ref: string,
61
+ ) {
62
+ super(message);
63
+ this.name = 'ManifestExtendsError';
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Resolve a manifest's `extends:` directive. Returns the manifest
69
+ * unchanged when no `extends:` was authored. Otherwise fetches the
70
+ * base via `lookupBase` and merges; the returned manifest carries the
71
+ * effective shape with `extends` cleared (the merge is one-shot).
72
+ */
73
+ export async function resolveManifestExtends(
74
+ authored: WorkspaceManifest,
75
+ lookupBase: ManifestBaseLookup,
76
+ ): Promise<WorkspaceManifest> {
77
+ if (!authored.extends) return authored;
78
+ const ref = authored.extends;
79
+ const base = await lookupBase(ref);
80
+ if (base === null) {
81
+ throw new ManifestExtendsError(
82
+ `manifest base '${ref}' not found at any scope tier (project/org/biome/system)`,
83
+ 'BASE_NOT_FOUND',
84
+ ref,
85
+ );
86
+ }
87
+ if (base.extends) {
88
+ throw new ManifestExtendsError(
89
+ `manifest base '${ref}' itself carries \`extends: ${base.extends}\`; multi-level inheritance is not supported (single-level only)`,
90
+ 'BASE_HAS_EXTENDS',
91
+ ref,
92
+ );
93
+ }
94
+ if (base.kind !== 'WorkspaceManifest' || base.apiVersion !== 'xema.dev/workspace/v1') {
95
+ throw new ManifestExtendsError(
96
+ `manifest base '${ref}' has unexpected envelope (apiVersion=${String(base.apiVersion)}, kind=${String(base.kind)})`,
97
+ 'BASE_MALFORMED',
98
+ ref,
99
+ );
100
+ }
101
+ const mergedSpec = mergeSpecs(base.spec, authored.spec);
102
+ return {
103
+ apiVersion: authored.apiVersion,
104
+ kind: authored.kind,
105
+ metadata: authored.metadata,
106
+ spec: mergedSpec,
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Deterministic merge between a base spec and a child spec.
112
+ *
113
+ * Rules (closed set, no surprises):
114
+ * • `inputs` — key-union; child's declaration wins on conflict.
115
+ * • `mounts` — key-union; child's declaration wins on conflict.
116
+ * • `seedFiles` — concatenated (base first); duplicates by
117
+ * `path` keep the LAST occurrence (child wins).
118
+ * • `env` — concatenated (base first); duplicates by
119
+ * `name` keep the LAST occurrence (child wins).
120
+ * • `skills` — concatenated (base first); dedup by `slug`,
121
+ * child wins on version pin conflict.
122
+ * • `toolSelection` — concatenated (base first); dedup by
123
+ * `(providerKind, resourceId, toolName?)`,
124
+ * child wins on conflict.
125
+ * • `credentials` — concatenated (base first); dedup by `name`,
126
+ * child wins on kind/sourceRef/required.
127
+ * • `permissions` — child fully replaces base when present;
128
+ * otherwise base flows through. Permissions
129
+ * are intentionally NOT unioned — narrowing
130
+ * authority on top of a base must be explicit
131
+ * and total, never a piecewise diff.
132
+ * • `persistence` — child fully replaces base when present.
133
+ * Persistence paths are a snapshot contract;
134
+ * unioning silently would leak state across
135
+ * unrelated profiles.
136
+ * • `outputSurface` — child fully replaces base when present.
137
+ * • `workingFiles` — concatenated (base first); dedup by `slug`,
138
+ * child wins on conflict. Lets the base seed
139
+ * shared working files while letting a child
140
+ * add document-buddy bindings without losing
141
+ * them to a silent drop.
142
+ * • `agent` — child's block fully replaces the base's.
143
+ * The agent identity is not inheritable.
144
+ *
145
+ * Empty / missing optional fields on either side are tolerated; the
146
+ * merged spec carries them only when at least one side did.
147
+ */
148
+ export function mergeSpecs(
149
+ base: WorkspaceManifestSpec,
150
+ child: WorkspaceManifestSpec,
151
+ ): WorkspaceManifestSpec {
152
+ const merged: WorkspaceManifestSpec = {
153
+ agent: child.agent,
154
+ };
155
+
156
+ const inputs = mergeRecord<ManifestInputDeclaration>(base.inputs, child.inputs);
157
+ if (inputs) (merged as { inputs?: typeof inputs }).inputs = inputs;
158
+
159
+ const mounts = mergeRecord<ManifestMountDeclaration>(base.mounts, child.mounts);
160
+ if (mounts) (merged as { mounts?: typeof mounts }).mounts = mounts;
161
+
162
+ const seedFiles = mergeUniqueByKey<ManifestSeedFile>(
163
+ base.seedFiles,
164
+ child.seedFiles,
165
+ (sf) => sf.path,
166
+ );
167
+ if (seedFiles) (merged as { seedFiles?: typeof seedFiles }).seedFiles = seedFiles;
168
+
169
+ const env = mergeUniqueByKey<ManifestEnvVar>(
170
+ base.env,
171
+ child.env,
172
+ (e) => e.name,
173
+ );
174
+ if (env) (merged as { env?: typeof env }).env = env;
175
+
176
+ const skills = mergeUniqueByKey<ManifestSkillRef>(
177
+ base.skills,
178
+ child.skills,
179
+ (s) => s.slug,
180
+ );
181
+ if (skills) (merged as { skills?: typeof skills }).skills = skills;
182
+
183
+ const toolSelection = mergeUniqueByKey<ToolSelectionEntry>(
184
+ base.toolSelection,
185
+ child.toolSelection,
186
+ toolSelectionEntryKey,
187
+ );
188
+ if (toolSelection)
189
+ (merged as { toolSelection?: typeof toolSelection }).toolSelection = toolSelection;
190
+
191
+ const credentials = mergeUniqueByKey<ManifestCredential>(
192
+ base.credentials,
193
+ child.credentials,
194
+ (c) => c.name,
195
+ );
196
+ if (credentials)
197
+ (merged as { credentials?: typeof credentials }).credentials = credentials;
198
+
199
+ const permissions: ManifestPermissions | undefined =
200
+ child.permissions ?? base.permissions;
201
+ if (permissions)
202
+ (merged as { permissions?: ManifestPermissions }).permissions = permissions;
203
+
204
+ const persistence: ManifestPersistence | undefined =
205
+ child.persistence ?? base.persistence;
206
+ if (persistence)
207
+ (merged as { persistence?: ManifestPersistence }).persistence = persistence;
208
+
209
+ const outputSurface: ManifestOutputSurface | undefined =
210
+ child.outputSurface ?? base.outputSurface;
211
+ if (outputSurface)
212
+ (merged as { outputSurface?: ManifestOutputSurface }).outputSurface =
213
+ outputSurface;
214
+
215
+ const workingFiles = mergeUniqueByKey<ManifestWorkingFile>(
216
+ base.workingFiles,
217
+ child.workingFiles,
218
+ (wf) => wf.slug,
219
+ );
220
+ if (workingFiles)
221
+ (merged as { workingFiles?: typeof workingFiles }).workingFiles =
222
+ workingFiles;
223
+
224
+ return merged;
225
+ }
226
+
227
+ function mergeRecord<T>(
228
+ base: Record<string, T> | undefined,
229
+ child: Record<string, T> | undefined,
230
+ ): Record<string, T> | undefined {
231
+ if (!base && !child) return undefined;
232
+ return { ...(base ?? {}), ...(child ?? {}) };
233
+ }
234
+
235
+ function toolSelectionEntryKey(entry: ToolSelectionEntry): string {
236
+ return entry.kind === 'tool'
237
+ ? `tool:${entry.providerKind}:${entry.resourceId}:${entry.toolName}`
238
+ : `provider:${entry.providerKind}:${entry.resourceId}`;
239
+ }
240
+
241
+ function mergeUniqueByKey<T>(
242
+ base: readonly T[] | undefined,
243
+ child: readonly T[] | undefined,
244
+ keyOf: (item: T) => string,
245
+ ): readonly T[] | undefined {
246
+ if (!base && !child) return undefined;
247
+ const out: T[] = [];
248
+ const indexByKey = new Map<string, number>();
249
+ for (const item of [...(base ?? []), ...(child ?? [])]) {
250
+ const key = keyOf(item);
251
+ const existing = indexByKey.get(key);
252
+ if (existing === undefined) {
253
+ indexByKey.set(key, out.length);
254
+ out.push(item);
255
+ } else {
256
+ out[existing] = item;
257
+ }
258
+ }
259
+ return out;
260
+ }