@shipfox/api-workflows 12.7.0 → 13.1.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 +24 -0
- package/dist/core/errors.d.ts +9 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +5 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/job-execution.d.ts.map +1 -1
- package/dist/core/job-execution.js +12 -2
- package/dist/core/job-execution.js.map +1 -1
- package/dist/core/step-config/agent.d.ts.map +1 -1
- package/dist/core/step-config/agent.js +9 -1
- package/dist/core/step-config/agent.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/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/step.d.ts.map +1 -1
- package/dist/presentation/dto/step.js +14 -0
- package/dist/presentation/dto/step.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/agent-runtime-config.d.ts.map +1 -1
- package/dist/presentation/routes/agent-runtime-config.js +22 -0
- package/dist/presentation/routes/agent-runtime-config.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 +4 -4
- package/src/core/errors.ts +17 -2
- package/src/core/job-execution.test.ts +54 -0
- package/src/core/job-execution.ts +6 -1
- package/src/core/step-config/agent.ts +8 -1
- package/src/core/step-config/complete-step-dispatch-config.test.ts +37 -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/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/step.test.ts +8 -0
- package/src/presentation/dto/step.ts +9 -0
- package/src/presentation/index.ts +0 -1
- package/src/presentation/routes/agent-runtime-config.test.ts +84 -2
- package/src/presentation/routes/agent-runtime-config.ts +24 -0
- 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
|
@@ -26,14 +26,14 @@ import {remainingMs} from './deadline.js';
|
|
|
26
26
|
* execution pending while it is queued, then performs the versioned pending → running
|
|
27
27
|
* transition after the current claim. One deadline spans both waits; claim never resets
|
|
28
28
|
* it. Signals can arrive in any order, so the precedence is finished > lease expired >
|
|
29
|
-
* claimed > timeout.
|
|
30
|
-
*
|
|
29
|
+
* claimed > timeout. Workflows commits queue and terminal facts with its state transitions;
|
|
30
|
+
* runners reconciles its local queue, lease, and reservation projections asynchronously.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
const {
|
|
34
34
|
setJobStatus,
|
|
35
35
|
setJobExecutionStatus,
|
|
36
|
-
|
|
36
|
+
queueJobExecutionActivity,
|
|
37
37
|
bulkSetStepStatuses,
|
|
38
38
|
failJobExecutionAsTimedOutActivity,
|
|
39
39
|
resolveLeaseExpiredJobExecutionActivity,
|
|
@@ -48,14 +48,6 @@ const {resolveJobStatusFromJobExecutionsActivity} = proxyActivities<
|
|
|
48
48
|
retry: {maximumAttempts: 5},
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
// Lease cleanup gets a bounded retry policy of its own: after a few attempts the
|
|
52
|
-
// workflow stops waiting on it (see releaseLeaseBestEffort) rather than blocking
|
|
53
|
-
// the DAG on runners-side availability.
|
|
54
|
-
const {releaseLeaseActivity} = proxyActivities<ReturnType<typeof createOrchestrationActivities>>({
|
|
55
|
-
startToCloseTimeout: '30s',
|
|
56
|
-
retry: {maximumAttempts: 5},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
51
|
const DEFAULT_EXECUTION_MAX_DURATION_MS = 6 * 60 * 60 * 1000;
|
|
60
52
|
|
|
61
53
|
export const jobFinishedSignal =
|
|
@@ -71,12 +63,9 @@ export interface JobClaimedSignalPayload {
|
|
|
71
63
|
export const jobClaimedSignal = defineSignal<[JobClaimedSignalPayload]>(JOB_CLAIMED_SIGNAL);
|
|
72
64
|
|
|
73
65
|
export interface JobExecutionOrchestrationInput {
|
|
74
|
-
workspaceId: string;
|
|
75
|
-
workflowRunId: string;
|
|
76
66
|
runAttemptId: string;
|
|
77
67
|
jobId: string;
|
|
78
68
|
jobExecutionId: string;
|
|
79
|
-
projectId: string;
|
|
80
69
|
jobVersion: number;
|
|
81
70
|
executionVersion: number;
|
|
82
71
|
executionTimeoutMs?: number | null | undefined;
|
|
@@ -89,17 +78,6 @@ export interface JobExecutionOrchestrationResult {
|
|
|
89
78
|
jobVersion: number;
|
|
90
79
|
}
|
|
91
80
|
|
|
92
|
-
async function releaseLeaseBestEffort(jobExecutionId: string): Promise<void> {
|
|
93
|
-
try {
|
|
94
|
-
await releaseLeaseActivity({jobExecutionId});
|
|
95
|
-
} catch (err) {
|
|
96
|
-
log.warn('lease release failed; stuck detector will reap the row', {
|
|
97
|
-
jobExecutionId,
|
|
98
|
-
error: String(err),
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
81
|
async function resolveJobStatusOrFailClosed(
|
|
104
82
|
input: JobExecutionOrchestrationInput,
|
|
105
83
|
): Promise<{status: RuntimeCompletionStatus; jobVersion: number}> {
|
|
@@ -121,7 +99,7 @@ async function resolveJobStatusOrFailClosed(
|
|
|
121
99
|
}
|
|
122
100
|
}
|
|
123
101
|
|
|
124
|
-
async function
|
|
102
|
+
async function queueJobExecution(input: JobExecutionOrchestrationInput) {
|
|
125
103
|
if (hasNoRequiredRunnerLabels(input.requiredLabels)) {
|
|
126
104
|
throw ApplicationFailure.nonRetryable(
|
|
127
105
|
`Job ${input.jobId} has no required runner labels`,
|
|
@@ -129,15 +107,12 @@ async function enqueueJobExecution(input: JobExecutionOrchestrationInput): Promi
|
|
|
129
107
|
);
|
|
130
108
|
}
|
|
131
109
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
projectId: input.projectId,
|
|
139
|
-
requiredLabels: input.requiredLabels,
|
|
140
|
-
});
|
|
110
|
+
return jobExecutionStartOutcome(
|
|
111
|
+
await queueJobExecutionActivity({
|
|
112
|
+
jobId: input.jobId,
|
|
113
|
+
jobExecutionId: input.jobExecutionId,
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
141
116
|
}
|
|
142
117
|
|
|
143
118
|
async function markJobExecutionRunning(
|
|
@@ -218,7 +193,6 @@ async function resolveFinishedJobExecution({
|
|
|
218
193
|
terminationReason: 'finished',
|
|
219
194
|
status,
|
|
220
195
|
});
|
|
221
|
-
await releaseLeaseBestEffort(input.jobExecutionId);
|
|
222
196
|
return {status, jobVersion: input.jobVersion};
|
|
223
197
|
}
|
|
224
198
|
const resolved = await resolveJobStatusOrFailClosed(input);
|
|
@@ -228,7 +202,6 @@ async function resolveFinishedJobExecution({
|
|
|
228
202
|
terminationReason: 'finished',
|
|
229
203
|
status: resolved.status,
|
|
230
204
|
});
|
|
231
|
-
await releaseLeaseBestEffort(input.jobExecutionId);
|
|
232
205
|
return resolved;
|
|
233
206
|
}
|
|
234
207
|
|
|
@@ -257,7 +230,6 @@ async function resolveLeaseExpiredJobExecution({
|
|
|
257
230
|
terminationReason: 'lease_expired',
|
|
258
231
|
status: leaseExpired.status,
|
|
259
232
|
});
|
|
260
|
-
await releaseLeaseBestEffort(input.jobExecutionId);
|
|
261
233
|
return {status: leaseExpired.status, jobVersion: input.jobVersion};
|
|
262
234
|
}
|
|
263
235
|
const {status, jobVersion} = await resolveJobStatusOrFailClosed(input);
|
|
@@ -267,13 +239,12 @@ async function resolveLeaseExpiredJobExecution({
|
|
|
267
239
|
terminationReason: 'lease_expired',
|
|
268
240
|
status,
|
|
269
241
|
});
|
|
270
|
-
await releaseLeaseBestEffort(input.jobExecutionId);
|
|
271
242
|
return {status, jobVersion};
|
|
272
243
|
}
|
|
273
244
|
|
|
274
|
-
// Timeout backstop. The activity atomically fails the execution, marks
|
|
275
|
-
//
|
|
276
|
-
//
|
|
245
|
+
// Timeout backstop. The activity atomically fails the execution, marks `timed_out_at`, and
|
|
246
|
+
// enqueues the generic terminal execution fact. The runners subscriber then asks the runner to
|
|
247
|
+
// cancel. The lease is intentionally NOT released here.
|
|
277
248
|
async function resolveTimedOutJobExecution({
|
|
278
249
|
input,
|
|
279
250
|
runningVersion,
|
|
@@ -314,7 +285,10 @@ export async function jobExecutionOrchestration(
|
|
|
314
285
|
// Register every signal before enqueue can block or publish a claim/outcome event. The
|
|
315
286
|
// handlers retain signals that arrive while the enqueue activity is in flight.
|
|
316
287
|
registerJobExecutionSignalHandlers(input.jobExecutionId, signals);
|
|
317
|
-
await
|
|
288
|
+
const queued = await queueJobExecution(input);
|
|
289
|
+
if (queued.kind === 'terminal') {
|
|
290
|
+
return {status: queued.result.status, jobVersion: input.jobVersion};
|
|
291
|
+
}
|
|
318
292
|
|
|
319
293
|
const timeoutMs = input.executionTimeoutMs ?? DEFAULT_EXECUTION_MAX_DURATION_MS;
|
|
320
294
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -63,9 +63,6 @@ describe('listener continue-as-new guard', () => {
|
|
|
63
63
|
|
|
64
64
|
it('carries listener loop state into the continued run', () => {
|
|
65
65
|
const input: JobListenerOrchestrationInput = {
|
|
66
|
-
workspaceId: 'workspace-1',
|
|
67
|
-
workflowRunId: 'run-1',
|
|
68
|
-
projectId: 'project-1',
|
|
69
66
|
runAttemptId: 'attempt-1',
|
|
70
67
|
jobId: 'job-1',
|
|
71
68
|
jobVersion: 3,
|
|
@@ -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})
|