@shipfox/api-workflows 12.6.0 → 13.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.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +23 -0
- package/dist/core/entities/workflow-run.d.ts +3 -0
- package/dist/core/entities/workflow-run.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/job-listeners.d.ts.map +1 -1
- package/dist/db/job-listeners.js +17 -1
- package/dist/db/job-listeners.js.map +1 -1
- package/dist/db/workflow-runs/job-executions.d.ts +2 -3
- package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
- package/dist/db/workflow-runs/job-executions.js +48 -20
- package/dist/db/workflow-runs/job-executions.js.map +1 -1
- package/dist/db/workflow-runs/outbox.d.ts +15 -0
- package/dist/db/workflow-runs/outbox.d.ts.map +1 -1
- package/dist/db/workflow-runs/outbox.js +35 -1
- package/dist/db/workflow-runs/outbox.js.map +1 -1
- package/dist/db/workflow-runs/queries.d.ts +5 -3
- package/dist/db/workflow-runs/queries.d.ts.map +1 -1
- package/dist/db/workflow-runs/queries.js +20 -6
- package/dist/db/workflow-runs/queries.js.map +1 -1
- package/dist/db/workflow-runs/run-graph.d.ts.map +1 -1
- package/dist/db/workflow-runs/run-graph.js +12 -0
- package/dist/db/workflow-runs/run-graph.js.map +1 -1
- package/dist/db/workflow-runs/run-status.d.ts.map +1 -1
- package/dist/db/workflow-runs/run-status.js +13 -1
- package/dist/db/workflow-runs/run-status.js.map +1 -1
- package/dist/db/workflow-runs.d.ts +2 -2
- package/dist/db/workflow-runs.d.ts.map +1 -1
- package/dist/db/workflow-runs.js +2 -2
- package/dist/db/workflow-runs.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/presentation/dto/workflow-run.d.ts.map +1 -1
- package/dist/presentation/dto/workflow-run.js +4 -2
- package/dist/presentation/dto/workflow-run.js.map +1 -1
- package/dist/presentation/index.d.ts +1 -1
- package/dist/presentation/index.d.ts.map +1 -1
- package/dist/presentation/index.js +1 -1
- package/dist/presentation/index.js.map +1 -1
- package/dist/presentation/routes/get-run.d.ts.map +1 -1
- package/dist/presentation/routes/get-run.js +2 -1
- package/dist/presentation/routes/get-run.js.map +1 -1
- package/dist/presentation/subscribers/index.d.ts +0 -1
- package/dist/presentation/subscribers/index.d.ts.map +1 -1
- package/dist/presentation/subscribers/index.js +0 -1
- package/dist/presentation/subscribers/index.js.map +1 -1
- package/dist/temporal/activities/index.d.ts +2 -18
- package/dist/temporal/activities/index.d.ts.map +1 -1
- package/dist/temporal/activities/index.js +3 -5
- package/dist/temporal/activities/index.js.map +1 -1
- package/dist/temporal/activities/orchestration-activities.d.ts +5 -14
- package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -1
- package/dist/temporal/activities/orchestration-activities.js +13 -34
- package/dist/temporal/activities/orchestration-activities.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +20 -68
- package/dist/temporal/workflows/job-execution-orchestration.d.ts +0 -3
- package/dist/temporal/workflows/job-execution-orchestration.d.ts.map +1 -1
- package/dist/temporal/workflows/job-execution-orchestration.js +17 -41
- package/dist/temporal/workflows/job-execution-orchestration.js.map +1 -1
- package/dist/temporal/workflows/job-listener-orchestration.d.ts +0 -3
- package/dist/temporal/workflows/job-listener-orchestration.d.ts.map +1 -1
- package/dist/temporal/workflows/job-listener-orchestration.js +1 -9
- package/dist/temporal/workflows/job-listener-orchestration.js.map +1 -1
- package/dist/temporal/workflows/run-orchestration.d.ts.map +1 -1
- package/dist/temporal/workflows/run-orchestration.js +2 -18
- package/dist/temporal/workflows/run-orchestration.js.map +1 -1
- package/dist/temporal/workflows/test-env.d.ts +4 -6
- package/dist/temporal/workflows/test-env.d.ts.map +1 -1
- package/dist/temporal/workflows/test-env.js +13 -24
- package/dist/temporal/workflows/test-env.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/core/entities/workflow-run.ts +3 -0
- package/src/db/index.ts +1 -1
- package/src/db/job-listeners.test.ts +21 -0
- package/src/db/job-listeners.ts +18 -0
- package/src/db/workflow-runs/job-executions.test.ts +110 -12
- package/src/db/workflow-runs/job-executions.ts +54 -29
- package/src/db/workflow-runs/outbox.ts +63 -0
- package/src/db/workflow-runs/queries.test.ts +27 -0
- package/src/db/workflow-runs/queries.ts +31 -5
- package/src/db/workflow-runs/run-graph.ts +17 -0
- package/src/db/workflow-runs/run-status.test.ts +13 -0
- package/src/db/workflow-runs/run-status.ts +15 -1
- package/src/db/workflow-runs.ts +3 -1
- package/src/index.ts +1 -5
- package/src/presentation/dto/workflow-run.ts +7 -1
- package/src/presentation/index.ts +0 -1
- package/src/presentation/routes/get-run.test.ts +31 -0
- package/src/presentation/routes/get-run.ts +1 -0
- package/src/presentation/routes/list-runs.test.ts +5 -1
- package/src/presentation/subscribers/index.ts +0 -1
- package/src/temporal/activities/index.ts +2 -8
- package/src/temporal/activities/orchestration-activities.test.ts +17 -1
- package/src/temporal/activities/orchestration-activities.ts +13 -46
- package/src/temporal/workflows/job-execution-orchestration.test.ts +6 -25
- package/src/temporal/workflows/job-execution-orchestration.ts +17 -43
- package/src/temporal/workflows/job-listener-orchestration-continuation.test.ts +0 -3
- package/src/temporal/workflows/job-listener-orchestration.test.ts +9 -12
- package/src/temporal/workflows/job-listener-orchestration.ts +0 -8
- package/src/temporal/workflows/run-orchestration.test.ts +22 -33
- package/src/temporal/workflows/run-orchestration.ts +4 -28
- package/src/temporal/workflows/test-env.ts +14 -34
- package/test/fixtures/runners-inter-module.ts +0 -3
- package/test/helpers/workflow-runs.ts +25 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/presentation/subscribers/on-runner-job-queued.d.ts +0 -3
- package/dist/presentation/subscribers/on-runner-job-queued.d.ts.map +0 -1
- package/dist/presentation/subscribers/on-runner-job-queued.js +0 -20
- package/dist/presentation/subscribers/on-runner-job-queued.js.map +0 -1
- package/src/presentation/subscribers/on-runner-job-queued.test.ts +0 -57
- package/src/presentation/subscribers/on-runner-job-queued.ts +0 -23
|
@@ -197,7 +197,7 @@ describe('jobListenerOrchestration', () => {
|
|
|
197
197
|
|
|
198
198
|
expect(listenerFiringOutcomeCalls()).toHaveLength(0);
|
|
199
199
|
expect(resolveJobListenerCalls().map((c) => c.params.reason)).toEqual(['until']);
|
|
200
|
-
expect(callsNamed('
|
|
200
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(0);
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
test('resolves with timeout when the listening deadline elapses', async () => {
|
|
@@ -233,9 +233,10 @@ describe('jobListenerOrchestration', () => {
|
|
|
233
233
|
|
|
234
234
|
await runListener({maxExecutions: 1});
|
|
235
235
|
|
|
236
|
-
expect(callsNamed('
|
|
237
|
-
expect(callsNamed('
|
|
238
|
-
|
|
236
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(1);
|
|
237
|
+
expect(callsNamed('queueJobExecutionActivity')[0]?.params).toMatchObject({
|
|
238
|
+
jobId,
|
|
239
|
+
jobExecutionId: 'exec-1',
|
|
239
240
|
});
|
|
240
241
|
expect(listenerFiringOutcomeCalls().map((c) => c.params.outcome)).toEqual(['succeeded']);
|
|
241
242
|
});
|
|
@@ -250,7 +251,7 @@ describe('jobListenerOrchestration', () => {
|
|
|
250
251
|
await runListener({maxExecutions: 1});
|
|
251
252
|
|
|
252
253
|
// The child actually ran (enqueued), then returned failed.
|
|
253
|
-
expect(callsNamed('
|
|
254
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(1);
|
|
254
255
|
expect(listenerFiringOutcomeCalls().map((c) => c.params.outcome)).toEqual(['failed']);
|
|
255
256
|
expect(resolveJobListenerCalls().map((c) => c.params.reason)).toEqual(['max_executions']);
|
|
256
257
|
});
|
|
@@ -265,7 +266,7 @@ describe('jobListenerOrchestration', () => {
|
|
|
265
266
|
await runListener({maxExecutions: 2});
|
|
266
267
|
|
|
267
268
|
// Sequence 1 failed at materialization (no child enqueued); sequence 2 ran and succeeded.
|
|
268
|
-
expect(callsNamed('
|
|
269
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(1);
|
|
269
270
|
expect(listenerFiringOutcomeCalls().map((c) => c.params.outcome)).toEqual([
|
|
270
271
|
'failed',
|
|
271
272
|
'succeeded',
|
|
@@ -369,15 +370,12 @@ describe('jobListenerOrchestration', () => {
|
|
|
369
370
|
});
|
|
370
371
|
|
|
371
372
|
const handle = await startListener({onResolve: 'cancel'});
|
|
372
|
-
await waitForActivity('
|
|
373
|
+
await waitForActivity('queueJobExecutionActivity');
|
|
373
374
|
await handle.signal(LISTENER_RESOLVE_SIGNAL);
|
|
374
375
|
await handle.result();
|
|
375
376
|
|
|
376
377
|
expect(settleListenerCalls().map((c) => c.params.status)).toEqual(['cancelled']);
|
|
377
378
|
expect(listenerFiringOutcomeCalls().map((c) => c.params.outcome)).toEqual(['cancelled']);
|
|
378
|
-
expect(callsNamed('cancelRunnerJobsActivity')).toEqual([
|
|
379
|
-
{name: 'cancelRunnerJobsActivity', params: {jobIds: [jobId]}},
|
|
380
|
-
]);
|
|
381
379
|
expect(resolveJobListenerCalls().map((c) => c.params.reason)).toEqual(['until']);
|
|
382
380
|
});
|
|
383
381
|
|
|
@@ -390,7 +388,7 @@ describe('jobListenerOrchestration', () => {
|
|
|
390
388
|
});
|
|
391
389
|
|
|
392
390
|
const handle = await startListener({onResolve: 'finish'});
|
|
393
|
-
await waitForActivity('
|
|
391
|
+
await waitForActivity('queueJobExecutionActivity');
|
|
394
392
|
// Resolution arrives mid-firing; finish mode must wait for the child, not cancel it.
|
|
395
393
|
await handle.signal(LISTENER_RESOLVE_SIGNAL);
|
|
396
394
|
const child = testEnv.client.workflow.getHandle(`job:${jobId}`);
|
|
@@ -398,7 +396,6 @@ describe('jobListenerOrchestration', () => {
|
|
|
398
396
|
await handle.result();
|
|
399
397
|
|
|
400
398
|
expect(settleListenerCalls()).toHaveLength(0);
|
|
401
|
-
expect(callsNamed('cancelRunnerJobsActivity')).toHaveLength(0);
|
|
402
399
|
expect(listenerFiringOutcomeCalls().map((c) => c.params.outcome)).toEqual(['succeeded']);
|
|
403
400
|
expect(resolveJobListenerCalls().map((c) => c.params.reason)).toEqual(['until']);
|
|
404
401
|
});
|
|
@@ -25,7 +25,6 @@ const {
|
|
|
25
25
|
resolveJobListenerActivity,
|
|
26
26
|
settleListenerJobExecutionActivity,
|
|
27
27
|
recordListenerFiringOutcomeActivity,
|
|
28
|
-
cancelRunnerJobsActivity,
|
|
29
28
|
} = proxyActivities<ReturnType<typeof createOrchestrationActivities>>({
|
|
30
29
|
startToCloseTimeout: '30s',
|
|
31
30
|
});
|
|
@@ -34,9 +33,6 @@ export const listenerEventsAvailableSignal = defineSignal<[]>(LISTENER_EVENTS_AV
|
|
|
34
33
|
export const listenerResolveSignal = defineSignal<[]>(LISTENER_RESOLVE_SIGNAL);
|
|
35
34
|
|
|
36
35
|
export interface JobListenerOrchestrationInput {
|
|
37
|
-
workspaceId: string;
|
|
38
|
-
workflowRunId: string;
|
|
39
|
-
projectId: string;
|
|
40
36
|
runAttemptId: string;
|
|
41
37
|
jobId: string;
|
|
42
38
|
jobVersion: number;
|
|
@@ -413,9 +409,6 @@ async function runListenerExecution(params: {
|
|
|
413
409
|
workflowIdReusePolicy: 'ALLOW_DUPLICATE',
|
|
414
410
|
args: [
|
|
415
411
|
{
|
|
416
|
-
workspaceId: params.input.workspaceId,
|
|
417
|
-
workflowRunId: params.input.workflowRunId,
|
|
418
|
-
projectId: params.input.projectId,
|
|
419
412
|
jobId: params.input.jobId,
|
|
420
413
|
jobExecutionId: params.jobExecutionId,
|
|
421
414
|
runAttemptId: params.input.runAttemptId,
|
|
@@ -447,7 +440,6 @@ async function runListenerExecution(params: {
|
|
|
447
440
|
status: 'cancelled',
|
|
448
441
|
});
|
|
449
442
|
await recordListenerFiringOutcomeActivity({outcome: 'cancelled'});
|
|
450
|
-
await cancelRunnerJobsActivity({jobIds: [params.input.jobId]});
|
|
451
443
|
return;
|
|
452
444
|
}
|
|
453
445
|
}
|
|
@@ -59,17 +59,13 @@ describe('runOrchestration', () => {
|
|
|
59
59
|
|
|
60
60
|
const runAttemptStatuses = setRunAttemptStatusCalls().map((c) => c.params.status);
|
|
61
61
|
expect(runAttemptStatuses).toEqual(['running', 'succeeded']);
|
|
62
|
-
const enqueueCalls = callsNamed('
|
|
62
|
+
const enqueueCalls = callsNamed('queueJobExecutionActivity');
|
|
63
63
|
expect(enqueueCalls).toHaveLength(3);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
runAttemptId: 'r1-attempt-1',
|
|
70
|
-
requiredLabels: ['ubuntu22'],
|
|
71
|
-
});
|
|
72
|
-
}
|
|
64
|
+
expect(enqueueCalls.map((call) => call.params)).toEqual([
|
|
65
|
+
{jobId: 'j1', jobExecutionId: 'j1'},
|
|
66
|
+
{jobId: 'j2', jobExecutionId: 'j2'},
|
|
67
|
+
{jobId: 'j3', jobExecutionId: 'j3'},
|
|
68
|
+
]);
|
|
73
69
|
});
|
|
74
70
|
|
|
75
71
|
test('already-succeeded upstream jobs are not re-enqueued', async () => {
|
|
@@ -83,7 +79,7 @@ describe('runOrchestration', () => {
|
|
|
83
79
|
|
|
84
80
|
const runAttemptStatuses = setRunAttemptStatusCalls().map((c) => c.params.status);
|
|
85
81
|
expect(runAttemptStatuses).toEqual(['running', 'succeeded']);
|
|
86
|
-
const enqueueCalls = callsNamed('
|
|
82
|
+
const enqueueCalls = callsNamed('queueJobExecutionActivity');
|
|
87
83
|
expect(enqueueCalls).toHaveLength(1);
|
|
88
84
|
expect(enqueueCalls[0]?.params).toMatchObject({jobId: 'j2'});
|
|
89
85
|
});
|
|
@@ -99,7 +95,7 @@ describe('runOrchestration', () => {
|
|
|
99
95
|
|
|
100
96
|
const runAttemptStatuses = setRunAttemptStatusCalls().map((c) => c.params.status);
|
|
101
97
|
expect(runAttemptStatuses).toEqual(['running', 'failed']);
|
|
102
|
-
const enqueueCalls = callsNamed('
|
|
98
|
+
const enqueueCalls = callsNamed('queueJobExecutionActivity');
|
|
103
99
|
expect(enqueueCalls).toHaveLength(1);
|
|
104
100
|
expect(enqueueCalls[0]?.params).toMatchObject({jobId: 'j2'});
|
|
105
101
|
});
|
|
@@ -116,7 +112,7 @@ describe('runOrchestration', () => {
|
|
|
116
112
|
|
|
117
113
|
const runAttemptStatuses = setRunAttemptStatusCalls().map((c) => c.params.status);
|
|
118
114
|
expect(runAttemptStatuses).toEqual(['running', 'succeeded']);
|
|
119
|
-
expect(callsNamed('
|
|
115
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(3);
|
|
120
116
|
});
|
|
121
117
|
|
|
122
118
|
test('single job fails, dependents are skipped', async () => {
|
|
@@ -132,7 +128,7 @@ describe('runOrchestration', () => {
|
|
|
132
128
|
const runAttemptStatuses = setRunAttemptStatusCalls().map((c) => c.params.status);
|
|
133
129
|
expect(runAttemptStatuses).toEqual(['running', 'failed']);
|
|
134
130
|
|
|
135
|
-
expect(callsNamed('
|
|
131
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(1);
|
|
136
132
|
const jobStatuses = setJobStatusCalls().map((c) => ({
|
|
137
133
|
id: c.params.jobId,
|
|
138
134
|
status: c.params.status,
|
|
@@ -162,7 +158,7 @@ describe('runOrchestration', () => {
|
|
|
162
158
|
|
|
163
159
|
const runAttemptStatuses = setRunAttemptStatusCalls().map((c) => c.params.status);
|
|
164
160
|
expect(runAttemptStatuses).toEqual(['running', 'failed']);
|
|
165
|
-
expect(callsNamed('
|
|
161
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(1);
|
|
166
162
|
|
|
167
163
|
const jobStatuses = setJobStatusCalls().map((c) => ({
|
|
168
164
|
id: c.params.jobId,
|
|
@@ -199,7 +195,7 @@ describe('runOrchestration', () => {
|
|
|
199
195
|
},
|
|
200
196
|
},
|
|
201
197
|
]);
|
|
202
|
-
expect(callsNamed('
|
|
198
|
+
expect(callsNamed('queueJobExecutionActivity').map((call) => call.params)).toEqual([
|
|
203
199
|
expect.objectContaining({jobId: 'j1'}),
|
|
204
200
|
expect.objectContaining({jobId: 'j2'}),
|
|
205
201
|
]);
|
|
@@ -221,7 +217,7 @@ describe('runOrchestration', () => {
|
|
|
221
217
|
|
|
222
218
|
await executeRun();
|
|
223
219
|
|
|
224
|
-
expect(callsNamed('
|
|
220
|
+
expect(callsNamed('queueJobExecutionActivity').map((call) => call.params)).toEqual([
|
|
225
221
|
expect.objectContaining({jobId: 'j1'}),
|
|
226
222
|
]);
|
|
227
223
|
expect(setRunAttemptStatusCalls().map((c) => c.params.status)).toEqual([
|
|
@@ -249,7 +245,7 @@ describe('runOrchestration', () => {
|
|
|
249
245
|
},
|
|
250
246
|
},
|
|
251
247
|
]);
|
|
252
|
-
const enqueuedJobs = callsNamed('
|
|
248
|
+
const enqueuedJobs = callsNamed('queueJobExecutionActivity').map((call) => call.params);
|
|
253
249
|
expect(enqueuedJobs).toHaveLength(3);
|
|
254
250
|
expect(enqueuedJobs).toEqual(
|
|
255
251
|
expect.arrayContaining([
|
|
@@ -273,7 +269,7 @@ describe('runOrchestration', () => {
|
|
|
273
269
|
|
|
274
270
|
await executeRun();
|
|
275
271
|
|
|
276
|
-
expect(callsNamed('
|
|
272
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(0);
|
|
277
273
|
expect(setRunAttemptStatusCalls().map((c) => c.params.status)).toEqual([
|
|
278
274
|
'running',
|
|
279
275
|
'succeeded',
|
|
@@ -310,7 +306,7 @@ describe('runOrchestration', () => {
|
|
|
310
306
|
expect(finalRunAttemptStatus?.params.version).toBeGreaterThan(0);
|
|
311
307
|
});
|
|
312
308
|
|
|
313
|
-
test('cancel signal stops scheduling
|
|
309
|
+
test('cancel signal stops scheduling without calling runners directly', async () => {
|
|
314
310
|
const jobs = [dagJob('j1', 'build'), dagJob('j2', 'deploy', ['build'])];
|
|
315
311
|
setCfg({dag: makeDag(jobs, 'r-cancel'), jobResults: new Map(), skipSignal: true});
|
|
316
312
|
|
|
@@ -319,16 +315,13 @@ describe('runOrchestration', () => {
|
|
|
319
315
|
workflowId: `run:${workflowRunId}`,
|
|
320
316
|
args: [{workflowRunId, workspaceId}],
|
|
321
317
|
});
|
|
322
|
-
await waitForActivity('
|
|
318
|
+
await waitForActivity('queueJobExecutionActivity');
|
|
323
319
|
|
|
324
320
|
await handle.signal('run-cancel');
|
|
325
321
|
await handle.result();
|
|
326
322
|
|
|
327
323
|
expect(setRunAttemptStatusCalls().map((c) => c.params.status)).toEqual(['running']);
|
|
328
|
-
expect(callsNamed('
|
|
329
|
-
expect(callsNamed('cancelRunnerJobsActivity')).toEqual([
|
|
330
|
-
{name: 'cancelRunnerJobsActivity', params: {jobIds: ['j1', 'j2']}},
|
|
331
|
-
]);
|
|
324
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(1);
|
|
332
325
|
});
|
|
333
326
|
|
|
334
327
|
test('starts listening jobs and cancels them with the run', async () => {
|
|
@@ -347,10 +340,7 @@ describe('runOrchestration', () => {
|
|
|
347
340
|
await handle.result();
|
|
348
341
|
|
|
349
342
|
expect(setRunAttemptStatusCalls().map((c) => c.params.status)).toEqual(['running']);
|
|
350
|
-
expect(callsNamed('
|
|
351
|
-
expect(callsNamed('cancelRunnerJobsActivity')).toEqual([
|
|
352
|
-
{name: 'cancelRunnerJobsActivity', params: {jobIds: ['j1']}},
|
|
353
|
-
]);
|
|
343
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(0);
|
|
354
344
|
});
|
|
355
345
|
|
|
356
346
|
test('aborts early when the initial running write reports an already-terminal run', async () => {
|
|
@@ -364,8 +354,7 @@ describe('runOrchestration', () => {
|
|
|
364
354
|
await executeRun();
|
|
365
355
|
|
|
366
356
|
expect(setRunAttemptStatusCalls().map((c) => c.params.status)).toEqual(['running']);
|
|
367
|
-
expect(callsNamed('
|
|
368
|
-
expect(callsNamed('cancelRunnerJobsActivity')).toHaveLength(0);
|
|
357
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(0);
|
|
369
358
|
});
|
|
370
359
|
|
|
371
360
|
test('diamond DAG with partial failure', async () => {
|
|
@@ -383,7 +372,7 @@ describe('runOrchestration', () => {
|
|
|
383
372
|
expect(runAttemptStatuses).toEqual(['running', 'failed']);
|
|
384
373
|
|
|
385
374
|
// A, B, C enqueued: D skipped because B failed
|
|
386
|
-
expect(callsNamed('
|
|
375
|
+
expect(callsNamed('queueJobExecutionActivity')).toHaveLength(3);
|
|
387
376
|
const jobStatuses = setJobStatusCalls().map((c) => ({
|
|
388
377
|
id: c.params.jobId,
|
|
389
378
|
status: c.params.status,
|
|
@@ -401,7 +390,7 @@ describe('runOrchestration', () => {
|
|
|
401
390
|
setCfg({
|
|
402
391
|
dag: makeDag(jobs, 'r7'),
|
|
403
392
|
jobResults: new Map(),
|
|
404
|
-
|
|
393
|
+
queueError: 'Runner service unavailable',
|
|
405
394
|
});
|
|
406
395
|
|
|
407
396
|
await expect(executeRun()).rejects.toThrow();
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
} from '@temporalio/workflow';
|
|
10
10
|
import {
|
|
11
11
|
createRuntimeRunProgress,
|
|
12
|
-
nonCompletedRuntimeJobIds,
|
|
13
12
|
type RuntimeRunProgress,
|
|
14
13
|
recordRuntimeJobResult,
|
|
15
14
|
recordSkippedRuntimeJob,
|
|
@@ -26,15 +25,10 @@ import {deadlineReached, remainingMs} from './deadline.js';
|
|
|
26
25
|
import {jobExecutionOrchestration} from './job-execution-orchestration.js';
|
|
27
26
|
import {jobListenerOrchestration} from './job-listener-orchestration.js';
|
|
28
27
|
|
|
29
|
-
const {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
setJobStatus,
|
|
34
|
-
cancelRunnerJobsActivity,
|
|
35
|
-
} = proxyActivities<ReturnType<typeof createOrchestrationActivities>>({
|
|
36
|
-
startToCloseTimeout: '30s',
|
|
37
|
-
});
|
|
28
|
+
const {loadRunAttemptDag, evaluateJobActivationsActivity, setRunAttemptStatus, setJobStatus} =
|
|
29
|
+
proxyActivities<ReturnType<typeof createOrchestrationActivities>>({
|
|
30
|
+
startToCloseTimeout: '30s',
|
|
31
|
+
});
|
|
38
32
|
|
|
39
33
|
const {failRunAsTimedOutActivity} = proxyActivities<
|
|
40
34
|
ReturnType<typeof createOrchestrationActivities>
|
|
@@ -74,12 +68,10 @@ export async function runOrchestration(input: RunOrchestrationInput): Promise<vo
|
|
|
74
68
|
|
|
75
69
|
while (true) {
|
|
76
70
|
if (cancelRequested) {
|
|
77
|
-
await cancelNonCompletedRunnerJobs(dag, progress);
|
|
78
71
|
return;
|
|
79
72
|
}
|
|
80
73
|
if (deadlineReached(runDeadline)) {
|
|
81
74
|
await failRunAsTimedOutActivity({runAttemptId: input.runAttemptId});
|
|
82
|
-
await cancelNonCompletedRunnerJobs(dag, progress);
|
|
83
75
|
return;
|
|
84
76
|
}
|
|
85
77
|
|
|
@@ -144,12 +136,10 @@ export async function runOrchestration(input: RunOrchestrationInput): Promise<vo
|
|
|
144
136
|
|
|
145
137
|
const settled = await waitForNextSettlement(inFlight, () => cancelRequested, runDeadline);
|
|
146
138
|
if (settled.kind === 'cancelled') {
|
|
147
|
-
await cancelNonCompletedRunnerJobs(dag, progress);
|
|
148
139
|
return;
|
|
149
140
|
}
|
|
150
141
|
if (settled.kind === 'timed-out') {
|
|
151
142
|
await failRunAsTimedOutActivity({runAttemptId: input.runAttemptId});
|
|
152
|
-
await cancelNonCompletedRunnerJobs(dag, progress);
|
|
153
143
|
return;
|
|
154
144
|
}
|
|
155
145
|
inFlight.delete(settled.job.key);
|
|
@@ -187,9 +177,6 @@ function launchJob(
|
|
|
187
177
|
workflowId: `job-listener:${job.id}`,
|
|
188
178
|
args: [
|
|
189
179
|
{
|
|
190
|
-
workspaceId: run.workspaceId,
|
|
191
|
-
workflowRunId: run.workflowRunId,
|
|
192
|
-
projectId: run.projectId,
|
|
193
180
|
jobId: job.id,
|
|
194
181
|
runAttemptId: run.runAttemptId,
|
|
195
182
|
jobVersion: runtimeJobVersion(job, progress),
|
|
@@ -235,9 +222,6 @@ function launchJob(
|
|
|
235
222
|
workflowId: `job:${job.id}`,
|
|
236
223
|
args: [
|
|
237
224
|
{
|
|
238
|
-
workspaceId: run.workspaceId,
|
|
239
|
-
workflowRunId: run.workflowRunId,
|
|
240
|
-
projectId: run.projectId,
|
|
241
225
|
jobId: job.id,
|
|
242
226
|
jobExecutionId: job.jobExecutionId,
|
|
243
227
|
runAttemptId: run.runAttemptId,
|
|
@@ -273,11 +257,3 @@ async function waitForNextSettlement(
|
|
|
273
257
|
);
|
|
274
258
|
return await Promise.race([childSettled, cancel]);
|
|
275
259
|
}
|
|
276
|
-
|
|
277
|
-
async function cancelNonCompletedRunnerJobs(
|
|
278
|
-
dag: RunDag,
|
|
279
|
-
progress: RuntimeRunProgress,
|
|
280
|
-
): Promise<void> {
|
|
281
|
-
const jobIds = nonCompletedRuntimeJobIds(dag.jobs, progress);
|
|
282
|
-
await cancelRunnerJobsActivity({jobIds});
|
|
283
|
-
}
|
|
@@ -25,11 +25,11 @@ export interface TestConfig {
|
|
|
25
25
|
dag: RunDag;
|
|
26
26
|
/** Map of jobId → status the mock runner should signal back */
|
|
27
27
|
jobResults: Map<string, RuntimeCompletionStatus>;
|
|
28
|
-
/** If set,
|
|
29
|
-
|
|
30
|
-
/** If true,
|
|
28
|
+
/** If set, queueJobExecutionActivity will throw with this message instead of signaling */
|
|
29
|
+
queueError?: string;
|
|
30
|
+
/** If true, queueJobExecutionActivity sends two job-finished signals (for dedup testing) */
|
|
31
31
|
duplicateSignal?: boolean;
|
|
32
|
-
/** If true,
|
|
32
|
+
/** If true, queueJobExecutionActivity does nothing without a signal for timeout testing */
|
|
33
33
|
skipSignal?: boolean;
|
|
34
34
|
/** If true, emit the claim but hold back the terminal outcome (for deadline testing) */
|
|
35
35
|
skipOutcomeSignal?: boolean;
|
|
@@ -43,8 +43,6 @@ export interface TestConfig {
|
|
|
43
43
|
leaseExpiredStatus?: RuntimeCompletionStatus;
|
|
44
44
|
/** If set, resolveJobStatusFromJobExecutionsActivity throws with this message */
|
|
45
45
|
resolveJobStatusError?: string;
|
|
46
|
-
/** If set, releaseLeaseActivity throws (non-retryable) to prove the workflow still returns */
|
|
47
|
-
releaseLeaseError?: string;
|
|
48
46
|
/** Scripted job activation decisions keyed by job id; defaults to start */
|
|
49
47
|
activationDecisions?: Map<string, JobActivationDecision>;
|
|
50
48
|
/** If set, failJobExecutionAsTimedOutActivity throws (for timeout error-path testing) */
|
|
@@ -289,30 +287,19 @@ function createMockActivities() {
|
|
|
289
287
|
calls.push({name: 'bulkSetStepStatuses', params});
|
|
290
288
|
},
|
|
291
289
|
|
|
292
|
-
cancelRunnerJobsActivity: (params: {jobIds: string[]}) => {
|
|
293
|
-
calls.push({name: 'cancelRunnerJobsActivity', params});
|
|
294
|
-
},
|
|
295
|
-
|
|
296
290
|
// Scheduling is step-less. The mock runner reports the job outcome by signalling
|
|
297
291
|
// the per-step terminal-completion signal (job-finished) and/or the lease-expiry
|
|
298
292
|
// signal, reproducing the outbox → subscriber → signal rail.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
workflowRunId: string;
|
|
302
|
-
runAttemptId: string;
|
|
303
|
-
jobId: string;
|
|
304
|
-
jobExecutionId: string;
|
|
305
|
-
projectId: string;
|
|
306
|
-
requiredLabels: string[];
|
|
307
|
-
}) => {
|
|
308
|
-
calls.push({name: 'enqueueJobExecutionForRunner', params});
|
|
293
|
+
queueJobExecutionActivity: async (params: {jobId: string; jobExecutionId: string}) => {
|
|
294
|
+
calls.push({name: 'queueJobExecutionActivity', params});
|
|
309
295
|
|
|
310
|
-
if (cfg.
|
|
296
|
+
if (cfg.queueError) {
|
|
311
297
|
const {ApplicationFailure} = await import('@temporalio/common');
|
|
312
|
-
throw ApplicationFailure.nonRetryable(cfg.
|
|
298
|
+
throw ApplicationFailure.nonRetryable(cfg.queueError);
|
|
313
299
|
}
|
|
314
300
|
|
|
315
|
-
|
|
301
|
+
const queued = {newVersion: nextVersion(), status: 'pending' as const};
|
|
302
|
+
if (cfg.skipSignal) return queued;
|
|
316
303
|
|
|
317
304
|
const status = cfg.jobResults.get(params.jobId) ?? 'succeeded';
|
|
318
305
|
const handle = testEnv.client.workflow.getHandle(`job:${params.jobId}`);
|
|
@@ -321,17 +308,17 @@ function createMockActivities() {
|
|
|
321
308
|
claimedAt: cfg.claimedAt ?? '2026-06-22T10:05:00.000Z',
|
|
322
309
|
});
|
|
323
310
|
|
|
324
|
-
if (cfg.skipOutcomeSignal) return;
|
|
311
|
+
if (cfg.skipOutcomeSignal) return queued;
|
|
325
312
|
|
|
326
313
|
if (cfg.signalLeaseExpired) {
|
|
327
314
|
await handle.signal(JOB_LEASE_EXPIRED_SIGNAL, {jobExecutionId: params.jobExecutionId});
|
|
328
|
-
return;
|
|
315
|
+
return queued;
|
|
329
316
|
}
|
|
330
317
|
|
|
331
318
|
if (cfg.signalBoth) {
|
|
332
319
|
await handle.signal(JOB_FINISHED_SIGNAL, {status, jobExecutionId: params.jobExecutionId});
|
|
333
320
|
await handle.signal(JOB_LEASE_EXPIRED_SIGNAL, {jobExecutionId: params.jobExecutionId});
|
|
334
|
-
return;
|
|
321
|
+
return queued;
|
|
335
322
|
}
|
|
336
323
|
|
|
337
324
|
// The terminal outbox is independent from the claim, but the mock delivers it from the
|
|
@@ -342,6 +329,7 @@ function createMockActivities() {
|
|
|
342
329
|
status,
|
|
343
330
|
duplicateSignal: cfg.duplicateSignal ?? false,
|
|
344
331
|
});
|
|
332
|
+
return queued;
|
|
345
333
|
},
|
|
346
334
|
|
|
347
335
|
resolveLeaseExpiredJobExecutionActivity: (params: {
|
|
@@ -369,14 +357,6 @@ function createMockActivities() {
|
|
|
369
357
|
};
|
|
370
358
|
},
|
|
371
359
|
|
|
372
|
-
releaseLeaseActivity: async (params: {jobExecutionId: string}) => {
|
|
373
|
-
calls.push({name: 'releaseLeaseActivity', params});
|
|
374
|
-
if (cfg.releaseLeaseError) {
|
|
375
|
-
const {ApplicationFailure} = await import('@temporalio/common');
|
|
376
|
-
throw ApplicationFailure.nonRetryable(cfg.releaseLeaseError);
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
|
|
380
360
|
failJobExecutionAsTimedOutActivity: async (params: {
|
|
381
361
|
jobExecutionId: string;
|
|
382
362
|
runAttemptId: string;
|
|
@@ -35,9 +35,6 @@ export function resetRunnersTestClient(): void {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export const runnersTestClient: RunnersInterModuleClient = {
|
|
38
|
-
enqueueJobExecution: async () => ({}),
|
|
39
|
-
releaseJobExecution: async () => ({}),
|
|
40
|
-
cancelJobs: async () => ({}),
|
|
41
38
|
getLeaseState: async (params) => ({active: activeLeases.has(leaseKey(params))}),
|
|
42
39
|
getEffectiveRunnerToolCapabilities: async ({runnerSessionId}) =>
|
|
43
40
|
toolCapabilities.get(runnerSessionId) ?? {capabilities: {harnesses: {}}, reportFresh: false},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
WORKFLOWS_JOB_EXECUTION_TERMINATED,
|
|
2
3
|
WORKFLOWS_JOB_TERMINATED,
|
|
3
4
|
WORKFLOWS_STEP_ATTEMPT_TERMINATED,
|
|
4
5
|
WORKFLOWS_WORKFLOW_RUN_ATTEMPT_CREATED,
|
|
@@ -137,6 +138,30 @@ export async function jobTerminatedEvents(jobId: string) {
|
|
|
137
138
|
);
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
export async function jobExecutionTerminatedEvents(jobExecutionId: string) {
|
|
142
|
+
const rows = await db()
|
|
143
|
+
.select({payload: workflowsOutbox.payload})
|
|
144
|
+
.from(workflowsOutbox)
|
|
145
|
+
.where(
|
|
146
|
+
and(
|
|
147
|
+
eq(workflowsOutbox.eventType, WORKFLOWS_JOB_EXECUTION_TERMINATED),
|
|
148
|
+
sql`${workflowsOutbox.payload}->>'jobExecutionId' = ${jobExecutionId}`,
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
return rows.map(
|
|
152
|
+
(row) =>
|
|
153
|
+
row.payload as {
|
|
154
|
+
jobId: string;
|
|
155
|
+
jobExecutionId: string;
|
|
156
|
+
workflowRunId: string;
|
|
157
|
+
workflowRunAttemptId: string;
|
|
158
|
+
status: string;
|
|
159
|
+
statusReason: string | null;
|
|
160
|
+
statusReasonMessage?: string | null;
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
140
165
|
export async function runTerminatedEvents(workflowRunId: string) {
|
|
141
166
|
const rows = await db()
|
|
142
167
|
.select({payload: workflowsOutbox.payload})
|