@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,266 @@
1
+ import {and, asc, eq, inArray, sql} from 'drizzle-orm';
2
+ import {isWorkflowRunTerminal, type WorkflowRun} from '#core/entities/workflow-run.js';
3
+ import {NoFailedJobsError, RunNotTerminalError, SourceRunNotFoundError} from '#core/errors.js';
4
+ import {deriveJobExecutionRunner} from '#core/workflow-run-creation.js';
5
+ import {recordWorkflowRunCreated} from '#metrics/instance.js';
6
+ import {db, type Tx} from '../db.js';
7
+ import {type JobExecutionDb, jobExecutions} from '../schema/job-executions.js';
8
+ import {type JobDb, jobs} from '../schema/jobs.js';
9
+ import {type StepDb, steps} from '../schema/steps.js';
10
+ import {type WorkflowRunAttemptDb, workflowRunAttempts} from '../schema/workflow-run-attempts.js';
11
+ import {toWorkflowRun, workflowRuns} from '../schema/workflow-runs.js';
12
+ import {type MaterializedRunGraphJob, persistMaterializedRunGraph} from './run-graph.js';
13
+ import {lockWorkflowRun} from './shared.js';
14
+
15
+ export interface CreateRerunWorkflowRunParams {
16
+ workflowRunId: string;
17
+ mode: 'all' | 'failed';
18
+ actorUserId: string;
19
+ }
20
+
21
+ export async function createRerunWorkflowRun(
22
+ params: CreateRerunWorkflowRunParams,
23
+ ): Promise<WorkflowRun> {
24
+ const result = await db().transaction(async (tx) => {
25
+ const workflowRunId = params.workflowRunId;
26
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${workflowRunId}))`);
27
+
28
+ const sourceRow = await lockWorkflowRun(workflowRunId, tx);
29
+ if (!sourceRow) throw new SourceRunNotFoundError(workflowRunId);
30
+
31
+ const [sourceAttemptRow] = await tx
32
+ .select()
33
+ .from(workflowRunAttempts)
34
+ .where(
35
+ and(
36
+ eq(workflowRunAttempts.workflowRunId, sourceRow.id),
37
+ eq(workflowRunAttempts.attempt, sourceRow.currentAttempt),
38
+ ),
39
+ )
40
+ .limit(1)
41
+ .for('update');
42
+ if (!sourceAttemptRow) {
43
+ throw new Error(
44
+ `Current attempt ${sourceRow.currentAttempt} missing for run ${sourceRow.id}`,
45
+ );
46
+ }
47
+ if (!isWorkflowRunTerminal(sourceAttemptRow.status)) {
48
+ throw new RunNotTerminalError(sourceRow.id);
49
+ }
50
+
51
+ const sourceJobs = await tx
52
+ .select()
53
+ .from(jobs)
54
+ .where(eq(jobs.workflowRunAttemptId, sourceAttemptRow.id))
55
+ .orderBy(asc(jobs.position), asc(jobs.id));
56
+
57
+ if (
58
+ params.mode === 'failed' &&
59
+ !sourceJobs.some((job) => job.status === 'failed' || job.status === 'cancelled')
60
+ ) {
61
+ throw new NoFailedJobsError(sourceRow.id);
62
+ }
63
+
64
+ const [attemptRow] = await tx
65
+ .select({value: sql<number>`coalesce(max(${workflowRunAttempts.attempt}), 1)`})
66
+ .from(workflowRunAttempts)
67
+ .where(eq(workflowRunAttempts.workflowRunId, sourceRow.id));
68
+ const attempt = Number(attemptRow?.value ?? 1) + 1;
69
+
70
+ const [newAttemptRow] = await tx
71
+ .insert(workflowRunAttempts)
72
+ .values({
73
+ workflowRunId: sourceRow.id,
74
+ attempt,
75
+ status: 'pending',
76
+ rerunMode: params.mode,
77
+ rerunByUserId: params.actorUserId,
78
+ model: sourceAttemptRow.model,
79
+ agentToolMaterialization: sourceAttemptRow.agentToolMaterialization,
80
+ })
81
+ .returning();
82
+ if (!newAttemptRow) throw new Error('Insert returned no rows');
83
+
84
+ const sourceGraph = await loadRerunSourceGraph(tx, sourceJobs);
85
+
86
+ const sourceRun = toWorkflowRun(sourceRow);
87
+ const graphJobs = materializeRerunGraphJobs({
88
+ mode: params.mode,
89
+ sourceRun,
90
+ sourceAttempt: sourceAttemptRow,
91
+ sourceJobs,
92
+ ...sourceGraph,
93
+ });
94
+ await persistMaterializedRunGraph(tx, {
95
+ run: sourceRun,
96
+ workflowRunAttempt: newAttemptRow,
97
+ materializedJobs: graphJobs,
98
+ });
99
+
100
+ const [newRunRow] = await tx
101
+ .update(workflowRuns)
102
+ .set({
103
+ currentAttempt: attempt,
104
+ status: 'pending',
105
+ version: sql`${workflowRuns.version} + 1`,
106
+ updatedAt: new Date(),
107
+ startedAt: null,
108
+ finishedAt: null,
109
+ })
110
+ .where(eq(workflowRuns.id, sourceRow.id))
111
+ .returning();
112
+ if (!newRunRow) throw new Error(`Workflow run missing after rerun: ${sourceRow.id}`);
113
+
114
+ return toWorkflowRun(newRunRow);
115
+ });
116
+
117
+ recordWorkflowRunCreated(result.triggerPayload.provider ?? result.triggerSource);
118
+
119
+ return result;
120
+ }
121
+
122
+ async function loadRerunSourceGraph(
123
+ tx: Tx,
124
+ sourceJobs: readonly JobDb[],
125
+ ): Promise<{
126
+ readonly sourceJobExecutionByJobId: ReadonlyMap<string, JobExecutionDb>;
127
+ readonly sourceJobByJobExecutionId: ReadonlyMap<string, JobDb>;
128
+ readonly sourceSteps: readonly StepDb[];
129
+ }> {
130
+ const sourceJobIds = sourceJobs.map((job) => job.id);
131
+ const sourceJobExecutionRows =
132
+ sourceJobIds.length === 0
133
+ ? []
134
+ : await tx
135
+ .select()
136
+ .from(jobExecutions)
137
+ .where(inArray(jobExecutions.jobId, sourceJobIds))
138
+ .orderBy(asc(jobExecutions.jobId), asc(jobExecutions.sequence), asc(jobExecutions.id));
139
+ const sourceJobExecutionByJobId = new Map<string, JobExecutionDb>();
140
+ for (const jobExecution of sourceJobExecutionRows) {
141
+ if (!sourceJobExecutionByJobId.has(jobExecution.jobId)) {
142
+ sourceJobExecutionByJobId.set(jobExecution.jobId, jobExecution);
143
+ }
144
+ }
145
+
146
+ const sourceJobExecutionIds = [...sourceJobExecutionByJobId.values()].map(
147
+ (jobExecution) => jobExecution.id,
148
+ );
149
+ const sourceSteps =
150
+ sourceJobExecutionIds.length === 0
151
+ ? []
152
+ : await tx
153
+ .select()
154
+ .from(steps)
155
+ .where(inArray(steps.jobExecutionId, sourceJobExecutionIds))
156
+ .orderBy(asc(steps.jobExecutionId), asc(steps.position), asc(steps.id));
157
+
158
+ const sourceJobById = new Map(sourceJobs.map((job) => [job.id, job]));
159
+ const sourceJobByJobExecutionId = new Map(
160
+ [...sourceJobExecutionByJobId.entries()].flatMap(([jobId, jobExecution]) => {
161
+ const job = sourceJobById.get(jobId);
162
+ return job ? [[jobExecution.id, job] as const] : [];
163
+ }),
164
+ );
165
+
166
+ return {sourceJobExecutionByJobId, sourceJobByJobExecutionId, sourceSteps};
167
+ }
168
+
169
+ function materializeRerunGraphJobs(params: {
170
+ readonly mode: CreateRerunWorkflowRunParams['mode'];
171
+ readonly sourceRun: WorkflowRun;
172
+ readonly sourceAttempt: WorkflowRunAttemptDb;
173
+ readonly sourceJobs: readonly JobDb[];
174
+ readonly sourceJobExecutionByJobId: ReadonlyMap<string, JobExecutionDb>;
175
+ readonly sourceJobByJobExecutionId: ReadonlyMap<string, JobDb>;
176
+ readonly sourceSteps: readonly StepDb[];
177
+ }): readonly MaterializedRunGraphJob[] {
178
+ const sourceModelJobByKey = new Map(
179
+ (params.sourceAttempt.model?.jobs ?? []).map((job) => [job.key, job]),
180
+ );
181
+ const sourceStepsByJobId = new Map<string, StepDb[]>();
182
+ for (const step of params.sourceSteps) {
183
+ const sourceJob = params.sourceJobByJobExecutionId.get(step.jobExecutionId);
184
+ if (!sourceJob) continue;
185
+ const sourceJobSteps = sourceStepsByJobId.get(sourceJob.id) ?? [];
186
+ sourceJobSteps.push(step);
187
+ sourceStepsByJobId.set(sourceJob.id, sourceJobSteps);
188
+ }
189
+
190
+ return params.sourceJobs.map((sourceJob) => {
191
+ const carriedOver = params.mode === 'failed' && sourceJob.status === 'succeeded';
192
+
193
+ return {
194
+ job: {
195
+ key: sourceJob.key,
196
+ name: sourceJob.name,
197
+ mode: sourceJob.mode,
198
+ status: carriedOver ? ('succeeded' as const) : ('pending' as const),
199
+ statusReason: null,
200
+ carriedOver,
201
+ checkoutPersistCredentials: sourceJob.checkoutPersistCredentials,
202
+ checkoutPermissionsContents: sourceJob.checkoutPermissionsContents,
203
+ success: sourceJob.success,
204
+ executionTimeoutMs: sourceJob.executionTimeoutMs,
205
+ listeningTimeoutMs: sourceJob.listeningTimeoutMs,
206
+ maxExecutions: sourceJob.maxExecutions,
207
+ onResolve: sourceJob.onResolve,
208
+ batchDebounceMs: sourceJob.batchDebounceMs,
209
+ batchMaxSize: sourceJob.batchMaxSize,
210
+ batchMaxWaitMs: sourceJob.batchMaxWaitMs,
211
+ listenerStatus: 'inactive' as const,
212
+ resolutionReason: null,
213
+ listeningOn: sourceJob.listeningOn ? [...sourceJob.listeningOn] : null,
214
+ listeningUntil: sourceJob.listeningUntil ? [...sourceJob.listeningUntil] : null,
215
+ outputs: carriedOver && sourceJob.outputs ? {...sourceJob.outputs} : null,
216
+ dependencies: [...sourceJob.dependencies],
217
+ runner: sourceJob.runner ? [...sourceJob.runner] : null,
218
+ position: sourceJob.position,
219
+ },
220
+ createExecution: (job) => {
221
+ if (job.mode === 'listening') return undefined;
222
+
223
+ const sourceExecution = params.sourceJobExecutionByJobId.get(sourceJob.id);
224
+ const modelJob = sourceModelJobByKey.get(job.key);
225
+ const executionName = sourceExecution?.name ?? `${job.key} #1`;
226
+ const runner =
227
+ carriedOver || modelJob === undefined
228
+ ? (sourceExecution?.runner ?? job.runner ?? null)
229
+ : deriveJobExecutionRunner({
230
+ run: params.sourceRun,
231
+ modelJob,
232
+ jobId: job.id,
233
+ sequence: 1,
234
+ executionName,
235
+ status: 'pending',
236
+ });
237
+
238
+ return {
239
+ sequence: 1,
240
+ name: executionName,
241
+ runner: runner ? [...runner] : null,
242
+ status: carriedOver ? ('succeeded' as const) : ('pending' as const),
243
+ statusReason: null,
244
+ outputs: carriedOver && sourceExecution?.outputs ? {...sourceExecution.outputs} : null,
245
+ ...(carriedOver ? {finishedAt: sql`now()`} : {}),
246
+ };
247
+ },
248
+ createSteps: () =>
249
+ (sourceStepsByJobId.get(sourceJob.id) ?? []).map((step) => ({
250
+ key: step.key,
251
+ name: step.name,
252
+ sourceLocation: step.sourceLocation,
253
+ status: carriedOver ? step.status : ('pending' as const),
254
+ statusReason: carriedOver ? step.statusReason : null,
255
+ type: step.type,
256
+ config: step.config,
257
+ condition: step.condition ?? null,
258
+ configPlan: step.configPlan,
259
+ authoredConfig: step.authoredConfig,
260
+ error: null,
261
+ position: step.position,
262
+ currentAttempt: 1,
263
+ })),
264
+ };
265
+ });
266
+ }
@@ -0,0 +1,425 @@
1
+ import {eq} from 'drizzle-orm';
2
+ import {WorkflowRunNotCancellableError} from '#core/errors.js';
3
+ import {nextStepForJob} from '#core/job-execution.js';
4
+ import {
5
+ buildModel,
6
+ createTestRun,
7
+ jobTerminatedEvents,
8
+ runCancelledEvents,
9
+ runTerminatedEvents,
10
+ stepAttemptTerminatedEvents,
11
+ } from '#test/helpers/workflow-runs.js';
12
+ import {db} from '../db.js';
13
+ import {workflowRunAttempts} from '../schema/workflow-run-attempts.js';
14
+ import {workflowRuns} from '../schema/workflow-runs.js';
15
+ import {
16
+ cancelWorkflowRun,
17
+ createRerunWorkflowRun,
18
+ createWorkflowRun,
19
+ getJobsByWorkflowRunId,
20
+ getStepsByJobId,
21
+ getWorkflowRunById,
22
+ listRunAttempts,
23
+ updateJobStatus,
24
+ updateWorkflowRunStatus,
25
+ } from '../workflow-runs.js';
26
+
27
+ describe('workflow run queries', () => {
28
+ let workspaceId: string;
29
+ let projectId: string;
30
+ let definitionId: string;
31
+
32
+ beforeEach(() => {
33
+ workspaceId = crypto.randomUUID();
34
+ projectId = crypto.randomUUID();
35
+ definitionId = crypto.randomUUID();
36
+ });
37
+
38
+ describe('updateWorkflowRunStatus', () => {
39
+ test('updates status and increments version', async () => {
40
+ const run = await createWorkflowRun({
41
+ workspaceId,
42
+ projectId,
43
+ definitionId,
44
+ model: buildModel(),
45
+ triggerPayload: {
46
+ source: 'manual',
47
+ event: 'fire',
48
+ subscriptionId: crypto.randomUUID(),
49
+ userId: crypto.randomUUID(),
50
+ },
51
+ });
52
+
53
+ const updated = await updateWorkflowRunStatus({
54
+ workflowRunId: run.id,
55
+ status: 'running',
56
+ expectedVersion: 1,
57
+ });
58
+
59
+ expect(updated.status).toBe('running');
60
+ expect(updated.version).toBe(2);
61
+ });
62
+
63
+ test('preserves terminal status reason when a later transition is ignored', async () => {
64
+ const run = await createWorkflowRun({
65
+ workspaceId,
66
+ projectId,
67
+ definitionId,
68
+ model: buildModel(),
69
+ triggerPayload: {
70
+ source: 'manual',
71
+ event: 'fire',
72
+ subscriptionId: crypto.randomUUID(),
73
+ userId: crypto.randomUUID(),
74
+ },
75
+ });
76
+ const job = (await getJobsByWorkflowRunId(run.id))[0];
77
+
78
+ const skipped = await updateJobStatus({
79
+ jobId: job?.id as string,
80
+ status: 'skipped',
81
+ expectedVersion: 1,
82
+ statusReason: 'dependency_not_completed',
83
+ });
84
+ const retry = await updateJobStatus({
85
+ jobId: job?.id as string,
86
+ status: 'running',
87
+ expectedVersion: 2,
88
+ });
89
+
90
+ expect(skipped.statusReason).toBe('dependency_not_completed');
91
+ expect(retry.status).toBe('skipped');
92
+ expect(retry.statusReason).toBe('dependency_not_completed');
93
+ });
94
+
95
+ test('throws on version mismatch', async () => {
96
+ const run = await createWorkflowRun({
97
+ workspaceId,
98
+ projectId,
99
+ definitionId,
100
+ model: buildModel(),
101
+ triggerPayload: {
102
+ source: 'manual',
103
+ event: 'fire',
104
+ subscriptionId: crypto.randomUUID(),
105
+ userId: crypto.randomUUID(),
106
+ },
107
+ });
108
+
109
+ await expect(
110
+ updateWorkflowRunStatus({workflowRunId: run.id, status: 'running', expectedVersion: 99}),
111
+ ).rejects.toThrow('Optimistic lock failure');
112
+ });
113
+
114
+ test('throws when run not found', async () => {
115
+ await expect(
116
+ updateWorkflowRunStatus({
117
+ workflowRunId: crypto.randomUUID(),
118
+ status: 'running',
119
+ expectedVersion: 1,
120
+ }),
121
+ ).rejects.toThrow('Workflow run not found');
122
+ });
123
+
124
+ test.each([
125
+ 'succeeded',
126
+ 'failed',
127
+ 'cancelled',
128
+ ] as const)('writes one run-terminated event when the status becomes %s', async (status) => {
129
+ const run = await createTestRun({workspaceId, projectId, definitionId});
130
+
131
+ await updateWorkflowRunStatus({workflowRunId: run.id, status, expectedVersion: 1});
132
+
133
+ const events = await runTerminatedEvents(run.id);
134
+ expect(events).toHaveLength(1);
135
+ expect(events[0]).toMatchObject({workflowRunId: run.id, projectId: run.projectId, status});
136
+ });
137
+
138
+ test('writes no run-terminated event for a non-terminal transition', async () => {
139
+ const run = await createTestRun({workspaceId, projectId, definitionId});
140
+
141
+ await updateWorkflowRunStatus({workflowRunId: run.id, status: 'running', expectedVersion: 1});
142
+
143
+ expect(await runTerminatedEvents(run.id)).toHaveLength(0);
144
+ });
145
+
146
+ test('does not mirror a non-current attempt terminal update to the run', async () => {
147
+ const run = await createTestRun({workspaceId, projectId, definitionId});
148
+ const attempts = await listRunAttempts({workflowRunId: run.id, projectId});
149
+ const firstAttempt = attempts[0];
150
+ if (!firstAttempt) throw new Error('Expected initial attempt');
151
+ await db().insert(workflowRunAttempts).values({
152
+ workflowRunId: run.id,
153
+ attempt: 2,
154
+ status: 'succeeded',
155
+ });
156
+ await db()
157
+ .update(workflowRuns)
158
+ .set({currentAttempt: 2, status: 'succeeded'})
159
+ .where(eq(workflowRuns.id, run.id));
160
+
161
+ const staleUpdate = await updateWorkflowRunStatus({
162
+ workflowRunAttemptId: firstAttempt.id,
163
+ status: 'failed',
164
+ expectedVersion: 1,
165
+ });
166
+
167
+ expect(staleUpdate.status).toBe('succeeded');
168
+ expect(await getWorkflowRunById(run.id)).toMatchObject({
169
+ status: 'succeeded',
170
+ currentAttempt: 2,
171
+ });
172
+ expect(await runTerminatedEvents(run.id)).toHaveLength(0);
173
+ });
174
+
175
+ test('idempotent retry: a second terminal update at the stale version emits once', async () => {
176
+ const run = await createTestRun({workspaceId, projectId, definitionId});
177
+
178
+ const first = await updateWorkflowRunStatus({
179
+ workflowRunId: run.id,
180
+ status: 'failed',
181
+ expectedVersion: 1,
182
+ });
183
+ const retry = await updateWorkflowRunStatus({
184
+ workflowRunId: run.id,
185
+ status: 'failed',
186
+ expectedVersion: 1,
187
+ });
188
+
189
+ expect(retry.version).toBe(first.version);
190
+ expect(await runTerminatedEvents(run.id)).toHaveLength(1);
191
+ });
192
+
193
+ test('terminal-tolerant mismatch: existing terminal run returns without re-emitting', async () => {
194
+ const run = await createTestRun({workspaceId, projectId, definitionId});
195
+ const cancelled = await updateWorkflowRunStatus({
196
+ workflowRunId: run.id,
197
+ status: 'cancelled',
198
+ expectedVersion: 1,
199
+ });
200
+
201
+ const retry = await updateWorkflowRunStatus({
202
+ workflowRunId: run.id,
203
+ status: 'running',
204
+ expectedVersion: 1,
205
+ });
206
+
207
+ expect(retry.status).toBe('cancelled');
208
+ expect(retry.version).toBe(cancelled.version);
209
+ expect(await runTerminatedEvents(run.id)).toHaveLength(1);
210
+ });
211
+
212
+ test('terminal-tolerant match: existing terminal run cannot be revived at the current version', async () => {
213
+ const run = await createTestRun({workspaceId, projectId, definitionId});
214
+ const cancelled = await updateWorkflowRunStatus({
215
+ workflowRunId: run.id,
216
+ status: 'cancelled',
217
+ expectedVersion: 1,
218
+ });
219
+
220
+ const retry = await updateWorkflowRunStatus({
221
+ workflowRunId: run.id,
222
+ status: 'running',
223
+ expectedVersion: cancelled.version,
224
+ });
225
+
226
+ expect(retry.status).toBe('cancelled');
227
+ expect(retry.version).toBe(cancelled.version);
228
+ expect(await getWorkflowRunById(run.id)).toMatchObject({
229
+ status: 'cancelled',
230
+ version: cancelled.version,
231
+ });
232
+ expect(await runTerminatedEvents(run.id)).toHaveLength(1);
233
+ });
234
+ });
235
+
236
+ describe('cancelWorkflowRun', () => {
237
+ test('cancels the run, non-terminal jobs, and only their non-terminal steps', async () => {
238
+ const run = await createWorkflowRun({
239
+ workspaceId,
240
+ projectId,
241
+ definitionId,
242
+ model: buildModel({
243
+ jobs: {
244
+ running: {steps: [{run: 'a'}, {run: 'b'}]},
245
+ succeeded: {steps: [{run: 'ok'}]},
246
+ skipped: {steps: [{run: 'skip'}]},
247
+ },
248
+ }),
249
+ triggerPayload: {
250
+ source: 'manual',
251
+ event: 'fire',
252
+ subscriptionId: crypto.randomUUID(),
253
+ userId: crypto.randomUUID(),
254
+ },
255
+ });
256
+ await updateWorkflowRunStatus({workflowRunId: run.id, status: 'running', expectedVersion: 1});
257
+ const [runningJobExecution, succeededJob, skippedJob] = await getJobsByWorkflowRunId(run.id);
258
+ if (!runningJobExecution || !succeededJob || !skippedJob) throw new Error('Expected jobs');
259
+ await updateJobStatus({jobId: runningJobExecution.id, status: 'running', expectedVersion: 1});
260
+ await nextStepForJob(runningJobExecution.id);
261
+ await updateJobStatus({jobId: succeededJob.id, status: 'succeeded', expectedVersion: 1});
262
+ await updateJobStatus({
263
+ jobId: skippedJob.id,
264
+ status: 'skipped',
265
+ expectedVersion: 1,
266
+ statusReason: 'dependency_not_completed',
267
+ });
268
+
269
+ const cancelled = await cancelWorkflowRun({workflowRunId: run.id});
270
+
271
+ expect(cancelled.status).toBe('cancelled');
272
+ expect(cancelled.finishedAt).not.toBeNull();
273
+ const [finalRunning, finalSucceeded, finalSkipped] = await getJobsByWorkflowRunId(run.id);
274
+ expect(finalRunning).toMatchObject({status: 'cancelled', statusReason: 'run_cancelled'});
275
+ expect(finalSucceeded).toMatchObject({status: 'succeeded', statusReason: null});
276
+ expect(finalSkipped).toMatchObject({
277
+ status: 'skipped',
278
+ statusReason: 'dependency_not_completed',
279
+ });
280
+ expect((await getStepsByJobId(runningJobExecution.id)).map((step) => step.status)).toEqual([
281
+ 'cancelled',
282
+ 'cancelled',
283
+ 'cancelled',
284
+ ]);
285
+ expect(
286
+ (await getStepsByJobId(skippedJob.id)).every((step) => step.status === 'pending'),
287
+ ).toBe(true);
288
+ expect(await runTerminatedEvents(run.id)).toEqual([
289
+ expect.objectContaining({workflowRunId: run.id, projectId, status: 'cancelled'}),
290
+ ]);
291
+ expect(await runCancelledEvents(run.id)).toEqual([
292
+ expect.objectContaining({workflowRunId: run.id, projectId}),
293
+ ]);
294
+ expect(await jobTerminatedEvents(runningJobExecution.id)).toEqual([
295
+ expect.objectContaining({
296
+ jobId: runningJobExecution.id,
297
+ workflowRunId: run.id,
298
+ status: 'cancelled',
299
+ statusReason: 'run_cancelled',
300
+ }),
301
+ ]);
302
+ expect(await stepAttemptTerminatedEvents(runningJobExecution.id)).toHaveLength(1);
303
+ expect(await jobTerminatedEvents(succeededJob.id)).toHaveLength(1);
304
+ expect(await jobTerminatedEvents(skippedJob.id)).toHaveLength(1);
305
+ });
306
+
307
+ test('cancels the current rerun attempt after current_attempt moves', async () => {
308
+ const run = await createTestRun({workspaceId, projectId, definitionId});
309
+ await updateWorkflowRunStatus({workflowRunId: run.id, status: 'failed', expectedVersion: 1});
310
+ const firstAttempt = (await listRunAttempts({workflowRunId: run.id, projectId}))[0];
311
+ if (!firstAttempt) throw new Error('Expected initial attempt');
312
+ await createRerunWorkflowRun({
313
+ workflowRunId: run.id,
314
+ mode: 'all',
315
+ actorUserId: crypto.randomUUID(),
316
+ });
317
+ const secondAttempt = (await listRunAttempts({workflowRunId: run.id, projectId})).find(
318
+ (attempt) => attempt.attempt === 2,
319
+ );
320
+ if (!secondAttempt) throw new Error('Expected rerun attempt');
321
+ await updateWorkflowRunStatus({workflowRunId: run.id, status: 'running', expectedVersion: 1});
322
+
323
+ await cancelWorkflowRun({workflowRunId: run.id});
324
+
325
+ expect(await runCancelledEvents(run.id)).toEqual([
326
+ expect.objectContaining({
327
+ workflowRunId: run.id,
328
+ workflowRunAttemptId: secondAttempt.id,
329
+ projectId,
330
+ }),
331
+ ]);
332
+ const terminatedEvents = await runTerminatedEvents(run.id);
333
+ expect(terminatedEvents).toHaveLength(2);
334
+ expect(terminatedEvents).toEqual(
335
+ expect.arrayContaining([
336
+ expect.objectContaining({
337
+ workflowRunId: run.id,
338
+ workflowRunAttemptId: firstAttempt.id,
339
+ projectId,
340
+ status: 'failed',
341
+ }),
342
+ expect.objectContaining({
343
+ workflowRunId: run.id,
344
+ workflowRunAttemptId: secondAttempt.id,
345
+ projectId,
346
+ status: 'cancelled',
347
+ }),
348
+ ]),
349
+ );
350
+ });
351
+
352
+ test('throws without changing an already-terminal run', async () => {
353
+ const run = await createTestRun({workspaceId, projectId, definitionId});
354
+ const finished = await updateWorkflowRunStatus({
355
+ workflowRunId: run.id,
356
+ status: 'succeeded',
357
+ expectedVersion: 1,
358
+ });
359
+
360
+ await expect(cancelWorkflowRun({workflowRunId: run.id})).rejects.toBeInstanceOf(
361
+ WorkflowRunNotCancellableError,
362
+ );
363
+
364
+ expect(await getWorkflowRunById(run.id)).toMatchObject({
365
+ status: 'succeeded',
366
+ version: finished.version,
367
+ });
368
+ expect(await runCancelledEvents(run.id)).toHaveLength(0);
369
+ });
370
+ });
371
+
372
+ describe('run lifecycle timing', () => {
373
+ test('run: stamps started_at on running and preserves it through the terminal transition', async () => {
374
+ const run = await createTestRun({workspaceId, projectId, definitionId});
375
+
376
+ const running = await updateWorkflowRunStatus({
377
+ workflowRunId: run.id,
378
+ status: 'running',
379
+ expectedVersion: 1,
380
+ });
381
+
382
+ expect(running.startedAt).not.toBeNull();
383
+ expect(running.finishedAt).toBeNull();
384
+
385
+ const finished = await updateWorkflowRunStatus({
386
+ workflowRunId: run.id,
387
+ status: 'succeeded',
388
+ expectedVersion: 2,
389
+ });
390
+
391
+ expect(finished.finishedAt).not.toBeNull();
392
+ expect(finished.startedAt?.getTime()).toBe(running.startedAt?.getTime());
393
+ });
394
+
395
+ test('run: cancelled straight from pending has no start but a finish', async () => {
396
+ const run = await createTestRun({workspaceId, projectId, definitionId});
397
+
398
+ const cancelled = await updateWorkflowRunStatus({
399
+ workflowRunId: run.id,
400
+ status: 'cancelled',
401
+ expectedVersion: 1,
402
+ });
403
+
404
+ expect(cancelled.startedAt).toBeNull();
405
+ expect(cancelled.finishedAt).not.toBeNull();
406
+ });
407
+
408
+ test('run: re-entering running keeps the first started_at (coalesce, not a fresh clock)', async () => {
409
+ const run = await createTestRun({workspaceId, projectId, definitionId});
410
+ const firstRunning = await updateWorkflowRunStatus({
411
+ workflowRunId: run.id,
412
+ status: 'running',
413
+ expectedVersion: 1,
414
+ });
415
+
416
+ const secondRunning = await updateWorkflowRunStatus({
417
+ workflowRunId: run.id,
418
+ status: 'running',
419
+ expectedVersion: 2,
420
+ });
421
+
422
+ expect(secondRunning.startedAt?.getTime()).toBe(firstRunning.startedAt?.getTime());
423
+ });
424
+ });
425
+ });