@shipfox/api-definitions 2.0.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 (401) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +215 -0
  5. package/LICENSE +21 -0
  6. package/dist/config.d.ts +10 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +35 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core/entities/index.d.ts +5 -0
  11. package/dist/core/entities/index.d.ts.map +1 -0
  12. package/dist/core/entities/index.js +6 -0
  13. package/dist/core/entities/index.js.map +1 -0
  14. package/dist/core/entities/integration-context.d.ts +11 -0
  15. package/dist/core/entities/integration-context.d.ts.map +1 -0
  16. package/dist/core/entities/integration-context.js +3 -0
  17. package/dist/core/entities/integration-context.js.map +1 -0
  18. package/dist/core/entities/sync-state.d.ts +19 -0
  19. package/dist/core/entities/sync-state.d.ts.map +1 -0
  20. package/dist/core/entities/sync-state.js +20 -0
  21. package/dist/core/entities/sync-state.js.map +1 -0
  22. package/dist/core/entities/workflow-definition.d.ts +35 -0
  23. package/dist/core/entities/workflow-definition.d.ts.map +1 -0
  24. package/dist/core/entities/workflow-definition.js +3 -0
  25. package/dist/core/entities/workflow-definition.js.map +1 -0
  26. package/dist/core/entities/workflow-model.d.ts +138 -0
  27. package/dist/core/entities/workflow-model.d.ts.map +1 -0
  28. package/dist/core/entities/workflow-model.js +3 -0
  29. package/dist/core/entities/workflow-model.js.map +1 -0
  30. package/dist/core/errors.d.ts +10 -0
  31. package/dist/core/errors.d.ts.map +1 -0
  32. package/dist/core/errors.js +14 -0
  33. package/dist/core/errors.js.map +1 -0
  34. package/dist/core/has-agent-step-integrations.d.ts +3 -0
  35. package/dist/core/has-agent-step-integrations.d.ts.map +1 -0
  36. package/dist/core/has-agent-step-integrations.js +5 -0
  37. package/dist/core/has-agent-step-integrations.js.map +1 -0
  38. package/dist/core/index.d.ts +9 -0
  39. package/dist/core/index.d.ts.map +1 -0
  40. package/dist/core/index.js +9 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/core/parse-definition.d.ts +9 -0
  43. package/dist/core/parse-definition.d.ts.map +1 -0
  44. package/dist/core/parse-definition.js +17 -0
  45. package/dist/core/parse-definition.js.map +1 -0
  46. package/dist/core/sync-definitions.d.ts +44 -0
  47. package/dist/core/sync-definitions.d.ts.map +1 -0
  48. package/dist/core/sync-definitions.js +140 -0
  49. package/dist/core/sync-definitions.js.map +1 -0
  50. package/dist/core/validate-definition.d.ts +20 -0
  51. package/dist/core/validate-definition.d.ts.map +1 -0
  52. package/dist/core/validate-definition.js +61 -0
  53. package/dist/core/validate-definition.js.map +1 -0
  54. package/dist/core/workflow-model/constants.d.ts +2 -0
  55. package/dist/core/workflow-model/constants.d.ts.map +1 -0
  56. package/dist/core/workflow-model/constants.js +3 -0
  57. package/dist/core/workflow-model/constants.js.map +1 -0
  58. package/dist/core/workflow-model/cron-trigger.d.ts +16 -0
  59. package/dist/core/workflow-model/cron-trigger.d.ts.map +1 -0
  60. package/dist/core/workflow-model/cron-trigger.js +85 -0
  61. package/dist/core/workflow-model/cron-trigger.js.map +1 -0
  62. package/dist/core/workflow-model/index.d.ts +4 -0
  63. package/dist/core/workflow-model/index.d.ts.map +1 -0
  64. package/dist/core/workflow-model/index.js +5 -0
  65. package/dist/core/workflow-model/index.js.map +1 -0
  66. package/dist/core/workflow-model/invalid-workflow-model-error.d.ts +15 -0
  67. package/dist/core/workflow-model/invalid-workflow-model-error.d.ts.map +1 -0
  68. package/dist/core/workflow-model/invalid-workflow-model-error.js +9 -0
  69. package/dist/core/workflow-model/invalid-workflow-model-error.js.map +1 -0
  70. package/dist/core/workflow-model/map-job-ids.d.ts +4 -0
  71. package/dist/core/workflow-model/map-job-ids.d.ts.map +1 -0
  72. package/dist/core/workflow-model/map-job-ids.js +33 -0
  73. package/dist/core/workflow-model/map-job-ids.js.map +1 -0
  74. package/dist/core/workflow-model/normalize-agent-integrations.d.ts +12 -0
  75. package/dist/core/workflow-model/normalize-agent-integrations.d.ts.map +1 -0
  76. package/dist/core/workflow-model/normalize-agent-integrations.js +157 -0
  77. package/dist/core/workflow-model/normalize-agent-integrations.js.map +1 -0
  78. package/dist/core/workflow-model/normalize-dependencies.d.ts +6 -0
  79. package/dist/core/workflow-model/normalize-dependencies.d.ts.map +1 -0
  80. package/dist/core/workflow-model/normalize-dependencies.js +119 -0
  81. package/dist/core/workflow-model/normalize-dependencies.js.map +1 -0
  82. package/dist/core/workflow-model/normalize-env.d.ts +17 -0
  83. package/dist/core/workflow-model/normalize-env.d.ts.map +1 -0
  84. package/dist/core/workflow-model/normalize-env.js +40 -0
  85. package/dist/core/workflow-model/normalize-env.js.map +1 -0
  86. package/dist/core/workflow-model/normalize-if-condition.d.ts +14 -0
  87. package/dist/core/workflow-model/normalize-if-condition.d.ts.map +1 -0
  88. package/dist/core/workflow-model/normalize-if-condition.js +75 -0
  89. package/dist/core/workflow-model/normalize-if-condition.js.map +1 -0
  90. package/dist/core/workflow-model/normalize-job-checkout.d.ts +5 -0
  91. package/dist/core/workflow-model/normalize-job-checkout.d.ts.map +1 -0
  92. package/dist/core/workflow-model/normalize-job-checkout.js +16 -0
  93. package/dist/core/workflow-model/normalize-job-checkout.js.map +1 -0
  94. package/dist/core/workflow-model/normalize-job-listening.d.ts +10 -0
  95. package/dist/core/workflow-model/normalize-job-listening.d.ts.map +1 -0
  96. package/dist/core/workflow-model/normalize-job-listening.js +125 -0
  97. package/dist/core/workflow-model/normalize-job-listening.js.map +1 -0
  98. package/dist/core/workflow-model/normalize-job-success.d.ts +11 -0
  99. package/dist/core/workflow-model/normalize-job-success.d.ts.map +1 -0
  100. package/dist/core/workflow-model/normalize-job-success.js +23 -0
  101. package/dist/core/workflow-model/normalize-job-success.js.map +1 -0
  102. package/dist/core/workflow-model/normalize-jobs.d.ts +12 -0
  103. package/dist/core/workflow-model/normalize-jobs.d.ts.map +1 -0
  104. package/dist/core/workflow-model/normalize-jobs.js +935 -0
  105. package/dist/core/workflow-model/normalize-jobs.js.map +1 -0
  106. package/dist/core/workflow-model/normalize-needs.d.ts +2 -0
  107. package/dist/core/workflow-model/normalize-needs.d.ts.map +1 -0
  108. package/dist/core/workflow-model/normalize-needs.js +16 -0
  109. package/dist/core/workflow-model/normalize-needs.js.map +1 -0
  110. package/dist/core/workflow-model/normalize-step-gate.d.ts +15 -0
  111. package/dist/core/workflow-model/normalize-step-gate.d.ts.map +1 -0
  112. package/dist/core/workflow-model/normalize-step-gate.js +89 -0
  113. package/dist/core/workflow-model/normalize-step-gate.js.map +1 -0
  114. package/dist/core/workflow-model/normalize-step-outputs.d.ts +10 -0
  115. package/dist/core/workflow-model/normalize-step-outputs.d.ts.map +1 -0
  116. package/dist/core/workflow-model/normalize-step-outputs.js +32 -0
  117. package/dist/core/workflow-model/normalize-step-outputs.js.map +1 -0
  118. package/dist/core/workflow-model/normalize-triggers.d.ts +11 -0
  119. package/dist/core/workflow-model/normalize-triggers.d.ts.map +1 -0
  120. package/dist/core/workflow-model/normalize-triggers.js +128 -0
  121. package/dist/core/workflow-model/normalize-triggers.js.map +1 -0
  122. package/dist/core/workflow-model/normalize-workflow-document.d.ts +11 -0
  123. package/dist/core/workflow-model/normalize-workflow-document.d.ts.map +1 -0
  124. package/dist/core/workflow-model/normalize-workflow-document.js +43 -0
  125. package/dist/core/workflow-model/normalize-workflow-document.js.map +1 -0
  126. package/dist/core/workflow-model/parse-duration-ms.d.ts +11 -0
  127. package/dist/core/workflow-model/parse-duration-ms.d.ts.map +1 -0
  128. package/dist/core/workflow-model/parse-duration-ms.js +47 -0
  129. package/dist/core/workflow-model/parse-duration-ms.js.map +1 -0
  130. package/dist/core/workflow-model/parse-interpolation-field.d.ts +14 -0
  131. package/dist/core/workflow-model/parse-interpolation-field.d.ts.map +1 -0
  132. package/dist/core/workflow-model/parse-interpolation-field.js +377 -0
  133. package/dist/core/workflow-model/parse-interpolation-field.js.map +1 -0
  134. package/dist/core/workflow-model/stable-id.d.ts +2 -0
  135. package/dist/core/workflow-model/stable-id.d.ts.map +1 -0
  136. package/dist/core/workflow-model/stable-id.js +8 -0
  137. package/dist/core/workflow-model/stable-id.js.map +1 -0
  138. package/dist/core/workflow-model/validate-job-references.d.ts +10 -0
  139. package/dist/core/workflow-model/validate-job-references.d.ts.map +1 -0
  140. package/dist/core/workflow-model/validate-job-references.js +42 -0
  141. package/dist/core/workflow-model/validate-job-references.js.map +1 -0
  142. package/dist/core/workflow-model/validate-predicate-expression.d.ts +14 -0
  143. package/dist/core/workflow-model/validate-predicate-expression.d.ts.map +1 -0
  144. package/dist/core/workflow-model/validate-predicate-expression.js +273 -0
  145. package/dist/core/workflow-model/validate-predicate-expression.js.map +1 -0
  146. package/dist/core/workflow-model/validation-issue.d.ts +8 -0
  147. package/dist/core/workflow-model/validation-issue.d.ts.map +1 -0
  148. package/dist/core/workflow-model/validation-issue.js +17 -0
  149. package/dist/core/workflow-model/validation-issue.js.map +1 -0
  150. package/dist/core/workflow-model/workflow-field-label.d.ts +3 -0
  151. package/dist/core/workflow-model/workflow-field-label.d.ts.map +1 -0
  152. package/dist/core/workflow-model/workflow-field-label.js +47 -0
  153. package/dist/core/workflow-model/workflow-field-label.js.map +1 -0
  154. package/dist/core/workflow-yaml/index.d.ts +3 -0
  155. package/dist/core/workflow-yaml/index.d.ts.map +1 -0
  156. package/dist/core/workflow-yaml/index.js +4 -0
  157. package/dist/core/workflow-yaml/index.js.map +1 -0
  158. package/dist/core/workflow-yaml/invalid-workflow-yaml-error.d.ts +16 -0
  159. package/dist/core/workflow-yaml/invalid-workflow-yaml-error.d.ts.map +1 -0
  160. package/dist/core/workflow-yaml/invalid-workflow-yaml-error.js +12 -0
  161. package/dist/core/workflow-yaml/invalid-workflow-yaml-error.js.map +1 -0
  162. package/dist/core/workflow-yaml/parse-workflow-yaml.d.ts +9 -0
  163. package/dist/core/workflow-yaml/parse-workflow-yaml.d.ts.map +1 -0
  164. package/dist/core/workflow-yaml/parse-workflow-yaml.js +52 -0
  165. package/dist/core/workflow-yaml/parse-workflow-yaml.js.map +1 -0
  166. package/dist/core/workflow-yaml/source-locations.d.ts +3 -0
  167. package/dist/core/workflow-yaml/source-locations.d.ts.map +1 -0
  168. package/dist/core/workflow-yaml/source-locations.js +48 -0
  169. package/dist/core/workflow-yaml/source-locations.js.map +1 -0
  170. package/dist/db/db.d.ts +1277 -0
  171. package/dist/db/db.d.ts.map +1 -0
  172. package/dist/db/db.js +22 -0
  173. package/dist/db/db.js.map +1 -0
  174. package/dist/db/definition-triggers.d.ts +4 -0
  175. package/dist/db/definition-triggers.d.ts.map +1 -0
  176. package/dist/db/definition-triggers.js +17 -0
  177. package/dist/db/definition-triggers.js.map +1 -0
  178. package/dist/db/definitions.d.ts +63 -0
  179. package/dist/db/definitions.d.ts.map +1 -0
  180. package/dist/db/definitions.js +208 -0
  181. package/dist/db/definitions.js.map +1 -0
  182. package/dist/db/index.d.ts +8 -0
  183. package/dist/db/index.d.ts.map +1 -0
  184. package/dist/db/index.js +10 -0
  185. package/dist/db/index.js.map +1 -0
  186. package/dist/db/schema/common.d.ts +2 -0
  187. package/dist/db/schema/common.d.ts.map +1 -0
  188. package/dist/db/schema/common.js +4 -0
  189. package/dist/db/schema/common.js.map +1 -0
  190. package/dist/db/schema/definitions.d.ts +237 -0
  191. package/dist/db/schema/definitions.d.ts.map +1 -0
  192. package/dist/db/schema/definitions.js +63 -0
  193. package/dist/db/schema/definitions.js.map +1 -0
  194. package/dist/db/schema/outbox.d.ts +195 -0
  195. package/dist/db/schema/outbox.d.ts.map +1 -0
  196. package/dist/db/schema/outbox.js +5 -0
  197. package/dist/db/schema/outbox.js.map +1 -0
  198. package/dist/db/schema/sync-states.d.ts +218 -0
  199. package/dist/db/schema/sync-states.d.ts.map +1 -0
  200. package/dist/db/schema/sync-states.js +68 -0
  201. package/dist/db/schema/sync-states.js.map +1 -0
  202. package/dist/db/sync-states.d.ts +21 -0
  203. package/dist/db/sync-states.d.ts.map +1 -0
  204. package/dist/db/sync-states.js +47 -0
  205. package/dist/db/sync-states.js.map +1 -0
  206. package/dist/index.d.ts +14 -0
  207. package/dist/index.d.ts.map +1 -0
  208. package/dist/index.js +63 -0
  209. package/dist/index.js.map +1 -0
  210. package/dist/presentation/dto/definition.d.ts +6 -0
  211. package/dist/presentation/dto/definition.d.ts.map +1 -0
  212. package/dist/presentation/dto/definition.js +35 -0
  213. package/dist/presentation/dto/definition.js.map +1 -0
  214. package/dist/presentation/dto/index.d.ts +2 -0
  215. package/dist/presentation/dto/index.d.ts.map +1 -0
  216. package/dist/presentation/dto/index.js +3 -0
  217. package/dist/presentation/dto/index.js.map +1 -0
  218. package/dist/presentation/index.d.ts +2 -0
  219. package/dist/presentation/index.d.ts.map +1 -0
  220. package/dist/presentation/index.js +3 -0
  221. package/dist/presentation/index.js.map +1 -0
  222. package/dist/presentation/routes/create-definition.d.ts +9 -0
  223. package/dist/presentation/routes/create-definition.d.ts.map +1 -0
  224. package/dist/presentation/routes/create-definition.js +76 -0
  225. package/dist/presentation/routes/create-definition.js.map +1 -0
  226. package/dist/presentation/routes/get-definition.d.ts +2 -0
  227. package/dist/presentation/routes/get-definition.d.ts.map +1 -0
  228. package/dist/presentation/routes/get-definition.js +43 -0
  229. package/dist/presentation/routes/get-definition.js.map +1 -0
  230. package/dist/presentation/routes/index.d.ts +7 -0
  231. package/dist/presentation/routes/index.d.ts.map +1 -0
  232. package/dist/presentation/routes/index.js +22 -0
  233. package/dist/presentation/routes/index.js.map +1 -0
  234. package/dist/presentation/routes/list-definitions.d.ts +2 -0
  235. package/dist/presentation/routes/list-definitions.d.ts.map +1 -0
  236. package/dist/presentation/routes/list-definitions.js +56 -0
  237. package/dist/presentation/routes/list-definitions.js.map +1 -0
  238. package/dist/presentation/routes/validate-definition.d.ts +2 -0
  239. package/dist/presentation/routes/validate-definition.d.ts.map +1 -0
  240. package/dist/presentation/routes/validate-definition.js +43 -0
  241. package/dist/presentation/routes/validate-definition.js.map +1 -0
  242. package/dist/presentation/subscribers/index.d.ts +3 -0
  243. package/dist/presentation/subscribers/index.d.ts.map +1 -0
  244. package/dist/presentation/subscribers/index.js +4 -0
  245. package/dist/presentation/subscribers/index.js.map +1 -0
  246. package/dist/presentation/subscribers/on-project-source-bound.d.ts +3 -0
  247. package/dist/presentation/subscribers/on-project-source-bound.d.ts.map +1 -0
  248. package/dist/presentation/subscribers/on-project-source-bound.js +11 -0
  249. package/dist/presentation/subscribers/on-project-source-bound.js.map +1 -0
  250. package/dist/presentation/subscribers/on-project-source-commit-observed.d.ts +3 -0
  251. package/dist/presentation/subscribers/on-project-source-commit-observed.d.ts.map +1 -0
  252. package/dist/presentation/subscribers/on-project-source-commit-observed.js +13 -0
  253. package/dist/presentation/subscribers/on-project-source-commit-observed.js.map +1 -0
  254. package/dist/presentation/subscribers/start-definition-sync.d.ts +10 -0
  255. package/dist/presentation/subscribers/start-definition-sync.d.ts.map +1 -0
  256. package/dist/presentation/subscribers/start-definition-sync.js +26 -0
  257. package/dist/presentation/subscribers/start-definition-sync.js.map +1 -0
  258. package/dist/temporal/activities/index.d.ts +3 -0
  259. package/dist/temporal/activities/index.d.ts.map +1 -0
  260. package/dist/temporal/activities/index.js +3 -0
  261. package/dist/temporal/activities/index.js.map +1 -0
  262. package/dist/temporal/activities/sync-activities.d.ts +47 -0
  263. package/dist/temporal/activities/sync-activities.d.ts.map +1 -0
  264. package/dist/temporal/activities/sync-activities.js +128 -0
  265. package/dist/temporal/activities/sync-activities.js.map +1 -0
  266. package/dist/temporal/constants.d.ts +3 -0
  267. package/dist/temporal/constants.d.ts.map +1 -0
  268. package/dist/temporal/constants.js +4 -0
  269. package/dist/temporal/constants.js.map +1 -0
  270. package/dist/temporal/index.d.ts +3 -0
  271. package/dist/temporal/index.d.ts.map +1 -0
  272. package/dist/temporal/index.js +4 -0
  273. package/dist/temporal/index.js.map +1 -0
  274. package/dist/temporal/workflows/definition-sync.d.ts +20 -0
  275. package/dist/temporal/workflows/definition-sync.d.ts.map +1 -0
  276. package/dist/temporal/workflows/definition-sync.js +95 -0
  277. package/dist/temporal/workflows/definition-sync.js.map +1 -0
  278. package/dist/temporal/workflows/index.d.ts +2 -0
  279. package/dist/temporal/workflows/index.d.ts.map +1 -0
  280. package/dist/temporal/workflows/index.js +3 -0
  281. package/dist/temporal/workflows/index.js.map +1 -0
  282. package/dist/tsconfig.test.tsbuildinfo +1 -0
  283. package/drizzle/0000_initial.sql +64 -0
  284. package/drizzle/0001_definition_list_pagination.sql +1 -0
  285. package/drizzle/meta/0000_snapshot.json +527 -0
  286. package/drizzle/meta/_journal.json +20 -0
  287. package/drizzle.config.ts +11 -0
  288. package/package.json +89 -0
  289. package/src/config.test.ts +23 -0
  290. package/src/config.ts +50 -0
  291. package/src/core/entities/index.ts +4 -0
  292. package/src/core/entities/integration-context.ts +21 -0
  293. package/src/core/entities/sync-state.ts +40 -0
  294. package/src/core/entities/workflow-definition.ts +38 -0
  295. package/src/core/entities/workflow-model.ts +162 -0
  296. package/src/core/errors.ts +21 -0
  297. package/src/core/has-agent-step-integrations.ts +7 -0
  298. package/src/core/index.ts +23 -0
  299. package/src/core/parse-definition.test.ts +200 -0
  300. package/src/core/parse-definition.ts +28 -0
  301. package/src/core/sync-definitions.test.ts +390 -0
  302. package/src/core/sync-definitions.ts +216 -0
  303. package/src/core/validate-definition.test.ts +152 -0
  304. package/src/core/validate-definition.ts +72 -0
  305. package/src/core/workflow-model/README.md +129 -0
  306. package/src/core/workflow-model/constants.ts +1 -0
  307. package/src/core/workflow-model/cron-trigger.test.ts +29 -0
  308. package/src/core/workflow-model/cron-trigger.ts +83 -0
  309. package/src/core/workflow-model/index.ts +9 -0
  310. package/src/core/workflow-model/invalid-workflow-model-error.ts +71 -0
  311. package/src/core/workflow-model/map-job-ids.ts +33 -0
  312. package/src/core/workflow-model/normalize-agent-integrations.ts +208 -0
  313. package/src/core/workflow-model/normalize-dependencies.ts +139 -0
  314. package/src/core/workflow-model/normalize-env.ts +48 -0
  315. package/src/core/workflow-model/normalize-if-condition.ts +126 -0
  316. package/src/core/workflow-model/normalize-job-checkout.ts +19 -0
  317. package/src/core/workflow-model/normalize-job-listening.ts +121 -0
  318. package/src/core/workflow-model/normalize-job-success.ts +28 -0
  319. package/src/core/workflow-model/normalize-jobs.ts +1010 -0
  320. package/src/core/workflow-model/normalize-needs.ts +12 -0
  321. package/src/core/workflow-model/normalize-step-gate.ts +90 -0
  322. package/src/core/workflow-model/normalize-step-outputs.ts +36 -0
  323. package/src/core/workflow-model/normalize-triggers.ts +130 -0
  324. package/src/core/workflow-model/normalize-workflow-document.test.ts +4463 -0
  325. package/src/core/workflow-model/normalize-workflow-document.ts +63 -0
  326. package/src/core/workflow-model/parse-duration-ms.ts +58 -0
  327. package/src/core/workflow-model/parse-interpolation-field.ts +566 -0
  328. package/src/core/workflow-model/stable-id.ts +12 -0
  329. package/src/core/workflow-model/validate-job-references.ts +54 -0
  330. package/src/core/workflow-model/validate-predicate-expression.test.ts +161 -0
  331. package/src/core/workflow-model/validate-predicate-expression.ts +406 -0
  332. package/src/core/workflow-model/validation-issue.ts +27 -0
  333. package/src/core/workflow-model/workflow-field-label.ts +50 -0
  334. package/src/core/workflow-yaml/README.md +77 -0
  335. package/src/core/workflow-yaml/index.ts +11 -0
  336. package/src/core/workflow-yaml/invalid-workflow-yaml-error.ts +23 -0
  337. package/src/core/workflow-yaml/parse-workflow-yaml.test.ts +71 -0
  338. package/src/core/workflow-yaml/parse-workflow-yaml.ts +74 -0
  339. package/src/core/workflow-yaml/source-locations.ts +62 -0
  340. package/src/db/db.ts +18 -0
  341. package/src/db/definition-triggers.test.ts +95 -0
  342. package/src/db/definition-triggers.ts +17 -0
  343. package/src/db/definitions.test.ts +1309 -0
  344. package/src/db/definitions.ts +357 -0
  345. package/src/db/index.ts +31 -0
  346. package/src/db/schema/common.ts +3 -0
  347. package/src/db/schema/definitions.test.ts +36 -0
  348. package/src/db/schema/definitions.ts +76 -0
  349. package/src/db/schema/outbox.ts +4 -0
  350. package/src/db/schema/sync-states.ts +79 -0
  351. package/src/db/sync-states.test.ts +90 -0
  352. package/src/db/sync-states.ts +86 -0
  353. package/src/index.ts +104 -0
  354. package/src/presentation/dto/definition.test.ts +59 -0
  355. package/src/presentation/dto/definition.ts +41 -0
  356. package/src/presentation/dto/index.ts +1 -0
  357. package/src/presentation/index.ts +1 -0
  358. package/src/presentation/routes/create-definition.test.ts +356 -0
  359. package/src/presentation/routes/create-definition.ts +101 -0
  360. package/src/presentation/routes/get-definition.test.ts +79 -0
  361. package/src/presentation/routes/get-definition.ts +37 -0
  362. package/src/presentation/routes/index.test.ts +41 -0
  363. package/src/presentation/routes/index.ts +28 -0
  364. package/src/presentation/routes/list-definitions.test.ts +195 -0
  365. package/src/presentation/routes/list-definitions.ts +49 -0
  366. package/src/presentation/routes/validate-definition.test.ts +63 -0
  367. package/src/presentation/routes/validate-definition.ts +46 -0
  368. package/src/presentation/subscribers/index.ts +2 -0
  369. package/src/presentation/subscribers/on-project-source-bound.test.ts +65 -0
  370. package/src/presentation/subscribers/on-project-source-bound.ts +11 -0
  371. package/src/presentation/subscribers/on-project-source-commit-observed.test.ts +56 -0
  372. package/src/presentation/subscribers/on-project-source-commit-observed.ts +15 -0
  373. package/src/presentation/subscribers/start-definition-sync.ts +38 -0
  374. package/src/temporal/activities/index.ts +10 -0
  375. package/src/temporal/activities/sync-activities.test.ts +337 -0
  376. package/src/temporal/activities/sync-activities.ts +206 -0
  377. package/src/temporal/constants.ts +2 -0
  378. package/src/temporal/index.ts +2 -0
  379. package/src/temporal/workflows/definition-sync.test.ts +59 -0
  380. package/src/temporal/workflows/definition-sync.ts +116 -0
  381. package/src/temporal/workflows/index.ts +1 -0
  382. package/test/env.ts +7 -0
  383. package/test/factories/definition.ts +66 -0
  384. package/test/fixtures/invalid-cycle.yml +13 -0
  385. package/test/fixtures/invalid-missing-name.yml +4 -0
  386. package/test/fixtures/invalid-yaml-syntax.yml +6 -0
  387. package/test/fixtures/valid-dag.yml +25 -0
  388. package/test/fixtures/valid-listening-job.yml +21 -0
  389. package/test/fixtures/valid-simple.yml +18 -0
  390. package/test/fixtures/workflow-yaml/invalid/invalid-yaml-syntax.yaml +6 -0
  391. package/test/fixtures/workflow-yaml/invalid/missing-step-run.yaml +5 -0
  392. package/test/fixtures/workflow-yaml/valid/simple-build.document.json +26 -0
  393. package/test/fixtures/workflow-yaml/valid/simple-build.yaml +14 -0
  394. package/test/globalSetup.ts +25 -0
  395. package/test/index.ts +1 -0
  396. package/test/setup.ts +17 -0
  397. package/tsconfig.build.json +9 -0
  398. package/tsconfig.build.tsbuildinfo +1 -0
  399. package/tsconfig.json +3 -0
  400. package/tsconfig.test.json +8 -0
  401. package/vitest.config.ts +11 -0
