@pikku/core 0.12.150 → 0.12.151
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/dist/function/function-runner.js +23 -0
- package/dist/function/trace-queue-service.d.ts +26 -0
- package/dist/function/trace-queue-service.js +48 -0
- package/dist/mesh.d.ts +2 -2
- package/dist/mesh.js +3 -3
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/mesh-auth.d.ts +3 -2
- package/dist/middleware/mesh-auth.js +7 -2
- package/dist/middleware/remote-jobs-secret.d.ts +1 -1
- package/dist/middleware/require-origin.d.ts +19 -0
- package/dist/middleware/require-origin.js +36 -2
- package/dist/services/deployment-service.d.ts +1 -1
- package/dist/services/incoming-webhook-service.d.ts +4 -2
- package/dist/services/incoming-webhook-service.js +4 -3
- package/dist/services/queue-webhook-service.js +7 -1
- package/dist/services/remote-jobs.d.ts +6 -1
- package/dist/services/scheduler-service.d.ts +3 -1
- package/dist/services/webhook-service.d.ts +2 -0
- package/dist/services/workflow-service.d.ts +4 -1
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/queue-identity-transport-tests.d.ts +18 -0
- package/dist/testing/queue-identity-transport-tests.js +162 -0
- package/dist/testing/queue-trace-conformance.d.ts +26 -0
- package/dist/testing/queue-trace-conformance.js +259 -0
- package/dist/testing/service-tests/channel-store-tests.js +11 -0
- package/dist/testing/service-tests/queued-workflow-harness.d.ts +18 -1
- package/dist/testing/service-tests/queued-workflow-harness.js +19 -3
- package/dist/testing/service-tests/workflow-service-tests.js +12 -0
- package/dist/testing/trace-matrix/assertions.d.ts +3 -0
- package/dist/testing/trace-matrix/assertions.js +190 -0
- package/dist/testing/trace-matrix/chain-harness.d.ts +119 -0
- package/dist/testing/trace-matrix/chain-harness.js +777 -0
- package/dist/testing/trace-matrix/dimensions.d.ts +55 -0
- package/dist/testing/trace-matrix/dimensions.js +305 -0
- package/dist/testing/trace-matrix/pairwise.d.ts +20 -0
- package/dist/testing/trace-matrix/pairwise.js +75 -0
- package/dist/testing/trace-matrix/report.d.ts +1 -0
- package/dist/testing/trace-matrix/report.js +16 -0
- package/dist/testing/trace-matrix/transports.d.ts +19 -0
- package/dist/testing/trace-matrix/transports.js +27 -0
- package/dist/testing/trace-matrix/workflow-cases.d.ts +24 -0
- package/dist/testing/trace-matrix/workflow-cases.js +147 -0
- package/dist/trace.d.ts +66 -0
- package/dist/trace.js +97 -0
- package/dist/types/core.types.d.ts +6 -2
- package/dist/wirings/addon/addon-reach.d.ts +3 -0
- package/dist/wirings/addon/addon-reach.js +9 -0
- package/dist/wirings/addon/define-plugin.d.ts +6 -0
- package/dist/wirings/addon/wire-addon.js +5 -0
- package/dist/wirings/addon/wire-remote-addon.d.ts +11 -0
- package/dist/wirings/agent/agent-finalize.d.ts +2 -0
- package/dist/wirings/agent/agent-finalize.js +1 -1
- package/dist/wirings/agent/agent-prepare.d.ts +3 -0
- package/dist/wirings/agent/agent-prepare.js +24 -8
- package/dist/wirings/agent/agent-rpc.js +1 -0
- package/dist/wirings/agent/agent-runner.js +3 -0
- package/dist/wirings/agent/agent-stream.js +4 -0
- package/dist/wirings/agent/voice-input.d.ts +1 -1
- package/dist/wirings/agent/voice-output.d.ts +1 -1
- package/dist/wirings/agent-scorer/agent-scorer-live.d.ts +4 -2
- package/dist/wirings/agent-scorer/agent-scorer-live.js +2 -2
- package/dist/wirings/channel/channel-common.d.ts +4 -1
- package/dist/wirings/channel/channel-common.js +3 -1
- package/dist/wirings/channel/channel-handler.js +2 -0
- package/dist/wirings/channel/channel-host-rpc.d.ts +1 -1
- package/dist/wirings/channel/channel-host-rpc.js +2 -2
- package/dist/wirings/channel/channel-rpc-responder.d.ts +7 -1
- package/dist/wirings/channel/channel-rpc-responder.js +2 -2
- package/dist/wirings/channel/channel-rpc-service.d.ts +1 -1
- package/dist/wirings/channel/channel-rpc-service.js +2 -2
- package/dist/wirings/channel/channel-rpc.types.d.ts +5 -2
- package/dist/wirings/channel/channel-rpc.types.js +1 -1
- package/dist/wirings/channel/channel-store.d.ts +4 -2
- package/dist/wirings/channel/channel-store.js +2 -2
- package/dist/wirings/channel/channel.types.d.ts +3 -0
- package/dist/wirings/channel/local/local-channel-runner.js +16 -5
- package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +2 -0
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +3 -1
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +27 -4
- package/dist/wirings/gateway/gateway-runner.js +3 -1
- package/dist/wirings/http/http-runner.d.ts +4 -4
- package/dist/wirings/http/http-runner.js +14 -18
- package/dist/wirings/http/http.types.d.ts +3 -3
- package/dist/wirings/mcp/mcp-runner.js +15 -5
- package/dist/wirings/queue/in-memory-queue-service.d.ts +1 -1
- package/dist/wirings/queue/in-memory-queue-service.js +8 -2
- package/dist/wirings/queue/index.d.ts +1 -0
- package/dist/wirings/queue/index.js +1 -0
- package/dist/wirings/queue/queue-runner.d.ts +3 -4
- package/dist/wirings/queue/queue-runner.js +8 -7
- package/dist/wirings/queue/queue-trace.d.ts +18 -0
- package/dist/wirings/queue/queue-trace.js +42 -0
- package/dist/wirings/queue/queue.types.d.ts +7 -0
- package/dist/wirings/queue/remote-queue-job.d.ts +1 -1
- package/dist/wirings/queue/remote-queue-job.js +5 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +7 -1
- package/dist/wirings/rpc/rpc-runner.js +43 -21
- package/dist/wirings/rpc/rpc-types.d.ts +2 -0
- package/dist/wirings/scheduler/remote-scheduled-job.d.ts +1 -1
- package/dist/wirings/scheduler/remote-scheduled-job.js +2 -2
- package/dist/wirings/scheduler/scheduler-runner.d.ts +4 -4
- package/dist/wirings/scheduler/scheduler-runner.js +7 -6
- package/dist/wirings/trigger/pikku-trigger-service.js +7 -4
- package/dist/wirings/trigger/webhook-source-runner.d.ts +7 -1
- package/dist/wirings/trigger/webhook-source-runner.js +11 -3
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +49 -30
- package/dist/wirings/workflow/index.d.ts +3 -1
- package/dist/wirings/workflow/index.js +1 -0
- package/dist/wirings/workflow/pikku-scenario-service.js +2 -0
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +37 -40
- package/dist/wirings/workflow/pikku-workflow-service.js +122 -133
- package/dist/wirings/workflow/workflow-child-step.d.ts +2 -2
- package/dist/wirings/workflow/workflow-child-step.js +11 -4
- package/dist/wirings/workflow/workflow-compensation.d.ts +6 -0
- package/dist/wirings/workflow/workflow-compensation.js +15 -1
- package/dist/wirings/workflow/workflow-constants.d.ts +13 -0
- package/dist/wirings/workflow/workflow-constants.js +13 -0
- package/dist/wirings/workflow/workflow-dsl-pass.d.ts +2 -0
- package/dist/wirings/workflow/workflow-dsl-pass.js +27 -11
- package/dist/wirings/workflow/workflow-recovery.d.ts +131 -3
- package/dist/wirings/workflow/workflow-recovery.js +233 -7
- package/dist/wirings/workflow/workflow-run-engine.types.d.ts +1 -0
- package/dist/wirings/workflow/workflow-run-mirror.d.ts +4 -0
- package/dist/wirings/workflow/workflow-run-mirror.js +16 -0
- package/dist/wirings/workflow/workflow-run-status.d.ts +2 -0
- package/dist/wirings/workflow/workflow-run-status.js +13 -0
- package/dist/wirings/workflow/workflow-run-trace.d.ts +22 -0
- package/dist/wirings/workflow/workflow-run-trace.js +24 -0
- package/dist/wirings/workflow/workflow-step-claim.d.ts +18 -0
- package/dist/wirings/workflow/workflow-step-claim.js +47 -0
- package/dist/wirings/workflow/workflow-step-retry.d.ts +6 -2
- package/dist/wirings/workflow/workflow-step-retry.js +8 -3
- package/dist/wirings/workflow/workflow-wire.d.ts +12 -0
- package/dist/wirings/workflow/workflow-wire.js +33 -0
- package/dist/wirings/workflow/workflow.types.d.ts +16 -0
- package/package.json +2 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getSingletonServices, pikkuState } from '../../pikku-state.js';
|
|
2
2
|
import { getDurationInMilliseconds } from '../../time-utils.js';
|
|
3
|
+
import { buildWorkflowWire } from './workflow-wire.js';
|
|
3
4
|
import { ChildWorkflowStartedException, continueGraph, executeGraphStep, runWorkflowGraph, stripInstanceOrdinal, } from './graph/graph-runner.js';
|
|
4
5
|
import { isExpectedError } from '../../errors/error-handler.js';
|
|
5
6
|
import { PikkuMissingMetaError } from '../../errors/errors.js';
|
|
@@ -7,8 +8,8 @@ import { RPCNotFoundError } from '../rpc/rpc-runner.js';
|
|
|
7
8
|
import { deriveInvocationId } from './workflow-invocation-id.js';
|
|
8
9
|
import { approvalDeciderFrom } from './workflow-approval-policy.js';
|
|
9
10
|
import { buildRunTimeline, reconstructStateAt, } from './run-timeline.js';
|
|
10
|
-
import { DEFAULT_STEP_RETRIES, RUN_LEASE_RETRY_MS, WORKFLOW_CHILD_POLL_MAX_MS, WORKFLOW_END_STATES, WORKFLOW_POLL_FACTOR, WORKFLOW_POLL_MIN_MS, WORKFLOW_TERMINAL_STATES, NO_COMPENSATE_STATE_PREFIX, forwardStepName, isCompensationStepName,
|
|
11
|
-
import { WorkflowAsyncException, WorkflowDispatchException, WorkflowNotFoundError,
|
|
11
|
+
import { DEFAULT_STEP_RETRIES, RUN_LEASE_RETRY_MS, WORKFLOW_CHILD_POLL_MAX_MS, WORKFLOW_END_STATES, WORKFLOW_POLL_FACTOR, WORKFLOW_POLL_MIN_MS, WORKFLOW_TERMINAL_STATES, NO_COMPENSATE_STATE_PREFIX, forwardStepName, isCompensationStepName, isRunSettled, isRunStopped, } from './workflow-constants.js';
|
|
12
|
+
import { WorkflowAsyncException, WorkflowCancelledException, WorkflowDispatchException, WorkflowNotFoundError, WorkflowRunNotFoundError, WorkflowStepNameNotString, WorkflowStepSupersededError, } from './workflow-errors.js';
|
|
12
13
|
import { resolveWorkflowMeta } from './workflow-meta-resolver.js';
|
|
13
14
|
import { storeWorkflowVersion } from './workflow-version-store.js';
|
|
14
15
|
import { jobGroupFor, orchestratorQueueName, requireQueueService, resolveWorkflowConfig, stepDispatchTarget, stepJobOptions, stepWorkerQueueName, } from './workflow-queue-routing.js';
|
|
@@ -16,16 +17,18 @@ import { wireWorkflowQueueWorkers } from './workflow-queue-wiring.js';
|
|
|
16
17
|
import { approvalStepNameFor, evaluateApprovalStep, recordApprovalDecision, } from './workflow-approval.js';
|
|
17
18
|
import { auditApprovalDecision } from './workflow-approval-audit.js';
|
|
18
19
|
import { recordSuspension, suspendStepNameFor } from './workflow-suspend.js';
|
|
19
|
-
import { claimStepByReadThenWrite } from './workflow-step-claim.js';
|
|
20
|
+
import { claimStepByReadThenWrite, takeBackLostInlineStep, warnStepAttemptsExhausted, warnStepRerun, } from './workflow-step-claim.js';
|
|
20
21
|
import { holdWorkflowLease, isRunLeaseError, nullWhenStepHeld, } from './workflow-run-lease.js';
|
|
21
|
-
import { summarizeRunStatus } from './workflow-run-status.js';
|
|
22
|
+
import { runOutput, summarizeRunStatus } from './workflow-run-status.js';
|
|
23
|
+
import { writeRunMirror } from './workflow-run-mirror.js';
|
|
22
24
|
import { runningStepLease, startStepLeaseRefresh, stepLeaseMsForQueue, } from './workflow-step-lease.js';
|
|
23
25
|
import { runInlineRetryLoop } from './workflow-step-retry.js';
|
|
24
26
|
import { runInlineChildStep, startQueuedChildStep, } from './workflow-child-step.js';
|
|
25
27
|
import { runDslWorkflowPass } from './workflow-dsl-pass.js';
|
|
26
|
-
import {
|
|
28
|
+
import { runLogger, runTraceWire, withRunTrace } from './workflow-run-trace.js';
|
|
29
|
+
import { childRunFailure, settleStoppedRun, failRunOrUnwind, isUnwound, compensatingForStep, cancelAndUnwind, createUnwindHost, driveUnwind, recordHasCompensation, isChildWorkflowRecord, } from './workflow-compensation.js';
|
|
27
30
|
import { runVersionMismatchFallback } from './workflow-version-fallback.js';
|
|
28
|
-
import {
|
|
31
|
+
import { WorkflowRecovery, } from './workflow-recovery.js';
|
|
29
32
|
/** The workflow engine; subclasses supply storage. Leases serialise runs and step claims over a shared store. */
|
|
30
33
|
export class PikkuWorkflowService {
|
|
31
34
|
runExtension;
|
|
@@ -68,6 +71,10 @@ export class PikkuWorkflowService {
|
|
|
68
71
|
get logger() {
|
|
69
72
|
return getSingletonServices()?.logger;
|
|
70
73
|
}
|
|
74
|
+
lostStepLog = {
|
|
75
|
+
logger: () => this.logger,
|
|
76
|
+
traceparent: (runId) => this.getRunTraceparent(runId),
|
|
77
|
+
};
|
|
71
78
|
mirror;
|
|
72
79
|
/**
|
|
73
80
|
* Serialises orchestration passes over a run and claims on a step. A service
|
|
@@ -91,19 +98,7 @@ export class PikkuWorkflowService {
|
|
|
91
98
|
}
|
|
92
99
|
async mirrored(write, mirror) {
|
|
93
100
|
const written = await write();
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
await mirror(this.mirror, written);
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
try {
|
|
100
|
-
this.logger?.warn?.(`[pikku] WorkflowRunMirror write failed: ${err?.message ?? err}`);
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
// knowledge: decisions/internals/workflow-run-mirror-is-never-a-source-of-truth.md
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
101
|
+
await writeRunMirror(this.mirror, () => this.logger, (target) => mirror(target, written));
|
|
107
102
|
return written;
|
|
108
103
|
}
|
|
109
104
|
wireQueueWorkers() {
|
|
@@ -144,6 +139,7 @@ export class PikkuWorkflowService {
|
|
|
144
139
|
}
|
|
145
140
|
}
|
|
146
141
|
async createRun(workflowName, input, inline, graphHash, wire, options) {
|
|
142
|
+
wire = withRunTrace(wire);
|
|
147
143
|
await storeWorkflowVersion(this, workflowName, graphHash);
|
|
148
144
|
return this.mirrored(() => this.createRunImpl(workflowName, input, inline, graphHash, wire, options), (mirror, runId) => mirror.createRun(runId, workflowName, input, inline, graphHash, wire, options));
|
|
149
145
|
}
|
|
@@ -252,77 +248,89 @@ export class PikkuWorkflowService {
|
|
|
252
248
|
await this.enqueueOrchestration(runId, workflowName);
|
|
253
249
|
}
|
|
254
250
|
async enqueueOrchestration(runId, workflowName, delay) {
|
|
255
|
-
|
|
256
|
-
workflowName ??= (await this.getRun(runId))?.workflow;
|
|
257
|
-
await queueService.add(this.getOrchestratorQueueName(workflowName), { runId }, {
|
|
251
|
+
await this.addOrchestratorJob(runId, workflowName, {
|
|
258
252
|
...this.resolveStepJobOptions(),
|
|
259
253
|
...(delay !== undefined ? { delay } : {}),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async addOrchestratorJob(runId, workflowName, options) {
|
|
257
|
+
const queueService = requireQueueService();
|
|
258
|
+
const run = await this.getRunIdentity(runId);
|
|
259
|
+
workflowName ??= run?.workflow;
|
|
260
|
+
await queueService.add(this.getOrchestratorQueueName(workflowName), { runId }, {
|
|
261
|
+
...options,
|
|
260
262
|
group: this.getJobGroup(workflowName),
|
|
263
|
+
traceparent: run?.wire?.traceparent,
|
|
261
264
|
});
|
|
262
265
|
}
|
|
263
266
|
/**
|
|
264
|
-
* Ids of runs that are stalled: still `running`, with no step
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
+
* Ids of runs that are stalled: still `running`, with no step something is
|
|
268
|
+
* expected to complete (`scheduled`, `suspended`, `running` under a live
|
|
269
|
+
* lease, or parked on a child run), and no step activity since `before`.
|
|
267
270
|
*
|
|
268
|
-
* A
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
+
* A lapsed lease is a dispatch that died, so it holds nothing out of the
|
|
272
|
+
* sweep. Nor does a `running` step with no lease and no child run: that is
|
|
273
|
+
* work inside an orchestration pass, whose owner is the run's lease, which the
|
|
274
|
+
* sweep checks before it touches the run.
|
|
271
275
|
*
|
|
272
|
-
* Returns nothing by default
|
|
273
|
-
* working unchanged; a store that overrides it gains crash recovery through
|
|
274
|
-
* `recoverStalledRuns`.
|
|
276
|
+
* Returns nothing by default, for a store that cannot express the query.
|
|
275
277
|
*/
|
|
276
278
|
async findStalledRunIds(_before, _limit) {
|
|
277
279
|
return [];
|
|
278
280
|
}
|
|
279
281
|
/**
|
|
280
|
-
* Runs holding a step
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* Returns nothing by default so a store that cannot express the query keeps
|
|
284
|
-
* working unchanged — and, because it does not opt in, gains no re-dispatches
|
|
285
|
-
* either. A store must have an atomic `claimStepForExecution` before
|
|
286
|
-
* overriding this, or no concurrency for one to exclude: the relay makes
|
|
287
|
-
* duplicate dispatch routine, and the claim is what keeps a duplicate from
|
|
288
|
-
* becoming a second execution. Every `@pikku/kysely` dialect qualifies on its
|
|
289
|
-
* status-guarded claim, `mongodb` on the same claim expressed as a
|
|
290
|
-
* single-document update, `in-memory` on being inline and single-process —
|
|
291
|
-
* none of them overrides this yet.
|
|
282
|
+
* Runs holding a step `pending` since before `before`. A store overriding it
|
|
283
|
+
* needs an atomic `claimStepForExecution` (see `sweepUndispatchedSteps`).
|
|
292
284
|
*/
|
|
293
285
|
async findUndispatchedSteps(_before, _limit) {
|
|
294
286
|
return [];
|
|
295
287
|
}
|
|
296
|
-
redispatchBackoff = new RedispatchBackoff();
|
|
297
|
-
get sweepDeps() {
|
|
298
|
-
return {
|
|
299
|
-
resume: (runId) => this.resumeWorkflow(runId),
|
|
300
|
-
logger: getSingletonServices()?.logger,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
288
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
289
|
+
* Steps parked on `scheduled` on a live run, oldest first, a page at a time.
|
|
290
|
+
* Returns nothing by default, for a store whose runs die with the process.
|
|
306
291
|
*/
|
|
307
|
-
async
|
|
308
|
-
return
|
|
292
|
+
async findScheduledSteps(_limit, _offset) {
|
|
293
|
+
return [];
|
|
309
294
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
295
|
+
recovery = new WorkflowRecovery({
|
|
296
|
+
leases: () => this.leaseService,
|
|
297
|
+
listStepStates: (runId) => this.listStepStates(runId),
|
|
298
|
+
withRunLease: (runId, fn) => this.withRunLease(runId, fn),
|
|
299
|
+
findStalledRunIds: (before, limit) => this.findStalledRunIds(before, limit),
|
|
300
|
+
findUndispatchedSteps: this.findUndispatchedSteps.bind(this),
|
|
301
|
+
findScheduledSteps: this.findScheduledSteps.bind(this),
|
|
302
|
+
resumeWorkflow: (runId) => this.resumeWorkflow(runId),
|
|
303
|
+
wakeSleep: this.executeWorkflowSleepCompleted.bind(this),
|
|
304
|
+
rearmSleep: this.scheduleSleep.bind(this),
|
|
305
|
+
queueStepWorker: (...args) => this.queueStepWorker(...args),
|
|
306
|
+
});
|
|
307
|
+
/** One recovery pass, for a host driving it from a scheduled task. */
|
|
308
|
+
recoverWorkflows(options) {
|
|
309
|
+
return this.recovery.recover(options);
|
|
310
|
+
}
|
|
311
|
+
/** Sweep now and on an interval, once the queue is consuming. */
|
|
312
|
+
startRecovery(options) {
|
|
313
|
+
this.recovery.start(options);
|
|
314
|
+
}
|
|
315
|
+
stopRecovery() {
|
|
316
|
+
this.recovery.stop();
|
|
317
|
+
}
|
|
318
|
+
/** Re-drive runs whose next move was lost. */
|
|
319
|
+
recoverStalledRuns(options) {
|
|
320
|
+
return this.recovery.recoverStalledRuns(options);
|
|
321
|
+
}
|
|
322
|
+
/** Re-drive steps whose dispatch was lost. Not self-starting. */
|
|
323
|
+
relayUndispatchedSteps(options) {
|
|
324
|
+
return this.recovery.relayUndispatchedSteps(options);
|
|
317
325
|
}
|
|
318
326
|
resolveStepJobOptions(stepOptions) {
|
|
319
327
|
return stepJobOptions(stepOptions);
|
|
320
328
|
}
|
|
321
329
|
async queueStepWorker(runId, stepName, rpcName, data, stepOptions, fromStepName) {
|
|
322
|
-
|
|
323
|
-
await queueService.add(this.getStepWorkerQueueName(rpcName), { runId, stepName, rpcName, data, fromStepName }, {
|
|
330
|
+
await requireQueueService().add(this.getStepWorkerQueueName(rpcName), { runId, stepName, rpcName, data, fromStepName }, {
|
|
324
331
|
...this.resolveStepJobOptions(stepOptions),
|
|
325
332
|
group: this.getJobGroup(rpcName),
|
|
333
|
+
traceparent: await this.getRunTraceparent(runId),
|
|
326
334
|
});
|
|
327
335
|
}
|
|
328
336
|
async executeWorkflowSleepCompleted(runId, stepId) {
|
|
@@ -330,17 +338,7 @@ export class PikkuWorkflowService {
|
|
|
330
338
|
await this.resumeWorkflow(runId);
|
|
331
339
|
}
|
|
332
340
|
async scheduleOrchestratorRetry(runId, retryDelay, workflowName) {
|
|
333
|
-
|
|
334
|
-
if (!workflowName) {
|
|
335
|
-
const run = await this.getRun(runId);
|
|
336
|
-
workflowName = run?.workflow;
|
|
337
|
-
}
|
|
338
|
-
await queueService.add(this.getOrchestratorQueueName(workflowName), { runId }, {
|
|
339
|
-
...(retryDelay
|
|
340
|
-
? { delay: getDurationInMilliseconds(retryDelay) }
|
|
341
|
-
: undefined),
|
|
342
|
-
group: this.getJobGroup(workflowName),
|
|
343
|
-
});
|
|
341
|
+
await this.addOrchestratorJob(runId, workflowName, retryDelay ? { delay: getDurationInMilliseconds(retryDelay) } : {});
|
|
344
342
|
}
|
|
345
343
|
async dispatchStep(runId, stepName, rpcName, data, stepOptions, fromStepName) {
|
|
346
344
|
const target = await stepDispatchTarget(rpcName, stepName, () => this.isInline(runId));
|
|
@@ -348,10 +346,7 @@ export class PikkuWorkflowService {
|
|
|
348
346
|
return false;
|
|
349
347
|
}
|
|
350
348
|
try {
|
|
351
|
-
await
|
|
352
|
-
...this.resolveStepJobOptions(stepOptions),
|
|
353
|
-
group: this.getJobGroup(rpcName),
|
|
354
|
-
});
|
|
349
|
+
await this.queueStepWorker(runId, stepName, rpcName, data, stepOptions, fromStepName);
|
|
355
350
|
}
|
|
356
351
|
catch (cause) {
|
|
357
352
|
throw new WorkflowDispatchException(runId, stepName, { cause });
|
|
@@ -363,7 +358,7 @@ export class PikkuWorkflowService {
|
|
|
363
358
|
!getSingletonServices()?.schedulerService) {
|
|
364
359
|
return false;
|
|
365
360
|
}
|
|
366
|
-
await getSingletonServices().schedulerService.scheduleRPC(duration, this.getConfig().sleeperRPCName, { runId, stepId });
|
|
361
|
+
await getSingletonServices().schedulerService.scheduleRPC(duration, this.getConfig().sleeperRPCName, { runId, stepId }, undefined, await this.getRunTraceparent(runId));
|
|
367
362
|
return true;
|
|
368
363
|
}
|
|
369
364
|
setRunExtension(create) {
|
|
@@ -372,6 +367,7 @@ export class PikkuWorkflowService {
|
|
|
372
367
|
updateRunStatus: this.updateRunStatus.bind(this),
|
|
373
368
|
onChildWorkflowFailed: this.onChildWorkflowFailed.bind(this),
|
|
374
369
|
verifyStepName: this.verifyStepName.bind(this),
|
|
370
|
+
getRunTraceparent: this.getRunTraceparent.bind(this),
|
|
375
371
|
};
|
|
376
372
|
const extension = create(engine);
|
|
377
373
|
this.runExtension = extension;
|
|
@@ -381,6 +377,10 @@ export class PikkuWorkflowService {
|
|
|
381
377
|
return this.runExtension;
|
|
382
378
|
}
|
|
383
379
|
async startWorkflow(name, input, wire, rpcService, options) {
|
|
380
|
+
wire = {
|
|
381
|
+
...wire,
|
|
382
|
+
traceparent: wire?.traceparent ?? rpcService?.traceparent,
|
|
383
|
+
};
|
|
384
384
|
const resolved = resolveWorkflowMeta(name);
|
|
385
385
|
const workflowMeta = resolved?.meta;
|
|
386
386
|
const packageName = resolved?.packageName ?? null;
|
|
@@ -401,7 +401,7 @@ export class PikkuWorkflowService {
|
|
|
401
401
|
throw new Error(`Workflow graph '${name}': '${options.startNode}' is not a declared entry node`);
|
|
402
402
|
}
|
|
403
403
|
const shouldInline = options?.inline || !getSingletonServices()?.queueService;
|
|
404
|
-
return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire, workflowMeta);
|
|
404
|
+
return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire, workflowMeta, options?.onRunCreated);
|
|
405
405
|
}
|
|
406
406
|
if (!workflowMeta.graphHash) {
|
|
407
407
|
throw new Error(`Missing workflow graphHash for '${name}'`);
|
|
@@ -434,7 +434,7 @@ export class PikkuWorkflowService {
|
|
|
434
434
|
message: error.message,
|
|
435
435
|
stack: error.stack,
|
|
436
436
|
});
|
|
437
|
-
getSingletonServices().logger.error(`Workflow ${name} (run ${runId}) failed:`, isExpectedError(error) ? error.message : error);
|
|
437
|
+
runLogger(getSingletonServices().logger, wire).error(`Workflow ${name} (run ${runId}) failed:`, isExpectedError(error) ? error.message : error);
|
|
438
438
|
const settled = await this.getRun(runId);
|
|
439
439
|
if (settled?.wire?.type === 'workflow' && isUnwound(settled.status)) {
|
|
440
440
|
throw childRunFailure(settled) ?? error;
|
|
@@ -461,16 +461,7 @@ export class PikkuWorkflowService {
|
|
|
461
461
|
}
|
|
462
462
|
async runToCompletion(name, input, rpcService, options) {
|
|
463
463
|
const { runId } = await this.startWorkflow(name, input, options?.wire ?? { type: 'internal' }, rpcService, { inline: true });
|
|
464
|
-
|
|
465
|
-
if (run.status === 'failed' ||
|
|
466
|
-
run.status === 'compensated' ||
|
|
467
|
-
run.status === 'compensation_failed') {
|
|
468
|
-
throw new WorkflowRunFailedError(run.error?.message);
|
|
469
|
-
}
|
|
470
|
-
if (run.status === 'cancelled') {
|
|
471
|
-
throw new WorkflowRunCancelledError();
|
|
472
|
-
}
|
|
473
|
-
return run.output;
|
|
464
|
+
return runOutput(await this.awaitRunEnd(runId, options?.pollIntervalMs ?? 1000));
|
|
474
465
|
}
|
|
475
466
|
async awaitRunEnd(runId, maxIntervalMs) {
|
|
476
467
|
let interval = Math.min(WORKFLOW_POLL_MIN_MS, maxIntervalMs);
|
|
@@ -547,6 +538,18 @@ export class PikkuWorkflowService {
|
|
|
547
538
|
}
|
|
548
539
|
return run;
|
|
549
540
|
}
|
|
541
|
+
/** The trace every hop of the run stays on. */
|
|
542
|
+
async getRunTraceparent(runId) {
|
|
543
|
+
return (await this.getRunIdentity(runId))?.wire?.traceparent;
|
|
544
|
+
}
|
|
545
|
+
/** Warn that a step lost its worker and is running again, as `attempt`. */
|
|
546
|
+
logStepRerun(runId, stepName, attempt) {
|
|
547
|
+
return warnStepRerun(this.lostStepLog, runId, stepName, attempt);
|
|
548
|
+
}
|
|
549
|
+
/** Warn that a step lost its worker with no attempts left, and failed. */
|
|
550
|
+
async logStepAttemptsExhausted(runId, stepName, attempts) {
|
|
551
|
+
await warnStepAttemptsExhausted(this.lostStepLog, runId, stepName, attempts);
|
|
552
|
+
}
|
|
550
553
|
lastStepName(runId) {
|
|
551
554
|
return this.runContexts.get(runId)?.lastStep;
|
|
552
555
|
}
|
|
@@ -616,6 +619,7 @@ export class PikkuWorkflowService {
|
|
|
616
619
|
workflowWire: (addonNs) => this.createWorkflowWire(run.workflow, runId, rpcService, addonNs),
|
|
617
620
|
extension: this.runExtension,
|
|
618
621
|
updateRunStatus: (status, output, error) => this.updateRunStatus(runId, status, output, error),
|
|
622
|
+
settleIfStopped: () => this.settleStoppedRun(runId, rpcService),
|
|
619
623
|
onChildCompleted: (result) => this.onChildWorkflowCompleted(run, result),
|
|
620
624
|
onChildFailed: (error) => this.onChildWorkflowFailed(run, error),
|
|
621
625
|
failOrUnwind: (error) => this.failOrUnwind(run, error, rpcService),
|
|
@@ -713,7 +717,7 @@ export class PikkuWorkflowService {
|
|
|
713
717
|
startWorkflow: (...args) => this.startWorkflow(...args),
|
|
714
718
|
setStepChildRunId: (...args) => this.setStepChildRunId(...args),
|
|
715
719
|
getRun: (id) => this.getRun(id),
|
|
716
|
-
}, runId, stepState.stepId, rpcName, data, rpcService, run.wire
|
|
720
|
+
}, runId, stepState.stepId, rpcName, data, rpcService, run.wire);
|
|
717
721
|
}
|
|
718
722
|
else {
|
|
719
723
|
result = await this.invokeStepRpc(runId, stepName, stepState, rpcName, data, rpcService, run, isCompensationStepName(stepName)
|
|
@@ -792,6 +796,7 @@ export class PikkuWorkflowService {
|
|
|
792
796
|
async invokeStepRpc(runId, stepName, stepState, rpcName, data, rpcService, knownRun, compensatingFor) {
|
|
793
797
|
const run = knownRun ?? (await this.getRunIdentity(runId));
|
|
794
798
|
return rpcService.rpcWithWire(rpcName, data, {
|
|
799
|
+
...runTraceWire(run?.wire?.traceparent),
|
|
795
800
|
...(run?.wire?.pikkuUserId ? { pikkuUserId: run.wire.pikkuUserId } : {}),
|
|
796
801
|
...(compensatingFor
|
|
797
802
|
? {
|
|
@@ -855,7 +860,7 @@ export class PikkuWorkflowService {
|
|
|
855
860
|
}
|
|
856
861
|
const retries = resolvedStepOptions.retries ?? this.getConfig().retries;
|
|
857
862
|
const retryDelay = resolvedStepOptions.retryDelay;
|
|
858
|
-
return runInlineRetryLoop(this, stepState, retries, retryDelay, async (currentStepState) => {
|
|
863
|
+
return runInlineRetryLoop(this, await takeBackLostInlineStep(this, runId, stepName, stepState), retries, retryDelay, async (currentStepState) => {
|
|
859
864
|
if (resolvedStepOptions.virtualUser) {
|
|
860
865
|
return resolvedStepOptions.virtualUser.invoke(rpcName, data);
|
|
861
866
|
}
|
|
@@ -864,7 +869,7 @@ export class PikkuWorkflowService {
|
|
|
864
869
|
startWorkflow: (...args) => this.startWorkflow(...args),
|
|
865
870
|
setStepChildRunId: (...args) => this.setStepChildRunId(...args),
|
|
866
871
|
awaitRunEnd: (id) => this.awaitRunEnd(id, WORKFLOW_CHILD_POLL_MAX_MS),
|
|
867
|
-
}, runId, currentStepState.stepId, rpcName, data, rpcService, (await this.getRunIdentity(runId))?.wire
|
|
872
|
+
}, runId, currentStepState.stepId, rpcName, data, rpcService, (await this.getRunIdentity(runId))?.wire);
|
|
868
873
|
}
|
|
869
874
|
return this.invokeStepRpc(runId, stepName, currentStepState, rpcName, data, rpcService);
|
|
870
875
|
}, async (error) => {
|
|
@@ -875,7 +880,7 @@ export class PikkuWorkflowService {
|
|
|
875
880
|
});
|
|
876
881
|
throw error;
|
|
877
882
|
}
|
|
878
|
-
});
|
|
883
|
+
}, () => this.throwIfRunStopped(runId));
|
|
879
884
|
}
|
|
880
885
|
async inlineStep(runId, logicalStepName, fn, stepOptions, data = null, rpcName = null) {
|
|
881
886
|
const fromStepName = this.lastStepName(runId);
|
|
@@ -887,7 +892,7 @@ export class PikkuWorkflowService {
|
|
|
887
892
|
const retries = stepOptions?.retries ?? this.getConfig().retries;
|
|
888
893
|
const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay;
|
|
889
894
|
if (await this.isInline(runId)) {
|
|
890
|
-
return runInlineRetryLoop(this, stepState, retries, retryDelay, () => fn());
|
|
895
|
+
return runInlineRetryLoop(this, stepState, retries, retryDelay, () => fn(), undefined, () => this.throwIfRunStopped(runId));
|
|
891
896
|
}
|
|
892
897
|
else {
|
|
893
898
|
let currentStepState = stepState;
|
|
@@ -899,7 +904,7 @@ export class PikkuWorkflowService {
|
|
|
899
904
|
}
|
|
900
905
|
catch (error) {
|
|
901
906
|
await this.setStepError(currentStepState.stepId, error);
|
|
902
|
-
if (currentStepState.attemptCount
|
|
907
|
+
if (currentStepState.attemptCount <= retries) {
|
|
903
908
|
await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
904
909
|
await this.scheduleOrchestratorRetry(runId, retryDelay);
|
|
905
910
|
throw new WorkflowAsyncException(runId, stepName);
|
|
@@ -971,41 +976,15 @@ export class PikkuWorkflowService {
|
|
|
971
976
|
return await this.withStepLock(runId, approvalStepName, () => evaluateApprovalStep(this.approvalStore, runId, reason, approvalStepName, fromStepName, options));
|
|
972
977
|
}
|
|
973
978
|
createWorkflowWire(name, runId, rpcService, addonNamespace) {
|
|
974
|
-
const workflowWire = {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
: rpcNameOrFn;
|
|
984
|
-
return await this.rpcStep(runId, stepName, resolvedRpcName, dataOrOptions, rpcService, options);
|
|
985
|
-
}
|
|
986
|
-
else {
|
|
987
|
-
return await this.inlineStep(runId, stepName, rpcNameOrFn, dataOrOptions);
|
|
988
|
-
}
|
|
989
|
-
},
|
|
990
|
-
sleep: async (stepName, duration) => {
|
|
991
|
-
this.verifyStepName(stepName);
|
|
992
|
-
await this.sleepStep(runId, stepName, getDurationInMilliseconds(duration));
|
|
993
|
-
},
|
|
994
|
-
suspend: async (reason) => {
|
|
995
|
-
this.verifyStepName(reason);
|
|
996
|
-
await this.suspendStep(runId, reason);
|
|
997
|
-
},
|
|
998
|
-
milestone: async (name) => {
|
|
999
|
-
this.verifyStepName(name);
|
|
1000
|
-
await this.inlineStep(runId, milestoneStepName(name), async () => ({
|
|
1001
|
-
name,
|
|
1002
|
-
}));
|
|
1003
|
-
},
|
|
1004
|
-
approval: (async (reason, options) => {
|
|
1005
|
-
this.verifyStepName(reason);
|
|
1006
|
-
return await this.approvalStep(runId, reason, options);
|
|
1007
|
-
}),
|
|
1008
|
-
};
|
|
979
|
+
const workflowWire = buildWorkflowWire(name, runId, addonNamespace, {
|
|
980
|
+
getRun: () => this.getRun(runId),
|
|
981
|
+
verifyStepName: (stepName) => this.verifyStepName(stepName),
|
|
982
|
+
rpcStep: (stepName, rpcName, data, options) => this.rpcStep(runId, stepName, rpcName, data, rpcService, options),
|
|
983
|
+
inlineStep: (stepName, fn, options) => this.inlineStep(runId, stepName, fn, options),
|
|
984
|
+
sleepStep: (stepName, ms) => this.sleepStep(runId, stepName, ms),
|
|
985
|
+
suspendStep: (reason) => this.suspendStep(runId, reason),
|
|
986
|
+
approvalStep: (reason, options) => this.approvalStep(runId, reason, options),
|
|
987
|
+
});
|
|
1009
988
|
this.runExtension?.decorateWorkflowWire(workflowWire, {
|
|
1010
989
|
name,
|
|
1011
990
|
runId,
|
|
@@ -1038,13 +1017,23 @@ export class PikkuWorkflowService {
|
|
|
1038
1017
|
const api = this.compensationApi(rpcService);
|
|
1039
1018
|
return failRunOrUnwind(api, run, error, () => this.onChildWorkflowFailed(run, error));
|
|
1040
1019
|
}
|
|
1020
|
+
/** Ends a run's body at its next inline attempt once the run has been stopped. */
|
|
1021
|
+
async throwIfRunStopped(runId) {
|
|
1022
|
+
if (isRunStopped((await this.getRun(runId))?.status ?? '')) {
|
|
1023
|
+
throw new WorkflowCancelledException(runId);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
settleStoppedRun(runId, rpcService) {
|
|
1027
|
+
return settleStoppedRun(this.compensationApi(rpcService), runId);
|
|
1028
|
+
}
|
|
1041
1029
|
/** Stops an unfinished run, children first, and unwinds what it completed. */
|
|
1042
1030
|
async cancelRun(runId, rpcService, reason) {
|
|
1043
1031
|
const api = this.compensationApi(rpcService);
|
|
1044
1032
|
return cancelAndUnwind(api, createUnwindHost(api), runId, reason);
|
|
1045
1033
|
}
|
|
1046
1034
|
async markFailedUnlessUnwound(runId, error) {
|
|
1047
|
-
|
|
1035
|
+
const status = (await this.getRun(runId))?.status;
|
|
1036
|
+
if (status === 'cancelled' || isUnwound(status))
|
|
1048
1037
|
return;
|
|
1049
1038
|
await this.updateRunStatus(runId, 'failed', undefined, error);
|
|
1050
1039
|
}
|
|
@@ -10,8 +10,8 @@ interface ChildStepApi {
|
|
|
10
10
|
}
|
|
11
11
|
export declare const runInlineChildStep: (api: ChildStepApi & {
|
|
12
12
|
awaitRunEnd(childRunId: string): Promise<WorkflowRun>;
|
|
13
|
-
}, runId: string, stepId: string, workflowName: string, data: unknown, rpcService: PikkuRPC,
|
|
13
|
+
}, runId: string, stepId: string, workflowName: string, data: unknown, rpcService: PikkuRPC, parentWire: WorkflowRunWire | undefined) => Promise<unknown>;
|
|
14
14
|
export declare const startQueuedChildStep: (api: ChildStepApi & {
|
|
15
15
|
getRun(id: string): Promise<WorkflowRun | null>;
|
|
16
|
-
}, runId: string, stepId: string, workflowName: string, data: unknown, rpcService: PikkuRPC,
|
|
16
|
+
}, runId: string, stepId: string, workflowName: string, data: unknown, rpcService: PikkuRPC, parentWire: WorkflowRunWire | undefined) => Promise<unknown>;
|
|
17
17
|
export {};
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { getSingletonServices } from '../../pikku-state.js';
|
|
2
2
|
import { ChildWorkflowStartedException } from './graph/graph-runner.js';
|
|
3
3
|
import { childRunFailure } from './workflow-compensation.js';
|
|
4
|
-
export const runInlineChildStep = async (api, runId, stepId, workflowName, data, rpcService,
|
|
5
|
-
const { runId: childRunId } = await api.startWorkflow(workflowName, data, {
|
|
4
|
+
export const runInlineChildStep = async (api, runId, stepId, workflowName, data, rpcService, parentWire) => {
|
|
5
|
+
const { runId: childRunId } = await api.startWorkflow(workflowName, data, {
|
|
6
|
+
type: 'workflow',
|
|
7
|
+
id: workflowName,
|
|
8
|
+
parentRunId: runId,
|
|
9
|
+
pikkuUserId: parentWire?.pikkuUserId,
|
|
10
|
+
traceparent: parentWire?.traceparent,
|
|
11
|
+
}, rpcService, { inline: true });
|
|
6
12
|
await api.setStepChildRunId(stepId, childRunId);
|
|
7
13
|
const childRun = await api.awaitRunEnd(childRunId);
|
|
8
14
|
const failure = childRunFailure(childRun);
|
|
@@ -10,14 +16,15 @@ export const runInlineChildStep = async (api, runId, stepId, workflowName, data,
|
|
|
10
16
|
throw failure;
|
|
11
17
|
return childRun.output;
|
|
12
18
|
};
|
|
13
|
-
export const startQueuedChildStep = async (api, runId, stepId, workflowName, data, rpcService,
|
|
19
|
+
export const startQueuedChildStep = async (api, runId, stepId, workflowName, data, rpcService, parentWire) => {
|
|
14
20
|
const shouldInline = !getSingletonServices()?.queueService;
|
|
15
21
|
const { runId: childRunId } = await api.startWorkflow(workflowName, data, {
|
|
16
22
|
type: 'workflow',
|
|
17
23
|
id: workflowName,
|
|
18
24
|
parentRunId: runId,
|
|
19
25
|
parentStepId: stepId,
|
|
20
|
-
pikkuUserId,
|
|
26
|
+
pikkuUserId: parentWire?.pikkuUserId,
|
|
27
|
+
traceparent: parentWire?.traceparent,
|
|
21
28
|
}, rpcService, { inline: shouldInline });
|
|
22
29
|
await api.setStepChildRunId(stepId, childRunId);
|
|
23
30
|
if (!shouldInline) {
|
|
@@ -57,6 +57,12 @@ export type UnwindCause = SerializedError & {
|
|
|
57
57
|
export declare const childRunFailure: (childRun: WorkflowRun) => Error | null;
|
|
58
58
|
export declare const beginUnwind: (api: CompensationApi, host: UnwindHost, run: WorkflowRun, cause: UnwindCause) => Promise<'unwinding' | 'finished' | 'not-needed'>;
|
|
59
59
|
export declare const isUnwound: (status: string | undefined) => boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The status of a run that can no longer take an outcome from a pass of its
|
|
62
|
+
* own. An unwind that stopped to wait on the steps in flight is finished here:
|
|
63
|
+
* those steps were this pass's, and nothing else will wake an inline run.
|
|
64
|
+
*/
|
|
65
|
+
export declare const settleStoppedRun: (api: CompensationApi, runId: string) => Promise<WorkflowStatus | undefined>;
|
|
60
66
|
export declare const failRunOrUnwind: (api: CompensationApi, run: WorkflowRun, error: Error & {
|
|
61
67
|
code?: string;
|
|
62
68
|
childCompensation?: ChildCompensationInfo;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_STEP_RETRIES, NO_COMPENSATE_STATE_PREFIX, WORKFLOW_TERMINAL_STATES, compensationStepName, } from './workflow-constants.js';
|
|
1
|
+
import { DEFAULT_STEP_RETRIES, NO_COMPENSATE_STATE_PREFIX, WORKFLOW_TERMINAL_STATES, isRunStopped, compensationStepName, } from './workflow-constants.js';
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
3
|
import { resolveNamespace } from '../rpc/rpc-runner.js';
|
|
4
4
|
import { runningStepLease } from './workflow-step-lease.js';
|
|
@@ -239,6 +239,20 @@ export const beginUnwind = async (api, host, run, cause) => {
|
|
|
239
239
|
export const isUnwound = (status) => status === 'compensating' ||
|
|
240
240
|
status === 'compensated' ||
|
|
241
241
|
status === 'compensation_failed';
|
|
242
|
+
/**
|
|
243
|
+
* The status of a run that can no longer take an outcome from a pass of its
|
|
244
|
+
* own. An unwind that stopped to wait on the steps in flight is finished here:
|
|
245
|
+
* those steps were this pass's, and nothing else will wake an inline run.
|
|
246
|
+
*/
|
|
247
|
+
export const settleStoppedRun = async (api, runId) => {
|
|
248
|
+
const status = (await api.getRun(runId))?.status;
|
|
249
|
+
if (!status || !isRunStopped(status))
|
|
250
|
+
return undefined;
|
|
251
|
+
if (status === 'compensating') {
|
|
252
|
+
await driveUnwind(createUnwindHost(api), runId);
|
|
253
|
+
}
|
|
254
|
+
return status;
|
|
255
|
+
};
|
|
242
256
|
export const failRunOrUnwind = async (api, run, error, onFailed) => {
|
|
243
257
|
const cause = {
|
|
244
258
|
message: error.message,
|
|
@@ -30,6 +30,11 @@ export declare const isMilestoneStepName: (stepName: string) => boolean;
|
|
|
30
30
|
* not the run, which resumes when its approval or signal arrives.
|
|
31
31
|
*/
|
|
32
32
|
export declare const isRunSettled: (status: string) => boolean;
|
|
33
|
+
/**
|
|
34
|
+
* True for a run its own pass must no longer write an outcome for. That is a
|
|
35
|
+
* settled run, or one unwinding, which `cancelRun` moves there on its own.
|
|
36
|
+
*/
|
|
37
|
+
export declare const isRunStopped: (status: string) => boolean;
|
|
33
38
|
export declare const WORKFLOW_POLL_MIN_MS = 10;
|
|
34
39
|
export declare const WORKFLOW_POLL_FACTOR = 1.6;
|
|
35
40
|
export declare const WORKFLOW_CHILD_POLL_MAX_MS = 500;
|
|
@@ -45,6 +50,14 @@ export declare const DEFAULT_STALLED_RUN_LIMIT = 100;
|
|
|
45
50
|
export declare const DEFAULT_UNDISPATCHED_STEP_MS = 30000;
|
|
46
51
|
/** Steps re-driven per `relayUndispatchedSteps` call, so one tick is bounded. */
|
|
47
52
|
export declare const DEFAULT_UNDISPATCHED_STEP_LIMIT = 100;
|
|
53
|
+
/** How far past its wake time a sleep sits before a sweep decides its timer is lost. */
|
|
54
|
+
export declare const SLEEP_WAKE_GRACE_MS = 5000;
|
|
55
|
+
/** `scheduled` steps read per query, so a sweep pages rather than loads them all. */
|
|
56
|
+
export declare const SCHEDULED_STEP_PAGE_SIZE = 200;
|
|
57
|
+
/** How often a long-running host sweeps for lost work, after its startup pass. */
|
|
58
|
+
export declare const DEFAULT_RECOVERY_INTERVAL_MS = 30000;
|
|
59
|
+
/** Idle window `startRecovery` treats as stalled; the run's lease is what makes it safe. */
|
|
60
|
+
export declare const DEFAULT_RECOVERY_STALLED_MS = 30000;
|
|
48
61
|
/** First wait before a step already re-dispatched once is re-dispatched again. */
|
|
49
62
|
export declare const REDISPATCH_BACKOFF_MS = 30000;
|
|
50
63
|
/** Ceiling on the doubling backoff, so a permanently stuck step still gets swept. */
|
|
@@ -46,6 +46,11 @@ export const isMilestoneStepName = (stepName) => stepName.startsWith(MILESTONE_S
|
|
|
46
46
|
* not the run, which resumes when its approval or signal arrives.
|
|
47
47
|
*/
|
|
48
48
|
export const isRunSettled = (status) => WORKFLOW_TERMINAL_STATES.has(status);
|
|
49
|
+
/**
|
|
50
|
+
* True for a run its own pass must no longer write an outcome for. That is a
|
|
51
|
+
* settled run, or one unwinding, which `cancelRun` moves there on its own.
|
|
52
|
+
*/
|
|
53
|
+
export const isRunStopped = (status) => isRunSettled(status) || status === 'compensating';
|
|
49
54
|
export const WORKFLOW_POLL_MIN_MS = 10;
|
|
50
55
|
export const WORKFLOW_POLL_FACTOR = 1.6;
|
|
51
56
|
export const WORKFLOW_CHILD_POLL_MAX_MS = 500;
|
|
@@ -61,6 +66,14 @@ export const DEFAULT_STALLED_RUN_LIMIT = 100;
|
|
|
61
66
|
export const DEFAULT_UNDISPATCHED_STEP_MS = 30_000;
|
|
62
67
|
/** Steps re-driven per `relayUndispatchedSteps` call, so one tick is bounded. */
|
|
63
68
|
export const DEFAULT_UNDISPATCHED_STEP_LIMIT = 100;
|
|
69
|
+
/** How far past its wake time a sleep sits before a sweep decides its timer is lost. */
|
|
70
|
+
export const SLEEP_WAKE_GRACE_MS = 5_000;
|
|
71
|
+
/** `scheduled` steps read per query, so a sweep pages rather than loads them all. */
|
|
72
|
+
export const SCHEDULED_STEP_PAGE_SIZE = 200;
|
|
73
|
+
/** How often a long-running host sweeps for lost work, after its startup pass. */
|
|
74
|
+
export const DEFAULT_RECOVERY_INTERVAL_MS = 30_000;
|
|
75
|
+
/** Idle window `startRecovery` treats as stalled; the run's lease is what makes it safe. */
|
|
76
|
+
export const DEFAULT_RECOVERY_STALLED_MS = 30_000;
|
|
64
77
|
/** First wait before a step already re-dispatched once is re-dispatched again. */
|
|
65
78
|
export const REDISPATCH_BACKOFF_MS = 30_000;
|
|
66
79
|
/** Ceiling on the doubling backoff, so a permanently stuck step still gets swept. */
|
|
@@ -5,6 +5,8 @@ export interface DslPassDeps {
|
|
|
5
5
|
workflowWire: (addonNamespace: string | null) => PikkuWorkflowWire;
|
|
6
6
|
extension: WorkflowRunExtension | undefined;
|
|
7
7
|
updateRunStatus: (status: WorkflowStatus, output?: unknown, error?: SerializedError) => Promise<void>;
|
|
8
|
+
/** The run's status if something has ended it, finishing any unwind that waited on this pass. */
|
|
9
|
+
settleIfStopped: () => Promise<WorkflowStatus | undefined>;
|
|
8
10
|
onChildCompleted: (result: unknown) => Promise<void>;
|
|
9
11
|
onChildFailed: (error: Error) => Promise<void>;
|
|
10
12
|
failOrUnwind: (error: Error) => Promise<'unwinding' | 'finished' | 'failed'>;
|