@shipfox/api-workflows 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 (661) 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 +311 -0
  5. package/LICENSE +21 -0
  6. package/dist/core/agent-tool-capability-warning.d.ts +7 -0
  7. package/dist/core/agent-tool-capability-warning.d.ts.map +1 -0
  8. package/dist/core/agent-tool-capability-warning.js +149 -0
  9. package/dist/core/agent-tool-capability-warning.js.map +1 -0
  10. package/dist/core/agent-tools.d.ts +51 -0
  11. package/dist/core/agent-tools.d.ts.map +1 -0
  12. package/dist/core/agent-tools.js +296 -0
  13. package/dist/core/agent-tools.js.map +1 -0
  14. package/dist/core/checkout.d.ts +29 -0
  15. package/dist/core/checkout.d.ts.map +1 -0
  16. package/dist/core/checkout.js +42 -0
  17. package/dist/core/checkout.js.map +1 -0
  18. package/dist/core/condition-trace.d.ts +13 -0
  19. package/dist/core/condition-trace.d.ts.map +1 -0
  20. package/dist/core/condition-trace.js +51 -0
  21. package/dist/core/condition-trace.js.map +1 -0
  22. package/dist/core/entities/index.d.ts +5 -0
  23. package/dist/core/entities/index.d.ts.map +1 -0
  24. package/dist/core/entities/index.js +3 -0
  25. package/dist/core/entities/index.js.map +1 -0
  26. package/dist/core/entities/job-execution.d.ts +30 -0
  27. package/dist/core/entities/job-execution.d.ts.map +1 -0
  28. package/dist/core/entities/job-execution.js +3 -0
  29. package/dist/core/entities/job-execution.js.map +1 -0
  30. package/dist/core/entities/job-listener-event.d.ts +15 -0
  31. package/dist/core/entities/job-listener-event.d.ts.map +1 -0
  32. package/dist/core/entities/job-listener-event.js +3 -0
  33. package/dist/core/entities/job-listener-event.js.map +1 -0
  34. package/dist/core/entities/job.d.ts +54 -0
  35. package/dist/core/entities/job.d.ts.map +1 -0
  36. package/dist/core/entities/job.js +29 -0
  37. package/dist/core/entities/job.js.map +1 -0
  38. package/dist/core/entities/step.d.ts +86 -0
  39. package/dist/core/entities/step.d.ts.map +1 -0
  40. package/dist/core/entities/step.js +12 -0
  41. package/dist/core/entities/step.js.map +1 -0
  42. package/dist/core/entities/workflow-run-attempt.d.ts +20 -0
  43. package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -0
  44. package/dist/core/entities/workflow-run-attempt.js +3 -0
  45. package/dist/core/entities/workflow-run-attempt.js.map +1 -0
  46. package/dist/core/entities/workflow-run.d.ts +66 -0
  47. package/dist/core/entities/workflow-run.d.ts.map +1 -0
  48. package/dist/core/entities/workflow-run.js +10 -0
  49. package/dist/core/entities/workflow-run.js.map +1 -0
  50. package/dist/core/errors.d.ts +85 -0
  51. package/dist/core/errors.d.ts.map +1 -0
  52. package/dist/core/errors.js +134 -0
  53. package/dist/core/errors.js.map +1 -0
  54. package/dist/core/index.d.ts +14 -0
  55. package/dist/core/index.d.ts.map +1 -0
  56. package/dist/core/index.js +9 -0
  57. package/dist/core/index.js.map +1 -0
  58. package/dist/core/job-execution-outcome.d.ts +29 -0
  59. package/dist/core/job-execution-outcome.d.ts.map +1 -0
  60. package/dist/core/job-execution-outcome.js +31 -0
  61. package/dist/core/job-execution-outcome.js.map +1 -0
  62. package/dist/core/job-execution.d.ts +37 -0
  63. package/dist/core/job-execution.d.ts.map +1 -0
  64. package/dist/core/job-execution.js +425 -0
  65. package/dist/core/job-execution.js.map +1 -0
  66. package/dist/core/job-transition/decide-job-activation.d.ts +29 -0
  67. package/dist/core/job-transition/decide-job-activation.d.ts.map +1 -0
  68. package/dist/core/job-transition/decide-job-activation.js +51 -0
  69. package/dist/core/job-transition/decide-job-activation.js.map +1 -0
  70. package/dist/core/job-transition/derive-job-execution-outputs.d.ts +18 -0
  71. package/dist/core/job-transition/derive-job-execution-outputs.d.ts.map +1 -0
  72. package/dist/core/job-transition/derive-job-execution-outputs.js +23 -0
  73. package/dist/core/job-transition/derive-job-execution-outputs.js.map +1 -0
  74. package/dist/core/job-transition/derive-job-success.d.ts +16 -0
  75. package/dist/core/job-transition/derive-job-success.d.ts.map +1 -0
  76. package/dist/core/job-transition/derive-job-success.js +47 -0
  77. package/dist/core/job-transition/derive-job-success.js.map +1 -0
  78. package/dist/core/job-transition/index.d.ts +5 -0
  79. package/dist/core/job-transition/index.d.ts.map +1 -0
  80. package/dist/core/job-transition/index.js +6 -0
  81. package/dist/core/job-transition/index.js.map +1 -0
  82. package/dist/core/job-transition/runtime-completion-status-for-job.d.ts +4 -0
  83. package/dist/core/job-transition/runtime-completion-status-for-job.d.ts.map +1 -0
  84. package/dist/core/job-transition/runtime-completion-status-for-job.js +8 -0
  85. package/dist/core/job-transition/runtime-completion-status-for-job.js.map +1 -0
  86. package/dist/core/listener-execution-materialization.d.ts +30 -0
  87. package/dist/core/listener-execution-materialization.d.ts.map +1 -0
  88. package/dist/core/listener-execution-materialization.js +87 -0
  89. package/dist/core/listener-execution-materialization.js.map +1 -0
  90. package/dist/core/run-workflow.d.ts +11 -0
  91. package/dist/core/run-workflow.d.ts.map +1 -0
  92. package/dist/core/run-workflow.js +28 -0
  93. package/dist/core/run-workflow.js.map +1 -0
  94. package/dist/core/source-control.d.ts +5 -0
  95. package/dist/core/source-control.d.ts.map +1 -0
  96. package/dist/core/source-control.js +19 -0
  97. package/dist/core/source-control.js.map +1 -0
  98. package/dist/core/step-config/agent.d.ts +49 -0
  99. package/dist/core/step-config/agent.d.ts.map +1 -0
  100. package/dist/core/step-config/agent.js +337 -0
  101. package/dist/core/step-config/agent.js.map +1 -0
  102. package/dist/core/step-config/assemble-run-context.d.ts +94 -0
  103. package/dist/core/step-config/assemble-run-context.d.ts.map +1 -0
  104. package/dist/core/step-config/assemble-run-context.js +393 -0
  105. package/dist/core/step-config/assemble-run-context.js.map +1 -0
  106. package/dist/core/step-config/complete-step-dispatch-config.d.ts +13 -0
  107. package/dist/core/step-config/complete-step-dispatch-config.d.ts.map +1 -0
  108. package/dist/core/step-config/complete-step-dispatch-config.js +38 -0
  109. package/dist/core/step-config/complete-step-dispatch-config.js.map +1 -0
  110. package/dist/core/step-config/fields.d.ts +38 -0
  111. package/dist/core/step-config/fields.d.ts.map +1 -0
  112. package/dist/core/step-config/fields.js +72 -0
  113. package/dist/core/step-config/fields.js.map +1 -0
  114. package/dist/core/step-config/index.d.ts +8 -0
  115. package/dist/core/step-config/index.d.ts.map +1 -0
  116. package/dist/core/step-config/index.js +7 -0
  117. package/dist/core/step-config/index.js.map +1 -0
  118. package/dist/core/step-config/materialize-job-execution-steps.d.ts +35 -0
  119. package/dist/core/step-config/materialize-job-execution-steps.d.ts.map +1 -0
  120. package/dist/core/step-config/materialize-job-execution-steps.js +92 -0
  121. package/dist/core/step-config/materialize-job-execution-steps.js.map +1 -0
  122. package/dist/core/step-config/materialize-workflow-model.d.ts +42 -0
  123. package/dist/core/step-config/materialize-workflow-model.d.ts.map +1 -0
  124. package/dist/core/step-config/materialize-workflow-model.js +96 -0
  125. package/dist/core/step-config/materialize-workflow-model.js.map +1 -0
  126. package/dist/core/step-config/resolve-job-execution-name.d.ts +18 -0
  127. package/dist/core/step-config/resolve-job-execution-name.d.ts.map +1 -0
  128. package/dist/core/step-config/resolve-job-execution-name.js +36 -0
  129. package/dist/core/step-config/resolve-job-execution-name.js.map +1 -0
  130. package/dist/core/step-config/resolve-step-config.d.ts +33 -0
  131. package/dist/core/step-config/resolve-step-config.d.ts.map +1 -0
  132. package/dist/core/step-config/resolve-step-config.js +164 -0
  133. package/dist/core/step-config/resolve-step-config.js.map +1 -0
  134. package/dist/core/step-config/run.d.ts +39 -0
  135. package/dist/core/step-config/run.d.ts.map +1 -0
  136. package/dist/core/step-config/run.js +360 -0
  137. package/dist/core/step-config/run.js.map +1 -0
  138. package/dist/core/step-config/workflow-evaluation-context.d.ts +6 -0
  139. package/dist/core/step-config/workflow-evaluation-context.d.ts.map +1 -0
  140. package/dist/core/step-config/workflow-evaluation-context.js +3 -0
  141. package/dist/core/step-config/workflow-evaluation-context.js.map +1 -0
  142. package/dist/core/step-transition/apply-step-transition.d.ts +26 -0
  143. package/dist/core/step-transition/apply-step-transition.d.ts.map +1 -0
  144. package/dist/core/step-transition/apply-step-transition.js +141 -0
  145. package/dist/core/step-transition/apply-step-transition.js.map +1 -0
  146. package/dist/core/step-transition/decide-step-transition.d.ts +71 -0
  147. package/dist/core/step-transition/decide-step-transition.d.ts.map +1 -0
  148. package/dist/core/step-transition/decide-step-transition.js +110 -0
  149. package/dist/core/step-transition/decide-step-transition.js.map +1 -0
  150. package/dist/core/step-transition/evaluate-gate.d.ts +28 -0
  151. package/dist/core/step-transition/evaluate-gate.d.ts.map +1 -0
  152. package/dist/core/step-transition/evaluate-gate.js +159 -0
  153. package/dist/core/step-transition/evaluate-gate.js.map +1 -0
  154. package/dist/core/step-transition/read-step-outputs.d.ts +3 -0
  155. package/dist/core/step-transition/read-step-outputs.d.ts.map +1 -0
  156. package/dist/core/step-transition/read-step-outputs.js +24 -0
  157. package/dist/core/step-transition/read-step-outputs.js.map +1 -0
  158. package/dist/core/workflow-run-creation.d.ts +43 -0
  159. package/dist/core/workflow-run-creation.d.ts.map +1 -0
  160. package/dist/core/workflow-run-creation.js +80 -0
  161. package/dist/core/workflow-run-creation.js.map +1 -0
  162. package/dist/core/workflow-scheduling/index.d.ts +5 -0
  163. package/dist/core/workflow-scheduling/index.d.ts.map +1 -0
  164. package/dist/core/workflow-scheduling/index.js +4 -0
  165. package/dist/core/workflow-scheduling/index.js.map +1 -0
  166. package/dist/core/workflow-scheduling/run-progress.d.ts +20 -0
  167. package/dist/core/workflow-scheduling/run-progress.d.ts.map +1 -0
  168. package/dist/core/workflow-scheduling/run-progress.js +40 -0
  169. package/dist/core/workflow-scheduling/run-progress.js.map +1 -0
  170. package/dist/core/workflow-scheduling/runtime-dag.d.ts +10 -0
  171. package/dist/core/workflow-scheduling/runtime-dag.d.ts.map +1 -0
  172. package/dist/core/workflow-scheduling/runtime-dag.js +3 -0
  173. package/dist/core/workflow-scheduling/runtime-dag.js.map +1 -0
  174. package/dist/core/workflow-scheduling/runtime-scheduling-command.d.ts +16 -0
  175. package/dist/core/workflow-scheduling/runtime-scheduling-command.d.ts.map +1 -0
  176. package/dist/core/workflow-scheduling/runtime-scheduling-command.js +3 -0
  177. package/dist/core/workflow-scheduling/runtime-scheduling-command.js.map +1 -0
  178. package/dist/core/workflow-scheduling/schedule-runtime-dag.d.ts +9 -0
  179. package/dist/core/workflow-scheduling/schedule-runtime-dag.d.ts.map +1 -0
  180. package/dist/core/workflow-scheduling/schedule-runtime-dag.js +72 -0
  181. package/dist/core/workflow-scheduling/schedule-runtime-dag.js.map +1 -0
  182. package/dist/db/db.d.ts +4959 -0
  183. package/dist/db/db.d.ts.map +1 -0
  184. package/dist/db/db.js +37 -0
  185. package/dist/db/db.js.map +1 -0
  186. package/dist/db/index.d.ts +9 -0
  187. package/dist/db/index.d.ts.map +1 -0
  188. package/dist/db/index.js +10 -0
  189. package/dist/db/index.js.map +1 -0
  190. package/dist/db/job-listener-events.d.ts +18 -0
  191. package/dist/db/job-listener-events.d.ts.map +1 -0
  192. package/dist/db/job-listener-events.js +59 -0
  193. package/dist/db/job-listener-events.js.map +1 -0
  194. package/dist/db/job-listeners.d.ts +55 -0
  195. package/dist/db/job-listeners.d.ts.map +1 -0
  196. package/dist/db/job-listeners.js +358 -0
  197. package/dist/db/job-listeners.js.map +1 -0
  198. package/dist/db/outbox-writes.d.ts +12 -0
  199. package/dist/db/outbox-writes.d.ts.map +1 -0
  200. package/dist/db/outbox-writes.js +20 -0
  201. package/dist/db/outbox-writes.js.map +1 -0
  202. package/dist/db/runner-lease-table.d.ts +469 -0
  203. package/dist/db/runner-lease-table.d.ts.map +1 -0
  204. package/dist/db/runner-lease-table.js +42 -0
  205. package/dist/db/runner-lease-table.js.map +1 -0
  206. package/dist/db/schema/common.d.ts +2 -0
  207. package/dist/db/schema/common.d.ts.map +1 -0
  208. package/dist/db/schema/common.js +4 -0
  209. package/dist/db/schema/common.js.map +1 -0
  210. package/dist/db/schema/job-executions.d.ts +311 -0
  211. package/dist/db/schema/job-executions.d.ts.map +1 -0
  212. package/dist/db/schema/job-executions.js +76 -0
  213. package/dist/db/schema/job-executions.js.map +1 -0
  214. package/dist/db/schema/job-listener-events.d.ts +200 -0
  215. package/dist/db/schema/job-listener-events.d.ts.map +1 -0
  216. package/dist/db/schema/job-listener-events.js +50 -0
  217. package/dist/db/schema/job-listener-events.js.map +1 -0
  218. package/dist/db/schema/jobs.d.ts +542 -0
  219. package/dist/db/schema/jobs.d.ts.map +1 -0
  220. package/dist/db/schema/jobs.js +121 -0
  221. package/dist/db/schema/jobs.js.map +1 -0
  222. package/dist/db/schema/outbox.d.ts +195 -0
  223. package/dist/db/schema/outbox.d.ts.map +1 -0
  224. package/dist/db/schema/outbox.js +5 -0
  225. package/dist/db/schema/outbox.js.map +1 -0
  226. package/dist/db/schema/step-attempts.d.ts +330 -0
  227. package/dist/db/schema/step-attempts.d.ts.map +1 -0
  228. package/dist/db/schema/step-attempts.js +80 -0
  229. package/dist/db/schema/step-attempts.js.map +1 -0
  230. package/dist/db/schema/steps.d.ts +352 -0
  231. package/dist/db/schema/steps.d.ts.map +1 -0
  232. package/dist/db/schema/steps.js +80 -0
  233. package/dist/db/schema/steps.js.map +1 -0
  234. package/dist/db/schema/workflow-run-attempts.d.ts +239 -0
  235. package/dist/db/schema/workflow-run-attempts.d.ts.map +1 -0
  236. package/dist/db/schema/workflow-run-attempts.js +53 -0
  237. package/dist/db/schema/workflow-run-attempts.js.map +1 -0
  238. package/dist/db/schema/workflow-runs.d.ts +360 -0
  239. package/dist/db/schema/workflow-runs.d.ts.map +1 -0
  240. package/dist/db/schema/workflow-runs.js +85 -0
  241. package/dist/db/schema/workflow-runs.js.map +1 -0
  242. package/dist/db/workflow-runs/job-executions.d.ts +49 -0
  243. package/dist/db/workflow-runs/job-executions.d.ts.map +1 -0
  244. package/dist/db/workflow-runs/job-executions.js +271 -0
  245. package/dist/db/workflow-runs/job-executions.js.map +1 -0
  246. package/dist/db/workflow-runs/jobs.d.ts +60 -0
  247. package/dist/db/workflow-runs/jobs.d.ts.map +1 -0
  248. package/dist/db/workflow-runs/jobs.js +288 -0
  249. package/dist/db/workflow-runs/jobs.js.map +1 -0
  250. package/dist/db/workflow-runs/outbox.d.ts +20 -0
  251. package/dist/db/workflow-runs/outbox.d.ts.map +1 -0
  252. package/dist/db/workflow-runs/outbox.js +68 -0
  253. package/dist/db/workflow-runs/outbox.js.map +1 -0
  254. package/dist/db/workflow-runs/queries.d.ts +71 -0
  255. package/dist/db/workflow-runs/queries.d.ts.map +1 -0
  256. package/dist/db/workflow-runs/queries.js +253 -0
  257. package/dist/db/workflow-runs/queries.js.map +1 -0
  258. package/dist/db/workflow-runs/run-create.d.ts +32 -0
  259. package/dist/db/workflow-runs/run-create.d.ts.map +1 -0
  260. package/dist/db/workflow-runs/run-create.js +288 -0
  261. package/dist/db/workflow-runs/run-create.js.map +1 -0
  262. package/dist/db/workflow-runs/run-graph.d.ts +27 -0
  263. package/dist/db/workflow-runs/run-graph.d.ts.map +1 -0
  264. package/dist/db/workflow-runs/run-graph.js +63 -0
  265. package/dist/db/workflow-runs/run-graph.js.map +1 -0
  266. package/dist/db/workflow-runs/run-rerun.d.ts +8 -0
  267. package/dist/db/workflow-runs/run-rerun.d.ts.map +1 -0
  268. package/dist/db/workflow-runs/run-rerun.js +207 -0
  269. package/dist/db/workflow-runs/run-rerun.js.map +1 -0
  270. package/dist/db/workflow-runs/run-status.d.ts +24 -0
  271. package/dist/db/workflow-runs/run-status.d.ts.map +1 -0
  272. package/dist/db/workflow-runs/run-status.js +249 -0
  273. package/dist/db/workflow-runs/run-status.js.map +1 -0
  274. package/dist/db/workflow-runs/runs.d.ts +7 -0
  275. package/dist/db/workflow-runs/runs.d.ts.map +1 -0
  276. package/dist/db/workflow-runs/runs.js +5 -0
  277. package/dist/db/workflow-runs/runs.js.map +1 -0
  278. package/dist/db/workflow-runs/shared.d.ts +24 -0
  279. package/dist/db/workflow-runs/shared.d.ts.map +1 -0
  280. package/dist/db/workflow-runs/shared.js +50 -0
  281. package/dist/db/workflow-runs/shared.js.map +1 -0
  282. package/dist/db/workflow-runs/steps.d.ts +83 -0
  283. package/dist/db/workflow-runs/steps.d.ts.map +1 -0
  284. package/dist/db/workflow-runs/steps.js +246 -0
  285. package/dist/db/workflow-runs/steps.js.map +1 -0
  286. package/dist/db/workflow-runs.d.ts +12 -0
  287. package/dist/db/workflow-runs.d.ts.map +1 -0
  288. package/dist/db/workflow-runs.js +8 -0
  289. package/dist/db/workflow-runs.js.map +1 -0
  290. package/dist/index.d.ts +10 -0
  291. package/dist/index.d.ts.map +1 -0
  292. package/dist/index.js +54 -0
  293. package/dist/index.js.map +1 -0
  294. package/dist/metrics/index.d.ts +2 -0
  295. package/dist/metrics/index.d.ts.map +1 -0
  296. package/dist/metrics/index.js +3 -0
  297. package/dist/metrics/index.js.map +1 -0
  298. package/dist/metrics/instance.d.ts +20 -0
  299. package/dist/metrics/instance.d.ts.map +1 -0
  300. package/dist/metrics/instance.js +127 -0
  301. package/dist/metrics/instance.js.map +1 -0
  302. package/dist/metrics/service.d.ts +2 -0
  303. package/dist/metrics/service.d.ts.map +1 -0
  304. package/dist/metrics/service.js +30 -0
  305. package/dist/metrics/service.js.map +1 -0
  306. package/dist/presentation/dto/checkout-token.d.ts +6 -0
  307. package/dist/presentation/dto/checkout-token.d.ts.map +1 -0
  308. package/dist/presentation/dto/checkout-token.js +74 -0
  309. package/dist/presentation/dto/checkout-token.js.map +1 -0
  310. package/dist/presentation/dto/index.d.ts +4 -0
  311. package/dist/presentation/dto/index.d.ts.map +1 -0
  312. package/dist/presentation/dto/index.js +5 -0
  313. package/dist/presentation/dto/index.js.map +1 -0
  314. package/dist/presentation/dto/job.d.ts +6 -0
  315. package/dist/presentation/dto/job.d.ts.map +1 -0
  316. package/dist/presentation/dto/job.js +59 -0
  317. package/dist/presentation/dto/job.js.map +1 -0
  318. package/dist/presentation/dto/step.d.ts +6 -0
  319. package/dist/presentation/dto/step.d.ts.map +1 -0
  320. package/dist/presentation/dto/step.js +162 -0
  321. package/dist/presentation/dto/step.js.map +1 -0
  322. package/dist/presentation/dto/workflow-run.d.ts +6 -0
  323. package/dist/presentation/dto/workflow-run.d.ts.map +1 -0
  324. package/dist/presentation/dto/workflow-run.js +35 -0
  325. package/dist/presentation/dto/workflow-run.js.map +1 -0
  326. package/dist/presentation/index.d.ts +3 -0
  327. package/dist/presentation/index.d.ts.map +1 -0
  328. package/dist/presentation/index.js +4 -0
  329. package/dist/presentation/index.js.map +1 -0
  330. package/dist/presentation/routes/agent-runtime-config.d.ts +2 -0
  331. package/dist/presentation/routes/agent-runtime-config.d.ts.map +1 -0
  332. package/dist/presentation/routes/agent-runtime-config.js +71 -0
  333. package/dist/presentation/routes/agent-runtime-config.js.map +1 -0
  334. package/dist/presentation/routes/cancel-run.d.ts +2 -0
  335. package/dist/presentation/routes/cancel-run.d.ts.map +1 -0
  336. package/dist/presentation/routes/cancel-run.js +46 -0
  337. package/dist/presentation/routes/cancel-run.js.map +1 -0
  338. package/dist/presentation/routes/checkout-token.d.ts +2 -0
  339. package/dist/presentation/routes/checkout-token.d.ts.map +1 -0
  340. package/dist/presentation/routes/checkout-token.js +64 -0
  341. package/dist/presentation/routes/checkout-token.js.map +1 -0
  342. package/dist/presentation/routes/get-run-aggregates.d.ts +2 -0
  343. package/dist/presentation/routes/get-run-aggregates.d.ts.map +1 -0
  344. package/dist/presentation/routes/get-run-aggregates.js +60 -0
  345. package/dist/presentation/routes/get-run-aggregates.js.map +1 -0
  346. package/dist/presentation/routes/get-run.d.ts +2 -0
  347. package/dist/presentation/routes/get-run.d.ts.map +1 -0
  348. package/dist/presentation/routes/get-run.js +60 -0
  349. package/dist/presentation/routes/get-run.js.map +1 -0
  350. package/dist/presentation/routes/get-step-secrets.d.ts +2 -0
  351. package/dist/presentation/routes/get-step-secrets.d.ts.map +1 -0
  352. package/dist/presentation/routes/get-step-secrets.js +115 -0
  353. package/dist/presentation/routes/get-step-secrets.js.map +1 -0
  354. package/dist/presentation/routes/index.d.ts +4 -0
  355. package/dist/presentation/routes/index.d.ts.map +1 -0
  356. package/dist/presentation/routes/index.js +41 -0
  357. package/dist/presentation/routes/index.js.map +1 -0
  358. package/dist/presentation/routes/leased-step.d.ts +14 -0
  359. package/dist/presentation/routes/leased-step.d.ts.map +1 -0
  360. package/dist/presentation/routes/leased-step.js +50 -0
  361. package/dist/presentation/routes/leased-step.js.map +1 -0
  362. package/dist/presentation/routes/list-run-attempts.d.ts +2 -0
  363. package/dist/presentation/routes/list-run-attempts.d.ts.map +1 -0
  364. package/dist/presentation/routes/list-run-attempts.js +35 -0
  365. package/dist/presentation/routes/list-run-attempts.js.map +1 -0
  366. package/dist/presentation/routes/list-runs.d.ts +2 -0
  367. package/dist/presentation/routes/list-runs.d.ts.map +1 -0
  368. package/dist/presentation/routes/list-runs.js +70 -0
  369. package/dist/presentation/routes/list-runs.js.map +1 -0
  370. package/dist/presentation/routes/next-step.d.ts +2 -0
  371. package/dist/presentation/routes/next-step.d.ts.map +1 -0
  372. package/dist/presentation/routes/next-step.js +65 -0
  373. package/dist/presentation/routes/next-step.js.map +1 -0
  374. package/dist/presentation/routes/report-step.d.ts +2 -0
  375. package/dist/presentation/routes/report-step.d.ts.map +1 -0
  376. package/dist/presentation/routes/report-step.js +73 -0
  377. package/dist/presentation/routes/report-step.js.map +1 -0
  378. package/dist/presentation/routes/require-accessible-run.d.ts +7 -0
  379. package/dist/presentation/routes/require-accessible-run.d.ts.map +1 -0
  380. package/dist/presentation/routes/require-accessible-run.js +25 -0
  381. package/dist/presentation/routes/require-accessible-run.js.map +1 -0
  382. package/dist/presentation/routes/rerun-run.d.ts +2 -0
  383. package/dist/presentation/routes/rerun-run.d.ts.map +1 -0
  384. package/dist/presentation/routes/rerun-run.js +56 -0
  385. package/dist/presentation/routes/rerun-run.js.map +1 -0
  386. package/dist/presentation/subscribers/index.d.ts +8 -0
  387. package/dist/presentation/subscribers/index.d.ts.map +1 -0
  388. package/dist/presentation/subscribers/index.js +9 -0
  389. package/dist/presentation/subscribers/index.js.map +1 -0
  390. package/dist/presentation/subscribers/on-job-event-delivered.d.ts +3 -0
  391. package/dist/presentation/subscribers/on-job-event-delivered.d.ts.map +1 -0
  392. package/dist/presentation/subscribers/on-job-event-delivered.js +24 -0
  393. package/dist/presentation/subscribers/on-job-event-delivered.js.map +1 -0
  394. package/dist/presentation/subscribers/on-job-steps-settled.d.ts +3 -0
  395. package/dist/presentation/subscribers/on-job-steps-settled.d.ts.map +1 -0
  396. package/dist/presentation/subscribers/on-job-steps-settled.js +36 -0
  397. package/dist/presentation/subscribers/on-job-steps-settled.js.map +1 -0
  398. package/dist/presentation/subscribers/on-runner-job-claimed.d.ts +3 -0
  399. package/dist/presentation/subscribers/on-runner-job-claimed.d.ts.map +1 -0
  400. package/dist/presentation/subscribers/on-runner-job-claimed.js +20 -0
  401. package/dist/presentation/subscribers/on-runner-job-claimed.js.map +1 -0
  402. package/dist/presentation/subscribers/on-runner-job-lease-expired.d.ts +3 -0
  403. package/dist/presentation/subscribers/on-runner-job-lease-expired.d.ts.map +1 -0
  404. package/dist/presentation/subscribers/on-runner-job-lease-expired.js +35 -0
  405. package/dist/presentation/subscribers/on-runner-job-lease-expired.js.map +1 -0
  406. package/dist/presentation/subscribers/on-runner-job-queued.d.ts +3 -0
  407. package/dist/presentation/subscribers/on-runner-job-queued.d.ts.map +1 -0
  408. package/dist/presentation/subscribers/on-runner-job-queued.js +20 -0
  409. package/dist/presentation/subscribers/on-runner-job-queued.js.map +1 -0
  410. package/dist/presentation/subscribers/on-workflow-run-attempt-created.d.ts +3 -0
  411. package/dist/presentation/subscribers/on-workflow-run-attempt-created.d.ts.map +1 -0
  412. package/dist/presentation/subscribers/on-workflow-run-attempt-created.js +35 -0
  413. package/dist/presentation/subscribers/on-workflow-run-attempt-created.js.map +1 -0
  414. package/dist/presentation/subscribers/on-workflow-run-cancelled.d.ts +3 -0
  415. package/dist/presentation/subscribers/on-workflow-run-cancelled.d.ts.map +1 -0
  416. package/dist/presentation/subscribers/on-workflow-run-cancelled.js +25 -0
  417. package/dist/presentation/subscribers/on-workflow-run-cancelled.js.map +1 -0
  418. package/dist/temporal/activities/index.d.ts +23 -0
  419. package/dist/temporal/activities/index.d.ts.map +1 -0
  420. package/dist/temporal/activities/index.js +26 -0
  421. package/dist/temporal/activities/index.js.map +1 -0
  422. package/dist/temporal/activities/orchestration-activities.d.ts +130 -0
  423. package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -0
  424. package/dist/temporal/activities/orchestration-activities.js +188 -0
  425. package/dist/temporal/activities/orchestration-activities.js.map +1 -0
  426. package/dist/temporal/constants.d.ts +7 -0
  427. package/dist/temporal/constants.d.ts.map +1 -0
  428. package/dist/temporal/constants.js +8 -0
  429. package/dist/temporal/constants.js.map +1 -0
  430. package/dist/temporal/index.d.ts +3 -0
  431. package/dist/temporal/index.d.ts.map +1 -0
  432. package/dist/temporal/index.js +4 -0
  433. package/dist/temporal/index.js.map +1 -0
  434. package/dist/temporal/workflow-not-found.d.ts +2 -0
  435. package/dist/temporal/workflow-not-found.d.ts.map +1 -0
  436. package/dist/temporal/workflow-not-found.js +10 -0
  437. package/dist/temporal/workflow-not-found.js.map +1 -0
  438. package/dist/temporal/workflows/deadline.d.ts +3 -0
  439. package/dist/temporal/workflows/deadline.d.ts.map +1 -0
  440. package/dist/temporal/workflows/deadline.js +8 -0
  441. package/dist/temporal/workflows/deadline.js.map +1 -0
  442. package/dist/temporal/workflows/index.d.ts +4 -0
  443. package/dist/temporal/workflows/index.d.ts.map +1 -0
  444. package/dist/temporal/workflows/index.js +5 -0
  445. package/dist/temporal/workflows/index.js.map +1 -0
  446. package/dist/temporal/workflows/job-execution-orchestration.d.ts +27 -0
  447. package/dist/temporal/workflows/job-execution-orchestration.d.ts.map +1 -0
  448. package/dist/temporal/workflows/job-execution-orchestration.js +265 -0
  449. package/dist/temporal/workflows/job-execution-orchestration.js.map +1 -0
  450. package/dist/temporal/workflows/job-listener-orchestration.d.ts +40 -0
  451. package/dist/temporal/workflows/job-listener-orchestration.d.ts.map +1 -0
  452. package/dist/temporal/workflows/job-listener-orchestration.js +332 -0
  453. package/dist/temporal/workflows/job-listener-orchestration.js.map +1 -0
  454. package/dist/temporal/workflows/run-orchestration.d.ts +8 -0
  455. package/dist/temporal/workflows/run-orchestration.d.ts.map +1 -0
  456. package/dist/temporal/workflows/run-orchestration.js +243 -0
  457. package/dist/temporal/workflows/run-orchestration.js.map +1 -0
  458. package/dist/temporal/workflows/test-env.d.ts +114 -0
  459. package/dist/temporal/workflows/test-env.d.ts.map +1 -0
  460. package/dist/temporal/workflows/test-env.js +323 -0
  461. package/dist/temporal/workflows/test-env.js.map +1 -0
  462. package/dist/tsconfig.test.tsbuildinfo +1 -0
  463. package/drizzle/0000_initial.sql +211 -0
  464. package/drizzle/0001_agent_tool_materialization.sql +1 -0
  465. package/drizzle/meta/0000_snapshot.json +1613 -0
  466. package/drizzle/meta/_journal.json +20 -0
  467. package/drizzle.config.ts +7 -0
  468. package/package.json +91 -0
  469. package/src/core/README.md +79 -0
  470. package/src/core/agent-tool-capability-warning.test.ts +280 -0
  471. package/src/core/agent-tool-capability-warning.ts +200 -0
  472. package/src/core/agent-tools.ts +422 -0
  473. package/src/core/checkout.test.ts +112 -0
  474. package/src/core/checkout.ts +63 -0
  475. package/src/core/condition-trace.ts +64 -0
  476. package/src/core/entities/index.ts +9 -0
  477. package/src/core/entities/job-execution.ts +32 -0
  478. package/src/core/entities/job-listener-event.ts +15 -0
  479. package/src/core/entities/job.ts +91 -0
  480. package/src/core/entities/step.ts +126 -0
  481. package/src/core/entities/workflow-run-attempt.ts +21 -0
  482. package/src/core/entities/workflow-run.ts +94 -0
  483. package/src/core/errors.test.ts +45 -0
  484. package/src/core/errors.ts +209 -0
  485. package/src/core/index.ts +54 -0
  486. package/src/core/job-execution-outcome.ts +64 -0
  487. package/src/core/job-execution.test.ts +1560 -0
  488. package/src/core/job-execution.ts +581 -0
  489. package/src/core/job-transition/decide-job-activation.test.ts +196 -0
  490. package/src/core/job-transition/decide-job-activation.ts +79 -0
  491. package/src/core/job-transition/derive-job-execution-outputs.ts +41 -0
  492. package/src/core/job-transition/derive-job-success.test.ts +133 -0
  493. package/src/core/job-transition/derive-job-success.ts +67 -0
  494. package/src/core/job-transition/index.ts +11 -0
  495. package/src/core/job-transition/runtime-completion-status-for-job.ts +14 -0
  496. package/src/core/listener-execution-materialization.test.ts +49 -0
  497. package/src/core/listener-execution-materialization.ts +162 -0
  498. package/src/core/run-workflow.test.ts +229 -0
  499. package/src/core/run-workflow.ts +41 -0
  500. package/src/core/source-control.test.ts +17 -0
  501. package/src/core/source-control.ts +22 -0
  502. package/src/core/step-config/agent.ts +459 -0
  503. package/src/core/step-config/assemble-run-context.test.ts +1021 -0
  504. package/src/core/step-config/assemble-run-context.ts +535 -0
  505. package/src/core/step-config/complete-step-dispatch-config.test.ts +354 -0
  506. package/src/core/step-config/complete-step-dispatch-config.ts +40 -0
  507. package/src/core/step-config/fields.ts +111 -0
  508. package/src/core/step-config/index.ts +41 -0
  509. package/src/core/step-config/materialize-job-execution-steps.test.ts +494 -0
  510. package/src/core/step-config/materialize-job-execution-steps.ts +136 -0
  511. package/src/core/step-config/materialize-workflow-model.test.ts +1214 -0
  512. package/src/core/step-config/materialize-workflow-model.ts +140 -0
  513. package/src/core/step-config/resolve-job-execution-name.test.ts +94 -0
  514. package/src/core/step-config/resolve-job-execution-name.ts +51 -0
  515. package/src/core/step-config/resolve-step-config.ts +199 -0
  516. package/src/core/step-config/run.ts +467 -0
  517. package/src/core/step-config/workflow-evaluation-context.ts +6 -0
  518. package/src/core/step-transition/apply-step-transition.ts +163 -0
  519. package/src/core/step-transition/decide-step-transition.test.ts +307 -0
  520. package/src/core/step-transition/decide-step-transition.ts +200 -0
  521. package/src/core/step-transition/evaluate-gate.test.ts +317 -0
  522. package/src/core/step-transition/evaluate-gate.ts +168 -0
  523. package/src/core/step-transition/read-step-outputs.ts +27 -0
  524. package/src/core/workflow-run-creation.test.ts +155 -0
  525. package/src/core/workflow-run-creation.ts +131 -0
  526. package/src/core/workflow-scheduling/index.ts +15 -0
  527. package/src/core/workflow-scheduling/run-progress.test.ts +46 -0
  528. package/src/core/workflow-scheduling/run-progress.ts +68 -0
  529. package/src/core/workflow-scheduling/runtime-dag.ts +10 -0
  530. package/src/core/workflow-scheduling/runtime-scheduling-command.ts +20 -0
  531. package/src/core/workflow-scheduling/schedule-runtime-dag.test.ts +197 -0
  532. package/src/core/workflow-scheduling/schedule-runtime-dag.ts +115 -0
  533. package/src/db/db.ts +40 -0
  534. package/src/db/index.ts +86 -0
  535. package/src/db/job-listener-events.test.ts +113 -0
  536. package/src/db/job-listener-events.ts +73 -0
  537. package/src/db/job-listeners.test.ts +865 -0
  538. package/src/db/job-listeners.ts +564 -0
  539. package/src/db/outbox-writes.ts +40 -0
  540. package/src/db/rerun-listener.test.ts +64 -0
  541. package/src/db/run-timeout.test.ts +102 -0
  542. package/src/db/runner-lease-table.ts +39 -0
  543. package/src/db/schema/common.ts +3 -0
  544. package/src/db/schema/job-executions.ts +85 -0
  545. package/src/db/schema/job-listener-events.ts +58 -0
  546. package/src/db/schema/jobs.ts +136 -0
  547. package/src/db/schema/outbox.ts +4 -0
  548. package/src/db/schema/step-attempts.ts +99 -0
  549. package/src/db/schema/steps.ts +99 -0
  550. package/src/db/schema/workflow-run-attempts.ts +59 -0
  551. package/src/db/schema/workflow-runs.ts +112 -0
  552. package/src/db/workflow-runs/job-executions.test.ts +99 -0
  553. package/src/db/workflow-runs/job-executions.ts +421 -0
  554. package/src/db/workflow-runs/job-status.test.ts +532 -0
  555. package/src/db/workflow-runs/jobs.test.ts +194 -0
  556. package/src/db/workflow-runs/jobs.ts +418 -0
  557. package/src/db/workflow-runs/outbox.ts +109 -0
  558. package/src/db/workflow-runs/queries.test.ts +256 -0
  559. package/src/db/workflow-runs/queries.ts +437 -0
  560. package/src/db/workflow-runs/run-create.test.ts +1281 -0
  561. package/src/db/workflow-runs/run-create.ts +373 -0
  562. package/src/db/workflow-runs/run-graph.ts +95 -0
  563. package/src/db/workflow-runs/run-rerun.test.ts +625 -0
  564. package/src/db/workflow-runs/run-rerun.ts +266 -0
  565. package/src/db/workflow-runs/run-status.test.ts +425 -0
  566. package/src/db/workflow-runs/run-status.ts +391 -0
  567. package/src/db/workflow-runs/runs.ts +17 -0
  568. package/src/db/workflow-runs/shared.ts +84 -0
  569. package/src/db/workflow-runs/steps.test.ts +191 -0
  570. package/src/db/workflow-runs/steps.ts +476 -0
  571. package/src/db/workflow-runs.ts +111 -0
  572. package/src/index.ts +99 -0
  573. package/src/metrics/index.ts +1 -0
  574. package/src/metrics/instance.ts +160 -0
  575. package/src/metrics/service.ts +30 -0
  576. package/src/presentation/dto/checkout-token.test.ts +107 -0
  577. package/src/presentation/dto/checkout-token.ts +85 -0
  578. package/src/presentation/dto/index.ts +3 -0
  579. package/src/presentation/dto/job.test.ts +108 -0
  580. package/src/presentation/dto/job.ts +64 -0
  581. package/src/presentation/dto/step.test.ts +307 -0
  582. package/src/presentation/dto/step.ts +145 -0
  583. package/src/presentation/dto/workflow-run.ts +38 -0
  584. package/src/presentation/index.ts +10 -0
  585. package/src/presentation/routes/agent-runtime-config.test.ts +478 -0
  586. package/src/presentation/routes/agent-runtime-config.ts +81 -0
  587. package/src/presentation/routes/cancel-run.test.ts +156 -0
  588. package/src/presentation/routes/cancel-run.ts +37 -0
  589. package/src/presentation/routes/checkout-token.test.ts +366 -0
  590. package/src/presentation/routes/checkout-token.ts +61 -0
  591. package/src/presentation/routes/get-run-aggregates.test.ts +114 -0
  592. package/src/presentation/routes/get-run-aggregates.ts +69 -0
  593. package/src/presentation/routes/get-run.test.ts +604 -0
  594. package/src/presentation/routes/get-run.ts +66 -0
  595. package/src/presentation/routes/get-step-secrets.test.ts +260 -0
  596. package/src/presentation/routes/get-step-secrets.ts +117 -0
  597. package/src/presentation/routes/index.test.ts +71 -0
  598. package/src/presentation/routes/index.ts +42 -0
  599. package/src/presentation/routes/leased-step.ts +54 -0
  600. package/src/presentation/routes/list-run-attempts.test.ts +163 -0
  601. package/src/presentation/routes/list-run-attempts.ts +27 -0
  602. package/src/presentation/routes/list-runs.test.ts +244 -0
  603. package/src/presentation/routes/list-runs.ts +83 -0
  604. package/src/presentation/routes/next-step.test.ts +366 -0
  605. package/src/presentation/routes/next-step.ts +62 -0
  606. package/src/presentation/routes/report-step.test.ts +374 -0
  607. package/src/presentation/routes/report-step.ts +61 -0
  608. package/src/presentation/routes/require-accessible-run.ts +27 -0
  609. package/src/presentation/routes/rerun-run.test.ts +202 -0
  610. package/src/presentation/routes/rerun-run.ts +48 -0
  611. package/src/presentation/subscribers/index.ts +7 -0
  612. package/src/presentation/subscribers/on-job-event-delivered.test.ts +71 -0
  613. package/src/presentation/subscribers/on-job-event-delivered.ts +30 -0
  614. package/src/presentation/subscribers/on-job-steps-settled.test.ts +75 -0
  615. package/src/presentation/subscribers/on-job-steps-settled.ts +42 -0
  616. package/src/presentation/subscribers/on-runner-job-claimed.test.ts +57 -0
  617. package/src/presentation/subscribers/on-runner-job-claimed.ts +23 -0
  618. package/src/presentation/subscribers/on-runner-job-lease-expired.test.ts +73 -0
  619. package/src/presentation/subscribers/on-runner-job-lease-expired.ts +36 -0
  620. package/src/presentation/subscribers/on-runner-job-queued.test.ts +57 -0
  621. package/src/presentation/subscribers/on-runner-job-queued.ts +23 -0
  622. package/src/presentation/subscribers/on-workflow-run-attempt-created.test.ts +69 -0
  623. package/src/presentation/subscribers/on-workflow-run-attempt-created.ts +40 -0
  624. package/src/presentation/subscribers/on-workflow-run-cancelled.test.ts +59 -0
  625. package/src/presentation/subscribers/on-workflow-run-cancelled.ts +29 -0
  626. package/src/temporal/activities/index.ts +45 -0
  627. package/src/temporal/activities/orchestration-activities.test.ts +115 -0
  628. package/src/temporal/activities/orchestration-activities.ts +305 -0
  629. package/src/temporal/constants.ts +6 -0
  630. package/src/temporal/index.ts +6 -0
  631. package/src/temporal/workflow-not-found.ts +9 -0
  632. package/src/temporal/workflows/deadline.ts +7 -0
  633. package/src/temporal/workflows/index.ts +11 -0
  634. package/src/temporal/workflows/job-execution-orchestration-timeout.test.ts +165 -0
  635. package/src/temporal/workflows/job-execution-orchestration.test.ts +311 -0
  636. package/src/temporal/workflows/job-execution-orchestration.ts +314 -0
  637. package/src/temporal/workflows/job-listener-orchestration-continuation.test.ts +93 -0
  638. package/src/temporal/workflows/job-listener-orchestration.test.ts +406 -0
  639. package/src/temporal/workflows/job-listener-orchestration.ts +490 -0
  640. package/src/temporal/workflows/run-orchestration.test.ts +409 -0
  641. package/src/temporal/workflows/run-orchestration.ts +283 -0
  642. package/src/temporal/workflows/test-env.ts +409 -0
  643. package/test/env.ts +11 -0
  644. package/test/factories/job.ts +83 -0
  645. package/test/factories/project.ts +16 -0
  646. package/test/factories/workflow-model.test.ts +26 -0
  647. package/test/factories/workflow-model.ts +268 -0
  648. package/test/factories/workflow-run.ts +58 -0
  649. package/test/fixtures/active-lease-token.ts +85 -0
  650. package/test/fixtures/job-with-steps.ts +42 -0
  651. package/test/fixtures/lease-token.ts +44 -0
  652. package/test/fixtures/strip-setup-step.ts +23 -0
  653. package/test/globalSetup.ts +54 -0
  654. package/test/helpers/workflow-runs.ts +210 -0
  655. package/test/index.ts +3 -0
  656. package/test/setup.ts +17 -0
  657. package/tsconfig.build.json +9 -0
  658. package/tsconfig.build.tsbuildinfo +1 -0
  659. package/tsconfig.json +3 -0
  660. package/tsconfig.test.json +8 -0
  661. package/vitest.config.ts +12 -0
