@shipfox/api-workflows 10.2.0 → 12.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 (354) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/CHANGELOG.md +117 -0
  3. package/README.md +75 -0
  4. package/dist/core/agent-defaults.d.ts +2 -1
  5. package/dist/core/agent-defaults.d.ts.map +1 -1
  6. package/dist/core/agent-defaults.js.map +1 -1
  7. package/dist/core/checkout.d.ts +7 -21
  8. package/dist/core/checkout.d.ts.map +1 -1
  9. package/dist/core/checkout.js +122 -41
  10. package/dist/core/checkout.js.map +1 -1
  11. package/dist/core/entities/job-execution.d.ts +8 -0
  12. package/dist/core/entities/job-execution.d.ts.map +1 -1
  13. package/dist/core/entities/job-execution.js +13 -1
  14. package/dist/core/entities/job-execution.js.map +1 -1
  15. package/dist/core/entities/job-listener-event.d.ts +2 -0
  16. package/dist/core/entities/job-listener-event.d.ts.map +1 -1
  17. package/dist/core/entities/job-listener-event.js.map +1 -1
  18. package/dist/core/entities/step.d.ts +13 -3
  19. package/dist/core/entities/step.d.ts.map +1 -1
  20. package/dist/core/entities/step.js +6 -0
  21. package/dist/core/entities/step.js.map +1 -1
  22. package/dist/core/entities/workflow-run-attempt.d.ts +1 -0
  23. package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -1
  24. package/dist/core/entities/workflow-run-attempt.js.map +1 -1
  25. package/dist/core/entities/workflow-run.d.ts +17 -0
  26. package/dist/core/entities/workflow-run.d.ts.map +1 -1
  27. package/dist/core/entities/workflow-run.js.map +1 -1
  28. package/dist/core/errors.d.ts +9 -2
  29. package/dist/core/errors.d.ts.map +1 -1
  30. package/dist/core/errors.js +10 -5
  31. package/dist/core/errors.js.map +1 -1
  32. package/dist/core/job-execution.d.ts.map +1 -1
  33. package/dist/core/job-execution.js +14 -8
  34. package/dist/core/job-execution.js.map +1 -1
  35. package/dist/core/job-transition/decide-job-activation.d.ts +1 -0
  36. package/dist/core/job-transition/decide-job-activation.d.ts.map +1 -1
  37. package/dist/core/job-transition/decide-job-activation.js +1 -0
  38. package/dist/core/job-transition/decide-job-activation.js.map +1 -1
  39. package/dist/core/job-transition/derive-job-success.d.ts +1 -0
  40. package/dist/core/job-transition/derive-job-success.d.ts.map +1 -1
  41. package/dist/core/job-transition/derive-job-success.js +5 -8
  42. package/dist/core/job-transition/derive-job-success.js.map +1 -1
  43. package/dist/core/listener-execution-materialization.d.ts +6 -3
  44. package/dist/core/listener-execution-materialization.d.ts.map +1 -1
  45. package/dist/core/listener-execution-materialization.js +44 -6
  46. package/dist/core/listener-execution-materialization.js.map +1 -1
  47. package/dist/core/resolve-trigger-reference.d.ts +11 -0
  48. package/dist/core/resolve-trigger-reference.d.ts.map +1 -0
  49. package/dist/core/resolve-trigger-reference.js +39 -0
  50. package/dist/core/resolve-trigger-reference.js.map +1 -0
  51. package/dist/core/run-workflow.d.ts +1 -0
  52. package/dist/core/run-workflow.d.ts.map +1 -1
  53. package/dist/core/run-workflow.js +1 -0
  54. package/dist/core/run-workflow.js.map +1 -1
  55. package/dist/core/step-config/agent.d.ts +1 -2
  56. package/dist/core/step-config/agent.d.ts.map +1 -1
  57. package/dist/core/step-config/agent.js +22 -7
  58. package/dist/core/step-config/agent.js.map +1 -1
  59. package/dist/core/step-config/assemble-run-context.d.ts +19 -8
  60. package/dist/core/step-config/assemble-run-context.d.ts.map +1 -1
  61. package/dist/core/step-config/assemble-run-context.js +51 -19
  62. package/dist/core/step-config/assemble-run-context.js.map +1 -1
  63. package/dist/core/step-config/complete-step-dispatch-config.d.ts.map +1 -1
  64. package/dist/core/step-config/complete-step-dispatch-config.js +72 -4
  65. package/dist/core/step-config/complete-step-dispatch-config.js.map +1 -1
  66. package/dist/core/step-config/index.d.ts +1 -0
  67. package/dist/core/step-config/index.d.ts.map +1 -1
  68. package/dist/core/step-config/index.js +1 -0
  69. package/dist/core/step-config/index.js.map +1 -1
  70. package/dist/core/step-config/materialize-job-execution-steps.d.ts +1 -1
  71. package/dist/core/step-config/materialize-job-execution-steps.d.ts.map +1 -1
  72. package/dist/core/step-config/materialize-job-execution-steps.js +49 -8
  73. package/dist/core/step-config/materialize-job-execution-steps.js.map +1 -1
  74. package/dist/core/step-config/materialize-workflow-model.d.ts +3 -3
  75. package/dist/core/step-config/materialize-workflow-model.d.ts.map +1 -1
  76. package/dist/core/step-config/materialize-workflow-model.js +9 -5
  77. package/dist/core/step-config/materialize-workflow-model.js.map +1 -1
  78. package/dist/core/step-config/resolve-job-execution-name.d.ts +3 -3
  79. package/dist/core/step-config/resolve-job-execution-name.d.ts.map +1 -1
  80. package/dist/core/step-config/resolve-job-execution-name.js +24 -13
  81. package/dist/core/step-config/resolve-job-execution-name.js.map +1 -1
  82. package/dist/core/step-config/resolve-step-config.d.ts.map +1 -1
  83. package/dist/core/step-config/resolve-step-config.js +174 -9
  84. package/dist/core/step-config/resolve-step-config.js.map +1 -1
  85. package/dist/core/step-config/resolve-workflow-run-name.d.ts +19 -0
  86. package/dist/core/step-config/resolve-workflow-run-name.d.ts.map +1 -0
  87. package/dist/core/step-config/resolve-workflow-run-name.js +73 -0
  88. package/dist/core/step-config/resolve-workflow-run-name.js.map +1 -0
  89. package/dist/core/step-transition/decide-step-transition.js.map +1 -1
  90. package/dist/core/step-transition/evaluate-gate.d.ts +2 -1
  91. package/dist/core/step-transition/evaluate-gate.d.ts.map +1 -1
  92. package/dist/core/step-transition/evaluate-gate.js +5 -3
  93. package/dist/core/step-transition/evaluate-gate.js.map +1 -1
  94. package/dist/core/workflow-run-creation.d.ts +3 -0
  95. package/dist/core/workflow-run-creation.d.ts.map +1 -1
  96. package/dist/core/workflow-run-creation.js +22 -7
  97. package/dist/core/workflow-run-creation.js.map +1 -1
  98. package/dist/db/db.d.ts +278 -10
  99. package/dist/db/db.d.ts.map +1 -1
  100. package/dist/db/db.js +2 -0
  101. package/dist/db/db.js.map +1 -1
  102. package/dist/db/index.d.ts +3 -2
  103. package/dist/db/index.d.ts.map +1 -1
  104. package/dist/db/index.js +2 -1
  105. package/dist/db/index.js.map +1 -1
  106. package/dist/db/job-listener-events.d.ts +2 -0
  107. package/dist/db/job-listener-events.d.ts.map +1 -1
  108. package/dist/db/job-listener-events.js +1 -0
  109. package/dist/db/job-listener-events.js.map +1 -1
  110. package/dist/db/job-listeners.d.ts +2 -0
  111. package/dist/db/job-listeners.d.ts.map +1 -1
  112. package/dist/db/job-listeners.js +81 -21
  113. package/dist/db/job-listeners.js.map +1 -1
  114. package/dist/db/schema/job-executions.d.ts +3 -3
  115. package/dist/db/schema/job-executions.d.ts.map +1 -1
  116. package/dist/db/schema/job-executions.js +8 -4
  117. package/dist/db/schema/job-executions.js.map +1 -1
  118. package/dist/db/schema/job-listener-events.d.ts +20 -0
  119. package/dist/db/schema/job-listener-events.d.ts.map +1 -1
  120. package/dist/db/schema/job-listener-events.js +2 -0
  121. package/dist/db/schema/job-listener-events.js.map +1 -1
  122. package/dist/db/schema/step-attempts.js +2 -2
  123. package/dist/db/schema/step-attempts.js.map +1 -1
  124. package/dist/db/schema/steps.d.ts +4 -2
  125. package/dist/db/schema/steps.d.ts.map +1 -1
  126. package/dist/db/schema/steps.js +1 -1
  127. package/dist/db/schema/steps.js.map +1 -1
  128. package/dist/db/schema/workflow-run-attempts.d.ts +21 -2
  129. package/dist/db/schema/workflow-run-attempts.d.ts.map +1 -1
  130. package/dist/db/schema/workflow-run-attempts.js +2 -0
  131. package/dist/db/schema/workflow-run-attempts.js.map +1 -1
  132. package/dist/db/schema/workflow-run-counters.d.ts +43 -0
  133. package/dist/db/schema/workflow-run-counters.d.ts.map +1 -0
  134. package/dist/db/schema/workflow-run-counters.js +10 -0
  135. package/dist/db/schema/workflow-run-counters.js.map +1 -0
  136. package/dist/db/schema/workflow-runs.d.ts +54 -1
  137. package/dist/db/schema/workflow-runs.d.ts.map +1 -1
  138. package/dist/db/schema/workflow-runs.js +10 -2
  139. package/dist/db/schema/workflow-runs.js.map +1 -1
  140. package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
  141. package/dist/db/workflow-runs/job-executions.js +35 -14
  142. package/dist/db/workflow-runs/job-executions.js.map +1 -1
  143. package/dist/db/workflow-runs/jobs.d.ts +2 -0
  144. package/dist/db/workflow-runs/jobs.d.ts.map +1 -1
  145. package/dist/db/workflow-runs/jobs.js +9 -5
  146. package/dist/db/workflow-runs/jobs.js.map +1 -1
  147. package/dist/db/workflow-runs/queries.d.ts +51 -0
  148. package/dist/db/workflow-runs/queries.d.ts.map +1 -1
  149. package/dist/db/workflow-runs/queries.js +82 -2
  150. package/dist/db/workflow-runs/queries.js.map +1 -1
  151. package/dist/db/workflow-runs/run-create.d.ts +1 -0
  152. package/dist/db/workflow-runs/run-create.d.ts.map +1 -1
  153. package/dist/db/workflow-runs/run-create.js +159 -32
  154. package/dist/db/workflow-runs/run-create.js.map +1 -1
  155. package/dist/db/workflow-runs/run-rerun.d.ts.map +1 -1
  156. package/dist/db/workflow-runs/run-rerun.js +13 -5
  157. package/dist/db/workflow-runs/run-rerun.js.map +1 -1
  158. package/dist/db/workflow-runs/shared.d.ts +1 -0
  159. package/dist/db/workflow-runs/shared.d.ts.map +1 -1
  160. package/dist/db/workflow-runs/shared.js +2 -1
  161. package/dist/db/workflow-runs/shared.js.map +1 -1
  162. package/dist/db/workflow-runs/steps.js +3 -3
  163. package/dist/db/workflow-runs/steps.js.map +1 -1
  164. package/dist/db/workflow-runs.d.ts +4 -3
  165. package/dist/db/workflow-runs.d.ts.map +1 -1
  166. package/dist/db/workflow-runs.js +3 -2
  167. package/dist/db/workflow-runs.js.map +1 -1
  168. package/dist/metrics/instance.d.ts +1 -0
  169. package/dist/metrics/instance.d.ts.map +1 -1
  170. package/dist/metrics/instance.js +9 -0
  171. package/dist/metrics/instance.js.map +1 -1
  172. package/dist/presentation/dto/checkout-token.d.ts +1 -0
  173. package/dist/presentation/dto/checkout-token.d.ts.map +1 -1
  174. package/dist/presentation/dto/checkout-token.js +1 -0
  175. package/dist/presentation/dto/checkout-token.js.map +1 -1
  176. package/dist/presentation/dto/index.d.ts +1 -1
  177. package/dist/presentation/dto/index.d.ts.map +1 -1
  178. package/dist/presentation/dto/index.js +1 -1
  179. package/dist/presentation/dto/index.js.map +1 -1
  180. package/dist/presentation/dto/step.d.ts.map +1 -1
  181. package/dist/presentation/dto/step.js +2 -2
  182. package/dist/presentation/dto/step.js.map +1 -1
  183. package/dist/presentation/dto/workflow-run.d.ts +3 -1
  184. package/dist/presentation/dto/workflow-run.d.ts.map +1 -1
  185. package/dist/presentation/dto/workflow-run.js +34 -0
  186. package/dist/presentation/dto/workflow-run.js.map +1 -1
  187. package/dist/presentation/inter-module.d.ts.map +1 -1
  188. package/dist/presentation/inter-module.js +31 -3
  189. package/dist/presentation/inter-module.js.map +1 -1
  190. package/dist/presentation/routes/checkout-token.d.ts +1 -1
  191. package/dist/presentation/routes/checkout-token.d.ts.map +1 -1
  192. package/dist/presentation/routes/checkout-token.js +32 -20
  193. package/dist/presentation/routes/checkout-token.js.map +1 -1
  194. package/dist/presentation/routes/leased-step.d.ts +2 -0
  195. package/dist/presentation/routes/leased-step.d.ts.map +1 -1
  196. package/dist/presentation/routes/leased-step.js +2 -1
  197. package/dist/presentation/routes/leased-step.js.map +1 -1
  198. package/dist/presentation/routes/list-runs.d.ts.map +1 -1
  199. package/dist/presentation/routes/list-runs.js +9 -3
  200. package/dist/presentation/routes/list-runs.js.map +1 -1
  201. package/dist/presentation/routes/project-access.d.ts +1 -0
  202. package/dist/presentation/routes/project-access.d.ts.map +1 -1
  203. package/dist/presentation/routes/report-step.d.ts.map +1 -1
  204. package/dist/presentation/routes/report-step.js +9 -2
  205. package/dist/presentation/routes/report-step.js.map +1 -1
  206. package/dist/presentation/routes/require-accessible-run.d.ts.map +1 -1
  207. package/dist/presentation/routes/require-accessible-run.js +1 -1
  208. package/dist/presentation/routes/require-accessible-run.js.map +1 -1
  209. package/dist/presentation/subscribers/on-job-steps-settled.js +1 -1
  210. package/dist/presentation/subscribers/on-job-steps-settled.js.map +1 -1
  211. package/dist/presentation/subscribers/on-runner-job-claimed.d.ts.map +1 -1
  212. package/dist/presentation/subscribers/on-runner-job-claimed.js +21 -0
  213. package/dist/presentation/subscribers/on-runner-job-claimed.js.map +1 -1
  214. package/dist/temporal/activities/index.d.ts.map +1 -1
  215. package/dist/temporal/activities/index.js +2 -1
  216. package/dist/temporal/activities/index.js.map +1 -1
  217. package/dist/temporal/activities/orchestration-activities.d.ts +1 -0
  218. package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -1
  219. package/dist/temporal/activities/orchestration-activities.js.map +1 -1
  220. package/dist/temporal/constants.d.ts +1 -0
  221. package/dist/temporal/constants.d.ts.map +1 -1
  222. package/dist/temporal/constants.js +1 -0
  223. package/dist/temporal/constants.js.map +1 -1
  224. package/dist/temporal/index.d.ts +1 -1
  225. package/dist/temporal/index.d.ts.map +1 -1
  226. package/dist/temporal/index.js +1 -1
  227. package/dist/temporal/index.js.map +1 -1
  228. package/dist/temporal/workflows/index.bundle.js +82 -43
  229. package/dist/temporal/workflows/index.d.ts +1 -1
  230. package/dist/temporal/workflows/index.d.ts.map +1 -1
  231. package/dist/temporal/workflows/index.js +1 -1
  232. package/dist/temporal/workflows/index.js.map +1 -1
  233. package/dist/temporal/workflows/job-execution-orchestration.d.ts +5 -0
  234. package/dist/temporal/workflows/job-execution-orchestration.d.ts.map +1 -1
  235. package/dist/temporal/workflows/job-execution-orchestration.js +77 -43
  236. package/dist/temporal/workflows/job-execution-orchestration.js.map +1 -1
  237. package/dist/temporal/workflows/test-env.d.ts +5 -1
  238. package/dist/temporal/workflows/test-env.d.ts.map +1 -1
  239. package/dist/temporal/workflows/test-env.js +32 -11
  240. package/dist/temporal/workflows/test-env.js.map +1 -1
  241. package/dist/tsconfig.test.tsbuildinfo +1 -1
  242. package/drizzle/0000_initial.sql +14 -2
  243. package/drizzle/0001_windy_sunfire.sql +1 -0
  244. package/drizzle/meta/0000_snapshot.json +93 -1
  245. package/drizzle/meta/0001_snapshot.json +1711 -0
  246. package/drizzle/meta/_journal.json +2 -2
  247. package/package.json +21 -21
  248. package/src/core/agent-defaults.ts +2 -1
  249. package/src/core/checkout.test.ts +378 -67
  250. package/src/core/checkout.ts +142 -59
  251. package/src/core/entities/job-execution.test.ts +21 -0
  252. package/src/core/entities/job-execution.ts +24 -0
  253. package/src/core/entities/job-listener-event.ts +3 -0
  254. package/src/core/entities/step.ts +15 -3
  255. package/src/core/entities/workflow-run-attempt.ts +1 -0
  256. package/src/core/entities/workflow-run.ts +16 -0
  257. package/src/core/errors.ts +21 -7
  258. package/src/core/job-execution.ts +27 -4
  259. package/src/core/job-transition/decide-job-activation.test.ts +4 -0
  260. package/src/core/job-transition/decide-job-activation.ts +2 -0
  261. package/src/core/job-transition/derive-job-success.test.ts +12 -0
  262. package/src/core/job-transition/derive-job-success.ts +6 -9
  263. package/src/core/listener-execution-materialization.test.ts +2 -0
  264. package/src/core/listener-execution-materialization.ts +57 -11
  265. package/src/core/resolve-trigger-reference.test.ts +160 -0
  266. package/src/core/resolve-trigger-reference.ts +53 -0
  267. package/src/core/run-workflow.ts +2 -0
  268. package/src/core/step-config/agent.ts +29 -12
  269. package/src/core/step-config/assemble-run-context.test.ts +149 -6
  270. package/src/core/step-config/assemble-run-context.ts +87 -25
  271. package/src/core/step-config/complete-step-dispatch-config.test.ts +125 -2
  272. package/src/core/step-config/complete-step-dispatch-config.ts +83 -1
  273. package/src/core/step-config/index.ts +7 -0
  274. package/src/core/step-config/materialize-job-execution-steps.test.ts +250 -1
  275. package/src/core/step-config/materialize-job-execution-steps.ts +57 -10
  276. package/src/core/step-config/materialize-workflow-model.test.ts +212 -4
  277. package/src/core/step-config/materialize-workflow-model.ts +33 -28
  278. package/src/core/step-config/resolve-job-execution-name.test.ts +37 -15
  279. package/src/core/step-config/resolve-job-execution-name.ts +30 -15
  280. package/src/core/step-config/resolve-step-config.ts +175 -10
  281. package/src/core/step-config/resolve-workflow-run-name.test.ts +115 -0
  282. package/src/core/step-config/resolve-workflow-run-name.ts +108 -0
  283. package/src/core/step-transition/decide-step-transition.ts +2 -2
  284. package/src/core/step-transition/evaluate-gate.test.ts +8 -0
  285. package/src/core/step-transition/evaluate-gate.ts +8 -1
  286. package/src/core/workflow-run-creation.test.ts +7 -4
  287. package/src/core/workflow-run-creation.ts +13 -7
  288. package/src/db/db.ts +2 -0
  289. package/src/db/index.ts +6 -0
  290. package/src/db/job-listener-events.test.ts +19 -0
  291. package/src/db/job-listener-events.ts +3 -0
  292. package/src/db/job-listeners.test.ts +187 -5
  293. package/src/db/job-listeners.ts +122 -30
  294. package/src/db/schema/job-executions.ts +12 -5
  295. package/src/db/schema/job-listener-events.ts +3 -0
  296. package/src/db/schema/step-attempts.ts +2 -2
  297. package/src/db/schema/steps.ts +2 -1
  298. package/src/db/schema/workflow-run-attempts.ts +2 -0
  299. package/src/db/schema/workflow-run-counters.ts +13 -0
  300. package/src/db/schema/workflow-runs.ts +11 -2
  301. package/src/db/workflow-runs/job-executions.ts +37 -15
  302. package/src/db/workflow-runs/jobs.ts +17 -4
  303. package/src/db/workflow-runs/queries.test.ts +206 -0
  304. package/src/db/workflow-runs/queries.ts +146 -2
  305. package/src/db/workflow-runs/run-create.test.ts +606 -8
  306. package/src/db/workflow-runs/run-create.ts +185 -40
  307. package/src/db/workflow-runs/run-rerun.test.ts +61 -2
  308. package/src/db/workflow-runs/run-rerun.ts +15 -5
  309. package/src/db/workflow-runs/shared.ts +2 -0
  310. package/src/db/workflow-runs/steps.ts +3 -3
  311. package/src/db/workflow-runs.ts +7 -0
  312. package/src/metrics/instance.ts +14 -0
  313. package/src/presentation/dto/checkout-token.test.ts +19 -9
  314. package/src/presentation/dto/checkout-token.ts +2 -1
  315. package/src/presentation/dto/index.ts +1 -1
  316. package/src/presentation/dto/job.test.ts +9 -0
  317. package/src/presentation/dto/step.test.ts +12 -0
  318. package/src/presentation/dto/step.ts +5 -2
  319. package/src/presentation/dto/workflow-run.ts +44 -2
  320. package/src/presentation/inter-module.test.ts +165 -0
  321. package/src/presentation/inter-module.ts +38 -4
  322. package/src/presentation/routes/cancel-run.test.ts +1 -1
  323. package/src/presentation/routes/checkout-token.test.ts +357 -142
  324. package/src/presentation/routes/checkout-token.ts +56 -26
  325. package/src/presentation/routes/get-run-aggregates.test.ts +1 -1
  326. package/src/presentation/routes/get-run.test.ts +29 -1
  327. package/src/presentation/routes/get-step-secrets.test.ts +7 -2
  328. package/src/presentation/routes/leased-step.ts +9 -1
  329. package/src/presentation/routes/list-run-attempts.test.ts +1 -1
  330. package/src/presentation/routes/list-runs.test.ts +58 -1
  331. package/src/presentation/routes/list-runs.ts +8 -3
  332. package/src/presentation/routes/report-step.test.ts +25 -0
  333. package/src/presentation/routes/report-step.ts +10 -2
  334. package/src/presentation/routes/require-accessible-run.ts +4 -1
  335. package/src/presentation/routes/rerun-run.test.ts +1 -1
  336. package/src/presentation/subscribers/on-job-steps-settled.ts +1 -1
  337. package/src/presentation/subscribers/on-runner-job-claimed.test.ts +49 -0
  338. package/src/presentation/subscribers/on-runner-job-claimed.ts +22 -0
  339. package/src/temporal/activities/index.ts +1 -0
  340. package/src/temporal/activities/orchestration-activities.ts +1 -0
  341. package/src/temporal/constants.ts +1 -0
  342. package/src/temporal/index.ts +1 -0
  343. package/src/temporal/workflows/index.ts +1 -0
  344. package/src/temporal/workflows/job-execution-orchestration.test.ts +142 -6
  345. package/src/temporal/workflows/job-execution-orchestration.ts +99 -50
  346. package/src/temporal/workflows/run-orchestration.test.ts +1 -1
  347. package/src/temporal/workflows/test-env.ts +50 -13
  348. package/test/factories/project.ts +2 -0
  349. package/test/factories/workflow-model.ts +80 -26
  350. package/test/factories/workflow-run.ts +4 -1
  351. package/test/fixtures/agent-inter-module.ts +2 -1
  352. package/test/fixtures/job-with-steps.ts +1 -1
  353. package/tsconfig.build.tsbuildinfo +1 -1
  354. package/drizzle/0001_agent_tool_materialization.sql +0 -1