@@ -0,0 +1,1010 @@
1
+ import {
2
+ agentThinkingByHarness,
3
+ getHarnessDescriptor,
4
+ getModelProviderEntry,
5
+ type HarnessToolDeploymentConfig,
6
+ listEnabledHarnessTools,
7
+ } from '@shipfox/api-agent-dto';
8
+ import {
9
+ type AvailabilitySite,
10
+ buildTypedRootsEnvironment,
11
+ type ExpressionType,
12
+ type ExpressionTypeEnvironment,
13
+ type WorkflowJobTypeOverlay,
14
+ type WorkflowStepTypeOverlay,
15
+ } from '@shipfox/expression';
16
+ import {
17
+ canonicalizeLabels,
18
+ findInvalidLabels,
19
+ MAX_RUNNER_LABEL_LENGTH,
20
+ MAX_RUNNER_LABELS,
21
+ RUNNER_LABEL_PATTERN,
22
+ } from '@shipfox/runner-labels';
23
+ import type {
24
+ WorkflowDocument,
25
+ WorkflowDocumentJob,
26
+ WorkflowDocumentStep,
27
+ } from '@shipfox/workflow-document';
28
+ import type {IntegrationValidationContext} from '../entities/integration-context.js';
29
+ import type {
30
+ WorkflowEnvTemplates,
31
+ WorkflowFieldTemplate,
32
+ WorkflowModelAgentStep,
33
+ WorkflowModelJob,
34
+ WorkflowModelRunStep,
35
+ WorkflowModelStep,
36
+ WorkflowOutputTemplates,
37
+ WorkflowStepSourceLocationMap,
38
+ } from '../entities/workflow-model.js';
39
+ import type {WorkflowModelValidationIssue} from './invalid-workflow-model-error.js';
40
+ import {normalizeAgentIntegrations} from './normalize-agent-integrations.js';
41
+ import {normalizeEnv} from './normalize-env.js';
42
+ import {normalizeIfCondition} from './normalize-if-condition.js';
43
+ import {normalizeJobCheckout} from './normalize-job-checkout.js';
44
+ import {normalizeJobListening} from './normalize-job-listening.js';
45
+ import {normalizeJobSuccess} from './normalize-job-success.js';
46
+ import {normalizeNeeds} from './normalize-needs.js';
47
+ import {normalizeStepGate} from './normalize-step-gate.js';
48
+ import {normalizeStepOutputs} from './normalize-step-outputs.js';
49
+ import {parseDurationMs} from './parse-duration-ms.js';
50
+ import {parseInterpolationField} from './parse-interpolation-field.js';
51
+ import {stableId} from './stable-id.js';
52
+ import {issue} from './validation-issue.js';
53
+
54
+ export interface NormalizeContext {
55
+ readonly defaultRunnerLabels: readonly string[];
56
+ readonly harnessToolDeploymentConfig: HarnessToolDeploymentConfig;
57
+ readonly integrationValidationContext?: IntegrationValidationContext | undefined;
58
+ }
59
+
60
+ export function normalizeJobs(
61
+ document: WorkflowDocument,
62
+ jobIdBySourceName: ReadonlyMap<string, string>,
63
+ issues: WorkflowModelValidationIssue[],
64
+ stepSourceLocations: WorkflowStepSourceLocationMap | undefined,
65
+ context: NormalizeContext,
66
+ ): readonly WorkflowModelJob[] {
67
+ const entries = Object.entries(document.jobs);
68
+ const pending = new Set(entries.map(([sourceName]) => sourceName));
69
+ const modelsBySourceName = new Map<string, WorkflowModelJob>();
70
+ const jobOutputTypesBySourceName = new Map<string, Readonly<Record<string, ExpressionType>>>();
71
+ const issuesBySourceName = new Map<string, WorkflowModelValidationIssue[]>();
72
+
73
+ while (pending.size > 0) {
74
+ let progressed = false;
75
+
76
+ for (const [sourceName, job] of entries) {
77
+ if (!pending.has(sourceName)) continue;
78
+
79
+ const dependencySourceNames = normalizeNeeds(job.needs).filter((dependency) =>
80
+ jobIdBySourceName.has(dependency),
81
+ );
82
+ if (dependencySourceNames.some((dependency) => pending.has(dependency))) continue;
83
+
84
+ const model = normalizeJob({
85
+ document,
86
+ sourceName,
87
+ job,
88
+ jobIdBySourceName,
89
+ issues: issuesForSourceName(issuesBySourceName, sourceName),
90
+ stepSourceLocations,
91
+ context,
92
+ jobOutputTypesBySourceName,
93
+ });
94
+ if (model !== undefined) modelsBySourceName.set(sourceName, model);
95
+ pending.delete(sourceName);
96
+ progressed = true;
97
+ }
98
+
99
+ if (progressed) continue;
100
+
101
+ for (const sourceName of pending) {
102
+ const job = document.jobs[sourceName];
103
+ if (job === undefined) continue;
104
+ const model = normalizeJob({
105
+ document,
106
+ sourceName,
107
+ job,
108
+ jobIdBySourceName,
109
+ issues: issuesForSourceName(issuesBySourceName, sourceName),
110
+ stepSourceLocations,
111
+ context,
112
+ jobOutputTypesBySourceName,
113
+ });
114
+ if (model !== undefined) modelsBySourceName.set(sourceName, model);
115
+ }
116
+ break;
117
+ }
118
+
119
+ for (const [sourceName] of entries) {
120
+ issues.push(...(issuesBySourceName.get(sourceName) ?? []));
121
+ }
122
+
123
+ return entries.flatMap(([sourceName]) => {
124
+ const model = modelsBySourceName.get(sourceName);
125
+ return model === undefined ? [] : [model];
126
+ });
127
+ }
128
+
129
+ function issuesForSourceName(
130
+ issuesBySourceName: Map<string, WorkflowModelValidationIssue[]>,
131
+ sourceName: string,
132
+ ): WorkflowModelValidationIssue[] {
133
+ const existing = issuesBySourceName.get(sourceName);
134
+ if (existing !== undefined) return existing;
135
+
136
+ const issues: WorkflowModelValidationIssue[] = [];
137
+ issuesBySourceName.set(sourceName, issues);
138
+ return issues;
139
+ }
140
+
141
+ function normalizeJob(params: {
142
+ document: WorkflowDocument;
143
+ sourceName: string;
144
+ job: WorkflowDocumentJob;
145
+ jobIdBySourceName: ReadonlyMap<string, string>;
146
+ issues: WorkflowModelValidationIssue[];
147
+ stepSourceLocations: WorkflowStepSourceLocationMap | undefined;
148
+ context: NormalizeContext;
149
+ jobOutputTypesBySourceName: Map<string, Readonly<Record<string, ExpressionType>>>;
150
+ }): WorkflowModelJob | undefined {
151
+ const id = params.jobIdBySourceName.get(params.sourceName);
152
+ if (id === undefined) return undefined;
153
+
154
+ const dependencies = normalizeJobDependencies({
155
+ sourceName: params.sourceName,
156
+ job: params.job,
157
+ jobIdBySourceName: params.jobIdBySourceName,
158
+ });
159
+ const allowedJobReferences = directNeedSourceNames({
160
+ sourceName: params.sourceName,
161
+ job: params.job,
162
+ jobIdBySourceName: params.jobIdBySourceName,
163
+ });
164
+ const upstreamJobs = upstreamJobTypeOverlays({
165
+ allowedJobReferences,
166
+ jobOutputTypesBySourceName: params.jobOutputTypesBySourceName,
167
+ });
168
+ const directNeedJobs = directNeedJobTypeOverlays({
169
+ allowedJobReferences,
170
+ jobOutputTypesBySourceName: params.jobOutputTypesBySourceName,
171
+ });
172
+ const upstreamJobsTypeOverlay =
173
+ upstreamJobs.length === 0 ? undefined : buildTypedRootsEnvironment({jobs: upstreamJobs});
174
+ const jobConditionTypeOverlay = buildTypedRootsEnvironment({
175
+ jobs: directNeedJobs,
176
+ needs: directNeedJobs,
177
+ });
178
+ // Step config can reference peer step outputs, which are completed at dispatch.
179
+ const stepFillSite: AvailabilitySite = 'step-dispatch';
180
+ const stepTypeOverlay = params.job.steps.some((step) => step.outputs !== undefined)
181
+ ? {}
182
+ : undefined;
183
+ const steps = normalizeJobSteps({
184
+ sourceName: params.sourceName,
185
+ jobId: id,
186
+ job: params.job,
187
+ issues: params.issues,
188
+ stepSourceLocations: params.stepSourceLocations,
189
+ fillSite: stepFillSite,
190
+ allowedJobReferences,
191
+ typeOverlay: stepTypeOverlay,
192
+ upstreamJobs,
193
+ directNeedJobs,
194
+ context: params.context,
195
+ });
196
+ const runner = normalizeRunner({
197
+ document: params.document,
198
+ job: params.job,
199
+ sourceName: params.sourceName,
200
+ issues: params.issues,
201
+ defaultRunnerLabels: params.context.defaultRunnerLabels,
202
+ });
203
+ const checkout = normalizeJobCheckout(params.job.checkout);
204
+ const jobEnv = normalizeEnv({
205
+ env: params.job.env,
206
+ path: ['jobs', params.sourceName, 'env'],
207
+ issues: params.issues,
208
+ allowedJobReferences,
209
+ typeOverlay: upstreamJobsTypeOverlay,
210
+ });
211
+ const success = normalizeJobSuccess({
212
+ source: params.job.success,
213
+ sourceName: params.sourceName,
214
+ issues: params.issues,
215
+ allowedJobReferences,
216
+ typeOverlay: upstreamJobsTypeOverlay,
217
+ });
218
+ const condition = normalizeIfCondition({
219
+ field: 'job.if',
220
+ source: params.job.if,
221
+ site: 'job-activation',
222
+ path: ['jobs', params.sourceName, 'if'],
223
+ invalidCode: 'invalid-job-if',
224
+ invalidMessage: 'Job if must be a valid wrapped CEL boolean expression.',
225
+ issues: params.issues,
226
+ allowedJobReferences,
227
+ typeOverlay: jobConditionTypeOverlay,
228
+ });
229
+ const outputs = normalizeJobOutputs({
230
+ sourceName: params.sourceName,
231
+ outputs: params.job.outputs,
232
+ issues: params.issues,
233
+ allowedJobReferences,
234
+ steps: params.job.steps,
235
+ upstreamJobs,
236
+ });
237
+ if (outputs?.types !== undefined) {
238
+ params.jobOutputTypesBySourceName.set(params.sourceName, outputs.types);
239
+ }
240
+ const executionTimeoutMs = parseDurationMs({
241
+ source: params.job.execution_timeout,
242
+ path: ['jobs', params.sourceName, 'execution_timeout'],
243
+ issues: params.issues,
244
+ });
245
+ const listening = normalizeJobListening({
246
+ job: params.job,
247
+ sourceName: params.sourceName,
248
+ issues: params.issues,
249
+ allowedJobReferences,
250
+ });
251
+ const name =
252
+ params.job.name === undefined
253
+ ? undefined
254
+ : (parseInterpolationField({
255
+ field: 'job.name',
256
+ source: params.job.name,
257
+ path: ['jobs', params.sourceName, 'name'],
258
+ issues: params.issues,
259
+ allowedJobReferences,
260
+ typeOverlay: upstreamJobsTypeOverlay,
261
+ }) ?? [{kind: 'literal' as const, value: params.job.name}]);
262
+
263
+ return {
264
+ id,
265
+ key: params.sourceName,
266
+ mode: listening === undefined ? 'one_shot' : 'listening',
267
+ runner: runner.labels,
268
+ ...(runner.templates.length === 0 ? {} : {runnerTemplates: runner.templates}),
269
+ checkout,
270
+ ...(condition === undefined ? {} : {if: condition}),
271
+ ...(success === undefined ? {} : {success}),
272
+ ...(outputs === undefined ? {} : {outputs: outputs.templates}),
273
+ ...(outputs?.types === undefined ? {} : {outputTypes: outputs.types}),
274
+ ...(executionTimeoutMs === undefined ? {} : {executionTimeoutMs}),
275
+ ...(listening === undefined ? {} : {listening}),
276
+ ...(name === undefined ? {} : {name}),
277
+ ...jobEnv,
278
+ dependencies,
279
+ steps,
280
+ };
281
+ }
282
+
283
+ function normalizeJobDependencies(params: {
284
+ sourceName: string;
285
+ job: WorkflowDocumentJob;
286
+ jobIdBySourceName: ReadonlyMap<string, string>;
287
+ }): readonly string[] {
288
+ return normalizeNeeds(params.job.needs).flatMap((dependencySourceName) => {
289
+ const dependencyId = params.jobIdBySourceName.get(dependencySourceName);
290
+ if (dependencyId === undefined || dependencySourceName === params.sourceName) return [];
291
+ return [dependencyId];
292
+ });
293
+ }
294
+
295
+ function directNeedSourceNames(params: {
296
+ sourceName: string;
297
+ job: WorkflowDocumentJob;
298
+ jobIdBySourceName: ReadonlyMap<string, string>;
299
+ }): ReadonlySet<string> {
300
+ return new Set(
301
+ normalizeNeeds(params.job.needs).filter(
302
+ (dependencySourceName) =>
303
+ dependencySourceName !== params.sourceName &&
304
+ params.jobIdBySourceName.has(dependencySourceName),
305
+ ),
306
+ );
307
+ }
308
+
309
+ function previousStepOverlays(
310
+ steps: readonly WorkflowDocumentStep[],
311
+ index: number,
312
+ ): readonly WorkflowStepTypeOverlay[] {
313
+ return steps.slice(0, index).flatMap((step) => {
314
+ if (step.key === undefined) return [];
315
+ return [{key: step.key, ...(step.outputs === undefined ? {} : {outputs: step.outputs})}];
316
+ });
317
+ }
318
+
319
+ function allStepOverlays(
320
+ steps: readonly WorkflowDocumentStep[],
321
+ ): readonly WorkflowStepTypeOverlay[] {
322
+ return previousStepOverlays(steps, steps.length);
323
+ }
324
+
325
+ function upstreamJobTypeOverlays(params: {
326
+ allowedJobReferences: ReadonlySet<string>;
327
+ jobOutputTypesBySourceName: ReadonlyMap<string, Readonly<Record<string, ExpressionType>>>;
328
+ }): readonly WorkflowJobTypeOverlay[] {
329
+ const overlays = [...params.allowedJobReferences].map((sourceName) => {
330
+ const outputs = params.jobOutputTypesBySourceName.get(sourceName);
331
+ return {key: sourceName, ...(outputs === undefined ? {} : {outputs})};
332
+ });
333
+ return overlays.some((overlay) => overlay.outputs !== undefined) ? overlays : [];
334
+ }
335
+
336
+ function directNeedJobTypeOverlays(params: {
337
+ allowedJobReferences: ReadonlySet<string>;
338
+ jobOutputTypesBySourceName: ReadonlyMap<string, Readonly<Record<string, ExpressionType>>>;
339
+ }): readonly WorkflowJobTypeOverlay[] {
340
+ return [...params.allowedJobReferences].map((sourceName) => {
341
+ const outputs = params.jobOutputTypesBySourceName.get(sourceName);
342
+ return {key: sourceName, ...(outputs === undefined ? {} : {outputs})};
343
+ });
344
+ }
345
+
346
+ function normalizeJobOutputs(params: {
347
+ sourceName: string;
348
+ outputs: WorkflowDocumentJob['outputs'];
349
+ issues: WorkflowModelValidationIssue[];
350
+ allowedJobReferences: ReadonlySet<string>;
351
+ steps: readonly WorkflowDocumentStep[];
352
+ upstreamJobs: readonly WorkflowJobTypeOverlay[];
353
+ }):
354
+ | {templates: WorkflowOutputTemplates; types: Readonly<Record<string, ExpressionType>>}
355
+ | undefined {
356
+ if (params.outputs === undefined) return undefined;
357
+
358
+ const templates: Record<string, WorkflowFieldTemplate> = Object.create(null) as Record<
359
+ string,
360
+ WorkflowFieldTemplate
361
+ >;
362
+ const types: Record<string, ExpressionType> = Object.create(null) as Record<
363
+ string,
364
+ ExpressionType
365
+ >;
366
+ const hasStepOutputDeclarations = params.steps.some((step) => step.outputs !== undefined);
367
+ const typeOverlay =
368
+ hasStepOutputDeclarations || params.upstreamJobs.length > 0
369
+ ? buildTypedRootsEnvironment({
370
+ ...(hasStepOutputDeclarations ? {steps: allStepOverlays(params.steps)} : {}),
371
+ ...(params.upstreamJobs.length === 0 ? {} : {jobs: params.upstreamJobs}),
372
+ })
373
+ : undefined;
374
+
375
+ for (const [key, source] of Object.entries(params.outputs)) {
376
+ const template = parseInterpolationField({
377
+ field: 'job.outputs',
378
+ source,
379
+ path: ['jobs', params.sourceName, 'outputs', key],
380
+ issues: params.issues,
381
+ fillSite: 'execution-resolution',
382
+ allowedJobReferences: params.allowedJobReferences,
383
+ typeOverlay,
384
+ }) ?? [{kind: 'literal' as const, value: source}];
385
+ templates[key] = template;
386
+ types[key] = inferJobOutputType({
387
+ sourceName: params.sourceName,
388
+ key,
389
+ source,
390
+ template,
391
+ issues: params.issues,
392
+ });
393
+ }
394
+
395
+ return {templates, types};
396
+ }
397
+
398
+ function inferJobOutputType(params: {
399
+ sourceName: string;
400
+ key: string;
401
+ source: string;
402
+ template: WorkflowFieldTemplate;
403
+ issues: WorkflowModelValidationIssue[];
404
+ }): ExpressionType {
405
+ if (params.template.length !== 1) return 'string';
406
+
407
+ const [segment] = params.template;
408
+ if (segment?.kind !== 'deferred') return 'string';
409
+
410
+ const resultType = segment.expression.resultType;
411
+ if (resultType === undefined) return 'string';
412
+ if (isScalarExpressionType(resultType)) return resultType;
413
+
414
+ params.issues.push(
415
+ issue({
416
+ code: 'invalid-job-output',
417
+ message: `Job output "${params.key}" must resolve to a scalar value.`,
418
+ path: ['jobs', params.sourceName, 'outputs', params.key],
419
+ details: {
420
+ output: params.key,
421
+ source: params.source,
422
+ },
423
+ }),
424
+ );
425
+ return 'string';
426
+ }
427
+
428
+ function isScalarExpressionType(type: ExpressionType): boolean {
429
+ return (
430
+ type === 'string' ||
431
+ type === 'int' ||
432
+ type === 'double' ||
433
+ type === 'bool' ||
434
+ type === 'null' ||
435
+ type === 'timestamp'
436
+ );
437
+ }
438
+
439
+ function normalizeJobSteps(params: {
440
+ sourceName: string;
441
+ jobId: string;
442
+ job: WorkflowDocumentJob;
443
+ issues: WorkflowModelValidationIssue[];
444
+ stepSourceLocations: WorkflowStepSourceLocationMap | undefined;
445
+ fillSite: AvailabilitySite;
446
+ allowedJobReferences: ReadonlySet<string>;
447
+ typeOverlay?: ExpressionTypeEnvironment | undefined;
448
+ upstreamJobs: readonly WorkflowJobTypeOverlay[];
449
+ directNeedJobs: readonly WorkflowJobTypeOverlay[];
450
+ context: NormalizeContext;
451
+ }): readonly WorkflowModelStep[] {
452
+ const usedStepIds = new Map<string, number>();
453
+
454
+ return params.job.steps.map((step, index) =>
455
+ normalizeStep({
456
+ step,
457
+ index,
458
+ sourceName: params.sourceName,
459
+ jobId: params.jobId,
460
+ allSteps: params.job.steps,
461
+ usedStepIds,
462
+ issues: params.issues,
463
+ stepSourceLocations: params.stepSourceLocations,
464
+ fillSite: params.fillSite,
465
+ allowedJobReferences: params.allowedJobReferences,
466
+ typeOverlay: params.typeOverlay,
467
+ upstreamJobs: params.upstreamJobs,
468
+ directNeedJobs: params.directNeedJobs,
469
+ context: params.context,
470
+ }),
471
+ );
472
+ }
473
+
474
+ function normalizeStep(params: {
475
+ step: WorkflowDocumentStep;
476
+ index: number;
477
+ sourceName: string;
478
+ jobId: string;
479
+ allSteps: readonly WorkflowDocumentStep[];
480
+ usedStepIds: Map<string, number>;
481
+ issues: WorkflowModelValidationIssue[];
482
+ stepSourceLocations: WorkflowStepSourceLocationMap | undefined;
483
+ fillSite: AvailabilitySite;
484
+ allowedJobReferences: ReadonlySet<string>;
485
+ typeOverlay?: ExpressionTypeEnvironment | undefined;
486
+ upstreamJobs: readonly WorkflowJobTypeOverlay[];
487
+ directNeedJobs: readonly WorkflowJobTypeOverlay[];
488
+ context: NormalizeContext;
489
+ }): WorkflowModelStep {
490
+ const stepKey = params.step.key;
491
+ const stepId =
492
+ stepKey === undefined
493
+ ? `${params.jobId}-step-${params.index + 1}`
494
+ : `${params.jobId}-${stableId(stepKey)}`;
495
+ const existingIndex = params.usedStepIds.get(stepId);
496
+
497
+ if (existingIndex !== undefined) {
498
+ params.issues.push(
499
+ issue({
500
+ code: 'duplicate-step-id',
501
+ message: `Steps ${existingIndex} and ${params.index} in job "${params.sourceName}" resolve to the same stable id "${stepId}".`,
502
+ path: ['jobs', params.sourceName, 'steps', params.index],
503
+ details: {id: stepId, indexes: [existingIndex, params.index]},
504
+ }),
505
+ );
506
+ } else {
507
+ params.usedStepIds.set(stepId, params.index);
508
+ }
509
+
510
+ const outputs = normalizeStepOutputs({
511
+ step: params.step,
512
+ sourceName: params.sourceName,
513
+ stepIndex: params.index,
514
+ issues: params.issues,
515
+ });
516
+ const currentStepOverlay =
517
+ stepKey === undefined
518
+ ? undefined
519
+ : ({
520
+ key: stepKey,
521
+ ...(outputs === undefined ? {} : {outputs}),
522
+ } satisfies WorkflowStepTypeOverlay);
523
+ const shouldBuildTypeOverlay = params.typeOverlay !== undefined || params.upstreamJobs.length > 0;
524
+ const typeOverlay = !shouldBuildTypeOverlay
525
+ ? undefined
526
+ : buildTypedRootsEnvironment({
527
+ steps: previousStepOverlays(params.allSteps, params.index),
528
+ ...(currentStepOverlay === undefined ? {} : {currentStep: currentStepOverlay}),
529
+ ...(params.upstreamJobs.length === 0 ? {} : {jobs: params.upstreamJobs}),
530
+ });
531
+ const conditionTypeOverlay = buildTypedRootsEnvironment({
532
+ steps: previousStepOverlays(params.allSteps, params.index),
533
+ ...(currentStepOverlay === undefined ? {} : {currentStep: currentStepOverlay}),
534
+ jobs: params.directNeedJobs,
535
+ needs: params.directNeedJobs,
536
+ });
537
+
538
+ const condition = normalizeIfCondition({
539
+ field: 'step.if',
540
+ source: params.step.if,
541
+ site: 'step-dispatch',
542
+ path: ['jobs', params.sourceName, 'steps', params.index, 'if'],
543
+ invalidCode: 'invalid-step-if',
544
+ invalidMessage: 'Step if must be a valid wrapped CEL boolean expression.',
545
+ issues: params.issues,
546
+ allowedJobReferences: params.allowedJobReferences,
547
+ typeOverlay: conditionTypeOverlay,
548
+ });
549
+ const gate = normalizeStepGate({
550
+ step: params.step,
551
+ sourceName: params.sourceName,
552
+ stepIndex: params.index,
553
+ stepId,
554
+ previousStepKeys: new Set(
555
+ params.allSteps
556
+ .slice(0, params.index)
557
+ .flatMap((candidate) => (candidate.key ? [candidate.key] : [])),
558
+ ),
559
+ issues: params.issues,
560
+ allowedJobReferences: params.allowedJobReferences,
561
+ typeOverlay,
562
+ });
563
+ const sourceLocation = params.stepSourceLocations?.get(params.sourceName)?.get(params.index);
564
+ const name =
565
+ params.step.name === undefined
566
+ ? undefined
567
+ : parseInterpolationField({
568
+ field: 'step.name',
569
+ source: params.step.name,
570
+ path: ['jobs', params.sourceName, 'steps', params.index, 'name'],
571
+ issues: params.issues,
572
+ fillSite: params.fillSite,
573
+ allowedJobReferences: params.allowedJobReferences,
574
+ typeOverlay,
575
+ });
576
+ const stepBase = {
577
+ id: stepId,
578
+ ...(stepKey === undefined ? {} : {key: stepKey}),
579
+ ...(params.step.name === undefined ? {} : {name: params.step.name}),
580
+ ...(outputs === undefined ? {} : {outputs}),
581
+ ...(sourceLocation === undefined ? {} : {sourceLocation}),
582
+ ...(condition === undefined ? {} : {if: condition}),
583
+ ...(gate === undefined ? {} : {gate}),
584
+ };
585
+
586
+ if (params.step.run !== undefined) {
587
+ return normalizeRunStep({
588
+ step: params.step,
589
+ stepBase,
590
+ sourceName: params.sourceName,
591
+ stepIndex: params.index,
592
+ name,
593
+ issues: params.issues,
594
+ fillSite: params.fillSite,
595
+ allowedJobReferences: params.allowedJobReferences,
596
+ typeOverlay,
597
+ });
598
+ }
599
+
600
+ if (params.step.prompt !== undefined) {
601
+ return normalizeAgentStep({
602
+ step: params.step,
603
+ stepBase,
604
+ sourceName: params.sourceName,
605
+ stepIndex: params.index,
606
+ name,
607
+ issues: params.issues,
608
+ fillSite: params.fillSite,
609
+ allowedJobReferences: params.allowedJobReferences,
610
+ typeOverlay,
611
+ context: params.context,
612
+ });
613
+ }
614
+
615
+ // workflowDocumentStepSchema requires either `run` or an agent `prompt`; this
616
+ // keeps the model-step union honest if callers bypass the document parser.
617
+ throw new Error(`Workflow step "${stepId}" is neither a run nor an agent step`);
618
+ }
619
+
620
+ function normalizeRunStep(params: {
621
+ step: WorkflowDocumentStep;
622
+ stepBase: WorkflowModelStepBaseFields;
623
+ sourceName: string;
624
+ stepIndex: number;
625
+ name: WorkflowFieldTemplate | undefined;
626
+ issues: WorkflowModelValidationIssue[];
627
+ fillSite: AvailabilitySite;
628
+ allowedJobReferences: ReadonlySet<string>;
629
+ typeOverlay?: ExpressionTypeEnvironment | undefined;
630
+ }): WorkflowModelRunStep {
631
+ if (params.step.run === undefined) {
632
+ throw new Error('Run step normalization requires a run command');
633
+ }
634
+
635
+ const commandTemplate = parseInterpolationField({
636
+ field: 'run',
637
+ source: params.step.run,
638
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'run'],
639
+ issues: params.issues,
640
+ fillSite: params.fillSite,
641
+ allowedJobReferences: params.allowedJobReferences,
642
+ typeOverlay: params.typeOverlay,
643
+ });
644
+ const stepEnv = normalizeEnv({
645
+ env: params.step.env,
646
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'env'],
647
+ issues: params.issues,
648
+ fillSite: params.fillSite,
649
+ allowedJobReferences: params.allowedJobReferences,
650
+ typeOverlay: params.typeOverlay,
651
+ });
652
+ const templates = optionalRunStepTemplates({
653
+ command: commandTemplate,
654
+ name: params.name,
655
+ env: stepEnv.templates?.env,
656
+ });
657
+
658
+ return {
659
+ ...params.stepBase,
660
+ kind: 'run',
661
+ command: {kind: 'shell', value: params.step.run},
662
+ ...(stepEnv.env === undefined ? {} : {env: stepEnv.env}),
663
+ ...(templates === undefined ? {} : {templates}),
664
+ };
665
+ }
666
+
667
+ function normalizeAgentStep(params: {
668
+ step: WorkflowDocumentStep;
669
+ stepBase: WorkflowModelStepBaseFields;
670
+ sourceName: string;
671
+ stepIndex: number;
672
+ name: WorkflowFieldTemplate | undefined;
673
+ issues: WorkflowModelValidationIssue[];
674
+ fillSite: AvailabilitySite;
675
+ allowedJobReferences: ReadonlySet<string>;
676
+ typeOverlay?: ExpressionTypeEnvironment | undefined;
677
+ context: NormalizeContext;
678
+ }): WorkflowModelAgentStep {
679
+ if (params.step.prompt === undefined) {
680
+ throw new Error('Agent step normalization requires a prompt');
681
+ }
682
+
683
+ const promptTemplate = parseInterpolationField({
684
+ field: 'agent.prompt',
685
+ source: params.step.prompt,
686
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'prompt'],
687
+ issues: params.issues,
688
+ fillSite: params.fillSite,
689
+ allowedJobReferences: params.allowedJobReferences,
690
+ typeOverlay: params.typeOverlay,
691
+ });
692
+ const modelTemplate =
693
+ params.step.model === undefined
694
+ ? undefined
695
+ : parseInterpolationField({
696
+ field: 'agent.model',
697
+ source: params.step.model,
698
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'model'],
699
+ issues: params.issues,
700
+ fillSite: params.fillSite,
701
+ allowedJobReferences: params.allowedJobReferences,
702
+ typeOverlay: params.typeOverlay,
703
+ });
704
+ const providerTemplate =
705
+ params.step.provider === undefined
706
+ ? undefined
707
+ : parseInterpolationField({
708
+ field: 'agent.provider',
709
+ source: params.step.provider,
710
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'provider'],
711
+ issues: params.issues,
712
+ fillSite: params.fillSite,
713
+ allowedJobReferences: params.allowedJobReferences,
714
+ typeOverlay: params.typeOverlay,
715
+ });
716
+ validateAgentStep({
717
+ step: params.step,
718
+ sourceName: params.sourceName,
719
+ stepIndex: params.stepIndex,
720
+ issues: params.issues,
721
+ validateLiteralProvider: providerTemplate === undefined,
722
+ harnessToolDeploymentConfig: params.context.harnessToolDeploymentConfig,
723
+ });
724
+ const integrations = normalizeAgentIntegrations({
725
+ integrations: params.step.integrations,
726
+ sourceName: params.sourceName,
727
+ stepIndex: params.stepIndex,
728
+ issues: params.issues,
729
+ integrationValidationContext: params.context.integrationValidationContext,
730
+ });
731
+ const templates = optionalAgentStepTemplates({
732
+ prompt: promptTemplate,
733
+ model: modelTemplate,
734
+ provider: providerTemplate,
735
+ name: params.name,
736
+ });
737
+
738
+ return {
739
+ ...params.stepBase,
740
+ kind: 'agent',
741
+ ...(params.step.harness === undefined ? {} : {harness: params.step.harness}),
742
+ ...(params.step.model === undefined ? {} : {model: params.step.model}),
743
+ ...(params.step.provider === undefined ? {} : {provider: params.step.provider}),
744
+ prompt: params.step.prompt,
745
+ ...(params.step.thinking === undefined ? {} : {thinking: params.step.thinking}),
746
+ ...(params.step.tools === undefined ? {} : {tools: params.step.tools}),
747
+ ...(integrations === undefined ? {} : {integrations}),
748
+ ...(templates === undefined ? {} : {templates}),
749
+ };
750
+ }
751
+
752
+ function validateAgentStep(params: {
753
+ step: WorkflowDocumentStep;
754
+ sourceName: string;
755
+ stepIndex: number;
756
+ issues: WorkflowModelValidationIssue[];
757
+ validateLiteralProvider: boolean;
758
+ harnessToolDeploymentConfig: HarnessToolDeploymentConfig;
759
+ }): void {
760
+ validateHarnessThinking(params);
761
+ validateHarnessTools(params);
762
+ if (!params.validateLiteralProvider) return;
763
+
764
+ const providerId = params.step.provider;
765
+ if (providerId === undefined) return;
766
+
767
+ const provider = getModelProviderEntry(providerId);
768
+ const harness = params.step.harness;
769
+ if (provider === undefined && harness === 'pi') return;
770
+
771
+ if (provider === undefined || provider.support_status !== 'supported') {
772
+ params.issues.push(
773
+ issue({
774
+ code: 'invalid-provider',
775
+ message: `Provider "${providerId}" is not supported.`,
776
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'provider'],
777
+ details: {provider: providerId},
778
+ }),
779
+ );
780
+ return;
781
+ }
782
+
783
+ if (harness === undefined) return;
784
+
785
+ const descriptor = getHarnessDescriptor(harness);
786
+ if (descriptor.supportedProviderIds.includes(providerId)) return;
787
+
788
+ params.issues.push(
789
+ issue({
790
+ code: 'harness-provider-incompatible',
791
+ message: `Harness "${harness}" does not support provider: ${providerId}. Supported providers: ${descriptor.supportedProviderIds.join(', ')}.`,
792
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'provider'],
793
+ details: {harness, provider: providerId, supportedProviders: descriptor.supportedProviderIds},
794
+ }),
795
+ );
796
+ }
797
+
798
+ function validateHarnessTools(params: {
799
+ step: WorkflowDocumentStep;
800
+ sourceName: string;
801
+ stepIndex: number;
802
+ issues: WorkflowModelValidationIssue[];
803
+ harnessToolDeploymentConfig: HarnessToolDeploymentConfig;
804
+ }): void {
805
+ const {harness, tools} = params.step;
806
+ if (tools === undefined) return;
807
+
808
+ if (harness === undefined) {
809
+ params.issues.push(
810
+ issue({
811
+ code: 'missing-harness-for-tools',
812
+ message:
813
+ 'Agent step tools require an explicit harness because tool names are harness-specific.',
814
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'tools'],
815
+ details: {tools},
816
+ }),
817
+ );
818
+ return;
819
+ }
820
+
821
+ const supportedTools = listEnabledHarnessTools(harness, params.harnessToolDeploymentConfig).map(
822
+ (tool) => tool.name,
823
+ );
824
+ const supportedToolSet = new Set(supportedTools);
825
+
826
+ tools.forEach((tool, toolIndex) => {
827
+ if (supportedToolSet.has(tool)) return;
828
+
829
+ params.issues.push(
830
+ issue({
831
+ code: 'harness-tool-incompatible',
832
+ message: `Harness "${harness}" does not support tool: ${tool}. Supported tools: ${supportedTools.join(', ')}.`,
833
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'tools', toolIndex],
834
+ details: {harness, tool, supportedTools},
835
+ }),
836
+ );
837
+ });
838
+ }
839
+
840
+ function validateHarnessThinking(params: {
841
+ step: WorkflowDocumentStep;
842
+ sourceName: string;
843
+ stepIndex: number;
844
+ issues: WorkflowModelValidationIssue[];
845
+ }): void {
846
+ const {harness, thinking} = params.step;
847
+ if (harness === undefined || thinking === undefined) return;
848
+
849
+ const thinkingSchema = agentThinkingByHarness[harness];
850
+ if (thinkingSchema.safeParse(thinking).success) return;
851
+
852
+ const supportedLevels = thinkingSchema.options;
853
+ params.issues.push(
854
+ issue({
855
+ code: 'harness-thinking-incompatible',
856
+ message: `Harness "${harness}" does not support thinking: ${thinking}. Supported levels: ${supportedLevels.join(', ')}.`,
857
+ path: ['jobs', params.sourceName, 'steps', params.stepIndex, 'thinking'],
858
+ details: {harness, thinking, supportedLevels},
859
+ }),
860
+ );
861
+ }
862
+ function normalizeRunner(params: {
863
+ document: WorkflowDocument;
864
+ job: WorkflowDocumentJob;
865
+ sourceName: string;
866
+ issues: WorkflowModelValidationIssue[];
867
+ defaultRunnerLabels: readonly string[];
868
+ }): {labels: readonly string[]; templates: readonly WorkflowFieldTemplate[]} {
869
+ const rawRunner = params.job.runner ?? params.document.runner;
870
+ if (rawRunner === undefined) {
871
+ const runnerLabels = params.defaultRunnerLabels;
872
+ validateRunnerLabels({...params, runnerLabels, allowEmpty: false});
873
+ return {labels: runnerLabels, templates: []};
874
+ }
875
+
876
+ const runnerValues = typeof rawRunner === 'string' ? [rawRunner] : rawRunner;
877
+ const literalLabels: string[] = [];
878
+ const templates: WorkflowFieldTemplate[] = [];
879
+ let templateValueCount = 0;
880
+ for (const [index, value] of runnerValues.entries()) {
881
+ const template = parseInterpolationField({
882
+ field: 'job.runner',
883
+ source: value,
884
+ path: ['jobs', params.sourceName, 'runner', index],
885
+ issues: params.issues,
886
+ fillSite: 'execution-creation',
887
+ });
888
+ const hasTemplateSyntax = hasInterpolationSyntax(value);
889
+ if (hasTemplateSyntax) templateValueCount += 1;
890
+ if (template === undefined && !hasTemplateSyntax) {
891
+ literalLabels.push(value);
892
+ } else if (template !== undefined) {
893
+ templates.push(template);
894
+ }
895
+ }
896
+
897
+ const runnerLabels = canonicalizeLabels(literalLabels);
898
+ validateRunnerLabels({
899
+ ...params,
900
+ runnerLabels,
901
+ runnerLabelCount: runnerLabels.length + templates.length,
902
+ allowEmpty: templateValueCount > 0,
903
+ });
904
+
905
+ return {labels: runnerLabels, templates};
906
+ }
907
+
908
+ function hasInterpolationSyntax(value: string): boolean {
909
+ return value.includes('${{');
910
+ }
911
+
912
+ function validateRunnerLabels(params: {
913
+ sourceName: string;
914
+ issues: WorkflowModelValidationIssue[];
915
+ runnerLabels: readonly string[];
916
+ runnerLabelCount?: number;
917
+ allowEmpty: boolean;
918
+ }): void {
919
+ const runnerLabels = params.runnerLabels;
920
+ const runnerLabelCount = params.runnerLabelCount ?? runnerLabels.length;
921
+ const invalid = findInvalidLabels(runnerLabels);
922
+
923
+ if (invalid.length > 0) {
924
+ params.issues.push(
925
+ issue({
926
+ code: 'invalid-runner-label',
927
+ message: `Job "${params.sourceName}" has invalid runner label(s): ${invalid.join(', ')}. Labels must match ${RUNNER_LABEL_PATTERN} and be at most ${MAX_RUNNER_LABEL_LENGTH} chars.`,
928
+ path: ['jobs', params.sourceName, 'runner'],
929
+ details: {labels: invalid},
930
+ }),
931
+ );
932
+ }
933
+
934
+ if (runnerLabels.length === 0 && !params.allowEmpty) {
935
+ params.issues.push(
936
+ issue({
937
+ code: 'missing-runner-label',
938
+ message: `Job "${params.sourceName}" must declare at least one runner label. Set "runner" on the job or the workflow, or configure DEFINITION_DEFAULT_RUNNER_LABEL.`,
939
+ path: ['jobs', params.sourceName, 'runner'],
940
+ }),
941
+ );
942
+ }
943
+
944
+ if (runnerLabelCount > MAX_RUNNER_LABELS) {
945
+ params.issues.push(
946
+ issue({
947
+ code: 'too-many-runner-labels',
948
+ message: `Job "${params.sourceName}" declares ${runnerLabelCount} runner labels; the maximum is ${MAX_RUNNER_LABELS}.`,
949
+ path: ['jobs', params.sourceName, 'runner'],
950
+ }),
951
+ );
952
+ }
953
+ }
954
+
955
+ type WorkflowModelStepBaseFields = Pick<
956
+ WorkflowModelStep,
957
+ 'id' | 'key' | 'name' | 'outputs' | 'sourceLocation' | 'gate'
958
+ >;
959
+
960
+ function optionalRunStepTemplates(params: {
961
+ command: WorkflowFieldTemplate | undefined;
962
+ name: WorkflowFieldTemplate | undefined;
963
+ env: WorkflowEnvTemplates | undefined;
964
+ }):
965
+ | {
966
+ command?: WorkflowFieldTemplate;
967
+ name?: WorkflowFieldTemplate;
968
+ env?: WorkflowEnvTemplates;
969
+ }
970
+ | undefined {
971
+ if (params.command === undefined && params.name === undefined && params.env === undefined) {
972
+ return undefined;
973
+ }
974
+
975
+ return {
976
+ ...(params.command === undefined ? {} : {command: params.command}),
977
+ ...(params.name === undefined ? {} : {name: params.name}),
978
+ ...(params.env === undefined ? {} : {env: params.env}),
979
+ };
980
+ }
981
+
982
+ function optionalAgentStepTemplates(params: {
983
+ prompt: WorkflowFieldTemplate | undefined;
984
+ model: WorkflowFieldTemplate | undefined;
985
+ provider: WorkflowFieldTemplate | undefined;
986
+ name: WorkflowFieldTemplate | undefined;
987
+ }):
988
+ | {
989
+ prompt?: WorkflowFieldTemplate;
990
+ model?: WorkflowFieldTemplate;
991
+ provider?: WorkflowFieldTemplate;
992
+ name?: WorkflowFieldTemplate;
993
+ }
994
+ | undefined {
995
+ if (
996
+ params.prompt === undefined &&
997
+ params.model === undefined &&
998
+ params.provider === undefined &&
999
+ params.name === undefined
1000
+ ) {
1001
+ return undefined;
1002
+ }
1003
+
1004
+ return {
1005
+ ...(params.prompt === undefined ? {} : {prompt: params.prompt}),
1006
+ ...(params.model === undefined ? {} : {model: params.model}),
1007
+ ...(params.provider === undefined ? {} : {provider: params.provider}),
1008
+ ...(params.name === undefined ? {} : {name: params.name}),
1009
+ };
1010
+ }