@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
@@ -5,6 +5,7 @@ import {
5
5
  getJobsByWorkflowRunId,
6
6
  updateJobExecutionStatus,
7
7
  } from '#db/index.js';
8
+ import {createTestSecretsClient} from '#test/fixtures/secrets-inter-module.js';
8
9
  import {stripSetupStep} from '#test/fixtures/strip-setup-step.js';
9
10
  import {workflowModel} from '#test/index.js';
10
11
  import {resolveLeaseExpiredJobExecutionActivity, setJobStatus} from './orchestration-activities.js';
@@ -12,6 +13,7 @@ import {resolveLeaseExpiredJobExecutionActivity, setJobStatus} from './orchestra
12
13
  let workspaceId: string;
13
14
  let projectId: string;
14
15
  let definitionId: string;
16
+ const secrets = createTestSecretsClient();
15
17
 
16
18
  beforeEach(() => {
17
19
  workspaceId = crypto.randomUUID();
@@ -51,10 +53,10 @@ describe('resolveLeaseExpiredJobExecutionActivity', () => {
51
53
  // reproduce a genuinely stepless (malformed) job.
52
54
  await stripSetupStep(jobId);
53
55
 
54
- const error = await resolveLeaseExpiredJobExecutionActivity({
55
- jobExecutionId,
56
- expectedVersion: runningVersion,
57
- }).catch((err: unknown) => err);
56
+ const error = await resolveLeaseExpiredJobExecutionActivity(
57
+ {jobExecutionId, expectedVersion: runningVersion},
58
+ secrets,
59
+ ).catch((err: unknown) => err);
58
60
 
59
61
  expect(error).toBeInstanceOf(ApplicationFailure);
60
62
  expect((error as ApplicationFailure).nonRetryable).toBe(true);
@@ -63,10 +65,10 @@ describe('resolveLeaseExpiredJobExecutionActivity', () => {
63
65
  test('a well-formed job resolves without raising', async () => {
64
66
  const {jobExecutionId, runningVersion} = await seedRunningJob(2);
65
67
 
66
- const result = await resolveLeaseExpiredJobExecutionActivity({
67
- jobExecutionId,
68
- expectedVersion: runningVersion,
69
- });
68
+ const result = await resolveLeaseExpiredJobExecutionActivity(
69
+ {jobExecutionId, expectedVersion: runningVersion},
70
+ secrets,
71
+ );
70
72
 
71
73
  expect(result.status).toBe('failed');
72
74
  });
@@ -1,4 +1,12 @@
1
- import {cancelRunnerJobs, enqueueJobExecution, releaseJobExecution} from '@shipfox/api-runners';
1
+ import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
2
+ import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
3
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
4
+ import {
5
+ type RunnersInterModuleClient,
6
+ runnersInterModuleContract,
7
+ } from '@shipfox/api-runners-dto/inter-module';
8
+ import type {SecretsInterModuleClient} from '@shipfox/api-secrets-dto/inter-module';
9
+ import {isInterModuleKnownError} from '@shipfox/inter-module';
2
10
  import {ApplicationFailure} from '@temporalio/common';
3
11
  import {defaultJobConditionTrace} from '#core/condition-trace.js';
4
12
  import type {JobStatus, JobStatusReason, ResolutionReason} from '#core/entities/job.js';
@@ -161,17 +169,21 @@ function jobStatusEvaluationTrace(params: {
161
169
  : undefined;
162
170
  }
163
171
 
164
- export async function setJobExecutionStatus(params: {
165
- jobExecutionId: string;
166
- status: Exclude<JobStatus, 'skipped'>;
167
- version: number;
168
- statusReason?: JobStatusReason | null | undefined;
169
- }): Promise<{newVersion: number; status: Exclude<JobStatus, 'skipped'>}> {
172
+ export async function setJobExecutionStatus(
173
+ params: {
174
+ jobExecutionId: string;
175
+ status: Exclude<JobStatus, 'skipped'>;
176
+ version: number;
177
+ statusReason?: JobStatusReason | null | undefined;
178
+ },
179
+ secrets?: Pick<SecretsInterModuleClient, 'getVariablesByNamespace'>,
180
+ ): Promise<{newVersion: number; status: Exclude<JobStatus, 'skipped'>}> {
170
181
  const updated = await updateJobExecutionStatus({
171
182
  jobExecutionId: params.jobExecutionId,
172
183
  status: params.status,
173
184
  expectedVersion: params.version,
174
185
  statusReason: params.statusReason,
186
+ secrets,
175
187
  });
176
188
  return {newVersion: updated.version, status: updated.status};
177
189
  }
@@ -183,12 +195,12 @@ export async function bulkSetStepStatuses(params: {
183
195
  await bulkUpdateStepStatuses(params);
184
196
  }
185
197
 
186
- export async function resolveLeaseExpiredJobExecutionActivity(params: {
187
- jobExecutionId: string;
188
- expectedVersion: number;
189
- }): Promise<{status: RuntimeCompletionStatus; executionVersion: number}> {
198
+ export async function resolveLeaseExpiredJobExecutionActivity(
199
+ params: {jobExecutionId: string; expectedVersion: number},
200
+ secrets: Pick<SecretsInterModuleClient, 'getVariablesByNamespace'>,
201
+ ): Promise<{status: RuntimeCompletionStatus; executionVersion: number}> {
190
202
  try {
191
- return await resolveJobExecutionAfterLeaseExpiry(params);
203
+ return await resolveJobExecutionAfterLeaseExpiry({...params, secrets});
192
204
  } catch (err) {
193
205
  if (err instanceof JobNotFoundError) {
194
206
  throw ApplicationFailure.nonRetryable(err.message, err.name);
@@ -200,50 +212,56 @@ export async function resolveLeaseExpiredJobExecutionActivity(params: {
200
212
  // Best-effort lease cleanup on job finalization. Idempotent: deleting an
201
213
  // already-released (or already-reaped) lease is a no-op. The workflow wraps the
202
214
  // call so a persistent failure never blocks the DAG result.
203
- export async function releaseLeaseActivity(params: {jobExecutionId: string}): Promise<void> {
204
- await releaseJobExecution({jobExecutionId: params.jobExecutionId});
215
+ export function createReleaseLeaseActivity(runners: RunnersInterModuleClient) {
216
+ return async function releaseLeaseActivity(params: {jobExecutionId: string}): Promise<void> {
217
+ await runners.releaseJobExecution({jobExecutionId: params.jobExecutionId});
218
+ };
205
219
  }
206
220
 
207
- export async function enqueueJobExecutionForRunner(params: {
208
- workspaceId: string;
209
- workflowRunId: string;
210
- jobId: string;
211
- jobExecutionId: string;
212
- runAttemptId: string;
213
- projectId: string;
214
- requiredLabels: string[];
215
- }): Promise<void> {
216
- try {
217
- await enqueueJobExecution({
218
- workspaceId: params.workspaceId,
219
- workflowRunId: params.workflowRunId,
220
- workflowRunAttemptId: params.runAttemptId,
221
- jobId: params.jobId,
222
- jobExecutionId: params.jobExecutionId,
223
- projectId: params.projectId,
224
- requiredLabels: params.requiredLabels,
225
- });
226
- } catch (err) {
227
- if (err instanceof Error && err.name === 'EmptyRequiredLabelsError') {
228
- throw ApplicationFailure.nonRetryable(err.message, err.name);
221
+ export function createEnqueueJobExecutionForRunner(runners: RunnersInterModuleClient) {
222
+ return async function enqueueJobExecutionForRunner(params: {
223
+ workspaceId: string;
224
+ workflowRunId: string;
225
+ jobId: string;
226
+ jobExecutionId: string;
227
+ runAttemptId: string;
228
+ projectId: string;
229
+ requiredLabels: string[];
230
+ }): Promise<void> {
231
+ try {
232
+ await runners.enqueueJobExecution({
233
+ workspaceId: params.workspaceId,
234
+ workflowRunId: params.workflowRunId,
235
+ workflowRunAttemptId: params.runAttemptId,
236
+ jobId: params.jobId,
237
+ jobExecutionId: params.jobExecutionId,
238
+ projectId: params.projectId,
239
+ requiredLabels: params.requiredLabels,
240
+ });
241
+ } catch (err) {
242
+ if (isInterModuleKnownError(runnersInterModuleContract.methods.enqueueJobExecution, err)) {
243
+ throw ApplicationFailure.nonRetryable(err.message, err.name);
244
+ }
245
+ throw err;
229
246
  }
230
- throw err;
231
- }
247
+ };
232
248
  }
233
249
 
234
- export async function cancelRunnerJobsActivity(params: {jobIds: string[]}): Promise<void> {
235
- await cancelRunnerJobs({jobIds: params.jobIds});
250
+ export function createCancelRunnerJobsActivity(runners: RunnersInterModuleClient) {
251
+ return async function cancelRunnerJobsActivity(params: {jobIds: string[]}): Promise<void> {
252
+ await runners.cancelJobs(params);
253
+ };
236
254
  }
237
255
 
238
- export async function failJobExecutionAsTimedOutActivity(params: {
239
- jobExecutionId: string;
240
- runAttemptId: string;
241
- expectedVersion: number;
242
- }): Promise<{newVersion: number}> {
256
+ export async function failJobExecutionAsTimedOutActivity(
257
+ params: {jobExecutionId: string; runAttemptId: string; expectedVersion: number},
258
+ secrets: Pick<SecretsInterModuleClient, 'getVariablesByNamespace'>,
259
+ ): Promise<{newVersion: number}> {
243
260
  const jobExecution = await failJobExecutionAsTimedOut({
244
261
  jobExecutionId: params.jobExecutionId,
245
262
  workflowRunAttemptId: params.runAttemptId,
246
263
  expectedVersion: params.expectedVersion,
264
+ secrets,
247
265
  });
248
266
  return {newVersion: jobExecution.version};
249
267
  }
@@ -265,12 +283,18 @@ export async function activateJobListenerActivity(params: {
265
283
  return await activateJobListener(params);
266
284
  }
267
285
 
268
- export async function drainListenerEventsActivity(params: {
269
- jobId: string;
270
- expectedSequence: number;
271
- maxSize?: number | undefined;
286
+ export function createDrainListenerEventsActivity(clients: {
287
+ agent: AgentInterModuleClient;
288
+ integrations?: IntegrationsModuleClient | undefined;
289
+ projects?: ProjectsModuleClient | undefined;
272
290
  }) {
273
- return await drainListenerEventsIntoExecution(params);
291
+ return async function drainListenerEventsActivity(params: {
292
+ jobId: string;
293
+ expectedSequence: number;
294
+ maxSize?: number | undefined;
295
+ }) {
296
+ return await drainListenerEventsIntoExecution({...params, ...clients});
297
+ };
274
298
  }
275
299
 
276
300
  export async function peekListenerBufferActivity(params: {jobId: string}) {
@@ -22,7 +22,7 @@ const WORKFLOWS_PATH = resolve(import.meta.dirname, '../../../dist/temporal/work
22
22
 
23
23
  beforeAll(async () => {
24
24
  await setupEnv();
25
- }, 60_000);
25
+ }, 15_000);
26
26
 
27
27
  afterAll(async () => {
28
28
  await teardownEnv();
@@ -43,7 +43,9 @@ const defaultJobInput = {
43
43
  requiredLabels: ['ubuntu22'],
44
44
  };
45
45
 
46
- function executeJob(input: typeof defaultJobInput): Promise<{status: string; jobVersion: number}> {
46
+ function executeJob(
47
+ input: typeof defaultJobInput & {executionTimeoutMs?: number | undefined},
48
+ ): Promise<{status: string; jobVersion: number}> {
47
49
  const normalized = {
48
50
  ...input,
49
51
  jobExecutionId:
@@ -297,6 +299,49 @@ describe('jobExecutionOrchestration', () => {
297
299
  expect(callsNamed('releaseLeaseActivity').length).toBeGreaterThanOrEqual(1);
298
300
  });
299
301
 
302
+ test('times out after the execution deadline and fails its steps without releasing the lease', async () => {
303
+ setCfg({
304
+ dag: makeDag([dagJob('job-timeout', 'build')]),
305
+ jobResults: new Map(),
306
+ skipSignal: true,
307
+ });
308
+
309
+ const result = await executeJob({
310
+ ...defaultJobInput,
311
+ jobId: 'job-timeout',
312
+ executionTimeoutMs: 250,
313
+ });
314
+
315
+ expect(result.status).toBe('failed');
316
+ expect(callsNamed('failJobExecutionAsTimedOutActivity')).toHaveLength(1);
317
+ expect(finalStatusesFor('job-timeout')).toEqual(['running']);
318
+ expect(callsNamed('bulkSetStepStatuses')).toContainEqual({
319
+ name: 'bulkSetStepStatuses',
320
+ params: {jobExecutionId: 'job-timeout', status: 'failed'},
321
+ });
322
+ expect(callsNamed('releaseLeaseActivity')).toHaveLength(0);
323
+ expect(callsNamed('resolveLeaseExpiredJobExecutionActivity')).toHaveLength(0);
324
+ });
325
+
326
+ test('surfaces a timeout activity failure', async () => {
327
+ setCfg({
328
+ dag: makeDag([dagJob('job-timeout-error', 'build')]),
329
+ jobResults: new Map(),
330
+ skipSignal: true,
331
+ failJobExecutionAsTimedOutError: 'simulated DB outage',
332
+ });
333
+
334
+ await expect(
335
+ executeJob({
336
+ ...defaultJobInput,
337
+ jobId: 'job-timeout-error',
338
+ executionTimeoutMs: 250,
339
+ }),
340
+ ).rejects.toThrow();
341
+
342
+ expect(finalStatusesFor('job-timeout-error')).toEqual(['running']);
343
+ });
344
+
300
345
  test('no signal — workflow stays blocked indefinitely', async () => {
301
346
  setCfg({dag: makeDag([]), jobResults: new Map(), skipSignal: true});
302
347
 
package/test/env.ts CHANGED
@@ -4,8 +4,6 @@ process.env.POSTGRES_USERNAME ??= 'shipfox';
4
4
  process.env.POSTGRES_PASSWORD ??= 'password';
5
5
  process.env.POSTGRES_DATABASE = 'api_test';
6
6
  process.env.POSTGRES_MAX_CONNECTIONS ??= '5';
7
- process.env.AUTH_JWT_SECRET = 'test-secret';
8
- process.env.AUTH_JOB_LEASE_TOKEN_SECRET = 'test-lease-secret';
9
- process.env.AUTH_RUNNER_SESSION_TOKEN_SECRET = 'test-runner-session-secret';
7
+ process.env.AUTH_ROOT_KEY = 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=';
10
8
  process.env.SECRETS_ENCRYPTION_KEK = 'ZmVkY2JhOTg3NjU0MzIxMGZlZGNiYTk4NzY1NDMyMTA=';
11
9
  process.env.TZ = 'UTC';
@@ -1,10 +1,15 @@
1
- import type {Project} from '@shipfox/api-projects';
2
1
  import {Factory} from 'fishery';
3
2
 
4
- // Build-only: the workflows routes resolve projects through a mocked
5
- // `getProjectById`, so these are never persisted (the projects table lives in
6
- // @shipfox/api-projects). Typed against the real `Project` so the mock cannot
7
- // drift from the contract under test.
3
+ interface Project {
4
+ id: string;
5
+ workspaceId: string;
6
+ sourceConnectionId: string;
7
+ sourceExternalRepositoryId: string;
8
+ name: string;
9
+ createdAt: Date;
10
+ updatedAt: Date;
11
+ }
12
+
8
13
  export const projectFactory = Factory.define<Project>(({sequence}) => ({
9
14
  id: crypto.randomUUID(),
10
15
  workspaceId: crypto.randomUUID(),
@@ -1,3 +1,4 @@
1
+ import type {WorkflowModel} from '@shipfox/api-definitions-dto';
1
2
  import {workflowModel} from './workflow-model.js';
2
3
 
3
4
  describe('workflowModel', () => {
@@ -21,6 +22,9 @@ describe('workflowModel', () => {
21
22
  },
22
23
  });
23
24
 
24
- expect(model.jobs.map((job) => job.runner)).toEqual([['ubuntu-22'], ['ubuntu-22', 'node-22']]);
25
+ expect(model.jobs.map((job: WorkflowModel['jobs'][number]) => job.runner)).toEqual([
26
+ ['ubuntu-22'],
27
+ ['ubuntu-22', 'node-22'],
28
+ ]);
25
29
  });
26
30
  });
@@ -1,4 +1,4 @@
1
- import {DEFAULT_JOB_CHECKOUT, type WorkflowModel} from '@shipfox/api-definitions';
1
+ import {DEFAULT_JOB_CHECKOUT, type WorkflowModel} from '@shipfox/api-definitions-dto';
2
2
  import {
3
3
  createWorkflowExpression,
4
4
  parseWorkflowTemplate,
@@ -1,4 +1,4 @@
1
- import type {WorkflowModel} from '@shipfox/api-definitions';
1
+ import type {WorkflowModel} from '@shipfox/api-definitions-dto';
2
2
  import {Factory} from 'fishery';
3
3
  import type {WorkflowRun} from '#core/entities/workflow-run.js';
4
4
  import {createWorkflowRun} from '#db/workflow-runs.js';
@@ -0,0 +1,27 @@
1
+ import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
2
+ import type {AgentDefaultsResolver} from '#core/agent-defaults.js';
3
+
4
+ export const agentTestClient: AgentInterModuleClient = {
5
+ resolveAgentConfig({config}) {
6
+ return Promise.resolve(resolveTestAgentDefaults(config));
7
+ },
8
+ resolveRuntimeCredentials({harness, provider, model, thinking}) {
9
+ return Promise.resolve({
10
+ harness,
11
+ provider_id: provider,
12
+ model,
13
+ thinking,
14
+ credentials: {api_key: 'test-agent-credential'},
15
+ });
16
+ },
17
+ };
18
+
19
+ export const resolveTestAgentDefaults: AgentDefaultsResolver = (config) => {
20
+ const provider = config.provider ?? 'anthropic';
21
+ return {
22
+ harness: config.harness ?? 'pi',
23
+ provider,
24
+ model: config.model ?? (provider === 'openai' ? 'gpt-5.5-pro' : 'claude-opus-4-8'),
25
+ thinking: config.thinking ?? 'xhigh',
26
+ };
27
+ };
@@ -0,0 +1,5 @@
1
+ import type {AnnotationsInterModuleClient} from '@shipfox/annotations-dto/inter-module';
2
+
3
+ export const annotationsTestClient: AnnotationsInterModuleClient = {
4
+ replaceOrRemoveAnnotation: async () => ({}),
5
+ };
@@ -0,0 +1,22 @@
1
+ import {JOB_LEASE_TOKEN_AUDIENCE} from '@shipfox/api-auth-dto';
2
+ import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';
3
+ import {jobLeaseTokenKey} from '@shipfox/node-auth-root-key';
4
+ import {signHs256} from '@shipfox/node-jwt';
5
+
6
+ const leaseSecret = jobLeaseTokenKey();
7
+
8
+ export const workflowsTestAuthClient: AuthInterModuleClient = {
9
+ mintRunnerSessionToken() {
10
+ throw new Error('Runner session token minting is not configured');
11
+ },
12
+ async mintJobLeaseToken(claims) {
13
+ return {
14
+ token: await signHs256({
15
+ payload: claims,
16
+ secret: leaseSecret,
17
+ expiresIn: '90m',
18
+ audience: JOB_LEASE_TOKEN_AUDIENCE,
19
+ }),
20
+ };
21
+ },
22
+ };
@@ -1,9 +1,10 @@
1
1
  import {JOB_LEASE_TOKEN_AUDIENCE} from '@shipfox/api-auth-dto';
2
+ import {jobLeaseTokenKey} from '@shipfox/node-auth-root-key';
2
3
  import {signHs256} from '@shipfox/node-jwt';
3
4
  import {getFirstJobExecutionByJobId} from '#db/workflow-runs.js';
4
5
 
5
6
  // Matches test/env.ts; the lease-token auth method reads this same value from config.
6
- const SECRET = process.env.AUTH_JOB_LEASE_TOKEN_SECRET ?? 'test-lease-secret';
7
+ const SECRET = jobLeaseTokenKey();
7
8
 
8
9
  export interface MintLeaseTokenParams {
9
10
  jobId: string;
@@ -0,0 +1,8 @@
1
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
+
3
+ export const projectsTestClient = {
4
+ getProjectById: async () => ({project: undefined}),
5
+ requireProjectForWorkspace: () => {
6
+ throw new Error('Project is not configured');
7
+ },
8
+ } as unknown as ProjectsModuleClient;
@@ -0,0 +1,52 @@
1
+ import {runnerToolCapabilitiesSchema} from '@shipfox/api-runners-dto';
2
+ import type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';
3
+ import {sql} from 'drizzle-orm';
4
+ import {db} from '#db/db.js';
5
+
6
+ export const runnersTestClient: RunnersInterModuleClient = {
7
+ enqueueJobExecution() {
8
+ return Promise.resolve({});
9
+ },
10
+ releaseJobExecution() {
11
+ return Promise.resolve({});
12
+ },
13
+ cancelJobs() {
14
+ return Promise.resolve({});
15
+ },
16
+ async getLeaseState({jobId, jobExecutionId, runnerSessionId}) {
17
+ const result = await db().execute<{active: boolean}>(sql`
18
+ SELECT EXISTS(
19
+ SELECT 1
20
+ FROM runners_running_jobs
21
+ WHERE job_id = ${jobId}
22
+ AND job_execution_id = ${jobExecutionId}
23
+ AND runner_session_id = ${runnerSessionId}
24
+ ) AS active
25
+ `);
26
+ return {active: result.rows[0]?.active ?? false};
27
+ },
28
+ async getEffectiveRunnerToolCapabilities({runnerSessionId}) {
29
+ const result = await db().execute<{
30
+ toolCapabilities: unknown;
31
+ toolCapabilitiesReportedAt: Date | string | null;
32
+ }>(sql`
33
+ SELECT
34
+ tool_capabilities AS "toolCapabilities",
35
+ tool_capabilities_reported_at AS "toolCapabilitiesReportedAt"
36
+ FROM runners_runner_sessions
37
+ WHERE id = ${runnerSessionId}
38
+ `);
39
+ const row = result.rows[0];
40
+ const reportedAt = row?.toolCapabilitiesReportedAt;
41
+ const reportFresh =
42
+ reportedAt !== null &&
43
+ reportedAt !== undefined &&
44
+ Date.now() - new Date(reportedAt).getTime() <= 60_000;
45
+ return {
46
+ capabilities: reportFresh
47
+ ? runnerToolCapabilitiesSchema.parse(row?.toolCapabilities ?? {harnesses: {}})
48
+ : {harnesses: {}},
49
+ reportFresh,
50
+ };
51
+ },
52
+ };
@@ -0,0 +1,61 @@
1
+ import type {SecretsInterModuleClient} from '@shipfox/api-secrets-dto/inter-module';
2
+
3
+ export function createTestSecretsClient(): SecretsInterModuleClient {
4
+ const values = new Map<string, string>();
5
+ const normalize = (params: {
6
+ workspaceId: string;
7
+ projectId?: string | null | undefined;
8
+ namespace?: string | undefined;
9
+ }) => ({
10
+ workspaceId: params.workspaceId,
11
+ projectId: params.projectId ?? null,
12
+ namespace: params.namespace ?? '',
13
+ });
14
+ const scopeId = (params: ReturnType<typeof normalize>) =>
15
+ `${params.workspaceId}\0${params.projectId ?? ''}\0${params.namespace}`;
16
+ const keyId = (params: ReturnType<typeof normalize>, key: string) => `${scopeId(params)}\0${key}`;
17
+ const entries = (input: {
18
+ workspaceId: string;
19
+ projectId?: string | null | undefined;
20
+ namespace?: string | undefined;
21
+ }) => {
22
+ const params = normalize(input);
23
+ const projectScope = `${scopeId(params)}\0`;
24
+ const workspaceScope = `${scopeId({...params, projectId: null})}\0`;
25
+ const selected = new Map<string, string>();
26
+ for (const [id, value] of values) {
27
+ if (id.startsWith(workspaceScope)) selected.set(id.slice(workspaceScope.length), value);
28
+ if (params.projectId && id.startsWith(projectScope))
29
+ selected.set(id.slice(projectScope.length), value);
30
+ }
31
+ return Object.fromEntries(selected);
32
+ };
33
+
34
+ return {
35
+ getSecret: async (params) => ({
36
+ value:
37
+ values.get(keyId(normalize(params), params.key)) ??
38
+ (params.projectId
39
+ ? values.get(keyId(normalize({...params, projectId: null}), params.key))
40
+ : undefined) ??
41
+ null,
42
+ }),
43
+ getSecretsByNamespace: async (params) => ({values: entries(params)}),
44
+ getVariablesByNamespace: async (params) => ({values: entries(params)}),
45
+ setSecrets: async (params) => {
46
+ await Promise.resolve();
47
+ for (const [key, value] of Object.entries(params.values))
48
+ values.set(keyId(normalize(params), key), value);
49
+ return {};
50
+ },
51
+ deleteSecrets: async (params) => {
52
+ await Promise.resolve();
53
+ const keys = params.keys ?? Object.keys(entries(params));
54
+ let deleted = 0;
55
+ for (const key of keys) {
56
+ if (values.delete(keyId(normalize(params), key))) deleted += 1;
57
+ }
58
+ return {deleted};
59
+ },
60
+ };
61
+ }
@@ -1,8 +1,8 @@
1
1
  import './env.js';
2
2
  import {annotationsModule} from '@shipfox/annotations';
3
- import {agentModule} from '@shipfox/api-agent';
4
- import {runnersModule} from '@shipfox/api-runners';
5
- import {secretsModule} from '@shipfox/api-secrets';
3
+ import {createAgentModule} from '@shipfox/api-agent';
4
+ import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';
5
+ import {createRunnersModule} from '@shipfox/api-runners';
6
6
  import {runMigrations} from '@shipfox/node-drizzle';
7
7
  import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres';
8
8
  import {sql} from 'drizzle-orm';
@@ -10,7 +10,15 @@ import {closeDb, db, migrationsPath} from '#db/index.js';
10
10
 
11
11
  export async function setup() {
12
12
  createPostgresClient();
13
+ const runnersModule = createRunnersModule({auth: {} as AuthInterModuleClient});
13
14
 
15
+ const agentModule = createAgentModule({
16
+ secrets: {
17
+ deleteSecrets: async () => ({deleted: 0}),
18
+ getSecretsByNamespace: async () => ({values: {}}),
19
+ setSecrets: async () => ({}),
20
+ },
21
+ });
14
22
  if (!agentModule.database || Array.isArray(agentModule.database)) {
15
23
  throw new Error('Agent module database is not configured');
16
24
  }
@@ -27,14 +35,6 @@ export async function setup() {
27
35
  runnersModule.database.migrationsPath,
28
36
  '__drizzle_migrations_runners',
29
37
  );
30
- if (!secretsModule.database || Array.isArray(secretsModule.database)) {
31
- throw new Error('Secrets module database is not configured');
32
- }
33
- await runMigrations(
34
- secretsModule.database.db(),
35
- secretsModule.database.migrationsPath,
36
- '__drizzle_migrations_secrets',
37
- );
38
38
  if (!annotationsModule.database || Array.isArray(annotationsModule.database)) {
39
39
  throw new Error('Annotations module database is not configured');
40
40
  }