@shipfox/api-workflows 4.0.0 → 6.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 (277) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +104 -0
  3. package/dist/core/agent-defaults.d.ts +17 -0
  4. package/dist/core/agent-defaults.d.ts.map +1 -0
  5. package/dist/core/agent-defaults.js +21 -0
  6. package/dist/core/agent-defaults.js.map +1 -0
  7. package/dist/core/agent-tool-capability-warning.d.ts +4 -0
  8. package/dist/core/agent-tool-capability-warning.d.ts.map +1 -1
  9. package/dist/core/agent-tool-capability-warning.js +15 -17
  10. package/dist/core/agent-tool-capability-warning.js.map +1 -1
  11. package/dist/core/agent-tools.d.ts +11 -9
  12. package/dist/core/agent-tools.d.ts.map +1 -1
  13. package/dist/core/agent-tools.js +23 -18
  14. package/dist/core/agent-tools.js.map +1 -1
  15. package/dist/core/checkout.d.ts +21 -7
  16. package/dist/core/checkout.d.ts.map +1 -1
  17. package/dist/core/checkout.js +23 -11
  18. package/dist/core/checkout.js.map +1 -1
  19. package/dist/core/entities/workflow-run-attempt.d.ts +1 -1
  20. package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -1
  21. package/dist/core/entities/workflow-run-attempt.js.map +1 -1
  22. package/dist/core/job-execution.d.ts +3 -1
  23. package/dist/core/job-execution.d.ts.map +1 -1
  24. package/dist/core/job-execution.js +14 -12
  25. package/dist/core/job-execution.js.map +1 -1
  26. package/dist/core/job-transition/derive-job-execution-outputs.d.ts +1 -1
  27. package/dist/core/job-transition/derive-job-execution-outputs.d.ts.map +1 -1
  28. package/dist/core/job-transition/derive-job-execution-outputs.js.map +1 -1
  29. package/dist/core/job-transition/derive-job-success.js +1 -1
  30. package/dist/core/job-transition/derive-job-success.js.map +1 -1
  31. package/dist/core/listener-execution-materialization.d.ts +3 -3
  32. package/dist/core/listener-execution-materialization.d.ts.map +1 -1
  33. package/dist/core/listener-execution-materialization.js +3 -4
  34. package/dist/core/listener-execution-materialization.js.map +1 -1
  35. package/dist/core/run-workflow.d.ts +12 -1
  36. package/dist/core/run-workflow.d.ts.map +1 -1
  37. package/dist/core/run-workflow.js +13 -8
  38. package/dist/core/run-workflow.js.map +1 -1
  39. package/dist/core/step-config/agent.d.ts +8 -8
  40. package/dist/core/step-config/agent.d.ts.map +1 -1
  41. package/dist/core/step-config/agent.js +12 -10
  42. package/dist/core/step-config/agent.js.map +1 -1
  43. package/dist/core/step-config/complete-step-dispatch-config.d.ts +4 -4
  44. package/dist/core/step-config/complete-step-dispatch-config.d.ts.map +1 -1
  45. package/dist/core/step-config/complete-step-dispatch-config.js +2 -2
  46. package/dist/core/step-config/complete-step-dispatch-config.js.map +1 -1
  47. package/dist/core/step-config/materialize-job-execution-steps.d.ts +3 -3
  48. package/dist/core/step-config/materialize-job-execution-steps.d.ts.map +1 -1
  49. package/dist/core/step-config/materialize-job-execution-steps.js +5 -6
  50. package/dist/core/step-config/materialize-job-execution-steps.js.map +1 -1
  51. package/dist/core/step-config/materialize-workflow-model.d.ts +3 -3
  52. package/dist/core/step-config/materialize-workflow-model.d.ts.map +1 -1
  53. package/dist/core/step-config/materialize-workflow-model.js +4 -4
  54. package/dist/core/step-config/materialize-workflow-model.js.map +1 -1
  55. package/dist/core/step-config/resolve-job-execution-name.d.ts +1 -1
  56. package/dist/core/step-config/resolve-job-execution-name.d.ts.map +1 -1
  57. package/dist/core/step-config/resolve-job-execution-name.js.map +1 -1
  58. package/dist/core/step-config/resolve-step-config.d.ts +4 -4
  59. package/dist/core/step-config/resolve-step-config.d.ts.map +1 -1
  60. package/dist/core/step-config/resolve-step-config.js +6 -6
  61. package/dist/core/step-config/resolve-step-config.js.map +1 -1
  62. package/dist/core/step-config/run.d.ts +1 -1
  63. package/dist/core/step-config/run.d.ts.map +1 -1
  64. package/dist/core/step-config/run.js.map +1 -1
  65. package/dist/core/workflow-run-creation.d.ts +3 -3
  66. package/dist/core/workflow-run-creation.d.ts.map +1 -1
  67. package/dist/core/workflow-run-creation.js +3 -4
  68. package/dist/core/workflow-run-creation.js.map +1 -1
  69. package/dist/db/db.d.ts +16 -4
  70. package/dist/db/db.d.ts.map +1 -1
  71. package/dist/db/job-listeners.d.ts +7 -1
  72. package/dist/db/job-listeners.d.ts.map +1 -1
  73. package/dist/db/job-listeners.js +10 -5
  74. package/dist/db/job-listeners.js.map +1 -1
  75. package/dist/db/runner-lease-table.d.ts +5 -5
  76. package/dist/db/runner-lease-table.d.ts.map +1 -1
  77. package/dist/db/runner-lease-table.js +3 -2
  78. package/dist/db/runner-lease-table.js.map +1 -1
  79. package/dist/db/schema/workflow-run-attempts.d.ts +9 -3
  80. package/dist/db/schema/workflow-run-attempts.d.ts.map +1 -1
  81. package/dist/db/schema/workflow-run-attempts.js +2 -1
  82. package/dist/db/schema/workflow-run-attempts.js.map +1 -1
  83. package/dist/db/schema/workflow-runs.js +1 -1
  84. package/dist/db/schema/workflow-runs.js.map +1 -1
  85. package/dist/db/workflow-runs/job-executions.d.ts +5 -0
  86. package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
  87. package/dist/db/workflow-runs/job-executions.js +14 -7
  88. package/dist/db/workflow-runs/job-executions.js.map +1 -1
  89. package/dist/db/workflow-runs/jobs.d.ts.map +1 -1
  90. package/dist/db/workflow-runs/jobs.js +3 -1
  91. package/dist/db/workflow-runs/jobs.js.map +1 -1
  92. package/dist/db/workflow-runs/run-create.d.ts +9 -2
  93. package/dist/db/workflow-runs/run-create.d.ts.map +1 -1
  94. package/dist/db/workflow-runs/run-create.js +12 -9
  95. package/dist/db/workflow-runs/run-create.js.map +1 -1
  96. package/dist/db/workflow-runs/run-rerun.d.ts.map +1 -1
  97. package/dist/db/workflow-runs/run-rerun.js +3 -1
  98. package/dist/db/workflow-runs/run-rerun.js.map +1 -1
  99. package/dist/index.d.ts +18 -4
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +61 -37
  102. package/dist/index.js.map +1 -1
  103. package/dist/presentation/dto/checkout-token.d.ts +14 -1
  104. package/dist/presentation/dto/checkout-token.d.ts.map +1 -1
  105. package/dist/presentation/dto/checkout-token.js.map +1 -1
  106. package/dist/presentation/index.d.ts +1 -1
  107. package/dist/presentation/index.d.ts.map +1 -1
  108. package/dist/presentation/index.js +1 -1
  109. package/dist/presentation/index.js.map +1 -1
  110. package/dist/presentation/inter-module.d.ts +18 -0
  111. package/dist/presentation/inter-module.d.ts.map +1 -0
  112. package/dist/presentation/inter-module.js +113 -0
  113. package/dist/presentation/inter-module.js.map +1 -0
  114. package/dist/presentation/routes/agent-runtime-config.d.ts +6 -1
  115. package/dist/presentation/routes/agent-runtime-config.d.ts.map +1 -1
  116. package/dist/presentation/routes/agent-runtime-config.js +59 -57
  117. package/dist/presentation/routes/agent-runtime-config.js.map +1 -1
  118. package/dist/presentation/routes/cancel-run.d.ts +2 -1
  119. package/dist/presentation/routes/cancel-run.d.ts.map +1 -1
  120. package/dist/presentation/routes/cancel-run.js +37 -34
  121. package/dist/presentation/routes/cancel-run.js.map +1 -1
  122. package/dist/presentation/routes/checkout-token.d.ts +8 -1
  123. package/dist/presentation/routes/checkout-token.d.ts.map +1 -1
  124. package/dist/presentation/routes/checkout-token.js +65 -47
  125. package/dist/presentation/routes/checkout-token.js.map +1 -1
  126. package/dist/presentation/routes/get-run-aggregates.d.ts +2 -1
  127. package/dist/presentation/routes/get-run-aggregates.d.ts.map +1 -1
  128. package/dist/presentation/routes/get-run-aggregates.js +44 -53
  129. package/dist/presentation/routes/get-run-aggregates.js.map +1 -1
  130. package/dist/presentation/routes/get-run.d.ts +2 -1
  131. package/dist/presentation/routes/get-run.d.ts.map +1 -1
  132. package/dist/presentation/routes/get-run.js +53 -50
  133. package/dist/presentation/routes/get-run.js.map +1 -1
  134. package/dist/presentation/routes/get-step-secrets.d.ts +3 -1
  135. package/dist/presentation/routes/get-step-secrets.d.ts.map +1 -1
  136. package/dist/presentation/routes/get-step-secrets.js +72 -68
  137. package/dist/presentation/routes/get-step-secrets.js.map +1 -1
  138. package/dist/presentation/routes/index.d.ts +19 -2
  139. package/dist/presentation/routes/index.d.ts.map +1 -1
  140. package/dist/presentation/routes/index.js +38 -30
  141. package/dist/presentation/routes/index.js.map +1 -1
  142. package/dist/presentation/routes/leased-step.d.ts +2 -0
  143. package/dist/presentation/routes/leased-step.d.ts.map +1 -1
  144. package/dist/presentation/routes/leased-step.js +1 -2
  145. package/dist/presentation/routes/leased-step.js.map +1 -1
  146. package/dist/presentation/routes/list-run-attempts.d.ts +2 -1
  147. package/dist/presentation/routes/list-run-attempts.d.ts.map +1 -1
  148. package/dist/presentation/routes/list-run-attempts.js +29 -26
  149. package/dist/presentation/routes/list-run-attempts.js.map +1 -1
  150. package/dist/presentation/routes/list-runs.d.ts +2 -1
  151. package/dist/presentation/routes/list-runs.d.ts.map +1 -1
  152. package/dist/presentation/routes/list-runs.js +51 -60
  153. package/dist/presentation/routes/list-runs.js.map +1 -1
  154. package/dist/presentation/routes/next-step.d.ts +10 -1
  155. package/dist/presentation/routes/next-step.d.ts.map +1 -1
  156. package/dist/presentation/routes/next-step.js +64 -50
  157. package/dist/presentation/routes/next-step.js.map +1 -1
  158. package/dist/presentation/routes/project-access.d.ts +10 -0
  159. package/dist/presentation/routes/project-access.d.ts.map +1 -0
  160. package/dist/presentation/routes/project-access.js +17 -0
  161. package/dist/presentation/routes/project-access.js.map +1 -0
  162. package/dist/presentation/routes/report-step.d.ts +2 -1
  163. package/dist/presentation/routes/report-step.d.ts.map +1 -1
  164. package/dist/presentation/routes/report-step.js +62 -61
  165. package/dist/presentation/routes/report-step.js.map +1 -1
  166. package/dist/presentation/routes/require-accessible-run.d.ts +3 -1
  167. package/dist/presentation/routes/require-accessible-run.d.ts.map +1 -1
  168. package/dist/presentation/routes/require-accessible-run.js +3 -6
  169. package/dist/presentation/routes/require-accessible-run.js.map +1 -1
  170. package/dist/presentation/routes/rerun-run.d.ts +2 -1
  171. package/dist/presentation/routes/rerun-run.d.ts.map +1 -1
  172. package/dist/presentation/routes/rerun-run.js +46 -43
  173. package/dist/presentation/routes/rerun-run.js.map +1 -1
  174. package/dist/temporal/activities/index.d.ts +44 -9
  175. package/dist/temporal/activities/index.d.ts.map +1 -1
  176. package/dist/temporal/activities/index.js +13 -9
  177. package/dist/temporal/activities/index.js.map +1 -1
  178. package/dist/temporal/activities/orchestration-activities.d.ts +20 -11
  179. package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -1
  180. package/dist/temporal/activities/orchestration-activities.js +48 -33
  181. package/dist/temporal/activities/orchestration-activities.js.map +1 -1
  182. package/dist/tsconfig.test.tsbuildinfo +1 -1
  183. package/package.json +41 -44
  184. package/src/core/agent-defaults.test.ts +28 -0
  185. package/src/core/agent-defaults.ts +36 -0
  186. package/src/core/agent-tool-capability-warning.test.ts +36 -11
  187. package/src/core/agent-tool-capability-warning.ts +27 -25
  188. package/src/core/agent-tools.ts +33 -36
  189. package/src/core/checkout.test.ts +38 -25
  190. package/src/core/checkout.ts +41 -14
  191. package/src/core/entities/workflow-run-attempt.ts +1 -1
  192. package/src/core/job-execution.ts +19 -9
  193. package/src/core/job-transition/derive-job-execution-outputs.ts +1 -1
  194. package/src/core/job-transition/derive-job-success.ts +1 -1
  195. package/src/core/listener-execution-materialization.test.ts +4 -2
  196. package/src/core/listener-execution-materialization.ts +6 -9
  197. package/src/core/run-workflow.test.ts +43 -38
  198. package/src/core/run-workflow.ts +26 -9
  199. package/src/core/step-config/agent.ts +24 -26
  200. package/src/core/step-config/complete-step-dispatch-config.test.ts +22 -23
  201. package/src/core/step-config/complete-step-dispatch-config.ts +6 -6
  202. package/src/core/step-config/materialize-job-execution-steps.test.ts +34 -23
  203. package/src/core/step-config/materialize-job-execution-steps.ts +38 -39
  204. package/src/core/step-config/materialize-workflow-model.test.ts +94 -82
  205. package/src/core/step-config/materialize-workflow-model.ts +31 -29
  206. package/src/core/step-config/resolve-job-execution-name.ts +1 -1
  207. package/src/core/step-config/resolve-step-config.ts +11 -9
  208. package/src/core/step-config/run.ts +1 -1
  209. package/src/core/workflow-run-creation.test.ts +14 -10
  210. package/src/core/workflow-run-creation.ts +6 -9
  211. package/src/db/job-listeners.ts +20 -5
  212. package/src/db/runner-lease-table.ts +2 -2
  213. package/src/db/schema/workflow-run-attempts.ts +7 -3
  214. package/src/db/schema/workflow-runs.ts +1 -1
  215. package/src/db/workflow-runs/job-executions.ts +15 -1
  216. package/src/db/workflow-runs/jobs.ts +4 -1
  217. package/src/db/workflow-runs/run-create.test.ts +7 -1
  218. package/src/db/workflow-runs/run-create.ts +25 -15
  219. package/src/db/workflow-runs/run-rerun.test.ts +1 -1
  220. package/src/db/workflow-runs/run-rerun.ts +6 -3
  221. package/src/index.ts +75 -33
  222. package/src/presentation/dto/checkout-token.ts +7 -1
  223. package/src/presentation/index.ts +1 -1
  224. package/src/presentation/inter-module.test.ts +149 -0
  225. package/src/presentation/inter-module.ts +132 -0
  226. package/src/presentation/routes/agent-runtime-config.test.ts +53 -10
  227. package/src/presentation/routes/agent-runtime-config.ts +89 -64
  228. package/src/presentation/routes/cancel-run.test.ts +10 -20
  229. package/src/presentation/routes/cancel-run.ts +30 -27
  230. package/src/presentation/routes/checkout-token.test.ts +52 -33
  231. package/src/presentation/routes/checkout-token.ts +109 -49
  232. package/src/presentation/routes/get-run-aggregates.test.ts +13 -7
  233. package/src/presentation/routes/get-run-aggregates.ts +54 -57
  234. package/src/presentation/routes/get-run.test.ts +10 -18
  235. package/src/presentation/routes/get-run.ts +51 -48
  236. package/src/presentation/routes/get-step-secrets.test.ts +29 -7
  237. package/src/presentation/routes/get-step-secrets.ts +72 -61
  238. package/src/presentation/routes/index.test.ts +16 -1
  239. package/src/presentation/routes/index.ts +54 -29
  240. package/src/presentation/routes/leased-step.ts +3 -2
  241. package/src/presentation/routes/list-run-attempts.test.ts +9 -19
  242. package/src/presentation/routes/list-run-attempts.ts +21 -18
  243. package/src/presentation/routes/list-runs.test.ts +13 -7
  244. package/src/presentation/routes/list-runs.ts +64 -67
  245. package/src/presentation/routes/next-step.test.ts +30 -11
  246. package/src/presentation/routes/next-step.ts +68 -48
  247. package/src/presentation/routes/project-access.ts +16 -0
  248. package/src/presentation/routes/report-step.test.ts +18 -2
  249. package/src/presentation/routes/report-step.ts +51 -49
  250. package/src/presentation/routes/require-accessible-run.ts +5 -2
  251. package/src/presentation/routes/rerun-run.test.ts +9 -19
  252. package/src/presentation/routes/rerun-run.ts +39 -36
  253. package/src/temporal/activities/index.ts +32 -12
  254. package/src/temporal/activities/orchestration-activities.test.ts +10 -8
  255. package/src/temporal/activities/orchestration-activities.ts +74 -50
  256. package/src/temporal/workflows/job-execution-orchestration.test.ts +47 -2
  257. package/test/env.ts +1 -3
  258. package/test/factories/project.ts +10 -5
  259. package/test/factories/workflow-model.test.ts +5 -1
  260. package/test/factories/workflow-model.ts +1 -1
  261. package/test/factories/workflow-run.ts +1 -1
  262. package/test/fixtures/agent-inter-module.ts +27 -0
  263. package/test/fixtures/annotations-inter-module.ts +5 -0
  264. package/test/fixtures/auth-inter-module.ts +22 -0
  265. package/test/fixtures/lease-token.ts +2 -1
  266. package/test/fixtures/projects-inter-module.ts +8 -0
  267. package/test/fixtures/runners-inter-module.ts +52 -0
  268. package/test/fixtures/secrets-inter-module.ts +61 -0
  269. package/test/globalSetup.ts +11 -11
  270. package/tsconfig.build.tsbuildinfo +1 -1
  271. package/dist/core/source-control.d.ts +0 -5
  272. package/dist/core/source-control.d.ts.map +0 -1
  273. package/dist/core/source-control.js +0 -19
  274. package/dist/core/source-control.js.map +0 -1
  275. package/src/core/source-control.test.ts +0 -17
  276. package/src/core/source-control.ts +0 -22
  277. package/src/temporal/workflows/job-execution-orchestration-timeout.test.ts +0 -165
