@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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The mechanical CAS-delete primitive `WorkflowCatalog.remove()` delegates
|
|
3
|
-
* to
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* to, plus the tombstone primitives that make its own resolution
|
|
4
|
+
* (restore-or-finalize) atomic too. Split into its own module to protect
|
|
5
|
+
* `workflow-catalog.ts`'s size against the repository's 500-line
|
|
6
|
+
* implementation-file ceiling — mirrors how `storage-io.ts`/`codec.ts`
|
|
7
|
+
* already sit alongside `workflow-catalog.ts` rather than inline in it.
|
|
7
8
|
*
|
|
8
9
|
* Fences the delete on BOTH the exact entry bytes read AND the exact active
|
|
9
10
|
* pointer bytes read, in one `conditionalBatch` — not entry bytes alone.
|
|
@@ -20,12 +21,37 @@
|
|
|
20
21
|
* counts computed by the caller and should not blindly re-attempt against
|
|
21
22
|
* possibly-stale counts.
|
|
22
23
|
*
|
|
24
|
+
* WFT-17/18 (Codex review, PR #958): a bare delete of the entry key, with
|
|
25
|
+
* `removeWorkflowRevision`'s own reference re-check and possible restore as
|
|
26
|
+
* a SEPARATE, later commit, left a crash window between the two commits
|
|
27
|
+
* where the entry was durably gone but a run still referenced it, with NO
|
|
28
|
+
* durable record of what was deleted for ANY process — including the one
|
|
29
|
+
* that crashed — to recover from. {@link removeCatalogEntry} now writes a
|
|
30
|
+
* `catalog-tombstone:<name>:<revision>` record (the exact deleted entry
|
|
31
|
+
* bytes) in the SAME `conditionalBatch` as the delete, so "the entry is
|
|
32
|
+
* gone" and "a durable record of what it was" land atomically together.
|
|
33
|
+
* {@link finalizeCatalogTombstone} and {@link restoreCatalogEntryFromTombstone}
|
|
34
|
+
* are the two possible resolutions, each its own single-CAS atomic
|
|
35
|
+
* operation conditioned on the tombstone's exact bytes — so a concurrent
|
|
36
|
+
* second attempt at resolving the SAME tombstone (this process retrying, or
|
|
37
|
+
* `catalog-readiness.ts`'s boot-time sweep racing a live caller) loses its
|
|
38
|
+
* CAS harmlessly rather than double-processing. Orphan resolution (a
|
|
39
|
+
* tombstone left behind by a process that crashed between the delete and
|
|
40
|
+
* its own resolution) is `resolveOrphanedCatalogTombstones` in
|
|
41
|
+
* `core/engine/catalog-tombstone-recovery.ts` — deliberately OUTSIDE
|
|
42
|
+
* `core/catalog/**`, not alongside these primitives, because it needs
|
|
43
|
+
* `core/engine/nonterminal-revision-count.ts`'s durable non-terminal-run
|
|
44
|
+
* scan and `core/catalog/**` never imports from `core/engine/**` (a
|
|
45
|
+
* directional boundary `check-import-cycles.ts` enforces); see that
|
|
46
|
+
* module's doc for why it lives there.
|
|
47
|
+
*
|
|
23
48
|
* @module core/catalog/removal
|
|
24
49
|
*/
|
|
25
50
|
import { type Storage } from '../../storage/interface.ts';
|
|
26
51
|
/** Outcome of {@link removeCatalogEntry}. */
|
|
27
52
|
export type WorkflowCatalogRemovalOutcome = Readonly<{
|
|
28
53
|
outcome: 'removed';
|
|
54
|
+
tombstoneBytes: Uint8Array;
|
|
29
55
|
}> | Readonly<{
|
|
30
56
|
outcome: 'not-found';
|
|
31
57
|
}> | Readonly<{
|
|
@@ -35,7 +61,9 @@ export type WorkflowCatalogRemovalOutcome = Readonly<{
|
|
|
35
61
|
outcome: 'conflict';
|
|
36
62
|
}>;
|
|
37
63
|
/**
|
|
38
|
-
* Durably delete the installed-revision record for `(name, revision)
|
|
64
|
+
* Durably delete the installed-revision record for `(name, revision)`,
|
|
65
|
+
* atomically replacing it with a tombstone carrying the deleted bytes (see
|
|
66
|
+
* this module's doc for why).
|
|
39
67
|
*
|
|
40
68
|
* Reads the current `catalog-entry:<name>:<revision>` bytes; `null` is
|
|
41
69
|
* `'not-found'` (a no-op — the caller may already believe the revision is
|
|
@@ -43,10 +71,35 @@ export type WorkflowCatalogRemovalOutcome = Readonly<{
|
|
|
43
71
|
* `'active'` when they decode to exactly this revision — a structural
|
|
44
72
|
* invariant independent of reference counts, since every future or
|
|
45
73
|
* resuming run resolves the active pointer, not a specific installed
|
|
46
|
-
* entry. Otherwise deletes the entry key
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
74
|
+
* entry. Otherwise deletes the entry key and puts the tombstone key (CAS'd
|
|
75
|
+
* on the tombstone being absent — a stale leftover from a previous,
|
|
76
|
+
* unresolved removal of this exact `(name, revision)` is a durable-store
|
|
77
|
+
* inconsistency this fails closed on rather than silently overwriting) via
|
|
78
|
+
* ONE `conditionalBatch`, also CAS'd on the exact entry bytes AND the exact
|
|
79
|
+
* active-pointer bytes read above (present as a no-op `put`-free
|
|
80
|
+
* precondition when active-pointer bytes are `null` — never activated). A
|
|
81
|
+
* CAS loss (any of the three keys changed concurrently) surfaces as
|
|
82
|
+
* `'conflict'`.
|
|
51
83
|
*/
|
|
52
84
|
export declare function removeCatalogEntry(storage: Storage, name: string, revision: string): Promise<WorkflowCatalogRemovalOutcome>;
|
|
85
|
+
/**
|
|
86
|
+
* Resolve an outstanding tombstone by completing the removal: CAS-delete
|
|
87
|
+
* the `catalog-tombstone:<name>:<revision>` key, conditioned on its exact
|
|
88
|
+
* bytes still being `tombstoneBytes`. Returns `false` (a harmless no-op for
|
|
89
|
+
* the caller, not an error) when the tombstone was already resolved by
|
|
90
|
+
* someone else — this same process re-driving after a transient failure,
|
|
91
|
+
* or a concurrent boot-time sweep on another process.
|
|
92
|
+
*/
|
|
93
|
+
export declare function finalizeCatalogTombstone(storage: Storage, name: string, revision: string, tombstoneBytes: Uint8Array): Promise<boolean>;
|
|
94
|
+
/**
|
|
95
|
+
* Resolve an outstanding tombstone by restoring the entry: ONE
|
|
96
|
+
* `conditionalBatch` that both re-`put`s the `catalog-entry:<name>:<revision>`
|
|
97
|
+
* key (byte-identical to what {@link removeCatalogEntry} deleted — the
|
|
98
|
+
* tombstone bytes ARE the entry bytes) and deletes the tombstone key,
|
|
99
|
+
* conditioned on the tombstone's exact bytes still being `tombstoneBytes`.
|
|
100
|
+
* Atomic by construction — there is no intermediate durable state where the
|
|
101
|
+
* entry is back but the tombstone still claims a removal is in flight, or
|
|
102
|
+
* vice versa. Returns `false` (harmless no-op) when the tombstone was
|
|
103
|
+
* already resolved by someone else.
|
|
104
|
+
*/
|
|
105
|
+
export declare function restoreCatalogEntryFromTombstone(storage: Storage, name: string, revision: string, tombstoneBytes: Uint8Array): Promise<boolean>;
|
|
@@ -7,8 +7,24 @@ export async function removeCatalogEntry(storage, name, revision) {
|
|
|
7
7
|
const activeKey = KEYS.catalogActive(name), activeBytes = await storage.get(activeKey), activePointer = activeBytes === null ? null : decodeActivePointer(activeBytes);
|
|
8
8
|
if (activePointer !== null && activePointer.revision === revision)
|
|
9
9
|
return { outcome: "active", activeRevision: activePointer.revision };
|
|
10
|
+
const tombstoneKey = KEYS.catalogTombstone(name, revision);
|
|
10
11
|
return await storageConditionalBatch(storage, [
|
|
11
12
|
{ key: entryKey, expectedValue: entryBytes },
|
|
12
|
-
{ key: activeKey, expectedValue: activeBytes }
|
|
13
|
-
|
|
13
|
+
{ key: activeKey, expectedValue: activeBytes },
|
|
14
|
+
{ key: tombstoneKey, expectedValue: null }
|
|
15
|
+
], [
|
|
16
|
+
{ type: "delete", key: entryKey },
|
|
17
|
+
{ type: "put", key: tombstoneKey, value: entryBytes }
|
|
18
|
+
]) ? { outcome: "removed", tombstoneBytes: entryBytes } : { outcome: "conflict" };
|
|
19
|
+
}
|
|
20
|
+
export async function finalizeCatalogTombstone(storage, name, revision, tombstoneBytes) {
|
|
21
|
+
const tombstoneKey = KEYS.catalogTombstone(name, revision);
|
|
22
|
+
return storageConditionalBatch(storage, [{ key: tombstoneKey, expectedValue: tombstoneBytes }], [{ type: "delete", key: tombstoneKey }]);
|
|
23
|
+
}
|
|
24
|
+
export async function restoreCatalogEntryFromTombstone(storage, name, revision, tombstoneBytes) {
|
|
25
|
+
const entryKey = KEYS.catalogEntry(name, revision), tombstoneKey = KEYS.catalogTombstone(name, revision);
|
|
26
|
+
return storageConditionalBatch(storage, [{ key: tombstoneKey, expectedValue: tombstoneBytes }], [
|
|
27
|
+
{ type: "put", key: entryKey, value: tombstoneBytes },
|
|
28
|
+
{ type: "delete", key: tombstoneKey }
|
|
29
|
+
]);
|
|
14
30
|
}
|
|
@@ -28,6 +28,29 @@ export type RestoredWorkflowCatalogState = {
|
|
|
28
28
|
* returns empty maps.
|
|
29
29
|
*/
|
|
30
30
|
export declare function restoreWorkflowCatalog(storage: Storage): Promise<RestoredWorkflowCatalogState>;
|
|
31
|
+
/**
|
|
32
|
+
* Decode and validate one durable catalog-entry record: parse as JSON,
|
|
33
|
+
* validate as a manifest via {@link parseWorkflowRevisionManifest}, and check
|
|
34
|
+
* that the decoded `(name, revision)` agrees with the caller-supplied
|
|
35
|
+
* expectation (normally read from the storage key itself). Fails closed on
|
|
36
|
+
* any disagreement — shared by {@link restoreWorkflowCatalog}'s per-entry
|
|
37
|
+
* restore, {@link readCatalogEntry}'s single-key read, and the by-name scan
|
|
38
|
+
* `WorkflowCatalog.listInstalledRevisions` uses, so the three consumers of
|
|
39
|
+
* one durable record shape can never validate it three different ways.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Decode and validate a raw catalog-entry-record byte string against an
|
|
43
|
+
* expected `(name, revision)` — exported for {@link import('./removal.ts').restoreCatalogEntryFromTombstone}'s
|
|
44
|
+
* caller (`WorkflowCatalog.restoreFromTombstone`) and the boot-time orphan
|
|
45
|
+
* sweep (`orphaned-tombstones.ts`) to re-populate the in-memory cache from
|
|
46
|
+
* a tombstone's bytes, which are byte-identical to the entry bytes this
|
|
47
|
+
* function already validates for {@link restoreWorkflowCatalog} and
|
|
48
|
+
* {@link readCatalogEntry}.
|
|
49
|
+
*/
|
|
50
|
+
export declare function decodeCatalogEntryRecord(key: string, bytes: Uint8Array, expectedName: string, expectedRevision: string): Promise<{
|
|
51
|
+
manifest: WorkflowRevisionManifest;
|
|
52
|
+
installedAt: number;
|
|
53
|
+
}>;
|
|
31
54
|
/**
|
|
32
55
|
* Read one durable installed-revision record for `(name, revision)`, or
|
|
33
56
|
* `null` when absent. Fails closed on corruption, exactly matching
|
|
@@ -42,7 +42,7 @@ export async function restoreWorkflowCatalog(storage) {
|
|
|
42
42
|
restoreOneActivePointer(active, key, bytes);
|
|
43
43
|
return { entries, active };
|
|
44
44
|
}
|
|
45
|
-
async function decodeCatalogEntryRecord(key, bytes, expectedName, expectedRevision) {
|
|
45
|
+
export async function decodeCatalogEntryRecord(key, bytes, expectedName, expectedRevision) {
|
|
46
46
|
const parsedRecord = parseCatalogEntryRecord(bytes);
|
|
47
47
|
if (parsedRecord === null)
|
|
48
48
|
failClosed("workflow catalog entry", key, "could not be parsed as JSON");
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { ActivityResolutionError } from "./errors.js";
|
|
2
|
-
function
|
|
3
|
-
const
|
|
4
|
-
if (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
const globalFn = internals.activityRegistry.resolve(activityName);
|
|
12
|
-
if (globalFn)
|
|
13
|
-
return { fn: globalFn, workflowType };
|
|
14
|
-
return;
|
|
2
|
+
function resolveViaKnownIdentity(internals, identity, activityName) {
|
|
3
|
+
const eagerFn = internals.activityRegistriesByWorkflow.get(identity.type)?.resolve(activityName);
|
|
4
|
+
if (eagerFn)
|
|
5
|
+
return { fn: eagerFn, workflowType: identity.type };
|
|
6
|
+
if (identity.revision !== void 0) {
|
|
7
|
+
const revisionFn = internals.sources.resolved.get(identity.type)?.get(identity.revision)?.activityRegistry.resolve(activityName);
|
|
8
|
+
if (revisionFn)
|
|
9
|
+
return { fn: revisionFn, workflowType: identity.type };
|
|
15
10
|
}
|
|
16
11
|
const globalFn = internals.activityRegistry.resolve(activityName);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
return globalFn ? { fn: globalFn, workflowType: identity.type } : void 0;
|
|
13
|
+
}
|
|
14
|
+
function resolveActivityViaRegistries(internals, workflowId, activityName) {
|
|
15
|
+
const identity = internals.workflowTypeByWorkflowId.get(workflowId);
|
|
16
|
+
if (identity !== void 0)
|
|
17
|
+
return resolveViaKnownIdentity(internals, identity, activityName);
|
|
18
|
+
const globalFn = internals.activityRegistry.resolve(activityName);
|
|
19
|
+
return globalFn ? { fn: globalFn, workflowType: "<unknown>" } : void 0;
|
|
20
20
|
}
|
|
21
21
|
export function getActivityFunctionWithMetadata(internals, workflowId, operation) {
|
|
22
22
|
const resolved = resolveActivityViaRegistries(internals, workflowId, operation.activityName);
|
|
@@ -32,6 +32,6 @@ export function resolveActivityFunction(internals, workflowId, operation) {
|
|
|
32
32
|
return resolved.fn;
|
|
33
33
|
if (operation.fn)
|
|
34
34
|
return operation.fn;
|
|
35
|
-
const workflowType = internals.workflowTypeByWorkflowId.get(workflowId) ?? "<unknown>";
|
|
35
|
+
const workflowType = internals.workflowTypeByWorkflowId.get(workflowId)?.type ?? "<unknown>";
|
|
36
36
|
throw new ActivityResolutionError(workflowType, operation.activityName);
|
|
37
37
|
}
|
|
@@ -21,7 +21,7 @@ export declare function purgeWorkflow(internals: EngineInternals, state: Workflo
|
|
|
21
21
|
* `onTerminalConflict: 'start-new'` restart path prepends them to the create batch
|
|
22
22
|
* so purge-and-recreate land as one atomic unit (no window where the prior run is
|
|
23
23
|
* gone but the new one has not committed). Keep this the single source of truth
|
|
24
|
-
* for "what a purge deletes" — do not fork the delete-set.
|
|
24
|
+
* for "what a purge deletes" — do not fork the delete-set. (`wf-gen:<id>` is deliberately excluded; WFT-153, see {@link buildBaseWorkflowDeleteKeys}.)
|
|
25
25
|
*/
|
|
26
26
|
export declare function collectWorkflowPurgeDeleteOperations(internals: EngineInternals, state: WorkflowState): Promise<BatchOperation[]>;
|
|
27
27
|
/**
|
|
@@ -12,15 +12,17 @@ import { asyncActivityWorkflowPrefix } from "./async-activity-records.js";
|
|
|
12
12
|
import { forgetCommittedCheckpointBytes } from "./checkpoint-commit-snapshots.js";
|
|
13
13
|
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
14
14
|
import { streamWorkflowStates } from "./listing.js";
|
|
15
|
+
import {
|
|
16
|
+
forEachResolvedDynamicRetentionPolicy,
|
|
17
|
+
hasUnresolvedDynamicSourceCandidate
|
|
18
|
+
} from "./registration.js";
|
|
15
19
|
import { decodeScheduleRunMetadata } from "./schedule-run-metadata.js";
|
|
16
20
|
import { createTerminalCleanupTimerId } from "./state-utilities.js";
|
|
17
21
|
import { buildExternalTerminalRotationFragment } from "./storage-io.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
isTerminalWorkflowStatus,
|
|
21
|
-
resolveRetentionForStatus
|
|
22
|
-
} from "./validation.js";
|
|
22
|
+
import { decodeWorkflowState, isTerminalWorkflowStatus } from "./validation.js";
|
|
23
|
+
import { foldWorkflowGenerationBumpForPurge } from "./workflow-generation-fence.js";
|
|
23
24
|
import { buildWorkflowVisibilityIndexTransition } from "./workflow-indexes.js";
|
|
25
|
+
import { getWorkflowRetentionDeadline } from "./workflow-retention-deadline.js";
|
|
24
26
|
export const TERMINAL_CLEANUP_DELAY_MS = 60000;
|
|
25
27
|
export async function purgeInternal(internals, filter, parameters, cleanupWaiters) {
|
|
26
28
|
const { effectiveLimit, manualOffset } = resolvePurgeWindow(internals, filter, parameters.limit);
|
|
@@ -59,16 +61,17 @@ function getMinimumRetentionMs(internals) {
|
|
|
59
61
|
considerRetentionPolicy(internals.options.retention);
|
|
60
62
|
for (const registration of internals.registrations.values())
|
|
61
63
|
considerRetentionPolicy(registration.retention);
|
|
64
|
+
forEachResolvedDynamicRetentionPolicy(internals, considerRetentionPolicy);
|
|
62
65
|
return minimumRetentionMs;
|
|
63
66
|
}
|
|
64
67
|
async function* streamExpiredRetentionWorkflowStates(internals, now) {
|
|
65
|
-
const minimumRetentionMs = getMinimumRetentionMs(internals);
|
|
66
|
-
if (minimumRetentionMs === null)
|
|
68
|
+
const untrustworthyBound = hasUnresolvedDynamicSourceCandidate(internals), minimumRetentionMs = untrustworthyBound ? null : getMinimumRetentionMs(internals);
|
|
69
|
+
if (!untrustworthyBound && minimumRetentionMs === null)
|
|
67
70
|
return;
|
|
68
|
-
const terminalWorkflowPrefix = KEYS.terminalWorkflowPrefix(),
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
const terminalWorkflowPrefix = KEYS.terminalWorkflowPrefix(), scanOptions = minimumRetentionMs === null ? {} : {
|
|
72
|
+
lte: `${terminalWorkflowPrefix}${String(now - minimumRetentionMs).padStart(16, "0")}:\xFF`
|
|
73
|
+
};
|
|
74
|
+
for await (const [key] of internals.storage.scan(terminalWorkflowPrefix, scanOptions)) {
|
|
72
75
|
const encodedWorkflowId = key.slice(key.lastIndexOf(":") + 1), workflowId = tryDecodeStorageKeyComponent(encodedWorkflowId);
|
|
73
76
|
if (workflowId === null)
|
|
74
77
|
continue;
|
|
@@ -121,20 +124,12 @@ async function shouldPurgeWorkflowState(internals, state, expiredOnly, now) {
|
|
|
121
124
|
return !1;
|
|
122
125
|
if (!expiredOnly)
|
|
123
126
|
return !0;
|
|
124
|
-
const deadline = getWorkflowRetentionDeadline(internals, state);
|
|
127
|
+
const deadline = await getWorkflowRetentionDeadline(internals, state);
|
|
125
128
|
return deadline !== null && deadline <= now;
|
|
126
129
|
}
|
|
127
|
-
function getWorkflowRetentionDeadline(internals, state) {
|
|
128
|
-
if (!isTerminalWorkflowStatus(state.status))
|
|
129
|
-
return null;
|
|
130
|
-
const policy = internals.registrations.get(state.type)?.retention ?? internals.options.retention, retentionMs = resolveRetentionForStatus(policy, state.status);
|
|
131
|
-
if (retentionMs === void 0)
|
|
132
|
-
return null;
|
|
133
|
-
return state.updatedAt + retentionMs;
|
|
134
|
-
}
|
|
135
130
|
export async function purgeWorkflow(internals, state, cleanupWaiters) {
|
|
136
|
-
const deleteOperations = await collectWorkflowPurgeDeleteOperations(internals, state), rotation = await buildExternalTerminalRotationFragment(internals, state.id), operations = [...deleteOperations, ...
|
|
137
|
-
await commitFencedEngineWrite(internals, null, operations,
|
|
131
|
+
const deleteOperations = await collectWorkflowPurgeDeleteOperations(internals, state), rotation = await buildExternalTerminalRotationFragment(internals, state.id), fenced = await foldWorkflowGenerationBumpForPurge(internals, state.id, rotation), operations = [...deleteOperations, ...fenced.operations];
|
|
132
|
+
await commitFencedEngineWrite(internals, null, operations, fenced.conditions, () => {
|
|
138
133
|
return Error(`Purge commit for workflow "${state.id}" lost its precondition.`);
|
|
139
134
|
});
|
|
140
135
|
clearPurgedWorkflowInMemoryState(internals, state.id, cleanupWaiters);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BulkOperationDryRunResult, BulkOperationOptions, BulkRetryFailedResult, ListFilter } from '../types.ts';
|
|
2
|
+
import type { EngineInternals } from './internals.ts';
|
|
3
|
+
import { type LifecycleCallbacks } from './lifecycle.ts';
|
|
4
|
+
/**
|
|
5
|
+
* `bulkOperations.ts`'s `retryFailedAll()` implementation, split into its
|
|
6
|
+
* own module (with the rest of the retry-failed cluster below) to keep
|
|
7
|
+
* `bulk-operations.ts` under the 800-line implementation-file ceiling —
|
|
8
|
+
* the same reason `bulk-operations-purge.ts` was split out earlier.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runBulkFailedWorkflowRetry(internals: EngineInternals, filter: ListFilter, options?: BulkOperationOptions,
|
|
11
|
+
/**
|
|
12
|
+
* Threaded down from `Engine#retryFailedAll()`'s own `#createLifecycleCallbacks()`
|
|
13
|
+
* (WFT-95 issue 2 follow-up). Needed only by `retryFailedWorkflow()`'s
|
|
14
|
+
* checkpoint-absent fallback, which must call the internal `startWorkflow()`
|
|
15
|
+
* directly (see its doc comment) rather than the public `engine.start()`.
|
|
16
|
+
* Sourced this way — as a parameter, rather than this module importing
|
|
17
|
+
* `createLifecycleCallbacks` from `callback-creators-core.ts` itself — to avoid
|
|
18
|
+
* introducing a `bulk-operations-retry.ts` -> `callback-creators-core.ts` ->
|
|
19
|
+
* `termination.ts` -> `termination/complete.ts` -> `bulk-operations.ts` ->
|
|
20
|
+
* `bulk-operations-retry.ts` import cycle. Optional so direct-internals test
|
|
21
|
+
* harnesses that never reach the checkpoint-absent fallback (e.g.
|
|
22
|
+
* `bulk-operations-retry-direct.test.ts`) do not need to construct one.
|
|
23
|
+
*/
|
|
24
|
+
callbacks?: LifecycleCallbacks): Promise<BulkRetryFailedResult | BulkOperationDryRunResult>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KEYS,
|
|
3
|
+
requireStorageCapability
|
|
4
|
+
} from "../../storage/interface.js";
|
|
5
|
+
import { assertScopedBulkWorkflowFilter } from "../bulk-workflow-filter.js";
|
|
6
|
+
import { deserializeCheckpoint } from "../checkpoint.js";
|
|
7
|
+
import { decode, encode } from "../codec.js";
|
|
8
|
+
import { buildTimerBatchOperations } from "../scheduler.js";
|
|
9
|
+
import { buildIndexOperations } from "../search-attributes.js";
|
|
10
|
+
import { buildTerminalWorkflowIndexOperations } from "./attributes-tags.js";
|
|
11
|
+
import {
|
|
12
|
+
buildActionableBulkWorkflowFilter,
|
|
13
|
+
normalizeBulkOperationOptions,
|
|
14
|
+
prepareBulkOperation,
|
|
15
|
+
resolveBulkOperationConcurrency,
|
|
16
|
+
runBulkWorkflowPool,
|
|
17
|
+
shouldPersistBulkAudit,
|
|
18
|
+
toBulkOperationError,
|
|
19
|
+
validateBulkConfirmation,
|
|
20
|
+
withBulkAuditEvent
|
|
21
|
+
} from "./bulk-operations-shared.js";
|
|
22
|
+
import { ensureWorkflowCatalogReady, isWorkflowCatalogReady } from "./catalog-readiness.js";
|
|
23
|
+
import { resolveExecutableRegistrationForRetry } from "./dynamic-source-execution.js";
|
|
24
|
+
import {
|
|
25
|
+
assertLeaseHeldForEngineWork,
|
|
26
|
+
commitFencedEngineWriteAllowingPreconditionFailure
|
|
27
|
+
} from "./fenced-write.js";
|
|
28
|
+
import { startWorkflow } from "./lifecycle.js";
|
|
29
|
+
import { buildCatalogEntryRevisionCondition } from "./lifecycle/start-commit.js";
|
|
30
|
+
import { createTerminalCleanupTimerId } from "./state-utilities.js";
|
|
31
|
+
import { loadWorkflowState, runSerializedWorkflowStateWrite } from "./storage-io.js";
|
|
32
|
+
import { decodeWorkflowState } from "./validation.js";
|
|
33
|
+
import {
|
|
34
|
+
commitWithWorkflowClaimFold,
|
|
35
|
+
prepareWorkflowClaimFold,
|
|
36
|
+
throwWorkflowClaimUnavailable
|
|
37
|
+
} from "./workflow-claim-fold.js";
|
|
38
|
+
import { buildWorkflowConcurrencyStartOperations } from "./workflow-concurrency.js";
|
|
39
|
+
import { buildWorkflowVisibilityIndexTransition } from "./workflow-indexes.js";
|
|
40
|
+
const FAILED_WORKFLOW_STATUSES = ["failed"], CHECKPOINT_RETRY_CONCURRENCY_ADMISSION_MAX_ATTEMPTS = 5;
|
|
41
|
+
export async function runBulkFailedWorkflowRetry(internals, filter, options = {}, callbacks) {
|
|
42
|
+
options = normalizeBulkOperationOptions(options);
|
|
43
|
+
assertScopedBulkWorkflowFilter(filter);
|
|
44
|
+
const actionableFilter = buildActionableBulkWorkflowFilter(internals, filter, FAILED_WORKFLOW_STATUSES), preparation = await prepareBulkOperation(internals, "retry-failed", actionableFilter, filter, {}, options);
|
|
45
|
+
if (options.dryRun === !0)
|
|
46
|
+
return preparation.preview;
|
|
47
|
+
validateBulkConfirmation(options, preparation);
|
|
48
|
+
const bulkConcurrency = resolveBulkOperationConcurrency(options);
|
|
49
|
+
let retried = 0;
|
|
50
|
+
const errors = [], retryResults = await runBulkWorkflowPool(preparation.workflowIds, bulkConcurrency, async (workflowId) => {
|
|
51
|
+
await retryFailedWorkflow(internals, workflowId, callbacks);
|
|
52
|
+
return { status: "retried" };
|
|
53
|
+
});
|
|
54
|
+
for (const retryResult of retryResults) {
|
|
55
|
+
if (retryResult.status === "rejected") {
|
|
56
|
+
errors.push(toBulkOperationError(internals, retryResult.item, retryResult.reason));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
retried += 1;
|
|
60
|
+
}
|
|
61
|
+
const result = { retried, failed: errors.length, errors };
|
|
62
|
+
if (!shouldPersistBulkAudit(options))
|
|
63
|
+
return result;
|
|
64
|
+
return withBulkAuditEvent(internals, preparation, options, result, retried);
|
|
65
|
+
}
|
|
66
|
+
async function retryFailedWorkflow(internals, workflowId, callbacks) {
|
|
67
|
+
const state = await loadWorkflowState(internals, workflowId);
|
|
68
|
+
if (state === null)
|
|
69
|
+
throw Error("Workflow no longer exists");
|
|
70
|
+
if (state.status !== "failed")
|
|
71
|
+
throw Error(`Workflow is ${state.status}, not failed`);
|
|
72
|
+
if (await internals.storage.get(KEYS.checkpoint(workflowId)) !== null) {
|
|
73
|
+
await reactivateFailedWorkflowFromCheckpoint(internals, state);
|
|
74
|
+
await internals.engine.resume(workflowId);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const restartOptions = {
|
|
78
|
+
id: workflowId,
|
|
79
|
+
onTerminalConflict: "start-new",
|
|
80
|
+
...state.tags !== void 0 ? { tags: state.tags } : {}
|
|
81
|
+
};
|
|
82
|
+
if (callbacks === void 0) {
|
|
83
|
+
await internals.engine.start(state.type, state.input, restartOptions);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
assertLeaseHeldForEngineWork(internals);
|
|
87
|
+
if (!isWorkflowCatalogReady(internals.engine))
|
|
88
|
+
await ensureWorkflowCatalogReady(internals.engine);
|
|
89
|
+
await startWorkflow(internals, state.type, state.input, restartOptions, void 0, callbacks, void 0, void 0, "bulk-retry-only");
|
|
90
|
+
}
|
|
91
|
+
async function reactivateFailedWorkflowFromCheckpoint(internals, state) {
|
|
92
|
+
const reactivated = await runSerializedWorkflowStateWrite(internals, state.id, async () => reactivateFailedWorkflowFromCheckpointSerialized(internals, state.id));
|
|
93
|
+
if (reactivated.terminalCleanupTimerId !== void 0)
|
|
94
|
+
await internals.scheduler.cancel(reactivated.terminalCleanupTimerId, state.id);
|
|
95
|
+
}
|
|
96
|
+
async function reactivateFailedWorkflowFromCheckpointSerialized(internals, workflowId) {
|
|
97
|
+
let lastConcurrencyStateKey;
|
|
98
|
+
for (let attempt = 0;attempt < CHECKPOINT_RETRY_CONCURRENCY_ADMISSION_MAX_ATTEMPTS; attempt += 1) {
|
|
99
|
+
const { currentState, checkpoint } = await loadFailedWorkflowForReactivation(internals, workflowId), commit = await buildReactivationCommit(internals, workflowId, currentState, checkpoint);
|
|
100
|
+
lastConcurrencyStateKey = commit.concurrencyStateKey ?? lastConcurrencyStateKey;
|
|
101
|
+
if (await commitFailedWorkflowReactivation(internals, workflowId, commit.operations, commit.conditions))
|
|
102
|
+
return {
|
|
103
|
+
terminalCleanupTimerId: currentState.terminalCleanupToken === void 0 ? void 0 : createTerminalCleanupTimerId(!1, currentState.terminalCleanupToken)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
throw Error(`Workflow concurrency admission for "${lastConcurrencyStateKey ?? workflowId}" changed too many times while retrying failed workflow "${workflowId}"`);
|
|
107
|
+
}
|
|
108
|
+
async function loadFailedWorkflowForReactivation(internals, workflowId) {
|
|
109
|
+
const currentStateBytes = await internals.storage.get(KEYS.workflow(workflowId));
|
|
110
|
+
if (currentStateBytes === null)
|
|
111
|
+
throw Error("Workflow no longer exists");
|
|
112
|
+
const currentState = decodeWorkflowState(currentStateBytes);
|
|
113
|
+
if (currentState.status !== "failed")
|
|
114
|
+
throw Error(`Workflow is ${currentState.status}, not failed`);
|
|
115
|
+
const currentCheckpointBytes = await internals.storage.get(KEYS.checkpoint(workflowId));
|
|
116
|
+
if (currentCheckpointBytes === null)
|
|
117
|
+
throw Error("Checkpoint no longer exists");
|
|
118
|
+
return { currentState, checkpoint: deserializeCheckpoint(currentCheckpointBytes) };
|
|
119
|
+
}
|
|
120
|
+
async function buildReactivationCommit(internals, workflowId, currentState, checkpoint) {
|
|
121
|
+
const { entry: registration } = await resolveExecutableRegistrationForRetry(internals, currentState.type, currentState.revision, workflowId), catalogEntryCondition = currentState.revision === void 0 ? void 0 : await buildCatalogEntryRevisionCondition(internals, currentState.type, currentState.revision), concurrencyStartOperations = registration.concurrency === void 0 ? void 0 : await buildWorkflowConcurrencyStartOperations(internals, currentState.type, workflowId, currentState.input, registration.concurrency), reactivatedState = buildReactivatedWorkflowState(internals, currentState), currentAttributes = await loadSearchAttributes(internals, workflowId);
|
|
122
|
+
return {
|
|
123
|
+
operations: buildReactivationBatchOperations(workflowId, currentState, reactivatedState, checkpoint, currentAttributes, concurrencyStartOperations?.operations),
|
|
124
|
+
conditions: [
|
|
125
|
+
...concurrencyStartOperations?.conditions ?? [],
|
|
126
|
+
...catalogEntryCondition === void 0 ? [] : [catalogEntryCondition]
|
|
127
|
+
],
|
|
128
|
+
concurrencyStateKey: concurrencyStartOperations?.stateKey
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function buildReactivationBatchOperations(workflowId, currentState, reactivatedState, checkpoint, currentAttributes, concurrencyOperations) {
|
|
132
|
+
const deadlineOperations = reactivatedState.executionDeadline === void 0 ? [] : buildTimerBatchOperations({
|
|
133
|
+
id: `deadline:${workflowId}`,
|
|
134
|
+
workflowId,
|
|
135
|
+
fireAt: reactivatedState.executionDeadline,
|
|
136
|
+
kind: "execution-deadline"
|
|
137
|
+
});
|
|
138
|
+
return [
|
|
139
|
+
...buildTerminalWorkflowIndexOperations(currentState, reactivatedState),
|
|
140
|
+
{ type: "put", key: KEYS.workflow(workflowId), value: encode(reactivatedState) },
|
|
141
|
+
...buildWorkflowVisibilityIndexTransition(workflowId, currentState, reactivatedState).batchOps,
|
|
142
|
+
...buildRetrySearchAttributeOperations(workflowId, currentAttributes, checkpoint.searchAttributes),
|
|
143
|
+
...deadlineOperations,
|
|
144
|
+
...concurrencyOperations ?? []
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
async function commitFailedWorkflowReactivation(internals, workflowId, operations, conditions) {
|
|
148
|
+
const claimFold = await prepareWorkflowClaimFold(internals, workflowId);
|
|
149
|
+
if (claimFold === void 0) {
|
|
150
|
+
if (conditions.length > 0)
|
|
151
|
+
requireStorageCapability(internals.storage, "conditionalBatch", "retry failed workflow");
|
|
152
|
+
return commitFencedEngineWriteAllowingPreconditionFailure(internals, workflowId, operations, conditions);
|
|
153
|
+
}
|
|
154
|
+
const result = await commitWithWorkflowClaimFold(internals, claimFold, operations, conditions, "retry failed workflow claim acquisition");
|
|
155
|
+
if (result.status === "committed")
|
|
156
|
+
return !0;
|
|
157
|
+
return result.claimConflict ? throwWorkflowClaimUnavailable(internals, workflowId) : !1;
|
|
158
|
+
}
|
|
159
|
+
function buildReactivatedWorkflowState(internals, state) {
|
|
160
|
+
const reactivatedState = {
|
|
161
|
+
...state,
|
|
162
|
+
status: "running",
|
|
163
|
+
updatedAt: internals.options.getNow()
|
|
164
|
+
};
|
|
165
|
+
delete reactivatedState.error;
|
|
166
|
+
delete reactivatedState.errorStack;
|
|
167
|
+
delete reactivatedState.failureCategory;
|
|
168
|
+
delete reactivatedState.result;
|
|
169
|
+
delete reactivatedState.terminationReason;
|
|
170
|
+
delete reactivatedState.terminalCleanupToken;
|
|
171
|
+
return reactivatedState;
|
|
172
|
+
}
|
|
173
|
+
async function loadSearchAttributes(internals, workflowId) {
|
|
174
|
+
const attributeBytes = await internals.storage.get(KEYS.attribute(workflowId));
|
|
175
|
+
if (attributeBytes === null)
|
|
176
|
+
return {};
|
|
177
|
+
return decode(attributeBytes);
|
|
178
|
+
}
|
|
179
|
+
function buildRetrySearchAttributeOperations(workflowId, currentAttributes, checkpointAttributes) {
|
|
180
|
+
const operations = buildIndexOperations(workflowId, currentAttributes, checkpointAttributes);
|
|
181
|
+
if (Object.keys(checkpointAttributes).length === 0)
|
|
182
|
+
operations.push({ type: "delete", key: KEYS.attribute(workflowId) });
|
|
183
|
+
else
|
|
184
|
+
operations.push({
|
|
185
|
+
type: "put",
|
|
186
|
+
key: KEYS.attribute(workflowId),
|
|
187
|
+
value: encode(checkpointAttributes)
|
|
188
|
+
});
|
|
189
|
+
return operations;
|
|
190
|
+
}
|
|
@@ -2,6 +2,24 @@ import type { BulkOperationAction, BulkOperationAuditEvent, BulkOperationCommitO
|
|
|
2
2
|
import type { EngineInternals } from './internals.ts';
|
|
3
3
|
export declare const BULK_OPERATION_SAMPLE_LIMIT = 20;
|
|
4
4
|
export declare const DEFAULT_BULK_OPERATION_CONCURRENCY = 1;
|
|
5
|
+
export type BulkWorkflowPoolResult<TItem, TResult> = {
|
|
6
|
+
item: TItem;
|
|
7
|
+
status: 'fulfilled';
|
|
8
|
+
value: TResult;
|
|
9
|
+
} | {
|
|
10
|
+
item: TItem;
|
|
11
|
+
status: 'rejected';
|
|
12
|
+
reason: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Shared bounded-concurrency runner every bulk operation (cancel, retry,
|
|
16
|
+
* signal, delete) uses to drive its per-workflow work: runs `items` in
|
|
17
|
+
* fixed-size batches of `concurrencyLimit`, capturing each item's outcome
|
|
18
|
+
* rather than letting one rejection abort the batch (mirrors
|
|
19
|
+
* `Promise.allSettled`, but keyed back to the originating `item` so a
|
|
20
|
+
* caller can map a failure to the workflow it belongs to).
|
|
21
|
+
*/
|
|
22
|
+
export declare function runBulkWorkflowPool<TItem, TResult>(items: readonly TItem[], concurrencyLimit: number, operation: (item: TItem) => Promise<TResult>): Promise<BulkWorkflowPoolResult<TItem, TResult>[]>;
|
|
5
23
|
export type BulkWorkflowSnapshot = {
|
|
6
24
|
id: string;
|
|
7
25
|
type: string;
|
|
@@ -15,6 +15,20 @@ import { streamWorkflowStateBatches } from "./listing.js";
|
|
|
15
15
|
import { isTerminalWorkflowStatus } from "./validation.js";
|
|
16
16
|
export const BULK_OPERATION_SAMPLE_LIMIT = 20, DEFAULT_BULK_OPERATION_CONCURRENCY = 1;
|
|
17
17
|
const DEFAULT_BULK_OPERATION_PRINCIPAL = { method: "in-process" };
|
|
18
|
+
export async function runBulkWorkflowPool(items, concurrencyLimit, operation) {
|
|
19
|
+
const results = [];
|
|
20
|
+
for (let batchStart = 0;batchStart < items.length; batchStart += concurrencyLimit) {
|
|
21
|
+
const batchItems = items.slice(batchStart, batchStart + concurrencyLimit);
|
|
22
|
+
results.push(...await Promise.all(batchItems.map(async (item) => {
|
|
23
|
+
try {
|
|
24
|
+
return { item, status: "fulfilled", value: await operation(item) };
|
|
25
|
+
} catch (reason) {
|
|
26
|
+
return { item, status: "rejected", reason };
|
|
27
|
+
}
|
|
28
|
+
})));
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
}
|
|
18
32
|
export function buildActionableBulkWorkflowFilter(_internals, filter, actionableStatuses) {
|
|
19
33
|
const effectiveStatuses = (filter.status === void 0 ? actionableStatuses : Array.isArray(filter.status) ? filter.status : [filter.status]).filter((status) => actionableStatuses.includes(status));
|
|
20
34
|
if (effectiveStatuses.length === 0)
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { BulkCancelResult, BulkDeleteResult, BulkOperationCommitOptions, BulkOperationDryRunOptions, BulkOperationDryRunResult, BulkOperationOptions, BulkRetryFailedResult, BulkSignalAllCommitOptions, BulkSignalAllDryRunOptions, BulkSignalResult, BulkTagResult, ListFilter, PurgeResult } from '../types.ts';
|
|
2
2
|
import { type CleanupWaiters } from './bulk-operations-purge.ts';
|
|
3
3
|
import type { EngineInternals } from './internals.ts';
|
|
4
|
+
import type { LifecycleCallbacks } from './lifecycle.ts';
|
|
4
5
|
export { purgeInternal, TERMINAL_CLEANUP_DELAY_MS } from './bulk-operations-purge.ts';
|
|
5
6
|
export declare function purge(internals: EngineInternals, filter: ListFilter | undefined, cleanupWaiters: CleanupWaiters): Promise<PurgeResult>;
|
|
6
7
|
export declare function cancelAll(internals: EngineInternals, filter: ListFilter, options: BulkOperationDryRunOptions): Promise<BulkOperationDryRunResult>;
|
|
7
8
|
export declare function cancelAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationCommitOptions): Promise<BulkCancelResult>;
|
|
8
9
|
export declare function cancelAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationDryRunOptions | BulkOperationCommitOptions): Promise<BulkCancelResult | BulkOperationDryRunResult>;
|
|
9
10
|
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options: BulkOperationDryRunOptions): Promise<BulkOperationDryRunResult>;
|
|
10
|
-
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationCommitOptions): Promise<BulkRetryFailedResult>;
|
|
11
|
-
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationDryRunOptions | BulkOperationCommitOptions): Promise<BulkRetryFailedResult | BulkOperationDryRunResult>;
|
|
11
|
+
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationCommitOptions, callbacks?: LifecycleCallbacks): Promise<BulkRetryFailedResult>;
|
|
12
|
+
export declare function retryFailedAll(internals: EngineInternals, filter: ListFilter, options?: BulkOperationDryRunOptions | BulkOperationCommitOptions, callbacks?: LifecycleCallbacks): Promise<BulkRetryFailedResult | BulkOperationDryRunResult>;
|
|
12
13
|
export declare function signalAll(internals: EngineInternals, filter: ListFilter, name: string, payload: unknown, options: BulkSignalAllDryRunOptions): Promise<BulkOperationDryRunResult>;
|
|
13
14
|
export declare function signalAll(internals: EngineInternals, filter: ListFilter, name: string, payload: unknown, options: BulkSignalAllCommitOptions): Promise<BulkSignalResult>;
|
|
14
15
|
export declare function signalAll(internals: EngineInternals, filter: ListFilter, name: string, payload?: unknown, options?: BulkOperationCommitOptions): Promise<BulkSignalResult>;
|