@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,257 @@
1
+ import type { Response as UndiciResponse } from 'undici-types';
2
+
3
+ import { BlobStoreKind } from './enums';
4
+ import { PayloadCodecError, PayloadCodecErrorCode } from './errors';
5
+ import {
6
+ type BlobRef,
7
+ type BlobStore,
8
+ type StoredBlob,
9
+ sha256Hex,
10
+ } from './blob-store';
11
+
12
+ export interface HttpBlobStoreOptions {
13
+ /** Base URL of artifact-store-api (e.g. http://artifact-store-api.xema-prod:80). */
14
+ readonly baseUrl: string;
15
+ /**
16
+ * Async provider for the service identity JWT. The codec mints new
17
+ * requests on every put/get so the token can expire without a restart.
18
+ */
19
+ readonly tokenProvider: () => Promise<string>;
20
+ /** Optional correlation id; appended to `X-Correlation-Id`. */
21
+ readonly correlationIdProvider?: () => string | null;
22
+ /** Absolute timeout for an individual put/get request (ms). */
23
+ readonly requestTimeoutMs?: number;
24
+ }
25
+
26
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
27
+ const PUT_PATH = '/blobs';
28
+ const GET_PATH_PREFIX = '/blobs/';
29
+
30
+ /**
31
+ * BlobStore backed by `artifact-store-api`. Contract the service exposes:
32
+ *
33
+ * POST /blobs Content-Type: any
34
+ * headers: Authorization: Bearer <jwt>; X-Org-Id: <tenant>
35
+ * body: raw bytes
36
+ * response: { data: { sha256, sizeBytes, contentType, createdAt } }
37
+ *
38
+ * GET /blobs/:sha256
39
+ * headers: Authorization: Bearer <jwt>; X-Org-Id: <tenant>
40
+ * response: raw bytes, Content-Type header from the upload
41
+ *
42
+ * `X-Org-Id` is required on every call — the artifact-store-api scopes
43
+ * blob rows by `(orgId, sha256)`, so reads/writes must declare a tenant.
44
+ * The codec passes the orgId at the call site (encode reads it from the
45
+ * configured orgIdProvider; decode reads it from the spill envelope).
46
+ *
47
+ * Authentication is service-to-service — the caller provides a bearer
48
+ * token (minted via `identity-api`). Idempotent: the server dedupes by
49
+ * `(orgId, sha256)`.
50
+ */
51
+ export class HttpBlobStore implements BlobStore {
52
+ readonly kind = BlobStoreKind.ARTIFACT_STORE;
53
+
54
+ constructor(private readonly options: HttpBlobStoreOptions) {
55
+ if (!options.baseUrl) {
56
+ throw new Error('HttpBlobStore: baseUrl is required.');
57
+ }
58
+ }
59
+
60
+ async put(
61
+ bytes: Uint8Array,
62
+ contentType: string,
63
+ orgId: string,
64
+ ): Promise<StoredBlob> {
65
+ assertOrgId(orgId);
66
+ const token = await this.options.tokenProvider();
67
+ const controller = new AbortController();
68
+ const timer = setTimeout(
69
+ () => controller.abort(),
70
+ this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
71
+ );
72
+ try {
73
+ // Node's undici fetch accepts Uint8Array directly; Buffer is a
74
+ // Uint8Array subclass AND a documented BodyInit in @types/node's
75
+ // ambient fetch types, so wrap once to keep TS happy without `any`.
76
+ const response = (await fetch(this.resolveUrl(PUT_PATH), {
77
+ method: 'POST',
78
+ headers: this.buildHeaders(token, contentType, orgId),
79
+ body: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength),
80
+ signal: controller.signal,
81
+ })) as unknown as UndiciResponse;
82
+ if (!response.ok) {
83
+ throw new PayloadCodecError(
84
+ PayloadCodecErrorCode.BLOB_PUT_FAILED,
85
+ `artifact-store-api POST /blobs failed: ${response.status} ${response.statusText}`,
86
+ { status: response.status, orgId },
87
+ );
88
+ }
89
+ const parsed = (await response.json()) as {
90
+ data?: {
91
+ sha256?: unknown;
92
+ sizeBytes?: unknown;
93
+ contentType?: unknown;
94
+ createdAt?: unknown;
95
+ uri?: unknown;
96
+ };
97
+ };
98
+ const data = parsed.data;
99
+ if (!data || typeof data.sha256 !== 'string') {
100
+ throw new PayloadCodecError(
101
+ PayloadCodecErrorCode.BLOB_PUT_FAILED,
102
+ 'artifact-store-api POST /blobs: malformed response envelope.',
103
+ { received: parsed },
104
+ );
105
+ }
106
+ const expectedSha = sha256Hex(bytes);
107
+ if (data.sha256 !== expectedSha) {
108
+ throw new PayloadCodecError(
109
+ PayloadCodecErrorCode.SHA256_MISMATCH,
110
+ 'artifact-store-api returned a different sha256 than the client computed.',
111
+ { expected: expectedSha, received: data.sha256 },
112
+ );
113
+ }
114
+ // The current artifact-store BlobResponseDto omits `uri`; the (orgId,
115
+ // sha256) pair is canonical for retrieval, so we synthesize a
116
+ // stable handle for diagnostics.
117
+ const uri =
118
+ typeof data.uri === 'string' && data.uri.length > 0
119
+ ? data.uri
120
+ : `artifact-store://${orgId}/${data.sha256}`;
121
+ const sizeBytes =
122
+ typeof data.sizeBytes === 'number' ? data.sizeBytes : bytes.byteLength;
123
+ const contentTypeOut =
124
+ typeof data.contentType === 'string' ? data.contentType : contentType;
125
+ const createdAt =
126
+ typeof data.createdAt === 'string'
127
+ ? data.createdAt
128
+ : new Date().toISOString();
129
+ return {
130
+ store: this.kind,
131
+ uri,
132
+ sha256: data.sha256,
133
+ orgId,
134
+ sizeBytes,
135
+ contentType: contentTypeOut,
136
+ createdAt,
137
+ };
138
+ } finally {
139
+ clearTimeout(timer);
140
+ }
141
+ }
142
+
143
+ async get(ref: BlobRef): Promise<Uint8Array> {
144
+ if (ref.store !== this.kind) {
145
+ throw new PayloadCodecError(
146
+ PayloadCodecErrorCode.UNKNOWN_STORE_KIND,
147
+ `HttpBlobStore cannot read blobs from store '${ref.store}'.`,
148
+ { expected: this.kind, received: ref.store },
149
+ );
150
+ }
151
+ assertOrgId(ref.orgId);
152
+ const token = await this.options.tokenProvider();
153
+ const controller = new AbortController();
154
+ const timer = setTimeout(
155
+ () => controller.abort(),
156
+ this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
157
+ );
158
+ try {
159
+ const response = (await fetch(
160
+ this.resolveUrl(`${GET_PATH_PREFIX}${encodeURIComponent(ref.sha256)}`),
161
+ {
162
+ method: 'GET',
163
+ headers: this.buildHeaders(token, 'application/octet-stream', ref.orgId),
164
+ signal: controller.signal,
165
+ },
166
+ )) as unknown as UndiciResponse;
167
+ if (!response.ok) {
168
+ throw new PayloadCodecError(
169
+ PayloadCodecErrorCode.BLOB_GET_FAILED,
170
+ `artifact-store-api GET /blobs/${ref.sha256} failed: ${response.status} ${response.statusText}`,
171
+ { status: response.status, sha256: ref.sha256, orgId: ref.orgId },
172
+ );
173
+ }
174
+ const buffer = new Uint8Array(await response.arrayBuffer());
175
+ const actualSha = sha256Hex(buffer);
176
+ if (actualSha !== ref.sha256) {
177
+ throw new PayloadCodecError(
178
+ PayloadCodecErrorCode.SHA256_MISMATCH,
179
+ 'Blob fetched from artifact-store-api does not match the requested sha256.',
180
+ { expected: ref.sha256, actual: actualSha },
181
+ );
182
+ }
183
+ return buffer;
184
+ } finally {
185
+ clearTimeout(timer);
186
+ }
187
+ }
188
+
189
+ async delete(
190
+ ref: Pick<BlobRef, 'sha256' | 'orgId'>,
191
+ ): Promise<{ deleted: boolean }> {
192
+ assertOrgId(ref.orgId);
193
+ const token = await this.options.tokenProvider();
194
+ const controller = new AbortController();
195
+ const timer = setTimeout(
196
+ () => controller.abort(),
197
+ this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
198
+ );
199
+ try {
200
+ const response = (await fetch(
201
+ this.resolveUrl(`${GET_PATH_PREFIX}${encodeURIComponent(ref.sha256)}`),
202
+ {
203
+ method: 'DELETE',
204
+ headers: this.buildHeaders(token, 'application/octet-stream', ref.orgId),
205
+ signal: controller.signal,
206
+ },
207
+ )) as unknown as UndiciResponse;
208
+ if (response.status === 204) {
209
+ // artifact-store-api returns 204 whether or not the row existed
210
+ // (idempotent). We log the call site's perspective ("requested
211
+ // delete completed") rather than the row-existed dimension —
212
+ // the sweeper persists the actual deletion count from its own
213
+ // Postgres delete in the SnapshotPointer table.
214
+ return { deleted: true };
215
+ }
216
+ if (response.status === 404) {
217
+ // Defensive: some auth proxies surface 404 for cross-tenant
218
+ // misses. Treat as "already gone" so retries don't loop.
219
+ return { deleted: false };
220
+ }
221
+ throw new PayloadCodecError(
222
+ PayloadCodecErrorCode.BLOB_GET_FAILED,
223
+ `artifact-store-api DELETE /blobs/${ref.sha256} failed: ${response.status} ${response.statusText}`,
224
+ { status: response.status, sha256: ref.sha256, orgId: ref.orgId },
225
+ );
226
+ } finally {
227
+ clearTimeout(timer);
228
+ }
229
+ }
230
+
231
+ private resolveUrl(path: string): string {
232
+ const base = this.options.baseUrl.replace(/\/+$/, '');
233
+ return `${base}${path}`;
234
+ }
235
+
236
+ private buildHeaders(token: string, contentType: string, orgId: string): Headers {
237
+ const headers = new Headers({
238
+ Authorization: `Bearer ${token}`,
239
+ 'Content-Type': contentType,
240
+ 'X-Org-Id': orgId,
241
+ });
242
+ const correlationId = this.options.correlationIdProvider?.();
243
+ if (correlationId) {
244
+ headers.set('X-Correlation-Id', correlationId);
245
+ }
246
+ return headers;
247
+ }
248
+ }
249
+
250
+ function assertOrgId(orgId: string): void {
251
+ if (typeof orgId !== 'string' || orgId.length === 0) {
252
+ throw new PayloadCodecError(
253
+ PayloadCodecErrorCode.ORG_ID_REQUIRED,
254
+ 'HttpBlobStore operations require a non-empty orgId for X-Org-Id.',
255
+ );
256
+ }
257
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Tiny byte-capacity LRU cache keyed by string (sha256). Kept intentionally
3
+ * dependency-free — adding a full LRU lib for this alone would bloat the
4
+ * package. Map's insertion-order iteration gives us O(1) recency tracking:
5
+ * re-insert on access, evict from the front when over budget.
6
+ *
7
+ * Entries that exceed the cache's total capacity are refused outright
8
+ * (returned bytes bypass the cache). This avoids the anti-pattern where
9
+ * inserting a single huge value evicts everything else.
10
+ */
11
+ export class BytesLruCache {
12
+ private readonly entries = new Map<string, Uint8Array>();
13
+ private totalBytes = 0;
14
+
15
+ constructor(private readonly capacityBytes: number) {
16
+ if (!Number.isFinite(capacityBytes) || capacityBytes < 0) {
17
+ throw new Error(`BytesLruCache: capacityBytes must be a non-negative finite number, got ${capacityBytes}.`);
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Return cached bytes for `key`, or null if absent. Accessing an entry
23
+ * marks it most-recently-used (moves to the back of the Map).
24
+ */
25
+ get(key: string): Uint8Array | null {
26
+ const found = this.entries.get(key);
27
+ if (found === undefined) {
28
+ return null;
29
+ }
30
+ // Refresh LRU position.
31
+ this.entries.delete(key);
32
+ this.entries.set(key, found);
33
+ return found;
34
+ }
35
+
36
+ /**
37
+ * Insert `value` under `key`. If `value` is larger than the total
38
+ * capacity, the cache skips insertion and the caller receives the
39
+ * untouched bytes. Evicts oldest entries until under capacity.
40
+ */
41
+ put(key: string, value: Uint8Array): void {
42
+ if (value.byteLength > this.capacityBytes) {
43
+ return; // Too big; don't thrash the cache.
44
+ }
45
+ if (this.entries.has(key)) {
46
+ const prev = this.entries.get(key)!;
47
+ this.totalBytes -= prev.byteLength;
48
+ this.entries.delete(key);
49
+ }
50
+ this.entries.set(key, value);
51
+ this.totalBytes += value.byteLength;
52
+ while (this.totalBytes > this.capacityBytes) {
53
+ const oldest = this.entries.keys().next();
54
+ if (oldest.done === true) {
55
+ break;
56
+ }
57
+ const evicted = this.entries.get(oldest.value);
58
+ if (evicted === undefined) {
59
+ break;
60
+ }
61
+ this.totalBytes -= evicted.byteLength;
62
+ this.entries.delete(oldest.value);
63
+ }
64
+ }
65
+
66
+ /** Clears the cache. Used by tests. */
67
+ clear(): void {
68
+ this.entries.clear();
69
+ this.totalBytes = 0;
70
+ }
71
+
72
+ /** Current occupancy in bytes. Used by tests + instrumentation. */
73
+ get size(): number {
74
+ return this.totalBytes;
75
+ }
76
+
77
+ /** Current number of entries. Used by tests. */
78
+ get count(): number {
79
+ return this.entries.size;
80
+ }
81
+ }
@@ -0,0 +1,98 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // ── Xema Workflow DSL — Barrel Export ──
3
+ //
4
+ // Pure TypeScript package (no NestJS, no Temporal, no DB access). Exposes:
5
+ // - JSON Schema Draft 2020-12 validation for workflow / reusable-workflow
6
+ // / action documents.
7
+ // - A deterministic expression engine (no `eval`, closed grammar).
8
+ // - A semantic compiler that produces CompiledRun structures consumed by
9
+ // workflow-engine-api and workflow-runtime-worker.
10
+ //
11
+ // Consumers: `workflow-engine-api` (compile + seed), `workflow-runtime-worker`
12
+ // (evaluate `if`/outputs/matrix-from/mount-plan at dispatch), and CI scripts
13
+ // that validate authored YAML in `biomes/software-dev/workflow-config/`.
14
+ // ═══════════════════════════════════════════════════════════════════════════
15
+
16
+ export { WorkflowDslError, isWorkflowDslError } from './lib/errors';
17
+ export {
18
+ validateActionInputs,
19
+ type ActionInputValidationFailure,
20
+ type ActionInputValidationResult,
21
+ } from './lib/action-input-validator';
22
+ export {
23
+ InstallationResourceKind,
24
+ collectInstallationResourceHints,
25
+ type InstallationResourceFieldHint,
26
+ } from './lib/installation-resource-kind';
27
+ export type { InstallationCompileScope } from './lib/compiler/types';
28
+ export {
29
+ parseAndValidateActionYaml,
30
+ parseAndValidateReusableWorkflowYaml,
31
+ parseAndValidateWorkflowYaml,
32
+ validateActionManifest,
33
+ validateReusableWorkflowDocument,
34
+ validateWorkflowDocument,
35
+ } from './lib/validate';
36
+ export {
37
+ EMPTY_CONTEXT_SEEDS,
38
+ ExpressionFunction,
39
+ ExpressionNodeKind,
40
+ ExpressionRoot,
41
+ compileExpression,
42
+ evaluateExpression,
43
+ extractInterpolations,
44
+ isInterpolationWrapped,
45
+ stripInterpolation,
46
+ type ExpressionContext,
47
+ type ExpressionNode,
48
+ type NeedsEntry,
49
+ } from './lib/expression';
50
+ export { parseDurationMs } from './lib/duration';
51
+ export {
52
+ DispatchInputFieldType,
53
+ DispatchInputStringFormat,
54
+ extractDispatchInputDescriptors,
55
+ type DispatchInputDescriptor,
56
+ } from './lib/dispatch-inputs';
57
+ export { dispatchInputsToJsonSchema } from './lib/dispatch-inputs/to-json-schema';
58
+ export { serializeWorkflowDocument } from './lib/serializer';
59
+ export {
60
+ canonicalJsonSha256,
61
+ canonicalJsonStringify,
62
+ compileManifestSource,
63
+ compileWorkflow,
64
+ getWorkspaceManifestContract,
65
+ isAgentShapedAction,
66
+ type CompileInput,
67
+ type ResolvedAgent,
68
+ type ResolvedDeliverableSpec,
69
+ type ResolvedRef,
70
+ } from './lib/compiler';
71
+ export {
72
+ extractZodTopLevelObjectKeys,
73
+ extractJsonSchemaTopLevelKeys,
74
+ } from './lib/deliverable-spec-keys';
75
+ export type {
76
+ ActionContract,
77
+ ActionManifest,
78
+ ActionManifestMetadata,
79
+ ActionManifestRetryDefaults,
80
+ ActionManifestSpec,
81
+ ActionManifestTimeoutDefaults,
82
+ WorkspaceManifestActionContract,
83
+ ScheduleDeclaration,
84
+ WebhookDeclaration,
85
+ WorkflowCallDeclaration,
86
+ WorkflowConcurrencyDeclaration,
87
+ WorkflowDefaults,
88
+ WorkflowDispatchDeclaration,
89
+ WorkflowDocument,
90
+ WorkflowDynamicStrategyDeclaration,
91
+ WorkflowInputDeclaration,
92
+ WorkflowJobDeclaration,
93
+ WorkflowMetadata,
94
+ WorkflowPermissions,
95
+ WorkflowRetryDeclaration,
96
+ WorkflowStrategyDeclaration,
97
+ WorkflowTriggerDeclarations,
98
+ } from './lib/types';
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Runtime validator for an action's `with:` payload against the JSON Schema
3
+ * declared in its manifest's `spec.inputs`.
4
+ *
5
+ * The DSL compiler pins `spec.inputs` into `ActionRef.inputsSchema` so the
6
+ * worker validates against the schema that was in effect when the run was
7
+ * compiled — not whatever's currently published. The worker calls
8
+ * {@link validateActionInputs} from the activity-registry wrapper just before
9
+ * dispatching the underlying activity; failures throw fast with a precise
10
+ * `inputs.<path>: <message>` pointer instead of letting bad payloads ride
11
+ * through to the downstream service.
12
+ *
13
+ * Implementation choices:
14
+ * - A dedicated Ajv instance (separate from the strict one used to validate
15
+ * manifests themselves) so user-authored schemas with looser conventions
16
+ * still compile. We turn off `strict` because action authors aren't
17
+ * schema-spec lawyers — `additionalProperties: false` plus `required`
18
+ * already catches typos.
19
+ * - Validators are memoized by schema reference identity. CompiledRun
20
+ * schemas are frozen objects whose identity is stable for the lifetime of
21
+ * the worker process, so a `WeakMap` cache means each schema compiles at
22
+ * most once per worker — even across concurrent dispatches.
23
+ * - We refuse to compile an empty / non-object schema. If a manifest didn't
24
+ * declare `inputs:`, the compiler emits `inputsSchema: null` and the
25
+ * caller skips validation entirely.
26
+ */
27
+ import Ajv2020, { type ErrorObject, type ValidateFunction } from 'ajv/dist/2020';
28
+ import addFormats from 'ajv-formats';
29
+
30
+ const ajv = new Ajv2020({
31
+ // Author-friendly: action manifests use plain JSON Schema, not Ajv-strict
32
+ // dialect. We still get type-safety from `additionalProperties: false`
33
+ // (every action schema declares it) plus `required:`.
34
+ strict: false,
35
+ allErrors: true,
36
+ useDefaults: false,
37
+ validateFormats: true,
38
+ });
39
+ addFormats(ajv);
40
+
41
+ /**
42
+ * Custom JSON Schema keyword `x-installation-resource` — marks a string
43
+ * field as referencing a resource bound to the calling biome
44
+ * installation. The compiler's installation-resource validator
45
+ * (workflow-engine `CompilerService`) reads the keyword + queries the
46
+ * installation's bound resources of the declared `kind`; if the value
47
+ * isn't in the bound set, the dispatch fails BEFORE the workflow
48
+ * starts.
49
+ *
50
+ * At the worker layer (here, AJV runtime) the keyword is a no-op
51
+ * accepted metadata — the compiler is the authoritative gate and runs
52
+ * with installation scope; by the time the worker validates `with:`
53
+ * the compile-time check has already confirmed the reference is bound.
54
+ *
55
+ * Declaration shape on an action manifest field:
56
+ * walletId:
57
+ * type: string
58
+ * x-installation-resource: { kind: 'wallet' }
59
+ *
60
+ * Closed `kind` set lives in `installation-resource-kind.ts` to keep
61
+ * the wire stable across the SDK, the engine, and biome-host-api.
62
+ */
63
+ ajv.addKeyword({
64
+ keyword: 'x-installation-resource',
65
+ metaSchema: {
66
+ type: 'object',
67
+ additionalProperties: false,
68
+ required: ['kind'],
69
+ properties: {
70
+ kind: { type: 'string', enum: ['wallet', 'repo', 'project', 'channel', 'space'] },
71
+ },
72
+ },
73
+ });
74
+
75
+ const validatorCache = new WeakMap<
76
+ Readonly<Record<string, unknown>>,
77
+ ValidateFunction
78
+ >();
79
+
80
+ export interface ActionInputValidationFailure {
81
+ readonly path: string;
82
+ readonly message: string;
83
+ }
84
+
85
+ export interface ActionInputValidationResult {
86
+ readonly valid: boolean;
87
+ readonly failures: readonly ActionInputValidationFailure[];
88
+ }
89
+
90
+ /**
91
+ * Validate `value` against the JSON Schema attached to a compiled
92
+ * ActionRef. Returns `{ valid: true, failures: [] }` on success.
93
+ *
94
+ * On failure, returns up to N failures (Ajv `allErrors: true`) each with a
95
+ * dotted `inputs.<path>` pointer so callers can compose
96
+ * `<actionId>: inputs.slug must match pattern …`-style messages.
97
+ *
98
+ * Throws synchronously only if the schema itself is unparseable (a bug in
99
+ * the compiler or a malformed manifest that slipped past `ACTION_SCHEMA`).
100
+ */
101
+ export function validateActionInputs(
102
+ schema: Readonly<Record<string, unknown>>,
103
+ value: unknown,
104
+ ): ActionInputValidationResult {
105
+ const validate = compileOrGet(schema);
106
+ if (validate(value)) {
107
+ return { valid: true, failures: [] };
108
+ }
109
+ const errors = validate.errors ?? [];
110
+ return {
111
+ valid: false,
112
+ failures: errors.map(formatError),
113
+ };
114
+ }
115
+
116
+ function compileOrGet(
117
+ schema: Readonly<Record<string, unknown>>,
118
+ ): ValidateFunction {
119
+ const cached = validatorCache.get(schema);
120
+ if (cached) return cached;
121
+ const compiled = ajv.compile(schema);
122
+ validatorCache.set(schema, compiled);
123
+ return compiled;
124
+ }
125
+
126
+ /**
127
+ * Translate an Ajv error object into a deterministic
128
+ * `inputs.<path>: <message>` shape.
129
+ *
130
+ * `instancePath` is JSON-pointer syntax (`/slug`, `/labels/foo`); we
131
+ * convert to dotted form and prepend `inputs.` so the pointer matches the
132
+ * call-site phrase the workflow author authored (`with.slug`, `with.labels.foo`).
133
+ */
134
+ function formatError(err: ErrorObject): ActionInputValidationFailure {
135
+ const dotted = err.instancePath
136
+ .replace(/^\//, '')
137
+ .split('/')
138
+ .filter((seg) => seg.length > 0)
139
+ .join('.');
140
+ const path = dotted.length > 0 ? `inputs.${dotted}` : 'inputs';
141
+ // For `required` violations Ajv puts the missing key in `params.missingProperty`
142
+ // and emits `instancePath: ''` — splice the key into the path so the message
143
+ // points at the offending field, not at the parent.
144
+ if (err.keyword === 'required' && typeof err.params?.['missingProperty'] === 'string') {
145
+ const missing = err.params['missingProperty'] as string;
146
+ const subPath = path === 'inputs' ? `inputs.${missing}` : `${path}.${missing}`;
147
+ return { path: subPath, message: 'is required but missing' };
148
+ }
149
+ // additionalProperties: name the unexpected key in the path so the
150
+ // workflow author can see exactly which `with:` field the schema refused.
151
+ if (err.keyword === 'additionalProperties' && typeof err.params?.['additionalProperty'] === 'string') {
152
+ const extra = err.params['additionalProperty'] as string;
153
+ const subPath = path === 'inputs' ? `inputs.${extra}` : `${path}.${extra}`;
154
+ return { path: subPath, message: 'is not declared on this action — remove it from the with-block' };
155
+ }
156
+ return {
157
+ path,
158
+ message: err.message ?? `failed ${err.keyword} check`,
159
+ };
160
+ }
@@ -0,0 +1,71 @@
1
+ import type {
2
+ ActionManifest,
3
+ AgentCompositionActionContract,
4
+ WorkspaceManifestActionContract,
5
+ } from '../types';
6
+
7
+ /**
8
+ * Returns the action's `workspace-manifest@v1` contract entry when the
9
+ * manifest declares one explicitly. This is the source of truth for the
10
+ * manifest-source triplet check in `manifest-source.ts` — biomes ship
11
+ * an action by adding `consumes: [{ kind: workspace-manifest, version: v1, ... }]`
12
+ * to its manifest and inherit the manifest picker, the canvas
13
+ * Inspector, and the runtime resolver lane for free.
14
+ */
15
+ export function getWorkspaceManifestContract(
16
+ actionManifest: ActionManifest | null,
17
+ ): WorkspaceManifestActionContract | null {
18
+ if (!actionManifest) return null;
19
+ const contracts = actionManifest.spec.consumes;
20
+ if (!contracts || contracts.length === 0) return null;
21
+ for (const c of contracts) {
22
+ if (c.kind === 'workspace-manifest' && c.version === 'v1') {
23
+ return c;
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+
29
+ /**
30
+ * Returns true when an action manifest opts into the workspace-manifest
31
+ * contract (preferred, declarative) OR matches the legacy `inputs:`
32
+ * shape heuristic (both `compositionRef` and `mounts` as properties).
33
+ * The heuristic remains a fallback so action manifests authored before
34
+ * the explicit contract still compile — Phase 9 sweeps the first-party
35
+ * actions and adds the explicit `consumes` entry, after which the
36
+ * heuristic can be removed in a follow-up PR.
37
+ */
38
+ export function isAgentShapedAction(
39
+ actionManifest: ActionManifest | null,
40
+ ): actionManifest is ActionManifest {
41
+ if (!actionManifest) return false;
42
+ if (getWorkspaceManifestContract(actionManifest) !== null) return true;
43
+ const inputs = actionManifest.spec.inputs as
44
+ | { properties?: Readonly<Record<string, unknown>> }
45
+ | undefined;
46
+ const properties = inputs?.properties;
47
+ if (!properties) return false;
48
+ return 'compositionRef' in properties && 'mounts' in properties;
49
+ }
50
+
51
+ /**
52
+ * Returns the action's `agent-composition@v1` contract entry when the
53
+ * manifest declares one. An action that opts into this contract accepts
54
+ * `with.composition` — a `slug@version` reference to an Agent Composition
55
+ * in the llm-registry-api composition registry. The runtime resolves the
56
+ * composition at dispatch time; the composition is the source of truth
57
+ * for the step's agent + sub-agents + skill/tool selection.
58
+ */
59
+ export function getAgentCompositionContract(
60
+ actionManifest: ActionManifest | null,
61
+ ): AgentCompositionActionContract | null {
62
+ if (!actionManifest) return null;
63
+ const contracts = actionManifest.spec.consumes;
64
+ if (!contracts || contracts.length === 0) return null;
65
+ for (const c of contracts) {
66
+ if (c.kind === 'agent-composition' && c.version === 'v1') {
67
+ return c;
68
+ }
69
+ }
70
+ return null;
71
+ }