@@ -1,4 +1,6 @@
1
+ import type {WorkflowFieldTemplate} from '@shipfox/api-definitions-dto';
1
2
  import {WORKFLOWS_WORKFLOW_RUN_ATTEMPT_CREATED} from '@shipfox/api-workflows-dto';
3
+ import {parseWorkflowTemplate, planInterpolationField} from '@shipfox/expression';
2
4
  import {and, eq, sql} from 'drizzle-orm';
3
5
  import type {AgentDefaultsResolver} from '#core/agent-defaults.js';
4
6
  import {InterpolationUnresolvableError} from '#core/errors.js';
@@ -9,9 +11,11 @@ import {buildModel, expression, shellRef, template} from '#test/helpers/workflow
9
11
  import {workflowModel} from '#test/index.js';
10
12
  import {db} from '../db.js';
11
13
  import {workflowsOutbox} from '../schema/outbox.js';
14
+ import {workflowRunCounters} from '../schema/workflow-run-counters.js';
12
15
  import {workflowRuns} from '../schema/workflow-runs.js';
13
16
  import {
14
17
  createWorkflowRun,
18
+ evaluateJobActivations,
15
19
  getJobExecutionsByJobId,
16
20
  getJobsByWorkflowRunId,
17
21
  getStepsByJobId,
@@ -23,6 +27,15 @@ import {
23
27
  updateJobExecutionStatus,
24
28
  } from '../workflow-runs.js';
25
29
 
30
+ function checkoutTemplate(source: string): WorkflowFieldTemplate {
31
+ const result = planInterpolationField({
32
+ field: 'checkout.repository',
33
+ segments: parseWorkflowTemplate(source),
34
+ });
35
+ if (!result.ok) throw new Error('Expected valid checkout template');
36
+ return result.plan.field.segments;
37
+ }
38
+
26
39
  describe('workflow run queries', () => {
27
40
  let workspaceId: string;
28
41
  let projectId: string;
@@ -35,6 +48,219 @@ describe('workflow run queries', () => {
35
48
  });
36
49
 
37
50
  describe('createWorkflowRun', () => {
51
+ test('persists normalized integration trigger facts alongside the payload', async () => {
52
+ const triggerConnectionId = crypto.randomUUID();
53
+ const integrations = {
54
+ resolveTriggerReference: vi.fn().mockResolvedValue({
55
+ externalRepositoryId: 'github:42',
56
+ ref: 'refs/heads/main',
57
+ commit: 'a'.repeat(40),
58
+ }),
59
+ resolveSourceRepository: vi.fn().mockResolvedValue({
60
+ connection: {id: triggerConnectionId, provider: 'github', slug: 'github-main'},
61
+ repository: {
62
+ externalRepositoryId: 'github:42',
63
+ owner: 'acme',
64
+ name: 'api',
65
+ fullName: 'acme/api',
66
+ defaultBranch: 'main',
67
+ visibility: 'private' as const,
68
+ cloneUrl: 'https://github.com/acme/api.git',
69
+ htmlUrl: 'https://github.com/acme/api',
70
+ },
71
+ }),
72
+ } as never;
73
+ const projects = {
74
+ getProjectBySource: vi.fn().mockResolvedValue({project: {id: 'project-1'}}),
75
+ } as never;
76
+
77
+ const run = await createWorkflowRun({
78
+ workspaceId,
79
+ projectId,
80
+ definitionId,
81
+ model: buildModel(),
82
+ triggerConnectionId,
83
+ triggerPayload: {
84
+ provider: 'github',
85
+ source: 'github-main',
86
+ event: 'push',
87
+ deliveryId: 'delivery-1',
88
+ data: {ref: 'refs/heads/main', headCommitSha: 'a'.repeat(40)},
89
+ },
90
+ integrations,
91
+ projects,
92
+ });
93
+
94
+ const triggerReference = {
95
+ project: {id: 'project-1'},
96
+ repository: 'acme/api',
97
+ ref: 'refs/heads/main',
98
+ commit: 'a'.repeat(40),
99
+ };
100
+ expect(run.triggerReference).toEqual(triggerReference);
101
+ await expect(
102
+ db()
103
+ .select({triggerReference: workflowRuns.triggerReference})
104
+ .from(workflowRuns)
105
+ .where(eq(workflowRuns.id, run.id)),
106
+ ).resolves.toEqual([{triggerReference}]);
107
+ });
108
+
109
+ test('creates a run when trigger enrichment fails', async () => {
110
+ const triggerConnectionId = crypto.randomUUID();
111
+ const integrations = {
112
+ resolveTriggerReference: vi.fn().mockResolvedValue({
113
+ externalRepositoryId: 'github:42',
114
+ ref: 'refs/heads/main',
115
+ commit: 'a'.repeat(40),
116
+ }),
117
+ resolveSourceRepository: vi.fn().mockRejectedValue(new Error('source unavailable')),
118
+ } as never;
119
+ const projects = {
120
+ getProjectBySource: vi.fn().mockResolvedValue({project: {id: 'project-1'}}),
121
+ } as never;
122
+
123
+ const run = await createWorkflowRun({
124
+ workspaceId,
125
+ projectId,
126
+ definitionId,
127
+ model: buildModel(),
128
+ triggerConnectionId,
129
+ triggerPayload: {
130
+ provider: 'github',
131
+ source: 'github-main',
132
+ event: 'push',
133
+ deliveryId: 'delivery-1',
134
+ data: {ref: 'refs/heads/main', headCommitSha: 'a'.repeat(40)},
135
+ },
136
+ integrations,
137
+ projects,
138
+ });
139
+
140
+ expect(run.triggerReference).toBeNull();
141
+ await expect(
142
+ db()
143
+ .select({triggerReference: workflowRuns.triggerReference})
144
+ .from(workflowRuns)
145
+ .where(eq(workflowRuns.id, run.id)),
146
+ ).resolves.toEqual([{triggerReference: null}]);
147
+ });
148
+
149
+ test('resolves and persists a dynamic run name while preserving the static snapshot', async () => {
150
+ const run = await createWorkflowRun({
151
+ workspaceId,
152
+ projectId,
153
+ definitionId,
154
+ model: buildModel({
155
+ name: 'Deploy application',
156
+ runName: `Deploy ${template('event.environment')}`,
157
+ }),
158
+ triggerPayload: {
159
+ source: 'github',
160
+ event: 'deployment',
161
+ deliveryId: 'delivery-1',
162
+ data: {environment: 'production'},
163
+ },
164
+ });
165
+
166
+ expect(run).toMatchObject({
167
+ name: 'Deploy production',
168
+ workflowName: 'Deploy application',
169
+ nameOverride: 'Deploy production',
170
+ });
171
+ await expect(getWorkflowRunById(run.id)).resolves.toMatchObject({
172
+ name: 'Deploy production',
173
+ workflowName: 'Deploy application',
174
+ nameOverride: 'Deploy production',
175
+ });
176
+ });
177
+
178
+ test('loads referenced variables before resolving the run name', async () => {
179
+ const secrets = createTestSecretsClient();
180
+ await secrets.setSecrets({
181
+ workspaceId,
182
+ projectId,
183
+ namespace: '',
184
+ values: {ENVIRONMENT: 'staging'},
185
+ });
186
+
187
+ const run = await createWorkflowRun({
188
+ workspaceId,
189
+ projectId,
190
+ definitionId,
191
+ model: buildModel({runName: `Deploy ${template('vars.ENVIRONMENT')}`}),
192
+ triggerPayload: {
193
+ source: 'manual',
194
+ event: 'fire',
195
+ subscriptionId: crypto.randomUUID(),
196
+ userId: crypto.randomUUID(),
197
+ },
198
+ secrets,
199
+ });
200
+
201
+ expect(run).toMatchObject({
202
+ name: 'Deploy staging',
203
+ workflowName: 'Test Workflow',
204
+ nameOverride: 'Deploy staging',
205
+ });
206
+ });
207
+
208
+ test('creates the run when only the run name references variables without a secrets client', async () => {
209
+ const run = await createWorkflowRun({
210
+ workspaceId,
211
+ projectId,
212
+ definitionId,
213
+ model: buildModel({
214
+ name: 'Deploy application',
215
+ runName: template('vars.ENVIRONMENT'),
216
+ }),
217
+ triggerPayload: {
218
+ source: 'manual',
219
+ event: 'fire',
220
+ subscriptionId: crypto.randomUUID(),
221
+ userId: crypto.randomUUID(),
222
+ },
223
+ });
224
+
225
+ expect(run).toMatchObject({
226
+ name: 'Deploy application',
227
+ workflowName: 'Deploy application',
228
+ nameOverride: null,
229
+ });
230
+ await expect(getJobsByWorkflowRunId(run.id)).resolves.toHaveLength(1);
231
+ });
232
+
233
+ test('falls back to the static name when run-name resolution cannot produce a value', async () => {
234
+ const run = await createWorkflowRun({
235
+ workspaceId,
236
+ projectId,
237
+ definitionId,
238
+ model: buildModel({
239
+ name: 'Deploy application',
240
+ runName: template('event.environment'),
241
+ }),
242
+ triggerPayload: {
243
+ source: 'github',
244
+ event: 'deployment',
245
+ deliveryId: 'delivery-1',
246
+ data: {},
247
+ },
248
+ });
249
+
250
+ expect(run).toMatchObject({
251
+ name: 'Deploy application',
252
+ workflowName: 'Deploy application',
253
+ nameOverride: null,
254
+ });
255
+ await expect(
256
+ db()
257
+ .select({name: workflowRuns.name, workflowName: workflowRuns.workflowName})
258
+ .from(workflowRuns)
259
+ .where(eq(workflowRuns.id, run.id)),
260
+ ).resolves.toEqual([{name: null, workflowName: 'Deploy application'}]);
261
+ await expect(getJobsByWorkflowRunId(run.id)).resolves.toHaveLength(1);
262
+ });
263
+
38
264
  test('inserts run, jobs, and steps atomically', async () => {
39
265
  const run = await createWorkflowRun({
40
266
  workspaceId,
@@ -53,6 +279,7 @@ describe('workflow run queries', () => {
53
279
  expect(run.id).toBeDefined();
54
280
  expect(run.projectId).toBe(projectId);
55
281
  expect(run.definitionId).toBe(definitionId);
282
+ expect(run.number).toBe(1);
56
283
  expect(run.status).toBe('pending');
57
284
  expect(run.triggerProvider).toBeNull();
58
285
  expect(run.triggerPayload).toMatchObject({source: 'manual', event: 'fire'});
@@ -75,7 +302,8 @@ describe('workflow run queries', () => {
75
302
  expect(jobExecutions[0]).toMatchObject({
76
303
  jobId: runJobs[0]?.id,
77
304
  sequence: 1,
78
- name: 'build #1',
305
+ nameOverride: null,
306
+ name: 'build',
79
307
  runner: ['ubuntu-latest'],
80
308
  evaluationTrace: null,
81
309
  });
@@ -88,11 +316,147 @@ describe('workflow run queries', () => {
88
316
  type: 'setup',
89
317
  name: 'Set up job',
90
318
  position: 0,
91
- config: {},
319
+ config: {
320
+ checkout: {
321
+ permissions: {contents: 'read'},
322
+ persist_credentials: true,
323
+ },
324
+ },
92
325
  });
93
326
  expect(jobSteps[1]).toMatchObject({position: 1, config: {run: 'echo hello'}});
94
327
  });
95
328
 
329
+ test('allocates distinct consecutive numbers for concurrent creations of one definition', async () => {
330
+ const [first, second] = await Promise.all([
331
+ createWorkflowRun({
332
+ workspaceId,
333
+ projectId,
334
+ definitionId,
335
+ model: buildModel({name: 'First'}),
336
+ triggerPayload: {
337
+ source: 'manual',
338
+ event: 'fire',
339
+ subscriptionId: crypto.randomUUID(),
340
+ userId: crypto.randomUUID(),
341
+ },
342
+ }),
343
+ createWorkflowRun({
344
+ workspaceId,
345
+ projectId,
346
+ definitionId,
347
+ model: buildModel({name: 'Second'}),
348
+ triggerPayload: {
349
+ source: 'manual',
350
+ event: 'fire',
351
+ subscriptionId: crypto.randomUUID(),
352
+ userId: crypto.randomUUID(),
353
+ },
354
+ }),
355
+ ]);
356
+
357
+ expect([first.number, second.number].sort((a, b) => a - b)).toEqual([1, 2]);
358
+ const [counter] = await db()
359
+ .select()
360
+ .from(workflowRunCounters)
361
+ .where(eq(workflowRunCounters.definitionId, definitionId));
362
+ expect(counter?.nextNumber).toBe(3);
363
+ });
364
+
365
+ test('makes the allocated number available to run-creation expressions', async () => {
366
+ const run = await createWorkflowRun({
367
+ workspaceId,
368
+ projectId,
369
+ definitionId,
370
+ model: buildModel({
371
+ jobs: {
372
+ build: {
373
+ executionName: `Build #${template('run.number')}`,
374
+ steps: [{run: 'echo build'}],
375
+ },
376
+ },
377
+ }),
378
+ triggerPayload: {
379
+ source: 'manual',
380
+ event: 'fire',
381
+ subscriptionId: crypto.randomUUID(),
382
+ userId: crypto.randomUUID(),
383
+ },
384
+ });
385
+ const [job] = await getJobsByWorkflowRunId(run.id);
386
+ if (!job) throw new Error('Expected workflow job');
387
+ const [execution] = await getJobExecutionsByJobId(job.id);
388
+
389
+ expect(execution?.name).toBe('Build #1');
390
+ });
391
+
392
+ test('rolls back number allocation when creation rolls back', async () => {
393
+ await expect(
394
+ createWorkflowRun({
395
+ workspaceId,
396
+ projectId,
397
+ definitionId,
398
+ model: buildModel({env: {TOKEN: template('vars.TOKEN')}}),
399
+ triggerPayload: {
400
+ source: 'manual',
401
+ event: 'fire',
402
+ subscriptionId: crypto.randomUUID(),
403
+ userId: crypto.randomUUID(),
404
+ },
405
+ }),
406
+ ).rejects.toThrow('Secrets client is not configured.');
407
+
408
+ const run = await createWorkflowRun({
409
+ workspaceId,
410
+ projectId,
411
+ definitionId,
412
+ model: buildModel(),
413
+ triggerPayload: {
414
+ source: 'manual',
415
+ event: 'fire',
416
+ subscriptionId: crypto.randomUUID(),
417
+ userId: crypto.randomUUID(),
418
+ },
419
+ });
420
+
421
+ expect(run.number).toBe(1);
422
+ const [counter] = await db()
423
+ .select()
424
+ .from(workflowRunCounters)
425
+ .where(eq(workflowRunCounters.definitionId, definitionId));
426
+ expect(counter?.nextNumber).toBe(2);
427
+ });
428
+
429
+ test('numbers definitions independently within one project', async () => {
430
+ const otherDefinitionId = crypto.randomUUID();
431
+ const first = await createWorkflowRun({
432
+ workspaceId,
433
+ projectId,
434
+ definitionId,
435
+ model: buildModel(),
436
+ triggerPayload: {
437
+ source: 'manual',
438
+ event: 'fire',
439
+ subscriptionId: crypto.randomUUID(),
440
+ userId: crypto.randomUUID(),
441
+ },
442
+ });
443
+ const second = await createWorkflowRun({
444
+ workspaceId,
445
+ projectId,
446
+ definitionId: otherDefinitionId,
447
+ model: buildModel(),
448
+ triggerPayload: {
449
+ source: 'manual',
450
+ event: 'fire',
451
+ subscriptionId: crypto.randomUUID(),
452
+ userId: crypto.randomUUID(),
453
+ },
454
+ });
455
+
456
+ expect(first.number).toBe(1);
457
+ expect(second.number).toBe(1);
458
+ });
459
+
96
460
  async function createJobOutputRun() {
97
461
  const run = await createWorkflowRun({
98
462
  workspaceId,
@@ -219,7 +583,7 @@ describe('workflow run queries', () => {
219
583
  env: {RUN_ID: template('run.id')},
220
584
  jobs: {
221
585
  build: {
222
- name: `Build ${template('event.ref')}`,
586
+ executionName: `Build ${template('event.ref')}`,
223
587
  steps: [
224
588
  {
225
589
  run: 'npm test',
@@ -257,15 +621,122 @@ describe('workflow run queries', () => {
257
621
  {
258
622
  expression: 'event.ref',
259
623
  roots: ['event'],
260
- fillTarget: 'ingest',
624
+ fillTarget: 'execution-creation',
261
625
  evaluatedAt: 'execution-creation',
262
626
  value: 'refs/heads/main',
263
- field: 'job.name',
627
+ field: 'job.execution_name',
264
628
  },
265
629
  ],
266
630
  });
267
631
  });
268
632
 
633
+ test('loads predicate vars once and persists the run-creation snapshot', async () => {
634
+ const secrets = createTestSecretsClient();
635
+ const values = {
636
+ JOB_IF: 'true',
637
+ JOB_SUCCESS: 'true',
638
+ STEP_IF: 'true',
639
+ GATE_SUCCESS: 'true',
640
+ LISTENER_ON: 'true',
641
+ LISTENER_UNTIL: 'true',
642
+ };
643
+ await secrets.setSecrets({workspaceId, projectId, values});
644
+
645
+ const model = workflowModel({
646
+ jobs: {
647
+ build: {
648
+ if: 'vars.JOB_IF == "true"',
649
+ success: 'vars.JOB_SUCCESS == "true"',
650
+ steps: [
651
+ {
652
+ if: expression('vars.STEP_IF == "true"'),
653
+ gate: {success: expression('vars.GATE_SUCCESS == "true"')},
654
+ run: 'echo build',
655
+ },
656
+ ],
657
+ },
658
+ listen: {
659
+ listening: {
660
+ on: [{source: 'github', event: 'push', filter: 'vars.LISTENER_ON == "true"'}],
661
+ until: [
662
+ {source: 'github', event: 'pull_request', filter: 'vars.LISTENER_UNTIL == "true"'},
663
+ ],
664
+ onResolve: 'finish',
665
+ },
666
+ steps: [{run: 'echo listen'}],
667
+ },
668
+ },
669
+ });
670
+
671
+ const run = await createWorkflowRun({
672
+ workspaceId,
673
+ projectId,
674
+ definitionId,
675
+ model,
676
+ triggerPayload: {
677
+ source: 'manual',
678
+ event: 'fire',
679
+ subscriptionId: crypto.randomUUID(),
680
+ userId: crypto.randomUUID(),
681
+ },
682
+ secrets,
683
+ });
684
+
685
+ const [attempt] = await listRunAttempts({workflowRunId: run.id, projectId});
686
+
687
+ expect(attempt?.vars).toEqual(values);
688
+ });
689
+
690
+ test('evaluates job and step predicates from the persisted vars snapshot', async () => {
691
+ const secrets = createTestSecretsClient();
692
+ await secrets.setSecrets({workspaceId, projectId, values: {ENABLED: 'true'}});
693
+ const model = workflowModel({
694
+ jobs: {
695
+ build: {
696
+ if: 'vars.ENABLED == "true"',
697
+ steps: [{if: expression('vars.ENABLED == "true"'), run: 'echo build'}],
698
+ },
699
+ },
700
+ });
701
+
702
+ const run = await createWorkflowRun({
703
+ workspaceId,
704
+ projectId,
705
+ definitionId,
706
+ model,
707
+ triggerPayload: {
708
+ source: 'manual',
709
+ event: 'fire',
710
+ subscriptionId: crypto.randomUUID(),
711
+ userId: crypto.randomUUID(),
712
+ },
713
+ secrets,
714
+ });
715
+ await secrets.setSecrets({workspaceId, projectId, values: {ENABLED: 'false'}});
716
+
717
+ const [job] = await getJobsByWorkflowRunId(run.id);
718
+ if (!job) throw new Error('Expected workflow job');
719
+ const activation = await evaluateJobActivations({
720
+ runAttemptId: job.workflowRunAttemptId,
721
+ jobs: [{jobId: job.id, expectedVersion: job.version}],
722
+ });
723
+ expect(activation).toEqual([{kind: 'start-job', jobId: job.id}]);
724
+
725
+ const [execution] = await getJobExecutionsByJobId(job.id);
726
+ if (!execution) throw new Error('Expected job execution');
727
+ const setup = await nextStepForJob(job.id);
728
+ if (setup.kind !== 'step') throw new Error('Expected setup step');
729
+ expect(setup).toMatchObject({kind: 'step', step: {type: 'setup'}});
730
+ await recordStepResult({
731
+ jobExecutionId: execution.id,
732
+ stepId: setup.step.id,
733
+ status: 'succeeded',
734
+ });
735
+
736
+ const guarded = await nextStepForJob(job.id);
737
+ expect(guarded).toMatchObject({kind: 'step', step: {position: 1}, dispatched: true});
738
+ });
739
+
269
740
  test('returns the persisted model in run detail', async () => {
270
741
  const model = buildModel({
271
742
  env: {RUN_ID: template('run.id')},
@@ -335,7 +806,8 @@ describe('workflow run queries', () => {
335
806
  runner: 'ubuntu-latest',
336
807
  jobs: {
337
808
  listen: {
338
- name: displayNameSource,
809
+ name: 'Process review',
810
+ executionName: displayNameSource,
339
811
  listening: {
340
812
  on: [{source: 'github', event: 'pull_request_review'}],
341
813
  until: [{source: 'github', event: 'pull_request'}],
@@ -370,7 +842,7 @@ describe('workflow run queries', () => {
370
842
  const build = runJobs.find((job) => job.key === 'build');
371
843
  expect(listen).toMatchObject({
372
844
  mode: 'listening',
373
- name: displayNameSource,
845
+ name: 'Process review',
374
846
  listeningTimeoutMs: 30 * 24 * 60 * 60 * 1000,
375
847
  maxExecutions: 3,
376
848
  onResolve: 'cancel',
@@ -405,7 +877,8 @@ describe('workflow run queries', () => {
405
877
  runner: 'ubuntu-latest',
406
878
  jobs: {
407
879
  listen: {
408
- name: displayNameSource,
880
+ name: 'Process review',
881
+ executionName: displayNameSource,
409
882
  listening: {
410
883
  on: [{source: 'github', event: 'pull_request_review'}],
411
884
  until: [{source: 'github', event: 'pull_request'}],
@@ -561,6 +1034,46 @@ describe('workflow run queries', () => {
561
1034
  }),
562
1035
  expected: {field: 'step.name', source: 'vars.REQUIRED'},
563
1036
  },
1037
+ {
1038
+ field: 'step.working_directory',
1039
+ model: () =>
1040
+ workflowModel({
1041
+ name: 'Missing working directory var',
1042
+ runner: 'ubuntu-latest',
1043
+ jobs: {
1044
+ build: {
1045
+ steps: [{workingDirectory: template('vars.REQUIRED'), run: 'echo ok'}],
1046
+ },
1047
+ },
1048
+ }),
1049
+ expected: {field: 'step.working_directory', source: 'vars.REQUIRED'},
1050
+ },
1051
+ {
1052
+ field: 'checkout.repository',
1053
+ model: () =>
1054
+ workflowModel({
1055
+ name: 'Missing checkout repository var',
1056
+ runner: 'ubuntu-latest',
1057
+ jobs: {
1058
+ build: {
1059
+ steps: [
1060
+ {
1061
+ checkout: {
1062
+ repository: template('vars.REQUIRED'),
1063
+ fetchDepth: 1,
1064
+ permissions: {contents: 'read'},
1065
+ persistCredentials: true,
1066
+ templates: {
1067
+ repository: checkoutTemplate(template('vars.REQUIRED')),
1068
+ },
1069
+ },
1070
+ },
1071
+ ],
1072
+ },
1073
+ },
1074
+ }),
1075
+ expected: {field: 'checkout.repository', source: 'vars.REQUIRED'},
1076
+ },
564
1077
  ] as const)('reports missing variables against $field', async ({model, expected}) => {
565
1078
  let error: unknown;
566
1079
  try {
@@ -585,6 +1098,42 @@ describe('workflow run queries', () => {
585
1098
  expect(error).toMatchObject(expected);
586
1099
  });
587
1100
 
1101
+ test('resolves a working directory that references a workspace variable', async () => {
1102
+ const secrets = createTestSecretsClient();
1103
+ await secrets.setSecrets({
1104
+ workspaceId,
1105
+ namespace: '',
1106
+ values: {BUILD_DIR: 'packages/api'},
1107
+ });
1108
+ const model = workflowModel({
1109
+ name: 'Working directory from var',
1110
+ runner: 'ubuntu-latest',
1111
+ jobs: {
1112
+ build: {
1113
+ steps: [{workingDirectory: template('vars.BUILD_DIR'), run: 'echo ok'}],
1114
+ },
1115
+ },
1116
+ });
1117
+
1118
+ const run = await createWorkflowRun({
1119
+ workspaceId,
1120
+ projectId,
1121
+ definitionId,
1122
+ model,
1123
+ triggerPayload: {
1124
+ source: 'manual',
1125
+ event: 'fire',
1126
+ subscriptionId: crypto.randomUUID(),
1127
+ userId: crypto.randomUUID(),
1128
+ },
1129
+ secrets,
1130
+ });
1131
+
1132
+ const [build] = await getJobsByWorkflowRunId(run.id);
1133
+ const [, step] = await getStepsByJobId(build?.id as string);
1134
+ expect(step?.config).toMatchObject({run: 'echo ok', working_directory: 'packages/api'});
1135
+ });
1136
+
588
1137
  test('writes workflows.workflow_run_attempt.created outbox event in same transaction', async () => {
589
1138
  const run = await createWorkflowRun({
590
1139
  workspaceId,
@@ -1186,6 +1735,7 @@ describe('workflow run queries', () => {
1186
1735
  });
1187
1736
 
1188
1737
  expect(second.id).toBe(first.id);
1738
+ expect(second.number).toBe(first.number);
1189
1739
  expect(second.triggerIdempotencyKey).toBe(idempotencyKey);
1190
1740
  expect(second.sourceSnapshot).toEqual({
1191
1741
  content: 'name: Original\njobs: {}\n',
@@ -1202,6 +1752,54 @@ describe('workflow run queries', () => {
1202
1752
  .from(workflowsOutbox)
1203
1753
  .where(sql`${workflowsOutbox.payload}->>'workflowRunId' = ${first.id}`);
1204
1754
  expect(outboxRows).toHaveLength(1);
1755
+ const [counter] = await db()
1756
+ .select()
1757
+ .from(workflowRunCounters)
1758
+ .where(eq(workflowRunCounters.definitionId, definitionId));
1759
+ expect(counter?.nextNumber).toBe(2);
1760
+ });
1761
+
1762
+ test('duplicate triggerIdempotencyKey returns the persisted run name without reevaluating it', async () => {
1763
+ const idempotencyKey = crypto.randomUUID();
1764
+ const first = await createWorkflowRun({
1765
+ workspaceId,
1766
+ projectId,
1767
+ definitionId,
1768
+ model: buildModel({
1769
+ name: 'Deploy application',
1770
+ runName: `Deploy ${template('event.environment')}`,
1771
+ }),
1772
+ triggerPayload: {
1773
+ source: 'github',
1774
+ event: 'deployment',
1775
+ deliveryId: 'delivery-1',
1776
+ data: {environment: 'production'},
1777
+ },
1778
+ triggerIdempotencyKey: idempotencyKey,
1779
+ });
1780
+ const replay = await createWorkflowRun({
1781
+ workspaceId,
1782
+ projectId,
1783
+ definitionId,
1784
+ model: buildModel({
1785
+ name: 'Changed workflow',
1786
+ runName: `Changed ${template('event.environment')}`,
1787
+ }),
1788
+ triggerPayload: {
1789
+ source: 'github',
1790
+ event: 'deployment',
1791
+ deliveryId: 'delivery-2',
1792
+ data: {environment: 'staging'},
1793
+ },
1794
+ triggerIdempotencyKey: idempotencyKey,
1795
+ });
1796
+
1797
+ expect(replay).toMatchObject({
1798
+ id: first.id,
1799
+ name: 'Deploy production',
1800
+ workflowName: 'Deploy application',
1801
+ nameOverride: 'Deploy production',
1802
+ });
1205
1803
  });
1206
1804
 
1207
1805
  test('duplicate triggerIdempotencyKey returns the existing run without re-materializing', async () => {