@@ -0,0 +1,1281 @@
1
+ import type {AgentDefaultsResolver} from '@shipfox/api-agent/core/resolve-agent-config';
2
+ import {normalizeWorkflowDocument} from '@shipfox/api-definitions';
3
+ import {WORKFLOWS_WORKFLOW_RUN_ATTEMPT_CREATED} from '@shipfox/api-workflows-dto';
4
+ import {and, eq, sql} from 'drizzle-orm';
5
+ import {InterpolationUnresolvableError} from '#core/errors.js';
6
+ import {nextStepForJob, recordStepResult} from '#core/job-execution.js';
7
+ import {buildModel, expression, shellRef, template} from '#test/helpers/workflow-runs.js';
8
+ import {db} from '../db.js';
9
+ import {workflowsOutbox} from '../schema/outbox.js';
10
+ import {workflowRuns} from '../schema/workflow-runs.js';
11
+ import {
12
+ createWorkflowRun,
13
+ getJobExecutionsByJobId,
14
+ getJobsByWorkflowRunId,
15
+ getStepsByJobId,
16
+ getWorkflowRunAttemptById,
17
+ getWorkflowRunById,
18
+ getWorkflowRunDetail,
19
+ listRunAttempts,
20
+ resolveJobStatusFromJobExecutions,
21
+ updateJobExecutionStatus,
22
+ } from '../workflow-runs.js';
23
+
24
+ describe('workflow run queries', () => {
25
+ let workspaceId: string;
26
+ let projectId: string;
27
+ let definitionId: string;
28
+
29
+ beforeEach(() => {
30
+ workspaceId = crypto.randomUUID();
31
+ projectId = crypto.randomUUID();
32
+ definitionId = crypto.randomUUID();
33
+ });
34
+
35
+ describe('createWorkflowRun', () => {
36
+ test('inserts run, jobs, and steps atomically', async () => {
37
+ const run = await createWorkflowRun({
38
+ workspaceId,
39
+ projectId,
40
+ definitionId,
41
+ model: buildModel(),
42
+ triggerPayload: {
43
+ source: 'manual',
44
+ event: 'fire',
45
+ subscriptionId: crypto.randomUUID(),
46
+ userId: crypto.randomUUID(),
47
+ },
48
+ });
49
+
50
+ expect(run.id).toBeDefined();
51
+ expect(run.projectId).toBe(projectId);
52
+ expect(run.definitionId).toBe(definitionId);
53
+ expect(run.status).toBe('pending');
54
+ expect(run.triggerProvider).toBeNull();
55
+ expect(run.triggerPayload).toMatchObject({source: 'manual', event: 'fire'});
56
+ expect(run.inputs).toBeNull();
57
+ expect(run.version).toBe(1);
58
+ expect(run.createdAt).toBeInstanceOf(Date);
59
+ expect(run.updatedAt).toBeInstanceOf(Date);
60
+
61
+ const runJobs = await getJobsByWorkflowRunId(run.id);
62
+ expect(runJobs).toHaveLength(1);
63
+ expect(runJobs[0]?.key).toBe('build');
64
+ expect(runJobs[0]?.name).toBeNull();
65
+ expect(runJobs[0]?.checkout).toEqual({
66
+ permissions: {contents: 'read'},
67
+ persistCredentials: true,
68
+ });
69
+
70
+ const jobExecutions = await getJobExecutionsByJobId(runJobs[0]?.id as string);
71
+ expect(jobExecutions).toHaveLength(1);
72
+ expect(jobExecutions[0]).toMatchObject({
73
+ jobId: runJobs[0]?.id,
74
+ sequence: 1,
75
+ name: 'build #1',
76
+ runner: ['ubuntu-latest'],
77
+ evaluationTrace: null,
78
+ });
79
+
80
+ // Every job gets a synthetic "Set up job" step at position 0; user steps follow.
81
+ const jobSteps = await getStepsByJobId(runJobs[0]?.id as string);
82
+ expect(jobSteps).toHaveLength(2);
83
+ expect(jobSteps.every((step) => step.jobExecutionId === jobExecutions[0]?.id)).toBe(true);
84
+ expect(jobSteps[0]).toMatchObject({
85
+ type: 'setup',
86
+ name: 'Set up job',
87
+ position: 0,
88
+ config: {},
89
+ });
90
+ expect(jobSteps[1]).toMatchObject({position: 1, config: {run: 'echo hello'}});
91
+ });
92
+
93
+ async function createJobOutputRun() {
94
+ const run = await createWorkflowRun({
95
+ workspaceId,
96
+ projectId,
97
+ definitionId,
98
+ model: buildModel({
99
+ jobs: {
100
+ build: {
101
+ steps: [{key: 'pack', run: 'echo pack'}],
102
+ outputs: {
103
+ image_sha: template('steps.pack.outputs.sha'),
104
+ },
105
+ },
106
+ deploy: {
107
+ needs: 'build',
108
+ steps: [
109
+ {
110
+ key: 'deploy',
111
+ run: 'deploy',
112
+ env: {IMAGE_SHA: template('jobs.build.outputs.image_sha')},
113
+ },
114
+ ],
115
+ },
116
+ },
117
+ }),
118
+ triggerPayload: {
119
+ source: 'manual',
120
+ event: 'fire',
121
+ subscriptionId: crypto.randomUUID(),
122
+ userId: crypto.randomUUID(),
123
+ },
124
+ });
125
+ const [build, deploy] = await getJobsByWorkflowRunId(run.id);
126
+ if (!build || !deploy) throw new Error('Expected build and deploy jobs');
127
+ const [buildExecution] = await getJobExecutionsByJobId(build.id);
128
+ if (!buildExecution) throw new Error('Expected build execution');
129
+ const [buildSetup, pack] = await getStepsByJobId(build.id);
130
+ if (!buildSetup || !pack) throw new Error('Expected build steps');
131
+ const [deploySetup] = await getStepsByJobId(deploy.id);
132
+ if (!deploySetup) throw new Error('Expected deploy setup step');
133
+
134
+ return {run, build, deploy, buildExecution, buildSetup, pack, deploySetup};
135
+ }
136
+
137
+ async function recordBuildStepOutputs(state: Awaited<ReturnType<typeof createJobOutputRun>>) {
138
+ await nextStepForJob(state.build.id);
139
+ await recordStepResult({
140
+ jobExecutionId: state.buildExecution.id,
141
+ stepId: state.buildSetup.id,
142
+ status: 'succeeded',
143
+ });
144
+ await nextStepForJob(state.build.id);
145
+ await recordStepResult({
146
+ jobExecutionId: state.buildExecution.id,
147
+ stepId: state.pack.id,
148
+ status: 'succeeded',
149
+ output: {sha: 'abc123'},
150
+ });
151
+ }
152
+
153
+ test('persists mapped outputs on the succeeded job execution', async () => {
154
+ const state = await createJobOutputRun();
155
+ await recordBuildStepOutputs(state);
156
+
157
+ const outputExecution = await updateJobExecutionStatus({
158
+ jobExecutionId: state.buildExecution.id,
159
+ expectedVersion: state.buildExecution.version,
160
+ status: 'succeeded',
161
+ });
162
+
163
+ expect(outputExecution.outputs).toEqual({image_sha: 'abc123'});
164
+ });
165
+
166
+ test('reduces the latest succeeded execution outputs onto the job', async () => {
167
+ const state = await createJobOutputRun();
168
+ await recordBuildStepOutputs(state);
169
+ await updateJobExecutionStatus({
170
+ jobExecutionId: state.buildExecution.id,
171
+ expectedVersion: state.buildExecution.version,
172
+ status: 'succeeded',
173
+ });
174
+
175
+ await resolveJobStatusFromJobExecutions({jobId: state.build.id});
176
+
177
+ const [build] = (await getJobsByWorkflowRunId(state.run.id)).filter(
178
+ (job) => job.id === state.build.id,
179
+ );
180
+ expect(build?.outputs).toEqual({image_sha: 'abc123'});
181
+ });
182
+
183
+ test('fills dependent step configs from direct dependency job outputs', async () => {
184
+ const state = await createJobOutputRun();
185
+ await recordBuildStepOutputs(state);
186
+ await updateJobExecutionStatus({
187
+ jobExecutionId: state.buildExecution.id,
188
+ expectedVersion: state.buildExecution.version,
189
+ status: 'succeeded',
190
+ });
191
+ await resolveJobStatusFromJobExecutions({jobId: state.build.id});
192
+ await nextStepForJob(state.deploy.id);
193
+ const [deployExecution] = await getJobExecutionsByJobId(state.deploy.id);
194
+ if (!deployExecution) throw new Error('Expected deploy execution');
195
+ await recordStepResult({
196
+ jobExecutionId: deployExecution.id,
197
+ stepId: state.deploySetup.id,
198
+ status: 'succeeded',
199
+ });
200
+
201
+ const deployStep = await nextStepForJob(state.deploy.id);
202
+
203
+ expect(deployStep).toEqual({
204
+ kind: 'step',
205
+ step: expect.objectContaining({
206
+ key: 'deploy',
207
+ config: expect.objectContaining({env: {IMAGE_SHA: 'abc123'}}),
208
+ }),
209
+ dispatched: true,
210
+ });
211
+ });
212
+
213
+ test('persists the parsed model on the run attempt', async () => {
214
+ const model = buildModel({
215
+ env: {RUN_ID: template('run.id')},
216
+ jobs: {
217
+ build: {
218
+ name: `Build ${template('event.ref')}`,
219
+ steps: [
220
+ {
221
+ run: 'npm test',
222
+ env: {REF: template('event.ref')},
223
+ gate: {success: expression('step.exit_code == 0')},
224
+ },
225
+ ],
226
+ },
227
+ },
228
+ });
229
+
230
+ const run = await createWorkflowRun({
231
+ workspaceId,
232
+ projectId,
233
+ definitionId,
234
+ model,
235
+ triggerPayload: {
236
+ source: 'github',
237
+ event: 'push',
238
+ deliveryId: 'delivery-1',
239
+ data: {ref: 'refs/heads/main'},
240
+ },
241
+ });
242
+
243
+ const [attemptSummary] = await listRunAttempts({workflowRunId: run.id, projectId});
244
+ const attempt = await getWorkflowRunAttemptById(attemptSummary?.id as string);
245
+ const [runJob] = await getJobsByWorkflowRunId(run.id);
246
+ if (!runJob) throw new Error('Expected workflow job');
247
+ const [jobExecution] = await getJobExecutionsByJobId(runJob.id);
248
+
249
+ expect(attempt?.model).toEqual(model);
250
+ expect(jobExecution).toMatchObject({
251
+ name: 'Build refs/heads/main',
252
+ evaluationTrace: [
253
+ {
254
+ expression: 'event.ref',
255
+ roots: ['event'],
256
+ fillTarget: 'ingest',
257
+ evaluatedAt: 'execution-creation',
258
+ value: 'refs/heads/main',
259
+ field: 'job.name',
260
+ },
261
+ ],
262
+ });
263
+ });
264
+
265
+ test('returns the persisted model in run detail', async () => {
266
+ const model = buildModel({
267
+ env: {RUN_ID: template('run.id')},
268
+ jobs: {
269
+ build: {
270
+ steps: [{run: 'echo first'}, {run: 'echo second'}],
271
+ },
272
+ },
273
+ });
274
+ const run = await createWorkflowRun({
275
+ workspaceId,
276
+ projectId,
277
+ definitionId,
278
+ model,
279
+ triggerPayload: {
280
+ source: 'manual',
281
+ event: 'fire',
282
+ subscriptionId: crypto.randomUUID(),
283
+ userId: crypto.randomUUID(),
284
+ },
285
+ });
286
+
287
+ const detail = await getWorkflowRunDetail(run.id);
288
+
289
+ expect(detail?.runAttempt.model).toEqual(model);
290
+ expect(detail?.jobs).toHaveLength(1);
291
+ expect(detail?.jobs[0]?.jobExecutions[0]?.steps).toHaveLength(3);
292
+ });
293
+
294
+ test('persists explicit checkout policy on jobs', async () => {
295
+ const run = await createWorkflowRun({
296
+ workspaceId,
297
+ projectId,
298
+ definitionId,
299
+ model: buildModel({
300
+ jobs: {
301
+ build: {
302
+ checkout: {
303
+ permissions: {contents: 'write'},
304
+ persistCredentials: false,
305
+ },
306
+ steps: [{run: 'echo hello'}],
307
+ },
308
+ },
309
+ }),
310
+ triggerPayload: {
311
+ source: 'manual',
312
+ event: 'fire',
313
+ subscriptionId: crypto.randomUUID(),
314
+ userId: crypto.randomUUID(),
315
+ },
316
+ });
317
+
318
+ const runJobs = await getJobsByWorkflowRunId(run.id);
319
+ expect(runJobs[0]?.checkout).toEqual({
320
+ permissions: {contents: 'write'},
321
+ persistCredentials: false,
322
+ });
323
+ });
324
+
325
+ test('persists listening job config without initial execution or steps', async () => {
326
+ const displayNameSource = ['Review batch $', '{{ execution.index }}'].join('');
327
+ const stepNameSource = ['Review $', '{{ execution.index }}'].join('');
328
+ const promptSource = ['Review $', '{{ execution.events[0].data.body }}'].join('');
329
+ const model = normalizeWorkflowDocument({
330
+ name: 'Listening workflow',
331
+ runner: 'ubuntu-latest',
332
+ jobs: {
333
+ listen: {
334
+ name: displayNameSource,
335
+ listening: {
336
+ on: [{source: 'github', event: 'pull_request_review'}],
337
+ until: [{source: 'github', event: 'pull_request'}],
338
+ timeout: '30d',
339
+ max_executions: 3,
340
+ batch: {debounce: '5s', max_size: 10, max_wait: '1h'},
341
+ on_resolve: 'cancel',
342
+ },
343
+ steps: [{name: stepNameSource, prompt: promptSource}],
344
+ },
345
+ build: {
346
+ steps: [{run: 'echo build'}],
347
+ },
348
+ },
349
+ });
350
+
351
+ const run = await createWorkflowRun({
352
+ workspaceId,
353
+ projectId,
354
+ definitionId,
355
+ model,
356
+ triggerPayload: {
357
+ source: 'manual',
358
+ event: 'fire',
359
+ subscriptionId: crypto.randomUUID(),
360
+ userId: crypto.randomUUID(),
361
+ },
362
+ });
363
+
364
+ const runJobs = await getJobsByWorkflowRunId(run.id);
365
+ const listen = runJobs.find((job) => job.key === 'listen');
366
+ const build = runJobs.find((job) => job.key === 'build');
367
+ expect(listen).toMatchObject({
368
+ mode: 'listening',
369
+ name: displayNameSource,
370
+ listeningTimeoutMs: 30 * 24 * 60 * 60 * 1000,
371
+ maxExecutions: 3,
372
+ onResolve: 'cancel',
373
+ batchDebounceMs: 5000,
374
+ batchMaxSize: 10,
375
+ batchMaxWaitMs: 60 * 60 * 1000,
376
+ listenerStatus: 'inactive',
377
+ resolutionReason: null,
378
+ listeningOn: [{source: 'github', event: 'pull_request_review'}],
379
+ listeningUntil: [{source: 'github', event: 'pull_request'}],
380
+ });
381
+ expect(build).toMatchObject({mode: 'one_shot', listenerStatus: 'inactive'});
382
+
383
+ const listenExecutions = await getJobExecutionsByJobId(listen?.id as string);
384
+ const listenSteps = await getStepsByJobId(listen?.id as string);
385
+ expect(listenExecutions).toEqual([]);
386
+ expect(listenSteps).toEqual([]);
387
+
388
+ const buildExecutions = await getJobExecutionsByJobId(build?.id as string);
389
+ const buildSteps = await getStepsByJobId(build?.id as string);
390
+ expect(buildExecutions).toHaveLength(1);
391
+ expect(buildSteps).toHaveLength(2);
392
+ expect(buildSteps[0]).toMatchObject({type: 'setup', name: 'Set up job', position: 0});
393
+ expect(buildSteps[1]).toMatchObject({type: 'run', config: {run: 'echo build'}, position: 1});
394
+ });
395
+
396
+ test('persists the listening workflow model on the run attempt', async () => {
397
+ const displayNameSource = ['Review batch $', '{{ execution.index }}'].join('');
398
+ const promptSource = ['Review $', '{{ execution.events[0].data.body }}'].join('');
399
+ const model = normalizeWorkflowDocument({
400
+ name: 'Listening workflow',
401
+ runner: 'ubuntu-latest',
402
+ jobs: {
403
+ listen: {
404
+ name: displayNameSource,
405
+ listening: {
406
+ on: [{source: 'github', event: 'pull_request_review'}],
407
+ until: [{source: 'github', event: 'pull_request'}],
408
+ timeout: '30d',
409
+ max_executions: 3,
410
+ batch: {debounce: '5s', max_size: 10, max_wait: '1h'},
411
+ on_resolve: 'cancel',
412
+ },
413
+ steps: [{prompt: promptSource}],
414
+ },
415
+ build: {
416
+ steps: [{run: 'echo build'}],
417
+ },
418
+ },
419
+ });
420
+
421
+ const run = await createWorkflowRun({
422
+ workspaceId,
423
+ projectId,
424
+ definitionId,
425
+ model,
426
+ triggerPayload: {
427
+ source: 'manual',
428
+ event: 'fire',
429
+ subscriptionId: crypto.randomUUID(),
430
+ userId: crypto.randomUUID(),
431
+ },
432
+ });
433
+
434
+ const [attemptSummary] = await listRunAttempts({workflowRunId: run.id, projectId});
435
+ const attempt = await getWorkflowRunAttemptById(attemptSummary?.id as string);
436
+ expect(attempt?.model).toEqual(model);
437
+ });
438
+
439
+ test('does not load variables referenced only by listening job executions at run creation', async () => {
440
+ const model = normalizeWorkflowDocument({
441
+ name: 'Listening vars workflow',
442
+ runner: 'ubuntu-latest',
443
+ jobs: {
444
+ listen: {
445
+ listening: {
446
+ on: [{source: 'github', event: 'pull_request_review'}],
447
+ until: [{source: 'github', event: 'pull_request'}],
448
+ },
449
+ steps: [
450
+ {
451
+ run: 'echo region',
452
+ env: {REGION: template('vars.REGION')},
453
+ },
454
+ ],
455
+ },
456
+ },
457
+ });
458
+
459
+ const run = await createWorkflowRun({
460
+ workspaceId,
461
+ projectId,
462
+ definitionId,
463
+ model,
464
+ triggerPayload: {
465
+ source: 'manual',
466
+ event: 'fire',
467
+ subscriptionId: crypto.randomUUID(),
468
+ userId: crypto.randomUUID(),
469
+ },
470
+ });
471
+
472
+ const runJobs = await getJobsByWorkflowRunId(run.id);
473
+ const listen = runJobs[0];
474
+ expect(listen).toMatchObject({mode: 'listening'});
475
+ await expect(getJobExecutionsByJobId(listen?.id as string)).resolves.toEqual([]);
476
+ });
477
+
478
+ test.each([
479
+ {
480
+ field: 'run',
481
+ model: () =>
482
+ normalizeWorkflowDocument({
483
+ name: 'Missing run var',
484
+ runner: 'ubuntu-latest',
485
+ jobs: {build: {steps: [{run: `echo ${template('vars.REQUIRED')}`}]}},
486
+ }),
487
+ expected: {field: 'run', source: 'vars.REQUIRED'},
488
+ },
489
+ {
490
+ field: 'env',
491
+ model: () =>
492
+ normalizeWorkflowDocument({
493
+ name: 'Missing env var',
494
+ runner: 'ubuntu-latest',
495
+ jobs: {
496
+ build: {
497
+ steps: [{run: 'echo ok', env: {REGION: template('vars.REQUIRED')}}],
498
+ },
499
+ },
500
+ }),
501
+ expected: {field: 'env', envKey: 'REGION', source: 'vars.REQUIRED'},
502
+ },
503
+ {
504
+ field: 'agent.prompt',
505
+ model: () =>
506
+ normalizeWorkflowDocument({
507
+ name: 'Missing prompt var',
508
+ runner: 'ubuntu-latest',
509
+ jobs: {fix: {steps: [{prompt: template('vars.REQUIRED')}]}},
510
+ }),
511
+ expected: {field: 'agent.prompt', source: 'vars.REQUIRED'},
512
+ },
513
+ {
514
+ field: 'agent.model',
515
+ model: () =>
516
+ normalizeWorkflowDocument({
517
+ name: 'Missing model var',
518
+ runner: 'ubuntu-latest',
519
+ jobs: {fix: {steps: [{prompt: 'Fix it', model: template('vars.REQUIRED')}]}},
520
+ }),
521
+ expected: {field: 'agent.model', source: 'vars.REQUIRED'},
522
+ },
523
+ {
524
+ field: 'agent.provider',
525
+ model: () =>
526
+ normalizeWorkflowDocument({
527
+ name: 'Missing provider var',
528
+ runner: 'ubuntu-latest',
529
+ jobs: {fix: {steps: [{prompt: 'Fix it', provider: template('vars.REQUIRED')}]}},
530
+ }),
531
+ expected: {field: 'agent.provider', source: 'vars.REQUIRED'},
532
+ },
533
+ {
534
+ field: 'job.runner',
535
+ model: () =>
536
+ normalizeWorkflowDocument({
537
+ name: 'Missing runner var',
538
+ runner: 'ubuntu-latest',
539
+ jobs: {build: {runner: template('vars.REQUIRED'), steps: [{run: 'echo ok'}]}},
540
+ }),
541
+ expected: {field: 'job.runner', source: 'vars.REQUIRED'},
542
+ },
543
+ {
544
+ field: 'step.name',
545
+ model: () =>
546
+ normalizeWorkflowDocument({
547
+ name: 'Missing step name var',
548
+ runner: 'ubuntu-latest',
549
+ jobs: {build: {steps: [{name: template('vars.REQUIRED'), run: 'echo ok'}]}},
550
+ }),
551
+ expected: {field: 'step.name', source: 'vars.REQUIRED'},
552
+ },
553
+ ] as const)('reports missing variables against $field', async ({model, expected}) => {
554
+ let error: unknown;
555
+ try {
556
+ await createWorkflowRun({
557
+ workspaceId,
558
+ projectId,
559
+ definitionId,
560
+ model: model(),
561
+ triggerPayload: {
562
+ source: 'manual',
563
+ event: 'fire',
564
+ subscriptionId: crypto.randomUUID(),
565
+ userId: crypto.randomUUID(),
566
+ },
567
+ });
568
+ } catch (caught) {
569
+ error = caught;
570
+ }
571
+
572
+ expect(error).toBeInstanceOf(InterpolationUnresolvableError);
573
+ expect(error).toMatchObject(expected);
574
+ });
575
+
576
+ test('writes workflows.workflow_run_attempt.created outbox event in same transaction', async () => {
577
+ const run = await createWorkflowRun({
578
+ workspaceId,
579
+ projectId,
580
+ definitionId,
581
+ model: buildModel(),
582
+ triggerPayload: {
583
+ source: 'manual',
584
+ event: 'fire',
585
+ subscriptionId: crypto.randomUUID(),
586
+ userId: crypto.randomUUID(),
587
+ },
588
+ });
589
+
590
+ const outboxRows = await db()
591
+ .select()
592
+ .from(workflowsOutbox)
593
+ .where(eq(workflowsOutbox.eventType, WORKFLOWS_WORKFLOW_RUN_ATTEMPT_CREATED));
594
+
595
+ const matchingRow = outboxRows.find(
596
+ (row) => (row.payload as Record<string, unknown>).workflowRunId === run.id,
597
+ );
598
+
599
+ expect(matchingRow).toBeDefined();
600
+ expect(matchingRow?.payload).toMatchObject({
601
+ workflowRunId: run.id,
602
+ attempt: 1,
603
+ workspaceId: run.workspaceId,
604
+ projectId: run.projectId,
605
+ definitionId: run.definitionId,
606
+ });
607
+ expect(matchingRow?.orderingKey).toBe(run.id);
608
+ expect(matchingRow?.dispatchedAt).toBeNull();
609
+ });
610
+
611
+ test('persists resolved step config and authored step config separately', async () => {
612
+ const model = normalizeWorkflowDocument({
613
+ name: 'Interpolated workflow',
614
+ runner: 'ubuntu-latest',
615
+ env: {RUN_ID: template('run.id'), REF: template('event.ref')},
616
+ jobs: {
617
+ build: {
618
+ steps: [{run: `echo "${template('run.id')}"`}],
619
+ },
620
+ },
621
+ });
622
+
623
+ const run = await createWorkflowRun({
624
+ workspaceId,
625
+ projectId,
626
+ definitionId,
627
+ model,
628
+ triggerPayload: {
629
+ source: 'github',
630
+ event: 'push',
631
+ deliveryId: 'delivery-1',
632
+ data: {ref: 'refs/heads/main'},
633
+ },
634
+ });
635
+
636
+ const [job] = await getJobsByWorkflowRunId(run.id);
637
+ const rows = (await getStepsByJobId(job?.id as string)).map((step) => ({
638
+ type: step.type,
639
+ config: step.config,
640
+ authoredConfig: step.authoredConfig,
641
+ }));
642
+
643
+ expect(rows[1]).toEqual({
644
+ type: 'run',
645
+ config: {
646
+ run: `echo "${shellRef('__sf_0')}"`,
647
+ env: {RUN_ID: run.id, REF: 'refs/heads/main', __sf_0: run.id},
648
+ },
649
+ authoredConfig: {
650
+ run: `echo "${template('run.id')}"`,
651
+ env: {RUN_ID: template('run.id'), REF: template('event.ref')},
652
+ },
653
+ });
654
+
655
+ const steps = await getStepsByJobId(job?.id as string);
656
+ expect(steps[1]?.authoredConfig).toEqual({
657
+ run: `echo "${template('run.id')}"`,
658
+ env: {RUN_ID: template('run.id'), REF: template('event.ref')},
659
+ });
660
+ });
661
+
662
+ test('resolves webhook trigger payload body and headers into step config', async () => {
663
+ const model = normalizeWorkflowDocument({
664
+ name: 'Webhook workflow',
665
+ runner: 'ubuntu-latest',
666
+ env: {
667
+ PAYMENT_ID: template('event.body.payment_id'),
668
+ SIGNATURE: template('event.headers["x-stripe-signature"]'),
669
+ },
670
+ jobs: {
671
+ build: {
672
+ steps: [{run: 'echo webhook'}],
673
+ },
674
+ },
675
+ });
676
+
677
+ const run = await createWorkflowRun({
678
+ workspaceId,
679
+ projectId,
680
+ definitionId,
681
+ model,
682
+ triggerPayload: {
683
+ provider: 'webhook',
684
+ source: 'stripe_prod',
685
+ event: 'received',
686
+ deliveryId: 'delivery-1',
687
+ data: {
688
+ method: 'POST',
689
+ headers: {'x-stripe-signature': 'sig_123'},
690
+ query: {mode: 'live'},
691
+ body: {payment_id: 'pay_123'},
692
+ },
693
+ },
694
+ });
695
+
696
+ const [job] = await getJobsByWorkflowRunId(run.id);
697
+ const steps = await getStepsByJobId(job?.id as string);
698
+
699
+ expect(steps[1]?.config).toEqual({
700
+ run: 'echo webhook',
701
+ env: {
702
+ PAYMENT_ID: 'pay_123',
703
+ SIGNATURE: 'sig_123',
704
+ },
705
+ });
706
+ });
707
+
708
+ test('fails for missing available untrusted interpolation paths', async () => {
709
+ const model = normalizeWorkflowDocument({
710
+ name: 'Diagnostic workflow',
711
+ runner: 'ubuntu-latest',
712
+ env: {REF: template('event.ref')},
713
+ jobs: {
714
+ build: {
715
+ steps: [{run: 'echo ok'}],
716
+ },
717
+ },
718
+ });
719
+
720
+ let error: unknown;
721
+ try {
722
+ await createWorkflowRun({
723
+ workspaceId,
724
+ projectId,
725
+ definitionId,
726
+ model,
727
+ triggerPayload: {
728
+ source: 'github',
729
+ event: 'push',
730
+ deliveryId: 'delivery-1',
731
+ data: {},
732
+ },
733
+ });
734
+ } catch (caught) {
735
+ error = caught;
736
+ }
737
+
738
+ expect(error).toBeInstanceOf(InterpolationUnresolvableError);
739
+ expect(error).toMatchObject({
740
+ field: 'env',
741
+ source: 'event.ref',
742
+ envKey: 'REF',
743
+ });
744
+ const runs = await db()
745
+ .select()
746
+ .from(workflowRuns)
747
+ .where(
748
+ and(
749
+ eq(workflowRuns.workspaceId, workspaceId),
750
+ eq(workflowRuns.definitionId, definitionId),
751
+ ),
752
+ );
753
+ expect(runs).toEqual([]);
754
+ });
755
+
756
+ test('rolls back outbox event when transaction fails', async () => {
757
+ const marker = crypto.randomUUID();
758
+
759
+ const transaction = db().transaction(async (tx) => {
760
+ await tx.insert(workflowsOutbox).values({
761
+ eventType: WORKFLOWS_WORKFLOW_RUN_ATTEMPT_CREATED,
762
+ payload: {workflowRunId: marker, projectId, definitionId},
763
+ });
764
+ throw new Error('Simulated failure');
765
+ });
766
+
767
+ await expect(transaction).rejects.toThrow('Simulated failure');
768
+
769
+ const leaked = await db()
770
+ .select()
771
+ .from(workflowsOutbox)
772
+ .where(sql`${workflowsOutbox.payload}->>'workflowRunId' = ${marker}`);
773
+
774
+ expect(leaked).toHaveLength(0);
775
+ });
776
+
777
+ test('normalizes needs string to array', async () => {
778
+ const run = await createWorkflowRun({
779
+ workspaceId,
780
+ projectId,
781
+ definitionId,
782
+ model: buildModel({
783
+ jobs: {
784
+ build: {steps: [{run: 'echo build'}]},
785
+ test: {needs: 'build', steps: [{run: 'echo test'}]},
786
+ },
787
+ }),
788
+ triggerPayload: {
789
+ source: 'manual',
790
+ event: 'fire',
791
+ subscriptionId: crypto.randomUUID(),
792
+ userId: crypto.randomUUID(),
793
+ },
794
+ });
795
+
796
+ const runJobs = await getJobsByWorkflowRunId(run.id);
797
+ const testJob = runJobs.find((j) => j.key === 'test');
798
+
799
+ expect(testJob?.dependencies).toEqual(['build']);
800
+ });
801
+
802
+ test('normalizes needs undefined to empty array', async () => {
803
+ const run = await createWorkflowRun({
804
+ workspaceId,
805
+ projectId,
806
+ definitionId,
807
+ model: buildModel(),
808
+ triggerPayload: {
809
+ source: 'manual',
810
+ event: 'fire',
811
+ subscriptionId: crypto.randomUUID(),
812
+ userId: crypto.randomUUID(),
813
+ },
814
+ });
815
+
816
+ const runJobs = await getJobsByWorkflowRunId(run.id);
817
+
818
+ expect(runJobs[0]?.dependencies).toEqual([]);
819
+ });
820
+
821
+ test('stores authored agent tools with runtime agent defaults resolved', async () => {
822
+ const run = await createWorkflowRun({
823
+ workspaceId,
824
+ projectId,
825
+ definitionId,
826
+ model: buildModel({
827
+ jobs: {
828
+ fix: {
829
+ steps: [
830
+ {
831
+ harness: 'pi',
832
+ tools: ['read', 'web_search'],
833
+ prompt: 'Fix the failing tests.',
834
+ },
835
+ ],
836
+ },
837
+ },
838
+ }),
839
+ triggerPayload: {
840
+ source: 'manual',
841
+ event: 'fire',
842
+ subscriptionId: crypto.randomUUID(),
843
+ userId: crypto.randomUUID(),
844
+ },
845
+ });
846
+
847
+ const runJobs = await getJobsByWorkflowRunId(run.id);
848
+ const jobSteps = await getStepsByJobId(runJobs[0]?.id as string);
849
+ const agentStep = jobSteps.find((step) => step.type === 'agent');
850
+
851
+ expect(agentStep).toMatchObject({
852
+ type: 'agent',
853
+ config: {
854
+ harness: 'pi',
855
+ model: 'claude-opus-4-8',
856
+ provider: 'anthropic',
857
+ thinking: 'xhigh',
858
+ tools: ['read', 'web_search'],
859
+ prompt: 'Fix the failing tests.',
860
+ },
861
+ });
862
+ });
863
+
864
+ test('stores agent step config resolved by the injected resolver', async () => {
865
+ const resolveAgentDefaults = vi.fn<AgentDefaultsResolver>().mockReturnValue({
866
+ harness: 'pi',
867
+ provider: 'openai',
868
+ model: 'gpt-5.5-pro',
869
+ thinking: 'medium',
870
+ });
871
+
872
+ const run = await createWorkflowRun({
873
+ workspaceId,
874
+ projectId,
875
+ definitionId,
876
+ model: buildModel({
877
+ jobs: {
878
+ fix: {steps: [{prompt: 'Fix the failing tests.'}]},
879
+ },
880
+ }),
881
+ triggerPayload: {
882
+ source: 'manual',
883
+ event: 'fire',
884
+ subscriptionId: crypto.randomUUID(),
885
+ userId: crypto.randomUUID(),
886
+ },
887
+ resolveAgentDefaults,
888
+ });
889
+
890
+ const runJobs = await getJobsByWorkflowRunId(run.id);
891
+ const jobSteps = await getStepsByJobId(runJobs[0]?.id as string);
892
+ const agentStep = jobSteps.find((step) => step.type === 'agent');
893
+ expect(resolveAgentDefaults).toHaveBeenCalledWith({
894
+ harness: undefined,
895
+ provider: undefined,
896
+ model: undefined,
897
+ thinking: undefined,
898
+ });
899
+ expect(agentStep?.config).toEqual({
900
+ harness: 'pi',
901
+ model: 'gpt-5.5-pro',
902
+ provider: 'openai',
903
+ thinking: 'medium',
904
+ prompt: 'Fix the failing tests.',
905
+ });
906
+ });
907
+
908
+ test('handles multi-job definitions with correct positions', async () => {
909
+ const run = await createWorkflowRun({
910
+ workspaceId,
911
+ projectId,
912
+ definitionId,
913
+ model: buildModel({
914
+ jobs: {
915
+ lint: {steps: [{run: 'echo lint'}]},
916
+ build: {steps: [{run: 'echo build'}]},
917
+ test: {needs: ['lint', 'build'], steps: [{run: 'echo test'}]},
918
+ },
919
+ }),
920
+ triggerPayload: {
921
+ source: 'manual',
922
+ event: 'fire',
923
+ subscriptionId: crypto.randomUUID(),
924
+ userId: crypto.randomUUID(),
925
+ },
926
+ });
927
+
928
+ const runJobs = await getJobsByWorkflowRunId(run.id);
929
+
930
+ expect(runJobs).toHaveLength(3);
931
+ expect(runJobs[0]?.position).toBe(0);
932
+ expect(runJobs[1]?.position).toBe(1);
933
+ expect(runJobs[2]?.position).toBe(2);
934
+ });
935
+
936
+ test('handles definition with empty jobs object', async () => {
937
+ const run = await createWorkflowRun({
938
+ workspaceId,
939
+ projectId,
940
+ definitionId,
941
+ model: buildModel({jobs: {}}),
942
+ triggerPayload: {
943
+ source: 'manual',
944
+ event: 'fire',
945
+ subscriptionId: crypto.randomUUID(),
946
+ userId: crypto.randomUUID(),
947
+ },
948
+ });
949
+
950
+ expect(run.id).toBeDefined();
951
+
952
+ const runJobs = await getJobsByWorkflowRunId(run.id);
953
+
954
+ expect(runJobs).toHaveLength(0);
955
+ });
956
+
957
+ test('handles job with zero steps', async () => {
958
+ const run = await createWorkflowRun({
959
+ workspaceId,
960
+ projectId,
961
+ definitionId,
962
+ model: buildModel({
963
+ jobs: {
964
+ empty: {steps: []},
965
+ },
966
+ }),
967
+ triggerPayload: {
968
+ source: 'manual',
969
+ event: 'fire',
970
+ subscriptionId: crypto.randomUUID(),
971
+ userId: crypto.randomUUID(),
972
+ },
973
+ });
974
+
975
+ const runJobs = await getJobsByWorkflowRunId(run.id);
976
+ expect(runJobs).toHaveLength(1);
977
+
978
+ // A job with no user steps still gets the synthetic setup step.
979
+ const jobSteps = await getStepsByJobId(runJobs[0]?.id as string);
980
+
981
+ expect(jobSteps).toHaveLength(1);
982
+ expect(jobSteps[0]).toMatchObject({type: 'setup', name: 'Set up job', position: 0});
983
+ });
984
+
985
+ test('stores step display names', async () => {
986
+ const run = await createWorkflowRun({
987
+ workspaceId,
988
+ projectId,
989
+ definitionId,
990
+ model: buildModel({
991
+ jobs: {
992
+ build: {
993
+ steps: [{name: 'Install deps', run: 'npm install'}, {run: 'npm build'}],
994
+ },
995
+ },
996
+ }),
997
+ triggerPayload: {
998
+ source: 'manual',
999
+ event: 'fire',
1000
+ subscriptionId: crypto.randomUUID(),
1001
+ userId: crypto.randomUUID(),
1002
+ },
1003
+ });
1004
+
1005
+ const runJobs = await getJobsByWorkflowRunId(run.id);
1006
+ const jobSteps = await getStepsByJobId(runJobs[0]?.id as string);
1007
+
1008
+ // Index 0 is the synthetic setup step; user steps start at index 1.
1009
+ expect(jobSteps[0]?.name).toBe('Set up job');
1010
+ expect(jobSteps[1]?.name).toBe('Install deps');
1011
+ expect(jobSteps[2]?.name).toBe('npm build');
1012
+ });
1013
+
1014
+ test('stores source locations for authored steps', async () => {
1015
+ const run = await createWorkflowRun({
1016
+ workspaceId,
1017
+ projectId,
1018
+ definitionId,
1019
+ model: buildModel({
1020
+ jobs: {
1021
+ build: {
1022
+ steps: [
1023
+ {run: 'npm install', sourceLocation: {startLine: 5, endLine: 6}},
1024
+ {run: 'npm test', sourceLocation: {startLine: 7, endLine: 10}},
1025
+ ],
1026
+ },
1027
+ },
1028
+ }),
1029
+ triggerPayload: {
1030
+ source: 'manual',
1031
+ event: 'fire',
1032
+ subscriptionId: crypto.randomUUID(),
1033
+ userId: crypto.randomUUID(),
1034
+ },
1035
+ });
1036
+
1037
+ const runJobs = await getJobsByWorkflowRunId(run.id);
1038
+ const jobSteps = await getStepsByJobId(runJobs[0]?.id as string);
1039
+
1040
+ expect(jobSteps.map((step) => step.sourceLocation)).toEqual([
1041
+ null,
1042
+ {startLine: 5, endLine: 6},
1043
+ {startLine: 7, endLine: 10},
1044
+ ]);
1045
+ });
1046
+
1047
+ test('stores frozen step config', async () => {
1048
+ const run = await createWorkflowRun({
1049
+ workspaceId,
1050
+ projectId,
1051
+ definitionId,
1052
+ model: buildModel({
1053
+ jobs: {
1054
+ build: {steps: [{run: 'make build'}]},
1055
+ },
1056
+ }),
1057
+ triggerPayload: {
1058
+ source: 'manual',
1059
+ event: 'fire',
1060
+ subscriptionId: crypto.randomUUID(),
1061
+ userId: crypto.randomUUID(),
1062
+ },
1063
+ });
1064
+
1065
+ const runJobs = await getJobsByWorkflowRunId(run.id);
1066
+ const jobSteps = await getStepsByJobId(runJobs[0]?.id as string);
1067
+
1068
+ // Index 0 is the synthetic setup step; the user run step is at index 1.
1069
+ expect(jobSteps[1]?.type).toBe('run');
1070
+ expect(jobSteps[1]?.config).toEqual({run: 'make build'});
1071
+ });
1072
+
1073
+ test('stores inputs when provided', async () => {
1074
+ const run = await createWorkflowRun({
1075
+ workspaceId,
1076
+ projectId,
1077
+ definitionId,
1078
+ model: buildModel(),
1079
+ triggerPayload: {
1080
+ source: 'manual',
1081
+ event: 'fire',
1082
+ subscriptionId: crypto.randomUUID(),
1083
+ userId: crypto.randomUUID(),
1084
+ },
1085
+ inputs: {env: 'staging', verbose: true},
1086
+ });
1087
+
1088
+ expect(run.inputs).toEqual({env: 'staging', verbose: true});
1089
+ });
1090
+
1091
+ test('stores the exact source snapshot when provided', async () => {
1092
+ const sourceContent = `name: Exact
1093
+ # keep comment and spacing
1094
+ jobs:
1095
+ build:
1096
+ steps:
1097
+ - run: echo "hello"
1098
+ `;
1099
+
1100
+ const run = await createWorkflowRun({
1101
+ workspaceId,
1102
+ projectId,
1103
+ definitionId,
1104
+ model: buildModel({name: 'Exact'}),
1105
+ sourceSnapshot: {content: sourceContent, format: 'yaml'},
1106
+ triggerPayload: {
1107
+ source: 'manual',
1108
+ event: 'fire',
1109
+ subscriptionId: crypto.randomUUID(),
1110
+ userId: crypto.randomUUID(),
1111
+ },
1112
+ });
1113
+
1114
+ const found = await getWorkflowRunById(run.id);
1115
+
1116
+ expect(run.sourceSnapshot).toEqual({content: sourceContent, format: 'yaml'});
1117
+ expect(found?.sourceSnapshot).toEqual({content: sourceContent, format: 'yaml'});
1118
+ });
1119
+
1120
+ test('stores null source snapshot when omitted', async () => {
1121
+ const run = await createWorkflowRun({
1122
+ workspaceId,
1123
+ projectId,
1124
+ definitionId,
1125
+ model: buildModel(),
1126
+ triggerPayload: {
1127
+ source: 'manual',
1128
+ event: 'fire',
1129
+ subscriptionId: crypto.randomUUID(),
1130
+ userId: crypto.randomUUID(),
1131
+ },
1132
+ });
1133
+
1134
+ const found = await getWorkflowRunById(run.id);
1135
+
1136
+ expect(run.sourceSnapshot).toBeNull();
1137
+ expect(found?.sourceSnapshot).toBeNull();
1138
+ });
1139
+
1140
+ test('duplicate triggerIdempotencyKey returns the existing run without writing jobs/steps/outbox a second time', async () => {
1141
+ const subscriptionId = crypto.randomUUID();
1142
+ const eventId = crypto.randomUUID();
1143
+ const idempotencyKey = `${subscriptionId}:${eventId}`;
1144
+ const model = buildModel({name: 'Original idempotent model'});
1145
+
1146
+ const first = await createWorkflowRun({
1147
+ workspaceId,
1148
+ projectId,
1149
+ definitionId,
1150
+ model,
1151
+ triggerPayload: {
1152
+ source: 'manual',
1153
+ event: 'fire',
1154
+ subscriptionId,
1155
+ userId: crypto.randomUUID(),
1156
+ },
1157
+ sourceSnapshot: {content: 'name: Original\njobs: {}\n', format: 'yaml'},
1158
+ triggerIdempotencyKey: idempotencyKey,
1159
+ });
1160
+ const second = await createWorkflowRun({
1161
+ workspaceId,
1162
+ projectId,
1163
+ definitionId,
1164
+ model: buildModel({name: 'Mutated idempotent model'}),
1165
+ triggerPayload: {
1166
+ source: 'manual',
1167
+ event: 'fire',
1168
+ subscriptionId,
1169
+ userId: crypto.randomUUID(),
1170
+ },
1171
+ sourceSnapshot: {content: 'name: Mutated\njobs: {}\n', format: 'yaml'},
1172
+ triggerIdempotencyKey: idempotencyKey,
1173
+ });
1174
+
1175
+ expect(second.id).toBe(first.id);
1176
+ expect(second.triggerIdempotencyKey).toBe(idempotencyKey);
1177
+ expect(second.sourceSnapshot).toEqual({
1178
+ content: 'name: Original\njobs: {}\n',
1179
+ format: 'yaml',
1180
+ });
1181
+
1182
+ const allJobs = await getJobsByWorkflowRunId(first.id);
1183
+ expect(allJobs).toHaveLength(1);
1184
+ const attempts = await listRunAttempts({workflowRunId: first.id, projectId});
1185
+ expect(attempts).toHaveLength(1);
1186
+ expect(attempts[0]?.model).toEqual(model);
1187
+ const outboxRows = await db()
1188
+ .select()
1189
+ .from(workflowsOutbox)
1190
+ .where(sql`${workflowsOutbox.payload}->>'workflowRunId' = ${first.id}`);
1191
+ expect(outboxRows).toHaveLength(1);
1192
+ });
1193
+
1194
+ test('duplicate triggerIdempotencyKey returns the existing run without re-materializing', async () => {
1195
+ const subscriptionId = crypto.randomUUID();
1196
+ const eventId = crypto.randomUUID();
1197
+ const idempotencyKey = `${subscriptionId}:${eventId}`;
1198
+ const model = buildModel({
1199
+ jobs: {
1200
+ fix: {steps: [{prompt: 'Fix the failing tests.'}]},
1201
+ },
1202
+ });
1203
+ const firstResolver = vi.fn<AgentDefaultsResolver>().mockReturnValue({
1204
+ harness: 'pi',
1205
+ provider: 'openai',
1206
+ model: 'gpt-5.5-pro',
1207
+ thinking: 'medium',
1208
+ });
1209
+ const secondResolver = vi.fn<AgentDefaultsResolver>().mockImplementation(() => {
1210
+ throw new Error('agent defaults unavailable');
1211
+ });
1212
+ const first = await createWorkflowRun({
1213
+ workspaceId,
1214
+ projectId,
1215
+ definitionId,
1216
+ model,
1217
+ triggerPayload: {
1218
+ source: 'manual',
1219
+ event: 'fire',
1220
+ subscriptionId,
1221
+ userId: crypto.randomUUID(),
1222
+ },
1223
+ triggerIdempotencyKey: idempotencyKey,
1224
+ resolveAgentDefaults: firstResolver,
1225
+ });
1226
+
1227
+ const replay = await createWorkflowRun({
1228
+ workspaceId,
1229
+ projectId,
1230
+ definitionId,
1231
+ model,
1232
+ triggerPayload: {
1233
+ source: 'manual',
1234
+ event: 'fire',
1235
+ subscriptionId,
1236
+ userId: crypto.randomUUID(),
1237
+ },
1238
+ triggerIdempotencyKey: idempotencyKey,
1239
+ resolveAgentDefaults: secondResolver,
1240
+ });
1241
+
1242
+ expect(replay.id).toBe(first.id);
1243
+ expect(firstResolver).toHaveBeenCalledTimes(1);
1244
+ expect(secondResolver).not.toHaveBeenCalled();
1245
+
1246
+ const allJobs = await getJobsByWorkflowRunId(first.id);
1247
+ expect(allJobs).toHaveLength(1);
1248
+ });
1249
+
1250
+ test('null triggerIdempotencyKey allows independent inserts', async () => {
1251
+ const a = await createWorkflowRun({
1252
+ workspaceId,
1253
+ projectId,
1254
+ definitionId,
1255
+ model: buildModel(),
1256
+ triggerPayload: {
1257
+ source: 'manual',
1258
+ event: 'fire',
1259
+ subscriptionId: crypto.randomUUID(),
1260
+ userId: crypto.randomUUID(),
1261
+ },
1262
+ });
1263
+ const b = await createWorkflowRun({
1264
+ workspaceId,
1265
+ projectId,
1266
+ definitionId,
1267
+ model: buildModel(),
1268
+ triggerPayload: {
1269
+ source: 'manual',
1270
+ event: 'fire',
1271
+ subscriptionId: crypto.randomUUID(),
1272
+ userId: crypto.randomUUID(),
1273
+ },
1274
+ });
1275
+
1276
+ expect(b.id).not.toBe(a.id);
1277
+ expect(a.triggerIdempotencyKey).toBeNull();
1278
+ expect(b.triggerIdempotencyKey).toBeNull();
1279
+ });
1280
+ });
1281
+ });