@@ -1,7 +1,12 @@
1
1
  import {requireLeasedJobContext} from '@shipfox/api-auth-context';
2
- import {integrationRouteErrorHandler} from '@shipfox/api-integration-core';
3
- import {isJobLeaseActive} from '@shipfox/api-runners';
2
+ import {
3
+ type IntegrationsModuleClient,
4
+ integrationsInterModuleContract,
5
+ } from '@shipfox/api-integration-core-dto';
6
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
7
+ import type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';
4
8
  import {checkoutTokenResponseSchema} from '@shipfox/api-workflows-dto';
9
+ import {isInterModuleKnownError} from '@shipfox/inter-module';
5
10
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
6
11
  import {createJobCheckoutSpec} from '#core/checkout.js';
7
12
  import {
@@ -9,53 +14,108 @@ import {
9
14
  JobNotFoundError,
10
15
  WorkflowRunNotFoundError,
11
16
  } from '#core/errors.js';
12
- import {sourceControl} from '#core/source-control.js';
13
17
  import {toCheckoutTokenDto} from '#presentation/dto/checkout-token.js';
14
18
 
15
- export const checkoutTokenRoute = defineRoute({
16
- method: 'POST',
17
- path: '/checkout-token',
18
- description:
19
- "Exchanges the runner's job lease for short-lived, read-only checkout credentials for the job's repository. The job is identified by the access token, so no job ID is needed. The checkout target is resolved server-side from the job's run and project; no credential material is stored on the job or run. Returns the repository URL, ref, and (when the provider needs auth) a short-lived credential that expires soon.",
20
- schema: {
21
- response: {
22
- 200: checkoutTokenResponseSchema,
19
+ export function createCheckoutTokenRoute(clients: {
20
+ runners: RunnersInterModuleClient;
21
+ integrations: IntegrationsModuleClient;
22
+ projects: ProjectsModuleClient;
23
+ }) {
24
+ return defineRoute({
25
+ method: 'POST',
26
+ path: '/checkout-token',
27
+ description:
28
+ "Exchanges the runner's job lease for short-lived, read-only checkout credentials for the job's repository. The job is identified by the access token, so no job ID is needed. The checkout target is resolved server-side from the job's run and project; no credential material is stored on the job or run. Returns the repository URL, ref, and (when the provider needs auth) a short-lived credential that expires soon.",
29
+ schema: {response: {200: checkoutTokenResponseSchema}},
30
+ errorHandler: (error) => {
31
+ const known = isInterModuleKnownError(
32
+ integrationsInterModuleContract.methods.createCheckoutSpec,
33
+ error,
34
+ )
35
+ ? error
36
+ : undefined;
37
+ if (error instanceof JobNotFoundError)
38
+ throw new ClientError(error.message, 'job-not-found', {status: 404});
39
+ if (error instanceof WorkflowRunNotFoundError)
40
+ throw new ClientError(error.message, 'run-not-found', {status: 404});
41
+ if (error instanceof CheckoutIntentUnresolvedError)
42
+ throw new ClientError(error.message, 'checkout-unavailable', {status: 404});
43
+ if (known?.code === 'connection-not-found')
44
+ throw new ClientError(
45
+ 'Integration connection not found',
46
+ 'integration-connection-not-found',
47
+ {
48
+ status: 404,
49
+ },
50
+ );
51
+ if (known?.code === 'connection-inactive')
52
+ throw new ClientError(
53
+ 'Integration connection is not active',
54
+ 'integration-connection-inactive',
55
+ {
56
+ status: 422,
57
+ },
58
+ );
59
+ if (known?.code === 'connection-workspace-mismatch')
60
+ throw new ClientError(
61
+ 'Integration connection does not belong to this workspace',
62
+ 'forbidden',
63
+ {status: 403},
64
+ );
65
+ if (known?.code === 'provider-unavailable')
66
+ throw new ClientError(
67
+ 'Integration provider is unavailable',
68
+ 'integration-provider-unavailable',
69
+ {
70
+ status: 422,
71
+ },
72
+ );
73
+ if (known?.code === 'capability-unavailable')
74
+ throw new ClientError(
75
+ 'Integration capability is unavailable',
76
+ 'integration-capability-unavailable',
77
+ {
78
+ status: 422,
79
+ },
80
+ );
81
+ if (known?.code === 'checkout-unsupported')
82
+ throw new ClientError(
83
+ 'Integration checkout is unsupported',
84
+ 'integration-checkout-unsupported',
85
+ {
86
+ status: 422,
87
+ },
88
+ );
89
+ if (known?.code === 'provider-failure') {
90
+ const status =
91
+ known.details.reason === 'rate-limited'
92
+ ? 429
93
+ : known.details.reason === 'timeout' || known.details.reason === 'provider-unavailable'
94
+ ? 503
95
+ : 422;
96
+ throw new ClientError('Integration provider request failed', known.details.reason, {
97
+ details: {retry_after_seconds: known.details.retryAfterSeconds},
98
+ status,
99
+ });
100
+ }
101
+ throw error;
23
102
  },
24
- },
25
- errorHandler: (error) => {
26
- if (error instanceof JobNotFoundError) {
27
- throw new ClientError(error.message, 'job-not-found', {status: 404});
28
- }
29
- if (error instanceof WorkflowRunNotFoundError) {
30
- throw new ClientError(error.message, 'run-not-found', {status: 404});
31
- }
32
- if (error instanceof CheckoutIntentUnresolvedError) {
33
- throw new ClientError(error.message, 'checkout-unavailable', {status: 404});
34
- }
35
- // Connection and provider errors (and the structural provider-error detection)
36
- // map through the shared handler, which re-throws unknowns to the global handler.
37
- return integrationRouteErrorHandler(error);
38
- },
39
- handler: async (request, reply) => {
40
- const leasedJob = requireLeasedJobContext(request);
41
-
42
- const leaseIsActive = await isJobLeaseActive({
43
- jobId: leasedJob.jobId,
44
- jobExecutionId: leasedJob.jobExecutionId,
45
- runnerSessionId: leasedJob.runnerSessionId,
46
- });
47
- if (!leaseIsActive) {
48
- throw new ClientError('Job lease is no longer active', 'lease-not-active', {status: 404});
49
- }
50
-
51
- const checkout = await createJobCheckoutSpec({
52
- jobId: leasedJob.jobId,
53
- sourceControl: sourceControl(),
54
- });
55
-
56
- // The body carries short-lived git credentials; forbid any intermediary or
57
- // client cache from retaining them.
58
- reply.header('cache-control', 'no-store');
59
- return toCheckoutTokenDto(checkout.spec, {persist: checkout.persistCredentials});
60
- },
61
- });
103
+ handler: async (request, reply) => {
104
+ const leasedJob = requireLeasedJobContext(request);
105
+ const {active: leaseIsActive} = await clients.runners.getLeaseState({
106
+ jobId: leasedJob.jobId,
107
+ jobExecutionId: leasedJob.jobExecutionId,
108
+ runnerSessionId: leasedJob.runnerSessionId,
109
+ });
110
+ if (!leaseIsActive)
111
+ throw new ClientError('Job lease is no longer active', 'lease-not-active', {status: 404});
112
+ const checkout = await createJobCheckoutSpec({
113
+ jobId: leasedJob.jobId,
114
+ integrations: clients.integrations,
115
+ projects: clients.projects,
116
+ });
117
+ reply.header('cache-control', 'no-store');
118
+ return toCheckoutTokenDto(checkout.spec, {persist: checkout.persistCredentials});
119
+ },
120
+ });
121
+ }
@@ -1,4 +1,5 @@
1
1
  import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
3
  import type {FastifyInstance} from 'fastify';
3
4
  import Fastify from 'fastify';
4
5
  import {serializerCompiler, validatorCompiler} from 'fastify-type-provider-zod';
@@ -8,15 +9,20 @@ import {getRunAggregatesRoute} from './get-run-aggregates.js';
8
9
 
9
10
  const projectAccessState = vi.hoisted(() => ({workspaceId: ''}));
10
11
 
11
- vi.mock('@shipfox/api-projects', () => ({
12
- ProjectNotFoundError: class ProjectNotFoundError extends Error {},
13
- requireProjectAccess: vi.fn(({projectId}) =>
12
+ const projects = {
13
+ getProjectById: vi.fn(({projectId}) =>
14
14
  Promise.resolve({
15
- project: {id: projectId, workspaceId: projectAccessState.workspaceId},
16
- workspaceId: projectAccessState.workspaceId,
15
+ project: {
16
+ id: projectId,
17
+ workspaceId: projectAccessState.workspaceId,
18
+ sourceConnectionId: crypto.randomUUID(),
19
+ sourceExternalRepositoryId: 'repo',
20
+ name: 'Project',
21
+ },
17
22
  }),
18
23
  ),
19
- }));
24
+ requireProjectForWorkspace: vi.fn(),
25
+ } as unknown as ProjectsModuleClient;
20
26
 
21
27
  describe('GET /api/workflows/runs/aggregates', () => {
22
28
  let app: FastifyInstance;
@@ -38,7 +44,7 @@ describe('GET /api/workflows/runs/aggregates', () => {
38
44
  );
39
45
  done();
40
46
  });
41
- app.get('/api/workflows/runs/aggregates', getRunAggregatesRoute);
47
+ app.get('/api/workflows/runs/aggregates', getRunAggregatesRoute(projects));
42
48
  await app.ready();
43
49
  });
44
50
 
@@ -1,69 +1,66 @@
1
- import {ProjectNotFoundError, requireProjectAccess} from '@shipfox/api-projects';
1
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
2
  import {
3
3
  workflowRunAggregatesQuerySchema,
4
4
  workflowRunAggregatesResponseSchema,
5
5
  } from '@shipfox/api-workflows-dto';
6
- import {ClientError, defineRoute} from '@shipfox/node-fastify';
6
+ import {defineRoute} from '@shipfox/node-fastify';
7
7
  import {logger} from '@shipfox/node-opentelemetry';
8
8
  import {getWorkflowRunAggregates} from '#db/index.js';
9
+ import {requireProjectAccess} from './project-access.js';
9
10
 
10
- export const getRunAggregatesRoute = defineRoute({
11
- method: 'GET',
12
- path: '/aggregates',
13
- description: 'Get faceted workflow run counts for a project',
14
- schema: {
15
- querystring: workflowRunAggregatesQuerySchema,
16
- response: {
17
- 200: workflowRunAggregatesResponseSchema,
11
+ export function getRunAggregatesRoute(projects: ProjectsModuleClient) {
12
+ return defineRoute({
13
+ method: 'GET',
14
+ path: '/aggregates',
15
+ description: 'Get faceted workflow run counts for a project',
16
+ schema: {
17
+ querystring: workflowRunAggregatesQuerySchema,
18
+ response: {
19
+ 200: workflowRunAggregatesResponseSchema,
20
+ },
18
21
  },
19
- },
20
- errorHandler: (error) => {
21
- if (error instanceof ProjectNotFoundError) {
22
- throw new ClientError(error.message, 'project-not-found', {status: 404});
23
- }
24
- throw error;
25
- },
26
- handler: async (request) => {
27
- const startedAt = performance.now();
28
- const {
29
- project_id: projectId,
30
- status,
31
- definition_id: definitionId,
32
- trigger_source: triggerSource,
33
- created_from: createdFrom,
34
- created_to: createdTo,
35
- } = request.query;
22
+ handler: async (request) => {
23
+ const startedAt = performance.now();
24
+ const {
25
+ project_id: projectId,
26
+ status,
27
+ definition_id: definitionId,
28
+ trigger_source: triggerSource,
29
+ created_from: createdFrom,
30
+ created_to: createdTo,
31
+ } = request.query;
36
32
 
37
- const {project} = await requireProjectAccess({request, projectId});
38
- const filters = {
39
- status,
40
- definitionId,
41
- triggerSource,
42
- createdFrom: createdFrom ? new Date(createdFrom) : undefined,
43
- createdTo: createdTo ? new Date(createdTo) : undefined,
44
- };
45
- const aggregates = await getWorkflowRunAggregates({projectId: project.id, filters});
33
+ const project = await requireProjectAccess(request, projectId, projects);
34
+ const filters = {
35
+ status,
36
+ definitionId,
37
+ triggerSource,
38
+ createdFrom: createdFrom ? new Date(createdFrom) : undefined,
39
+ createdTo: createdTo ? new Date(createdTo) : undefined,
40
+ };
41
+ const aggregates = await getWorkflowRunAggregates({projectId: project.id, filters});
46
42
 
47
- logger().info(
48
- {
49
- projectId: project.id,
50
- filterKeys: Object.entries(filters)
51
- .filter(([, value]) => value !== undefined)
52
- .map(([key]) => key),
53
- aggregateBucketSizes: {
54
- status: aggregates.status.length,
55
- triggerSource: aggregates.triggerSource.length,
56
- workflow: aggregates.workflow.length,
43
+ logger().info(
44
+ {
45
+ projectId: project.id,
46
+ filterKeys: Object.entries(filters)
47
+ .filter(([, value]) => value !== undefined)
48
+ .map(([key]) => key),
49
+ aggregateBucketSizes: {
50
+ status: aggregates.status.length,
51
+ triggerSource: aggregates.triggerSource.length,
52
+ workflow: aggregates.workflow.length,
53
+ },
54
+ durationMs: Math.round(performance.now() - startedAt),
57
55
  },
58
- durationMs: Math.round(performance.now() - startedAt),
59
- },
60
- 'Aggregated workflow runs',
61
- );
56
+ 'Aggregated workflow runs',
57
+ );
62
58
 
63
- return {
64
- status: aggregates.status,
65
- trigger_source: aggregates.triggerSource,
66
- workflow: aggregates.workflow,
67
- };
68
- },
69
- });
59
+ return {
60
+ status: aggregates.status,
61
+ trigger_source: aggregates.triggerSource,
62
+ workflow: aggregates.workflow,
63
+ };
64
+ },
65
+ });
66
+ }
@@ -1,5 +1,5 @@
1
1
  import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
- import {requireProjectAccess} from '@shipfox/api-projects';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
3
3
  import {ClientError} from '@shipfox/node-fastify';
4
4
  import {eq} from 'drizzle-orm';
5
5
  import type {FastifyInstance} from 'fastify';
@@ -40,16 +40,11 @@ async function recordStepResult(
40
40
  return recordJobExecutionStepResult({...rest, jobExecutionId: step.jobExecutionId});
41
41
  }
42
42
 
43
- vi.mock('@shipfox/api-projects', () => ({
44
- requireProjectAccess: vi.fn(({projectId}) =>
45
- Promise.resolve({
46
- project: {id: projectId, workspaceId: projectAccessState.workspaceId},
47
- workspaceId: projectAccessState.workspaceId,
48
- }),
49
- ),
50
- }));
51
-
52
- const mockRequireProjectAccess = vi.mocked(requireProjectAccess);
43
+ const getProjectById = vi.fn();
44
+ const projects = {
45
+ getProjectById,
46
+ requireProjectForWorkspace: vi.fn(),
47
+ } as unknown as ProjectsModuleClient;
53
48
 
54
49
  describe('GET /api/workflows/runs/:id', () => {
55
50
  let app: FastifyInstance;
@@ -70,14 +65,14 @@ describe('GET /api/workflows/runs/:id', () => {
70
65
  );
71
66
  done();
72
67
  });
73
- app.get('/api/workflows/runs/:id', getRunRoute);
68
+ app.get('/api/workflows/runs/:id', getRunRoute(projects));
74
69
  await app.ready();
75
70
  });
76
71
 
77
72
  beforeEach(() => {
78
73
  workspaceId = crypto.randomUUID();
79
74
  projectAccessState.workspaceId = workspaceId;
80
- mockRequireProjectAccess.mockImplementation(({projectId}) =>
75
+ getProjectById.mockImplementation(({projectId}) =>
81
76
  Promise.resolve({
82
77
  project: {
83
78
  id: projectId,
@@ -85,10 +80,7 @@ describe('GET /api/workflows/runs/:id', () => {
85
80
  sourceConnectionId: crypto.randomUUID(),
86
81
  sourceExternalRepositoryId: `repo:${crypto.randomUUID()}`,
87
82
  name: 'Project',
88
- createdAt: new Date(),
89
- updatedAt: new Date(),
90
83
  },
91
- workspaceId,
92
84
  }),
93
85
  );
94
86
  });
@@ -362,7 +354,7 @@ jobs:
362
354
  userId: crypto.randomUUID(),
363
355
  },
364
356
  });
365
- mockRequireProjectAccess.mockRejectedValueOnce(
357
+ getProjectById.mockRejectedValueOnce(
366
358
  new ClientError('Not a member of this workspace', 'forbidden', {status: 403}),
367
359
  );
368
360
 
@@ -389,7 +381,7 @@ jobs:
389
381
  userId: crypto.randomUUID(),
390
382
  },
391
383
  });
392
- mockRequireProjectAccess.mockRejectedValueOnce(new Error('database connection lost'));
384
+ getProjectById.mockRejectedValueOnce(new Error('database connection lost'));
393
385
 
394
386
  const res = await app.inject({
395
387
  method: 'GET',
@@ -1,3 +1,4 @@
1
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
1
2
  import {workflowRunDetailResponseSchema} from '@shipfox/api-workflows-dto';
2
3
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
3
4
  import {z} from 'zod';
@@ -12,55 +13,57 @@ import {
12
13
  } from '#presentation/dto/index.js';
13
14
  import {requireAccessibleRun} from './require-accessible-run.js';
14
15
 
15
- export const getRunRoute = defineRoute({
16
- method: 'GET',
17
- path: '/:id',
18
- description: 'Get a workflow run by ID with jobs and steps',
19
- schema: {
20
- params: z.object({
21
- id: z.string().uuid(),
22
- }),
23
- querystring: z.object({
24
- attempt: z.coerce.number().int().positive().optional(),
25
- }),
26
- response: {
27
- 200: workflowRunDetailResponseSchema,
16
+ export function getRunRoute(projects: ProjectsModuleClient) {
17
+ return defineRoute({
18
+ method: 'GET',
19
+ path: '/:id',
20
+ description: 'Get a workflow run by ID with jobs and steps',
21
+ schema: {
22
+ params: z.object({
23
+ id: z.string().uuid(),
24
+ }),
25
+ querystring: z.object({
26
+ attempt: z.coerce.number().int().positive().optional(),
27
+ }),
28
+ response: {
29
+ 200: workflowRunDetailResponseSchema,
30
+ },
28
31
  },
29
- },
30
- handler: async (request) => {
31
- const {id} = request.params;
32
- await requireAccessibleRun({request, id});
32
+ handler: async (request) => {
33
+ const {id} = request.params;
34
+ await requireAccessibleRun({request, id, projects});
33
35
 
34
- const run = await getWorkflowRunDetail(id, request.query.attempt);
35
- if (!run) {
36
- throw new ClientError('Run not found', 'not-found', {status: 404});
37
- }
36
+ const run = await getWorkflowRunDetail(id, request.query.attempt);
37
+ if (!run) {
38
+ throw new ClientError('Run not found', 'not-found', {status: 404});
39
+ }
38
40
 
39
- const jobDtos = run.jobs.map((job) => ({
40
- ...toJobDto(job),
41
- job_executions: job.jobExecutions.map((jobExecution) => ({
42
- ...toJobExecutionDto(jobExecution),
43
- steps: jobExecution.steps.map((step) => {
44
- const attempts = step.attempts.map(toStepAttemptDto);
45
- const latestTerminalAttempt = attempts
46
- .filter((attempt) => attempt.status !== 'running')
47
- .at(-1);
48
- return {
49
- ...toStepDto(step),
50
- exit_code: latestTerminalAttempt?.exit_code ?? null,
51
- outputs: latestTerminalAttempt?.outputs ?? null,
52
- response: latestTerminalAttempt?.response ?? null,
53
- gate_result: latestTerminalAttempt?.gate_result ?? null,
54
- attempts,
55
- };
56
- }),
57
- })),
58
- }));
41
+ const jobDtos = run.jobs.map((job) => ({
42
+ ...toJobDto(job),
43
+ job_executions: job.jobExecutions.map((jobExecution) => ({
44
+ ...toJobExecutionDto(jobExecution),
45
+ steps: jobExecution.steps.map((step) => {
46
+ const attempts = step.attempts.map(toStepAttemptDto);
47
+ const latestTerminalAttempt = attempts
48
+ .filter((attempt) => attempt.status !== 'running')
49
+ .at(-1);
50
+ return {
51
+ ...toStepDto(step),
52
+ exit_code: latestTerminalAttempt?.exit_code ?? null,
53
+ outputs: latestTerminalAttempt?.outputs ?? null,
54
+ response: latestTerminalAttempt?.response ?? null,
55
+ gate_result: latestTerminalAttempt?.gate_result ?? null,
56
+ attempts,
57
+ };
58
+ }),
59
+ })),
60
+ }));
59
61
 
60
- return {
61
- ...toRunDto(run, run.latestAttempt),
62
- run_attempt: toRunAttemptDto(run.runAttempt),
63
- jobs: jobDtos,
64
- };
65
- },
66
- });
62
+ return {
63
+ ...toRunDto(run, run.latestAttempt),
64
+ run_attempt: toRunAttemptDto(run.runAttempt),
65
+ jobs: jobDtos,
66
+ };
67
+ },
68
+ });
69
+ }
@@ -1,5 +1,4 @@
1
1
  import {createLeaseTokenAuthMethod} from '@shipfox/api-auth';
2
- import {setSecrets} from '@shipfox/api-secrets';
3
2
  import {closeApp, createApp, type FastifyInstance} from '@shipfox/node-fastify';
4
3
  import {createCapturingLogger} from '@shipfox/node-log/test';
5
4
  import {eq} from 'drizzle-orm';
@@ -15,18 +14,35 @@ import {
15
14
  } from '#db/workflow-runs.js';
16
15
  import {workflowModel} from '#test/factories/workflow-model.js';
17
16
  import {mintActiveLeaseToken} from '#test/fixtures/active-lease-token.js';
18
- import {leaseTokenRouteGroup} from './index.js';
17
+ import {agentTestClient} from '#test/fixtures/agent-inter-module.js';
18
+ import {annotationsTestClient} from '#test/fixtures/annotations-inter-module.js';
19
+ import {workflowsTestAuthClient} from '#test/fixtures/auth-inter-module.js';
20
+ import {projectsTestClient} from '#test/fixtures/projects-inter-module.js';
21
+ import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
22
+ import {createTestSecretsClient} from '#test/fixtures/secrets-inter-module.js';
23
+ import {createLeaseTokenRouteGroup} from './index.js';
19
24
 
20
25
  const URL_PREFIX = '/runs/jobs/current/steps';
21
26
 
22
27
  describe('GET /runs/jobs/current/steps/:stepId/secrets', () => {
23
28
  let app: FastifyInstance;
29
+ const secrets = createTestSecretsClient();
24
30
  const {logger, lines: logLines, clear: clearLogLines} = createCapturingLogger();
25
31
 
26
32
  beforeAll(async () => {
27
33
  app = await createApp({
28
34
  auth: [createLeaseTokenAuthMethod()],
29
- routes: [leaseTokenRouteGroup],
35
+ routes: [
36
+ createLeaseTokenRouteGroup({
37
+ agent: agentTestClient,
38
+ annotations: annotationsTestClient,
39
+ auth: workflowsTestAuthClient,
40
+ integrations: {} as never,
41
+ projects: projectsTestClient,
42
+ runners: runnersTestClient,
43
+ secrets,
44
+ }),
45
+ ],
30
46
  swagger: false,
31
47
  fastifyOptions: {loggerInstance: logger},
32
48
  });
@@ -56,7 +72,7 @@ describe('GET /runs/jobs/current/steps/:stepId/secrets', () => {
56
72
  segments: [{kind: 'secret', store: 'local', key: 'API_TOKEN'}],
57
73
  },
58
74
  ]);
59
- await setSecrets({
75
+ await secrets.setSecrets({
60
76
  workspaceId: run.workspaceId,
61
77
  projectId: run.projectId,
62
78
  values: {API_TOKEN: 'runtime-secret', UNUSED_TOKEN: 'unused-secret'},
@@ -87,13 +103,19 @@ describe('GET /runs/jobs/current/steps/:stepId/secrets', () => {
87
103
  segments: [{kind: 'secret', store: 'local', key: 'API_TOKEN'}],
88
104
  },
89
105
  ]);
90
- await setSecrets({workspaceId: run.workspaceId, values: {API_TOKEN: 'workspace-secret'}});
91
- await setSecrets({
106
+ await secrets.setSecrets({
107
+ workspaceId: run.workspaceId,
108
+ values: {API_TOKEN: 'workspace-secret'},
109
+ });
110
+ await secrets.setSecrets({
92
111
  workspaceId: run.workspaceId,
93
112
  projectId: run.projectId,
94
113
  values: {API_TOKEN: 'project-secret'},
95
114
  });
96
- await setSecrets({workspaceId: hostileWorkspaceId, values: {API_TOKEN: 'hostile-secret'}});
115
+ await secrets.setSecrets({
116
+ workspaceId: hostileWorkspaceId,
117
+ values: {API_TOKEN: 'hostile-secret'},
118
+ });
97
119
  const token = await mintActiveLeaseToken({
98
120
  jobId: job.id,
99
121
  token: {workspaceId: hostileWorkspaceId, projectId: crypto.randomUUID()},