@lostgradient/weft 0.24.0 → 0.24.2
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/README.md +2 -2
- package/dist/cli/conformance.js +5 -2
- package/dist/cli/generated/operation-catalog.snapshot.json +95 -1
- package/dist/cli/generated/operation-client.generated.d.ts +28 -2
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/cli/parse-schedule-arguments.js +15 -3
- package/dist/cli/schedule.js +2 -1
- package/dist/cli/types.d.ts +2 -1
- package/dist/client/http-client-requests.js +6 -9
- package/dist/client/start-body.js +1 -0
- package/dist/core/catalog/index.d.ts +2 -2
- package/dist/core/catalog/index.js +9 -2
- package/dist/core/catalog/reference-counts.d.ts +29 -14
- package/dist/core/catalog/removal.d.ts +63 -10
- package/dist/core/catalog/removal.js +18 -2
- package/dist/core/catalog/storage-io.d.ts +23 -0
- package/dist/core/catalog/storage-io.js +1 -1
- package/dist/core/engine/activity-resolution.js +17 -17
- package/dist/core/engine/bulk-operations-purge.d.ts +1 -1
- package/dist/core/engine/bulk-operations-purge.js +17 -22
- package/dist/core/engine/bulk-operations-retry.d.ts +24 -0
- package/dist/core/engine/bulk-operations-retry.js +190 -0
- package/dist/core/engine/bulk-operations-shared.d.ts +18 -0
- package/dist/core/engine/bulk-operations-shared.js +14 -0
- package/dist/core/engine/bulk-operations.d.ts +3 -2
- package/dist/core/engine/bulk-operations.js +10 -169
- package/dist/core/engine/callback-creators-bundles.js +5 -3
- package/dist/core/engine/callback-creators-core.js +9 -1
- package/dist/core/engine/callback-creators-schedule.js +2 -2
- package/dist/core/engine/catalog-readiness.js +2 -0
- package/dist/core/engine/catalog-removal.d.ts +107 -3
- package/dist/core/engine/catalog-removal.js +66 -13
- package/dist/core/engine/catalog-tombstone-recovery.d.ts +56 -0
- package/dist/core/engine/catalog-tombstone-recovery.js +39 -0
- package/dist/core/engine/child-workflow.d.ts +13 -1
- package/dist/core/engine/child-workflow.js +22 -4
- package/dist/core/engine/constraints.js +5 -1
- package/dist/core/engine/construction.d.ts +1 -1
- package/dist/core/engine/decode-revision.d.ts +32 -0
- package/dist/core/engine/decode-revision.js +9 -0
- package/dist/core/engine/disposal.d.ts +6 -2
- package/dist/core/engine/disposal.js +24 -10
- package/dist/core/engine/dynamic-source-errors.d.ts +81 -0
- package/dist/core/engine/dynamic-source-errors.js +25 -0
- package/dist/core/engine/dynamic-source-execution.d.ts +173 -0
- package/dist/core/engine/dynamic-source-execution.js +108 -0
- package/dist/core/engine/engine-internal-types.d.ts +16 -0
- package/dist/core/engine/engine-workflows-namespace.d.ts +10 -0
- package/dist/core/engine/engine-workflows-namespace.js +3 -1
- package/dist/core/engine/generation-codec.d.ts +22 -0
- package/dist/core/engine/generation-codec.js +2 -0
- package/dist/core/engine/index.d.ts +18 -10
- package/dist/core/engine/index.js +22 -11
- package/dist/core/engine/inline-launch-queue.js +1 -1
- package/dist/core/engine/internals.d.ts +26 -30
- package/dist/core/engine/lifecycle/checkpoint-launch.d.ts +29 -0
- package/dist/core/engine/lifecycle/checkpoint-launch.js +75 -0
- package/dist/core/engine/lifecycle/delayed-start-registration.d.ts +30 -0
- package/dist/core/engine/lifecycle/delayed-start-registration.js +11 -0
- package/dist/core/engine/lifecycle/fork-helpers.d.ts +33 -1
- package/dist/core/engine/lifecycle/fork-helpers.js +3 -1
- package/dist/core/engine/lifecycle/recovery-isolation.d.ts +20 -0
- package/dist/core/engine/lifecycle/recovery-isolation.js +26 -0
- package/dist/core/engine/lifecycle/recovery-revision-groups.d.ts +75 -0
- package/dist/core/engine/lifecycle/recovery-revision-groups.js +64 -0
- package/dist/core/engine/lifecycle/resume-body.d.ts +44 -0
- package/dist/core/engine/lifecycle/resume-body.js +211 -0
- package/dist/core/engine/lifecycle/resume-generation-guard.d.ts +58 -0
- package/dist/core/engine/lifecycle/resume-generation-guard.js +11 -0
- package/dist/core/engine/lifecycle/resume.d.ts +46 -3
- package/dist/core/engine/lifecycle/resume.js +21 -201
- package/dist/core/engine/lifecycle/shared.d.ts +43 -0
- package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +80 -2
- package/dist/core/engine/lifecycle/standalone-claim-acquire.js +20 -4
- package/dist/core/engine/lifecycle/start-commit-errors.d.ts +12 -0
- package/dist/core/engine/lifecycle/start-commit-errors.js +6 -0
- package/dist/core/engine/lifecycle/start-commit.d.ts +24 -7
- package/dist/core/engine/lifecycle/start-commit.js +57 -15
- package/dist/core/engine/lifecycle/start-exec.d.ts +18 -2
- package/dist/core/engine/lifecycle/start-exec.js +7 -5
- package/dist/core/engine/lifecycle/start-or-signal-create.js +1 -3
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +8 -2
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +11 -3
- package/dist/core/engine/lifecycle/start-or-signal.js +1 -1
- package/dist/core/engine/lifecycle/start-precondition-attribution.d.ts +55 -0
- package/dist/core/engine/lifecycle/start-precondition-attribution.js +13 -0
- package/dist/core/engine/lifecycle/start-revision-resolution.d.ts +34 -0
- package/dist/core/engine/lifecycle/start-revision-resolution.js +11 -0
- package/dist/core/engine/lifecycle/start-schedule-timing.d.ts +11 -0
- package/dist/core/engine/lifecycle/start-schedule-timing.js +21 -0
- package/dist/core/engine/lifecycle/start-state.d.ts +16 -0
- package/dist/core/engine/lifecycle/start-state.js +60 -0
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.d.ts +146 -4
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +33 -4
- package/dist/core/engine/lifecycle/start.d.ts +54 -7
- package/dist/core/engine/lifecycle/start.js +53 -90
- package/dist/core/engine/lifecycle/transition.d.ts +12 -4
- package/dist/core/engine/lifecycle/transition.js +40 -95
- package/dist/core/engine/lifecycle.d.ts +5 -2
- package/dist/core/engine/lifecycle.js +7 -6
- package/dist/core/engine/listing.js +15 -1
- package/dist/core/engine/memo-durable-activity.js +4 -1
- package/dist/core/engine/nonterminal-revision-count.d.ts +25 -0
- package/dist/core/engine/nonterminal-revision-count.js +16 -0
- package/dist/core/engine/operations-time.d.ts +5 -23
- package/dist/core/engine/operations-time.js +13 -11
- package/dist/core/engine/ownership-bootstrap.d.ts +1 -1
- package/dist/core/engine/pinned-schedule-revision-count.d.ts +26 -0
- package/dist/core/engine/pinned-schedule-revision-count.js +19 -0
- package/dist/core/engine/pinned-schedule-revision.d.ts +76 -0
- package/dist/core/engine/pinned-schedule-revision.js +53 -0
- package/dist/core/engine/registration.d.ts +30 -0
- package/dist/core/engine/registration.js +17 -2
- package/dist/core/engine/retention.js +9 -2
- package/dist/core/engine/revision-errors.d.ts +71 -0
- package/dist/core/engine/revision-errors.js +13 -0
- package/dist/core/engine/schedule-overlap.js +2 -1
- package/dist/core/engine/schedule-revision-fire.d.ts +17 -0
- package/dist/core/engine/schedule-revision-fire.js +3 -0
- package/dist/core/engine/schedule-run.d.ts +10 -0
- package/dist/core/engine/schedule-run.js +2 -1
- package/dist/core/engine/schedules.d.ts +11 -1
- package/dist/core/engine/schedules.js +28 -9
- package/dist/core/engine/source-diagnostics.d.ts +76 -0
- package/dist/core/engine/source-diagnostics.js +105 -0
- package/dist/core/engine/source-registration.js +4 -2
- package/dist/core/engine/source-resolution.d.ts +25 -9
- package/dist/core/engine/source-resolution.js +49 -14
- package/dist/core/engine/source-runtime-state.d.ts +92 -0
- package/dist/core/engine/source-runtime-state.js +36 -0
- package/dist/core/engine/storage-io.d.ts +19 -2
- package/dist/core/engine/storage-io.js +21 -2
- package/dist/core/engine/termination/complete.js +1 -1
- package/dist/core/engine/termination/finalizer-registration.d.ts +18 -0
- package/dist/core/engine/termination/finalizer-registration.js +21 -0
- package/dist/core/engine/termination/finalizer.d.ts +16 -19
- package/dist/core/engine/termination/finalizer.js +3 -3
- package/dist/core/engine/termination/suspend.js +3 -0
- package/dist/core/engine/time-operation-callbacks.d.ts +32 -0
- package/dist/core/engine/time-operation-callbacks.js +0 -0
- package/dist/core/engine/validation/schedule-cadence.d.ts +16 -0
- package/dist/core/engine/validation/schedule-cadence.js +20 -0
- package/dist/core/engine/validation/schedule-options.d.ts +4 -1
- package/dist/core/engine/validation/schedule-options.js +14 -0
- package/dist/core/engine/validation/schedule-revision.d.ts +24 -0
- package/dist/core/engine/validation/schedule-revision.js +21 -0
- package/dist/core/engine/validation/schedule-warnings.d.ts +11 -0
- package/dist/core/engine/validation/schedule-warnings.js +5 -0
- package/dist/core/engine/validation/schedule.d.ts +29 -4
- package/dist/core/engine/validation/schedule.js +19 -36
- package/dist/core/engine/validation.js +13 -17
- package/dist/core/engine/workflow-claim-reclaim-target.d.ts +1 -1
- package/dist/core/engine/workflow-claim-reclaim-target.js +22 -8
- package/dist/core/engine/workflow-claim-registry.d.ts +79 -75
- package/dist/core/engine/workflow-claim-registry.js +15 -2
- package/dist/core/engine/workflow-generation-fence.d.ts +74 -0
- package/dist/core/engine/workflow-generation-fence.js +19 -0
- package/dist/core/engine/workflow-retention-deadline.d.ts +16 -0
- package/dist/core/engine/workflow-retention-deadline.js +33 -0
- package/dist/core/events/event-map.d.ts +5 -0
- package/dist/core/events/index.d.ts +1 -0
- package/dist/core/events/index.js +1 -0
- package/dist/core/events/workflow-source-events.d.ts +102 -0
- package/dist/core/events/workflow-source-events.js +57 -0
- package/dist/core/execution-strategy.d.ts +7 -0
- package/dist/core/inline-execution-strategy.context-options.d.ts +1 -1
- package/dist/core/inline-execution-strategy.js +1 -1
- package/dist/core/start-workflow-validation.d.ts +15 -0
- package/dist/core/start-workflow-validation.js +11 -1
- package/dist/core/types/checkpoint.d.ts +14 -0
- package/dist/core/types/schedules.d.ts +52 -1
- package/dist/core/types/state.d.ts +16 -0
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +4 -1
- package/dist/core/worker-execution-strategy.d.ts +1 -0
- package/dist/core/worker-execution-strategy.js +13 -1
- package/dist/core/worker-fault-handling.d.ts +10 -0
- package/dist/core/worker-fault-handling.js +1 -0
- package/dist/core/worker-inbound-message.d.ts +1 -0
- package/dist/core/worker-inbound-message.js +1 -0
- package/dist/core/worker-protocol-guard.js +4 -0
- package/dist/core/worker-protocol.d.ts +7 -0
- package/dist/core/worker-protocol.js +2 -1
- package/dist/core/worker-turn-watchdog.d.ts +8 -1
- package/dist/core/worker-turn-watchdog.js +3 -2
- package/dist/core/workflow-identifiers.d.ts +37 -0
- package/dist/core/workflow-identifiers.js +19 -1
- package/dist/diagnostics/format.js +5 -0
- package/dist/diagnostics/types.d.ts +24 -0
- package/dist/diagnostics/version-check.js +8 -2
- package/dist/http.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -0
- package/dist/indexeddb.js +1 -1
- package/dist/json-schema.js +3 -3
- package/dist/server/fault-to-json-rpc.js +2 -1
- package/dist/server/fleet-event-feed.js +6 -6
- package/dist/server/index.d.ts +11 -0
- package/dist/server/operation-fault.d.ts +2 -0
- package/dist/server/operation-fault.js +2 -1
- package/dist/server/operations/create-schedule.d.ts +2 -0
- package/dist/server/operations/create-schedule.js +10 -6
- package/dist/server/operations/get-catalog-diagnostics.d.ts +28 -0
- package/dist/server/operations/get-catalog-diagnostics.js +10 -2
- package/dist/server/operations/get-task-detail-schema.d.ts +8 -0
- package/dist/server/operations/get-task-detail-schema.js +1 -0
- package/dist/server/operations/get-task-detail.d.ts +8 -0
- package/dist/server/operations/get-task-detail.js +1 -0
- package/dist/server/operations/get-task-diagnostics.d.ts +4 -4
- package/dist/server/operations/preload-workflow-revision.d.ts +28 -0
- package/dist/server/operations/preload-workflow-revision.js +60 -0
- package/dist/server/operations/schedule-faults.d.ts +1 -0
- package/dist/server/operations/schedule-faults.js +32 -4
- package/dist/server/operations/schedule-rest-body.d.ts +1 -0
- package/dist/server/operations/schedule-rest-body.js +2 -1
- package/dist/server/operations/static-registrations.js +6 -0
- package/dist/server/operations/update-schedule.d.ts +2 -0
- package/dist/server/operations/update-schedule.js +4 -2
- package/dist/server/operations/workflow-catalog-operation-helpers.js +27 -0
- package/dist/server/rest-binding.d.ts +11 -0
- package/dist/server/runtime/task-dispatch-envelope.d.ts +41 -0
- package/dist/server/runtime/task-dispatch-envelope.js +54 -0
- package/dist/server/runtime/task-dispatch-revision.d.ts +29 -0
- package/dist/server/runtime/task-dispatch-revision.js +13 -0
- package/dist/server/runtime/task-dispatch.d.ts +3 -1
- package/dist/server/runtime/task-dispatch.js +20 -44
- package/dist/server/runtime/task-ledger-recovery.js +1 -1
- package/dist/server/runtime/task-ledger-runtime.d.ts +14 -2
- package/dist/server/runtime/task-ledger-runtime.js +5 -3
- package/dist/server/runtime/task-polling.js +9 -3
- package/dist/server/runtime/task-reconciliation.js +2 -1
- package/dist/server/runtime/websocket-worker.js +9 -0
- package/dist/server/task-ledger-codec.d.ts +28 -0
- package/dist/server/task-ledger-codec.js +7 -1
- package/dist/server/task-ledger-transition-helpers.js +1 -0
- package/dist/server/task-ledger-types.d.ts +9 -0
- package/dist/server/task-ledger.d.ts +1 -1
- package/dist/server/task-ledger.js +3 -1
- package/dist/server/task-queue-types.d.ts +2 -0
- package/dist/storage/bun-sql.js +135 -100
- package/dist/storage/catalog-keys.d.ts +19 -0
- package/dist/storage/catalog-keys.js +3 -1
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/generation-keys.d.ts +52 -0
- package/dist/storage/generation-keys.js +4 -0
- package/dist/storage/index.d.ts +76 -63
- package/dist/storage/interface.d.ts +24 -230
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +2 -0
- package/dist/storage/lease-keys.d.ts +48 -0
- package/dist/storage/lease-keys.js +8 -0
- package/dist/storage/lmdb.d.ts +30 -2
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +3 -3
- package/dist/storage/node-sqlite.js +135 -100
- package/dist/storage/postgres.js +3 -3
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/signal-keys.d.ts +23 -0
- package/dist/storage/signal-keys.js +9 -0
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +2 -2
- package/dist/storage/workflow-lifecycle-keys.d.ts +128 -0
- package/dist/storage/workflow-lifecycle-keys.js +46 -0
- package/dist/storage/workflow-record-keys.d.ts +98 -0
- package/dist/storage/workflow-record-keys.js +47 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/dist/worker/protocol-messages.d.ts +17 -0
- package/dist/worker/protocol-schemas.d.ts +32 -0
- package/dist/worker/protocol-schemas.js +7 -3
- package/dist/worker/protocol-task-result.js +35 -3
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/types.d.ts +2 -0
- package/dist/worker/registry.d.ts +1 -1
- package/dist/worker/registry.js +3 -1
- package/dist/workers/workflow-runner.d.ts +9 -0
- package/dist/workers/workflow-runner.js +9 -2
- package/dist/workers/workflow-worker-entry.js +9 -4
- package/package.json +2 -2
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RegExpExtensionDecodeError } from "../../codec/extension-codec.js";
|
|
2
|
+
import { VersionMismatchError } from "../../versioning.js";
|
|
3
|
+
import { DynamicWorkflowSourceUnavailableError } from "../dynamic-source-errors.js";
|
|
4
|
+
import { WorkflowClaimUnavailableError } from "../lease-errors.js";
|
|
5
|
+
import { WorkflowRevisionUnavailableError } from "../revision-errors.js";
|
|
6
|
+
export async function isolateRecoveryFailure(workflowId, callbacks, options, error) {
|
|
7
|
+
if (error instanceof RegExpExtensionDecodeError) {
|
|
8
|
+
await callbacks.failWorkflowForCheckpointDecodeError(workflowId, error);
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
if (error instanceof VersionMismatchError && options?.versionMismatchPolicy !== "throw") {
|
|
12
|
+
await callbacks.failWorkflowForVersionMismatch(workflowId, error);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
if (error instanceof WorkflowClaimUnavailableError)
|
|
16
|
+
return null;
|
|
17
|
+
if (error instanceof WorkflowRevisionUnavailableError) {
|
|
18
|
+
await callbacks.failWorkflowForRevisionUnavailable(workflowId, error);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (error instanceof DynamicWorkflowSourceUnavailableError) {
|
|
22
|
+
await callbacks.failWorkflowForUnavailableDynamicSource(workflowId, error);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `recoverAll()`'s per-`(type, revision)` preload barrier (WFT-17/WFT-18),
|
|
3
|
+
* replacing the per-`type`-only barrier `recovery-dynamic-sources.ts` used
|
|
4
|
+
* to own. Split out of `transition.ts`, which has no headroom under the
|
|
5
|
+
* repository's 500-line implementation-file ceiling for this logic inline
|
|
6
|
+
* — the same reason `recovery-dynamic-sources.ts` was split out for
|
|
7
|
+
* WFT-15/16.
|
|
8
|
+
*
|
|
9
|
+
* Recovery scans non-terminal workflow states, groups them by their exact
|
|
10
|
+
* `(type, revision)` pin (a legacy record with no persisted `revision`
|
|
11
|
+
* groups under `undefined` for its type), and preloads/classifies every
|
|
12
|
+
* group BEFORE any group's runs advance — ready, or unavailable with the
|
|
13
|
+
* error every run in that group fails with. Grouping and classification use
|
|
14
|
+
* nested `Map<type, Map<revision, …>>` structures rather than a
|
|
15
|
+
* delimiter-joined composite key, so an arbitrary workflow `type` or
|
|
16
|
+
* `revision` string can never collide with a delimiter.
|
|
17
|
+
*
|
|
18
|
+
* @module core/engine/lifecycle/recovery-revision-groups
|
|
19
|
+
*/
|
|
20
|
+
import { DynamicWorkflowSourceUnavailableError } from '../dynamic-source-errors.ts';
|
|
21
|
+
import type { EngineInternals } from '../internals.ts';
|
|
22
|
+
import { WorkflowRevisionUnavailableError } from '../revision-errors.ts';
|
|
23
|
+
import type { LifecycleCallbacks } from './shared.ts';
|
|
24
|
+
/** One preflight-recoverable entry's identity, as `transition.ts`'s preflight scan produces it. */
|
|
25
|
+
export type RecoverableRevisionEntry = {
|
|
26
|
+
readonly workflowId: string;
|
|
27
|
+
readonly type: string;
|
|
28
|
+
readonly revision: string | undefined;
|
|
29
|
+
};
|
|
30
|
+
/** One `(type, revision)` group: every non-terminal run pinned to that exact revision. */
|
|
31
|
+
export type RecoveryRevisionGroup = {
|
|
32
|
+
readonly type: string;
|
|
33
|
+
readonly revision: string | undefined;
|
|
34
|
+
readonly workflowIds: readonly string[];
|
|
35
|
+
};
|
|
36
|
+
/** `type -> revision -> group`. A legacy entry (no persisted `revision`) groups under the `undefined` key. */
|
|
37
|
+
export type RecoveryRevisionGroups = ReadonlyMap<string, ReadonlyMap<string | undefined, RecoveryRevisionGroup>>;
|
|
38
|
+
/**
|
|
39
|
+
* Group preflight-recoverable entries by their exact `(type, revision)` pin.
|
|
40
|
+
* Every non-terminal run pinned to the same revision of the same type lands
|
|
41
|
+
* in one group, regardless of scan order.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildRecoveryRevisionGroups(entries: readonly RecoverableRevisionEntry[]): RecoveryRevisionGroups;
|
|
44
|
+
/** Outcome of classifying one `(type, revision)` group. */
|
|
45
|
+
export type RevisionGroupClassification = {
|
|
46
|
+
readonly status: 'ready';
|
|
47
|
+
} | {
|
|
48
|
+
readonly status: 'unavailable';
|
|
49
|
+
readonly error: WorkflowRevisionUnavailableError | DynamicWorkflowSourceUnavailableError;
|
|
50
|
+
};
|
|
51
|
+
/** `type -> revision -> classification`, mirroring {@link RecoveryRevisionGroups}'s shape. */
|
|
52
|
+
export type RevisionGroupClassifications = ReadonlyMap<string, ReadonlyMap<string | undefined, RevisionGroupClassification>>;
|
|
53
|
+
/**
|
|
54
|
+
* Resolve every `(type, revision)` group to `'ready'` or `'unavailable'`,
|
|
55
|
+
* concurrently, one `resolveExecutableRegistrationForRevision()` call per
|
|
56
|
+
* group — even when many non-terminal runs share the same pin. An eager
|
|
57
|
+
* type's groups are omitted from the result entirely (eager is always
|
|
58
|
+
* ready and needs no preload call); only dynamic-source groups are
|
|
59
|
+
* classified. A disposal mid-preload aborts the whole barrier by
|
|
60
|
+
* rethrowing, matching `recoverEntryOrIsolateFailure`'s own un-isolated
|
|
61
|
+
* treatment of {@link EngineDisposedError}.
|
|
62
|
+
*/
|
|
63
|
+
export declare function classifyRevisionGroups(internals: EngineInternals, callbacks: LifecycleCallbacks, groups: RecoveryRevisionGroups): Promise<RevisionGroupClassifications>;
|
|
64
|
+
/**
|
|
65
|
+
* Wrap `callbacks` so `recoverAll()`'s own per-entry `resume()` calls below
|
|
66
|
+
* see a batch's cached `classifyRevisionGroups()` failure for the entry's
|
|
67
|
+
* `(type, revision)` group, WITHOUT touching any state shared with other
|
|
68
|
+
* callers. `classifications` is a plain, closure-local `Map` structure —
|
|
69
|
+
* nothing outside `recoverAll()` ever sees it, so a concurrent, unrelated
|
|
70
|
+
* `engine.start()`, `engine.resume()`, or a second concurrent `recoverAll()`
|
|
71
|
+
* batch for the SAME `(type, revision)` keeps using the real, un-wrapped
|
|
72
|
+
* `callbacks.resolveExecutableRegistrationForRevision` and can never observe
|
|
73
|
+
* (or race the reset of) this batch's classification.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createRecoveryScopedRevisionCallbacks(callbacks: LifecycleCallbacks, classifications: RevisionGroupClassifications): LifecycleCallbacks;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { DynamicWorkflowSourceUnavailableError } from "../dynamic-source-errors.js";
|
|
2
|
+
import { EngineDisposedError } from "../errors.js";
|
|
3
|
+
import { WorkflowRevisionUnavailableError } from "../revision-errors.js";
|
|
4
|
+
export function buildRecoveryRevisionGroups(entries) {
|
|
5
|
+
const groups = new Map;
|
|
6
|
+
for (const entry of entries) {
|
|
7
|
+
let byRevision = groups.get(entry.type);
|
|
8
|
+
if (byRevision === void 0) {
|
|
9
|
+
byRevision = new Map;
|
|
10
|
+
groups.set(entry.type, byRevision);
|
|
11
|
+
}
|
|
12
|
+
const existing = byRevision.get(entry.revision);
|
|
13
|
+
if (existing === void 0)
|
|
14
|
+
byRevision.set(entry.revision, {
|
|
15
|
+
type: entry.type,
|
|
16
|
+
revision: entry.revision,
|
|
17
|
+
workflowIds: [entry.workflowId]
|
|
18
|
+
});
|
|
19
|
+
else
|
|
20
|
+
existing.workflowIds.push(entry.workflowId);
|
|
21
|
+
}
|
|
22
|
+
return groups;
|
|
23
|
+
}
|
|
24
|
+
export async function classifyRevisionGroups(internals, callbacks, groups) {
|
|
25
|
+
const dynamicGroups = [];
|
|
26
|
+
for (const [type, byRevision] of groups) {
|
|
27
|
+
if (internals.registrations.has(type))
|
|
28
|
+
continue;
|
|
29
|
+
dynamicGroups.push(...byRevision.values());
|
|
30
|
+
}
|
|
31
|
+
const results = new Map;
|
|
32
|
+
if (dynamicGroups.length === 0)
|
|
33
|
+
return results;
|
|
34
|
+
const settled = await Promise.allSettled(dynamicGroups.map((group) => callbacks.resolveExecutableRegistrationForRevision(group.type, group.revision)));
|
|
35
|
+
for (const [index, outcome] of settled.entries()) {
|
|
36
|
+
const group = dynamicGroups[index];
|
|
37
|
+
let byRevision = results.get(group.type);
|
|
38
|
+
if (byRevision === void 0) {
|
|
39
|
+
byRevision = new Map;
|
|
40
|
+
results.set(group.type, byRevision);
|
|
41
|
+
}
|
|
42
|
+
if (outcome.status === "fulfilled") {
|
|
43
|
+
byRevision.set(group.revision, { status: "ready" });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const reason = outcome.reason;
|
|
47
|
+
if (reason instanceof EngineDisposedError)
|
|
48
|
+
throw reason;
|
|
49
|
+
const error = reason instanceof WorkflowRevisionUnavailableError || reason instanceof DynamicWorkflowSourceUnavailableError ? reason : new DynamicWorkflowSourceUnavailableError(group.type, group.revision, "load-failed", reason);
|
|
50
|
+
byRevision.set(group.revision, { status: "unavailable", error });
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
export function createRecoveryScopedRevisionCallbacks(callbacks, classifications) {
|
|
55
|
+
return {
|
|
56
|
+
...callbacks,
|
|
57
|
+
resolveExecutableRegistrationForRevision: (type, revision) => {
|
|
58
|
+
const classification = classifications.get(type)?.get(revision);
|
|
59
|
+
if (classification?.status === "unavailable")
|
|
60
|
+
return Promise.reject(classification.error);
|
|
61
|
+
return callbacks.resolveExecutableRegistrationForRevision(type, revision);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `resumeWorkflowFromStorage()`'s guts — split out of `resume.ts`, which has
|
|
3
|
+
* no headroom under the repository's 500-line implementation-file ceiling
|
|
4
|
+
* for the WFT-134 claim-release wrapping added around this logic (mirrors
|
|
5
|
+
* `resume-generation-guard.ts`'s same split). `resume.ts` itself is now just
|
|
6
|
+
* the public entry point: load/validate state, acquire a claim if needed,
|
|
7
|
+
* and call {@link performResumeAfterClaimAcquired} below inside the
|
|
8
|
+
* claim-release `try`/`catch`.
|
|
9
|
+
*
|
|
10
|
+
* @module core/engine/lifecycle/resume-body
|
|
11
|
+
*/
|
|
12
|
+
import { type EventHeadRecord } from '../../event-log.ts';
|
|
13
|
+
import type { Checkpoint, WorkflowState } from '../../types.ts';
|
|
14
|
+
import { type WorkflowVersionTuple } from '../../workflow-version-tuple.ts';
|
|
15
|
+
import { type WorkflowHandle } from '../handles.ts';
|
|
16
|
+
import type { EngineInternals } from '../internals.ts';
|
|
17
|
+
import { deriveResumeGeneration } from './resume-generation-guard.ts';
|
|
18
|
+
import { type LifecycleCallbacks, type RecoverAllOptions, type RegistrationEntry } from './shared.ts';
|
|
19
|
+
export type SerializedResumeArgs = {
|
|
20
|
+
workflowId: string;
|
|
21
|
+
resumeCheckpoint: Checkpoint;
|
|
22
|
+
serializedCheckpoint: Uint8Array;
|
|
23
|
+
registeredVersionTuple: WorkflowVersionTuple;
|
|
24
|
+
restoredHead: EventHeadRecord;
|
|
25
|
+
workflowStartHeaders: Map<string, string> | undefined;
|
|
26
|
+
registration: RegistrationEntry;
|
|
27
|
+
/**
|
|
28
|
+
* The EXACT revision `registration` resolved against, NOT `state.revision`
|
|
29
|
+
* re-read independently (WFT-19 round 5): a legacy record with one registered
|
|
30
|
+
* candidate has `state.revision === undefined` even though the resolver
|
|
31
|
+
* resolved it — caching `state.revision` here would disable exact-revision lookup.
|
|
32
|
+
*/
|
|
33
|
+
resolvedRevision: string | undefined;
|
|
34
|
+
expectedGeneration: ReturnType<typeof deriveResumeGeneration>;
|
|
35
|
+
callbacks: LifecycleCallbacks;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Everything `resumeWorkflowFromStorage` does once a claim (if any) is
|
|
39
|
+
* acquired: load tracking/checkpoint/registration state, prepare the resume,
|
|
40
|
+
* and either relaunch inline or in a worker. `resumeWorkflowFromStorage`
|
|
41
|
+
* itself just wraps a call to this in the WFT-134 claim-release
|
|
42
|
+
* `try`/`catch`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function performResumeAfterClaimAcquired(internals: EngineInternals, workflowId: string, state: WorkflowState, dispatchResumedEvent: boolean, callbacks: LifecycleCallbacks, onRecoveredWorkflow?: RecoverAllOptions['onRecoveredWorkflow']): Promise<WorkflowHandle>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { KEYS } from "../../../storage/interface.js";
|
|
2
|
+
import { deserializeCheckpoint, serializeCheckpoint } from "../../checkpoint.js";
|
|
3
|
+
import { encode } from "../../codec.js";
|
|
4
|
+
import { Context, setContextWorkflowInterceptor } from "../../context.js";
|
|
5
|
+
import { EventLog } from "../../event-log.js";
|
|
6
|
+
import { WorkflowResumedEvent } from "../../events.js";
|
|
7
|
+
import { buildTimerBatchOperations } from "../../scheduler.js";
|
|
8
|
+
import { createCancelHandlerRegistration, resetCancelHandlers } from "../cancel-handlers.js";
|
|
9
|
+
import { rememberCommittedCheckpointBytes } from "../checkpoint-commit-snapshots.js";
|
|
10
|
+
import { rehydrateChildCancellationHandlers } from "../child-workflow-cancellation.js";
|
|
11
|
+
import { resolveExecutableRegistrationOrRenamedNotFound } from "../dynamic-source-execution.js";
|
|
12
|
+
import { commitFencedEngineWrite } from "../fenced-write.js";
|
|
13
|
+
import { getWorkflowExecutionStartedAt } from "../handles.js";
|
|
14
|
+
import { loadWorkflowState } from "../storage-io.js";
|
|
15
|
+
import { getComposedWorkflowInterceptor } from "../strategy-helpers.js";
|
|
16
|
+
import { buildWorkflowVisibilityIndexTransition } from "../workflow-indexes.js";
|
|
17
|
+
import { prepareResumeState } from "./persist.js";
|
|
18
|
+
import {
|
|
19
|
+
reprovideRecoveredServices,
|
|
20
|
+
workflowServicesResolverInfoFromState
|
|
21
|
+
} from "./recovered-services.js";
|
|
22
|
+
import { assertSameGeneration, deriveResumeGeneration } from "./resume-generation-guard.js";
|
|
23
|
+
import {
|
|
24
|
+
enforceHistoryPolicyBeforeReplay,
|
|
25
|
+
loadTerminalCleanupTrackedState,
|
|
26
|
+
loadWorkflowStartHeaders,
|
|
27
|
+
setWorkflowStartHeaders
|
|
28
|
+
} from "./shared.js";
|
|
29
|
+
async function prepareRecoveredServicesOrFail(internals, state, callbacks, resolverInfo) {
|
|
30
|
+
return reprovideRecoveredServices(internals, state, callbacks.failWorkflowForUnavailableServices, callbacks.handleCleanupError, callbacks.dispatchEvent, resolverInfo);
|
|
31
|
+
}
|
|
32
|
+
async function runRecoveredWorkflowHookOrFail(internals, state, handle, resolverInfo, onRecoveredWorkflow, callbacks) {
|
|
33
|
+
const info = {
|
|
34
|
+
...resolverInfo,
|
|
35
|
+
handle,
|
|
36
|
+
launchOptions: resolverInfo.launchOptions ?? { id: state.id },
|
|
37
|
+
services: internals.workflowServices.get(state.id)
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
await onRecoveredWorkflow(info);
|
|
41
|
+
return !1;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const reason = error instanceof Error ? error.message : String(error), hookError = Error(`Recovery hook failed for workflow "${state.id}": ${reason}`);
|
|
44
|
+
try {
|
|
45
|
+
await callbacks.failWorkflowForRecoveryHook(state.id, hookError);
|
|
46
|
+
} catch (commitError) {
|
|
47
|
+
callbacks.handleCleanupError("onRecoveredWorkflow", commitError, state.id);
|
|
48
|
+
}
|
|
49
|
+
return !0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function prepareRecoveredWorkflowOrFail(internals, state, callbacks, onRecoveredWorkflow) {
|
|
53
|
+
const resolverInfo = onRecoveredWorkflow === void 0 ? void 0 : await workflowServicesResolverInfoFromState(internals, state), handle = callbacks.getHandle(state.id);
|
|
54
|
+
if (await prepareRecoveredServicesOrFail(internals, state, callbacks, resolverInfo))
|
|
55
|
+
return { handle, shouldStop: !0 };
|
|
56
|
+
if (onRecoveredWorkflow === void 0 || resolverInfo === void 0)
|
|
57
|
+
return { handle, shouldStop: !1 };
|
|
58
|
+
const shouldStop = await runRecoveredWorkflowHookOrFail(internals, state, handle, resolverInfo, onRecoveredWorkflow, callbacks);
|
|
59
|
+
return { handle, shouldStop };
|
|
60
|
+
}
|
|
61
|
+
function assertResumeNotTerminating(internals, workflowId) {
|
|
62
|
+
if (internals.terminalizingWorkflows.has(workflowId))
|
|
63
|
+
throw Error(`Cannot resume workflow "${workflowId}": termination is in progress`);
|
|
64
|
+
}
|
|
65
|
+
function commitSerializedResumeState(internals, args) {
|
|
66
|
+
const {
|
|
67
|
+
workflowId,
|
|
68
|
+
resumeCheckpoint,
|
|
69
|
+
serializedCheckpoint,
|
|
70
|
+
registeredVersionTuple,
|
|
71
|
+
restoredHead,
|
|
72
|
+
callbacks
|
|
73
|
+
} = args;
|
|
74
|
+
internals.checkpoints.set(workflowId, resumeCheckpoint);
|
|
75
|
+
rememberCommittedCheckpointBytes(internals, workflowId, serializedCheckpoint);
|
|
76
|
+
internals.workflowVersionTuples.set(workflowId, registeredVersionTuple);
|
|
77
|
+
internals.eventLogHeads.set(workflowId, restoredHead);
|
|
78
|
+
setWorkflowStartHeaders(internals, workflowId, args.workflowStartHeaders, callbacks);
|
|
79
|
+
internals.parkedInlineWorkflows.delete(workflowId);
|
|
80
|
+
}
|
|
81
|
+
async function relaunchInlineWorkflowAfterResume(internals, latestState, args) {
|
|
82
|
+
const { workflowId, resumeCheckpoint, registration, resolvedRevision, callbacks } = args, accumulatedResults = new Map(resumeCheckpoint.accumulatedResults), workflowAbort = new AbortController;
|
|
83
|
+
internals.workflowTypeByWorkflowId.set(workflowId, {
|
|
84
|
+
type: latestState.type,
|
|
85
|
+
revision: resolvedRevision
|
|
86
|
+
});
|
|
87
|
+
resetCancelHandlers(internals, workflowId);
|
|
88
|
+
await rehydrateChildCancellationHandlers(internals, workflowId, callbacks);
|
|
89
|
+
const context = new Context({
|
|
90
|
+
workflowId,
|
|
91
|
+
...latestState.workflowExecutionToken !== void 0 && {
|
|
92
|
+
workflowExecutionToken: latestState.workflowExecutionToken
|
|
93
|
+
},
|
|
94
|
+
workflowType: latestState.type,
|
|
95
|
+
startedAt: getWorkflowExecutionStartedAt(latestState),
|
|
96
|
+
abortController: workflowAbort,
|
|
97
|
+
getNow: internals.options.getNow,
|
|
98
|
+
resolveWorkflowType: callbacks.resolveWorkflowTypeTarget,
|
|
99
|
+
executionStateOwnerId: latestState.executionStateOwnerId ?? workflowId,
|
|
100
|
+
accumulatedResults,
|
|
101
|
+
locals: resumeCheckpoint.locals,
|
|
102
|
+
searchAttributes: resumeCheckpoint.searchAttributes,
|
|
103
|
+
registerCancelHandler: createCancelHandlerRegistration(internals, workflowId),
|
|
104
|
+
...registration.searchAttributes && {
|
|
105
|
+
searchAttributeSchema: registration.searchAttributes
|
|
106
|
+
},
|
|
107
|
+
sleepReferenceTime: resumeCheckpoint.createdAt,
|
|
108
|
+
...latestState.executionDeadline !== void 0 && {
|
|
109
|
+
deadline: latestState.executionDeadline
|
|
110
|
+
},
|
|
111
|
+
services: internals.workflowServices.get(workflowId),
|
|
112
|
+
...internals.options.onLog != null && { logSink: internals.options.onLog }
|
|
113
|
+
});
|
|
114
|
+
setContextWorkflowInterceptor(context, getComposedWorkflowInterceptor(internals));
|
|
115
|
+
if (internals.options.development)
|
|
116
|
+
context.explain(!0);
|
|
117
|
+
const generator = registration.handler(context, latestState.input), inlineStrategy = internals.inlineStrategy;
|
|
118
|
+
inlineStrategy.adoptWorkflow(workflowId, generator, context, workflowAbort);
|
|
119
|
+
inlineStrategy.continueWorkflow(workflowId, void 0);
|
|
120
|
+
}
|
|
121
|
+
async function relaunchWorkerWorkflowAfterResume(internals, latestState, args) {
|
|
122
|
+
const { workflowId, resumeCheckpoint, workflowStartHeaders, resolvedRevision, callbacks } = args;
|
|
123
|
+
internals.workflowTypeByWorkflowId.set(workflowId, {
|
|
124
|
+
type: latestState.type,
|
|
125
|
+
revision: resolvedRevision
|
|
126
|
+
});
|
|
127
|
+
resetCancelHandlers(internals, workflowId);
|
|
128
|
+
await rehydrateChildCancellationHandlers(internals, workflowId, callbacks);
|
|
129
|
+
const serialized = serializeCheckpoint(resumeCheckpoint);
|
|
130
|
+
internals.strategy.startWorkflow({
|
|
131
|
+
workflowId,
|
|
132
|
+
...latestState.workflowExecutionToken !== void 0 && {
|
|
133
|
+
workflowExecutionToken: latestState.workflowExecutionToken
|
|
134
|
+
},
|
|
135
|
+
workflowType: latestState.type,
|
|
136
|
+
input: latestState.input,
|
|
137
|
+
checkpoint: serialized,
|
|
138
|
+
nestingDepth: internals.workflowNestingDepths.get(workflowId) ?? 0,
|
|
139
|
+
executionStateOwnerId: latestState.executionStateOwnerId ?? workflowId,
|
|
140
|
+
...latestState.executionDeadline !== void 0 && {
|
|
141
|
+
deadline: latestState.executionDeadline
|
|
142
|
+
},
|
|
143
|
+
...workflowStartHeaders !== void 0 && workflowStartHeaders.size > 0 && {
|
|
144
|
+
headers: [...workflowStartHeaders]
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function reactivateSuspendedWorkflowState(internals, state) {
|
|
149
|
+
if (state.status !== "suspended")
|
|
150
|
+
return;
|
|
151
|
+
const previousState = { ...state };
|
|
152
|
+
state.status = "running";
|
|
153
|
+
state.updatedAt = internals.options.getNow();
|
|
154
|
+
await commitFencedEngineWrite(internals, state.id, [
|
|
155
|
+
{ type: "put", key: KEYS.workflow(state.id), value: encode(state) },
|
|
156
|
+
...buildWorkflowVisibilityIndexTransition(state.id, previousState, state).batchOps,
|
|
157
|
+
...state.executionDeadline !== void 0 ? buildTimerBatchOperations({
|
|
158
|
+
id: `deadline:${state.id}`,
|
|
159
|
+
workflowId: state.id,
|
|
160
|
+
fireAt: state.executionDeadline,
|
|
161
|
+
kind: "execution-deadline"
|
|
162
|
+
}) : []
|
|
163
|
+
], [], () => Error(`Resume of workflow "${state.id}" lost its CAS race.`));
|
|
164
|
+
}
|
|
165
|
+
async function performSerializedResume(internals, args) {
|
|
166
|
+
const { workflowId } = args;
|
|
167
|
+
assertResumeNotTerminating(internals, workflowId);
|
|
168
|
+
const latestState = await loadWorkflowState(internals, workflowId);
|
|
169
|
+
assertResumeNotTerminating(internals, workflowId);
|
|
170
|
+
if (!latestState)
|
|
171
|
+
throw Error(`Workflow "${workflowId}" not found in storage`);
|
|
172
|
+
if (latestState.status !== "running" && latestState.status !== "suspended")
|
|
173
|
+
throw Error(`Cannot resume workflow "${workflowId}": status is "${latestState.status}", expected "running" or "suspended"`);
|
|
174
|
+
assertSameGeneration(workflowId, latestState, args.expectedGeneration);
|
|
175
|
+
await reactivateSuspendedWorkflowState(internals, latestState);
|
|
176
|
+
commitSerializedResumeState(internals, args);
|
|
177
|
+
if (internals.inlineStrategy) {
|
|
178
|
+
await relaunchInlineWorkflowAfterResume(internals, latestState, args);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
await relaunchWorkerWorkflowAfterResume(internals, latestState, args);
|
|
182
|
+
}
|
|
183
|
+
export async function performResumeAfterClaimAcquired(internals, workflowId, state, dispatchResumedEvent, callbacks, onRecoveredWorkflow) {
|
|
184
|
+
await loadTerminalCleanupTrackedState(internals, workflowId, callbacks);
|
|
185
|
+
const checkpointBytes = await internals.storage.get(KEYS.checkpoint(workflowId));
|
|
186
|
+
if (!checkpointBytes)
|
|
187
|
+
throw Error(`Checkpoint not found for workflow "${workflowId}"`);
|
|
188
|
+
const checkpoint = deserializeCheckpoint(checkpointBytes), { entry: registration, revision: resolvedRevision } = await resolveExecutableRegistrationOrRenamedNotFound((type) => callbacks.resolveExecutableRegistrationForRevision(type, state.revision), state.type, () => Error(`No workflow registered with name "${state.type}" (needed to resume "${workflowId}")`)), preparedResumeState = await prepareResumeState(internals, workflowId, state, checkpoint, checkpointBytes, registration, callbacks), resumeCheckpoint = preparedResumeState.checkpoint, registeredVersionTuple = preparedResumeState.versionTuple, restoredHead = await new EventLog(internals.storage, workflowId).loadHead();
|
|
189
|
+
if (await enforceHistoryPolicyBeforeReplay(internals, workflowId, restoredHead, callbacks))
|
|
190
|
+
return callbacks.getHandle(workflowId);
|
|
191
|
+
const workflowStartHeaders = await loadWorkflowStartHeaders(internals, workflowId, callbacks), { handle, shouldStop } = await prepareRecoveredWorkflowOrFail(internals, state, callbacks, onRecoveredWorkflow);
|
|
192
|
+
if (shouldStop)
|
|
193
|
+
return handle;
|
|
194
|
+
await callbacks.runSerializedWorkflowStateWrite(workflowId, () => performSerializedResume(internals, {
|
|
195
|
+
workflowId,
|
|
196
|
+
resumeCheckpoint,
|
|
197
|
+
serializedCheckpoint: preparedResumeState.serializedCheckpoint,
|
|
198
|
+
registeredVersionTuple,
|
|
199
|
+
restoredHead,
|
|
200
|
+
workflowStartHeaders,
|
|
201
|
+
registration,
|
|
202
|
+
resolvedRevision,
|
|
203
|
+
expectedGeneration: deriveResumeGeneration(state),
|
|
204
|
+
callbacks
|
|
205
|
+
}));
|
|
206
|
+
if (dispatchResumedEvent)
|
|
207
|
+
callbacks.dispatchEvent(new WorkflowResumedEvent(workflowId, resumeCheckpoint.step));
|
|
208
|
+
if (internals.inlineStrategy)
|
|
209
|
+
callbacks.swallowPromiseRejection(callbacks.processPendingUpdatesAfterInlineAdvance(workflowId));
|
|
210
|
+
return handle;
|
|
211
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `resumeWorkflowFromStorage()`'s generation-identity re-check (WFT-19
|
|
3
|
+
* review round 7, chatgpt-codex-connector + stevekinney) — split out of
|
|
4
|
+
* `resume.ts`, which has no headroom under the repository's 500-line
|
|
5
|
+
* implementation-file ceiling for this doc-heavy addition.
|
|
6
|
+
*
|
|
7
|
+
* @module core/engine/lifecycle/resume-generation-guard
|
|
8
|
+
*/
|
|
9
|
+
import type { WorkflowState } from '../../types.ts';
|
|
10
|
+
/**
|
|
11
|
+
* The generation a resume's `registration`/`resumeCheckpoint`/`resolvedRevision`
|
|
12
|
+
* were all resolved against — captured from the SAME `WorkflowState` decoded
|
|
13
|
+
* at the very top of `resumeWorkflowFromStorage()`, before
|
|
14
|
+
* `acquireStandaloneClaimBeforeResume`, the checkpoint load, and registration
|
|
15
|
+
* resolution all ran.
|
|
16
|
+
*
|
|
17
|
+
* `performSerializedResume()` re-reads workflow state fresh inside its
|
|
18
|
+
* serialized section but (before this guard existed) only re-checked
|
|
19
|
+
* `status` — never identity. An `onTerminalConflict: 'start-new'`
|
|
20
|
+
* replacement landing on this SAME `workflowId` between the top-of-function
|
|
21
|
+
* read and the serialized section's fresh read (e.g. a concurrent
|
|
22
|
+
* cancel/timeout terminalizes this run, then a fresh `start()` call replaces
|
|
23
|
+
* it) purges the old record and writes a brand-new one — new
|
|
24
|
+
* `type`/`revision` possibly, and ALWAYS a fresh `workflowExecutionToken`
|
|
25
|
+
* (`crypto.randomUUID()`, minted by every `start()` — see
|
|
26
|
+
* `start-state.ts`'s `buildInitialIdentitySlice`). Without
|
|
27
|
+
* {@link assertSameGeneration}, the stale `registration`/
|
|
28
|
+
* `resumeCheckpoint` a resume closure already built would replay against the
|
|
29
|
+
* replacement's fresh state: `commitSerializedResumeState` populates
|
|
30
|
+
* `internals.checkpoints` from the OLD checkpoint, and
|
|
31
|
+
* `relaunchInlineWorkflowAfterResume` drives the OLD handler — silently
|
|
32
|
+
* corrupting the replacement's run.
|
|
33
|
+
*
|
|
34
|
+
* A DIFFERENT engine's replacement is additionally caught earlier, by
|
|
35
|
+
* `acquireStandaloneClaimBeforeResume`'s durable `wakeOwnershipCheck`
|
|
36
|
+
* (workflow-lease mode only) — but that check compares only holder
|
|
37
|
+
* identity/epoch, never workflow identity, so a SAME-engine replacement
|
|
38
|
+
* (whose fold-acquire updates this engine's own already-held claim in
|
|
39
|
+
* place, leaving the epoch check trivially satisfied) is not caught there.
|
|
40
|
+
* This guard closes that gap for every ownership mode, not just
|
|
41
|
+
* workflow-lease.
|
|
42
|
+
*/
|
|
43
|
+
export type ResumeGeneration = {
|
|
44
|
+
type: string;
|
|
45
|
+
revision: string | undefined;
|
|
46
|
+
workflowExecutionToken: string | undefined;
|
|
47
|
+
};
|
|
48
|
+
/** Captures {@link ResumeGeneration} off the `WorkflowState` a resume decoded at its top, before any `await`. */
|
|
49
|
+
export declare function deriveResumeGeneration(state: WorkflowState): ResumeGeneration;
|
|
50
|
+
/**
|
|
51
|
+
* Throws when `latestState` — freshly re-read inside `performSerializedResume()`'s
|
|
52
|
+
* serialized section — no longer belongs to the same generation `expected`
|
|
53
|
+
* names. See {@link ResumeGeneration}'s doc for the exact race this
|
|
54
|
+
* closes. All three fields are compared — `workflowExecutionToken` alone
|
|
55
|
+
* would miss a legacy record that carries `undefined` on both sides of the
|
|
56
|
+
* race.
|
|
57
|
+
*/
|
|
58
|
+
export declare function assertSameGeneration(workflowId: string, latestState: WorkflowState, expected: ResumeGeneration): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function deriveResumeGeneration(state) {
|
|
2
|
+
return {
|
|
3
|
+
type: state.type,
|
|
4
|
+
revision: state.revision,
|
|
5
|
+
workflowExecutionToken: state.workflowExecutionToken
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function assertSameGeneration(workflowId, latestState, expected) {
|
|
9
|
+
if (latestState.workflowExecutionToken !== expected.workflowExecutionToken || latestState.type !== expected.type || latestState.revision !== expected.revision)
|
|
10
|
+
throw Error(`Cannot resume workflow "${workflowId}": its persisted state changed generation (a replacement run landed at this same id) between resume's registration lookup ` + "and its serialized commit \u2014 retry against the new generation instead of replaying " + "stale state.");
|
|
11
|
+
}
|
|
@@ -1,4 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `engine.resume(id)` / `recoverAll()`'s standalone-replay-from-storage entry
|
|
3
|
+
* point. The actual work — load tracking/checkpoint/registration state,
|
|
4
|
+
* prepare the resume, and relaunch inline or in a worker — lives in
|
|
5
|
+
* `resume-body.ts`'s `performResumeAfterClaimAcquired` (split out to stay
|
|
6
|
+
* under this repository's 500-line implementation-file ceiling once the
|
|
7
|
+
* WFT-134 claim-release wrapping below was added). This file's own job is
|
|
8
|
+
* just: load and validate state, acquire a claim if this run doesn't already
|
|
9
|
+
* hold one, and release a claim it freshly acquired if the resume turns out
|
|
10
|
+
* not to succeed after all — UNLESS `options.deferClaimReleaseOnRejection`
|
|
11
|
+
* asks this call to leave that claim installed for its caller instead (see
|
|
12
|
+
* {@link ResumeFromStorageOptions}).
|
|
13
|
+
*
|
|
14
|
+
* @module core/engine/lifecycle/resume
|
|
15
|
+
*/
|
|
16
|
+
import type { WorkflowHandle } from '../handles.ts';
|
|
2
17
|
import type { EngineInternals } from '../internals.ts';
|
|
3
|
-
import {
|
|
4
|
-
|
|
18
|
+
import type { LifecycleCallbacks, RecoverAllOptions } from './shared.ts';
|
|
19
|
+
/**
|
|
20
|
+
* An out-parameter this call populates with the epoch of a claim it freshly
|
|
21
|
+
* acquired, so a caller using `deferClaimReleaseOnRejection` can release that
|
|
22
|
+
* EXACT generation itself once it is done using it (WFT-134 review round 2,
|
|
23
|
+
* issue D). Left at `{ epoch: null }` when no fresh acquisition happened —
|
|
24
|
+
* folding disabled, no registry, the cached claim matched, or acquisition
|
|
25
|
+
* itself threw `WorkflowClaimUnavailableError` before returning.
|
|
26
|
+
*/
|
|
27
|
+
export type FreshResumeClaimTracker = {
|
|
28
|
+
epoch: number | null;
|
|
29
|
+
};
|
|
30
|
+
/** Options for {@link resumeWorkflowFromStorage}. */
|
|
31
|
+
export type ResumeFromStorageOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* `true` for `recoverAll()`'s per-entry recovery loop (WFT-134 issue D):
|
|
34
|
+
* its own `recoverEntryOrIsolateFailure` isolates several errors into a
|
|
35
|
+
* fenced `failWorkflowForXxx()` commit that needs a freshly-acquired claim
|
|
36
|
+
* to still be installed, so this call must NOT release on rejection —
|
|
37
|
+
* instead it records the acquired epoch in `freshClaimTracker` and lets the
|
|
38
|
+
* caller release once its own isolated-failure handling is done. Default
|
|
39
|
+
* (`false`/omitted) is correct for an explicit `engine.resume()` caller,
|
|
40
|
+
* which has nothing further to do with the claim and must release
|
|
41
|
+
* immediately on any rejection.
|
|
42
|
+
*/
|
|
43
|
+
deferClaimReleaseOnRejection?: boolean;
|
|
44
|
+
/** Populated with the freshly-acquired epoch, if any — see {@link FreshResumeClaimTracker}. */
|
|
45
|
+
freshClaimTracker?: FreshResumeClaimTracker;
|
|
46
|
+
};
|
|
47
|
+
export declare function resumeWorkflowFromStorage(internals: EngineInternals, workflowId: string, dispatchResumedEvent: boolean, callbacks: LifecycleCallbacks, onRecoveredWorkflow?: RecoverAllOptions['onRecoveredWorkflow'], options?: ResumeFromStorageOptions): Promise<WorkflowHandle>;
|