@lostgradient/weft 0.24.0 → 0.24.1
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.js +14 -20
- package/dist/core/engine/bulk-operations-retry.d.ts +9 -0
- package/dist/core/engine/bulk-operations-retry.js +177 -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.js +8 -167
- package/dist/core/engine/callback-creators-bundles.js +4 -2
- 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/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/index.d.ts +18 -10
- package/dist/core/engine/index.js +21 -10
- 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-revision-groups.d.ts +75 -0
- package/dist/core/engine/lifecycle/recovery-revision-groups.js +64 -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.js +18 -9
- package/dist/core/engine/lifecycle/shared.d.ts +43 -0
- package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +12 -0
- package/dist/core/engine/lifecycle/standalone-claim-acquire.js +6 -0
- 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 +22 -7
- package/dist/core/engine/lifecycle/start-commit.js +49 -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.js +1 -1
- package/dist/core/engine/lifecycle/start-precondition-attribution.d.ts +49 -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-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 +66 -3
- package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +13 -3
- package/dist/core/engine/lifecycle/start.d.ts +16 -6
- package/dist/core/engine/lifecycle/start.js +38 -68
- package/dist/core/engine/lifecycle/transition.d.ts +3 -4
- package/dist/core/engine/lifecycle/transition.js +30 -80
- package/dist/core/engine/lifecycle.d.ts +4 -2
- package/dist/core/engine/lifecycle.js +6 -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-revision-fire.d.ts +17 -0
- package/dist/core/engine/schedule-revision-fire.js +3 -0
- package/dist/core/engine/schedule-run.js +2 -1
- package/dist/core/engine/schedules.d.ts +2 -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/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/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 +4 -3
- package/dist/core/engine/validation/schedule.js +15 -26
- package/dist/core/engine/validation.js +3 -0
- package/dist/core/engine/workflow-claim-reclaim-target.d.ts +1 -1
- package/dist/core/engine/workflow-claim-reclaim-target.js +16 -2
- 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/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/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 +1 -1
- 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/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/runtime/task-dispatch-envelope.d.ts +14 -0
- package/dist/server/runtime/task-dispatch-envelope.js +32 -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.js +12 -37
- 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 +9 -0
- package/dist/server/task-ledger-codec.js +4 -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 +2 -1
- package/dist/server/task-queue-types.d.ts +2 -0
- package/dist/storage/bun-sql.js +3 -1
- 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/index.d.ts +3 -1
- package/dist/storage/interface.d.ts +2 -0
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +1 -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 +2 -2
- package/dist/storage/node-sqlite.js +3 -1
- package/dist/storage/postgres.js +2 -2
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +1 -1
- 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,15 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
KEYS,
|
|
3
|
-
requireStorageCapability,
|
|
4
|
-
storageHas
|
|
5
|
-
} from "../../storage/interface.js";
|
|
1
|
+
import { KEYS, storageHas } from "../../storage/interface.js";
|
|
6
2
|
import { assertScopedBulkWorkflowFilter } from "../bulk-workflow-filter.js";
|
|
7
|
-
import {
|
|
8
|
-
import { decode, encode } from "../codec.js";
|
|
9
|
-
import { buildTimerBatchOperations } from "../scheduler.js";
|
|
10
|
-
import { buildIndexOperations } from "../search-attributes.js";
|
|
11
|
-
import { buildTerminalWorkflowIndexOperations, bulkMutateWorkflowTags } from "./attributes-tags.js";
|
|
3
|
+
import { bulkMutateWorkflowTags } from "./attributes-tags.js";
|
|
12
4
|
import { purgeInternal, purgeWorkflow } from "./bulk-operations-purge.js";
|
|
5
|
+
import { runBulkFailedWorkflowRetry } from "./bulk-operations-retry.js";
|
|
13
6
|
import {
|
|
14
7
|
buildActionableBulkWorkflowFilter,
|
|
15
8
|
buildBulkOperationPreparation,
|
|
@@ -17,43 +10,19 @@ import {
|
|
|
17
10
|
normalizeBulkOperationOptions,
|
|
18
11
|
prepareBulkOperation,
|
|
19
12
|
resolveBulkOperationConcurrency,
|
|
13
|
+
runBulkWorkflowPool,
|
|
20
14
|
shouldPersistBulkAudit,
|
|
21
15
|
toBulkOperationError,
|
|
22
16
|
validateBulkConfirmation,
|
|
23
17
|
withBulkAuditEvent
|
|
24
18
|
} from "./bulk-operations-shared.js";
|
|
25
19
|
import { BulkDeleteRequiresTerminalWorkflowsError } from "./errors.js";
|
|
26
|
-
import {
|
|
27
|
-
assertLeaseHeldForEngineWork,
|
|
28
|
-
commitFencedEngineWriteAllowingPreconditionFailure
|
|
29
|
-
} from "./fenced-write.js";
|
|
20
|
+
import { assertLeaseHeldForEngineWork } from "./fenced-write.js";
|
|
30
21
|
import { BULK_OPERATION_BATCH_SIZE } from "./listing.js";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import { decodeWorkflowState, isTerminalWorkflowStatus } from "./validation.js";
|
|
34
|
-
import {
|
|
35
|
-
commitWithWorkflowClaimFold,
|
|
36
|
-
prepareWorkflowClaimFold,
|
|
37
|
-
throwWorkflowClaimUnavailable
|
|
38
|
-
} from "./workflow-claim-fold.js";
|
|
39
|
-
import { buildWorkflowConcurrencyStartOperations } from "./workflow-concurrency.js";
|
|
40
|
-
import { buildWorkflowVisibilityIndexTransition } from "./workflow-indexes.js";
|
|
22
|
+
import { loadWorkflowState } from "./storage-io.js";
|
|
23
|
+
import { isTerminalWorkflowStatus } from "./validation.js";
|
|
41
24
|
export { purgeInternal, TERMINAL_CLEANUP_DELAY_MS } from "./bulk-operations-purge.js";
|
|
42
|
-
const ACTIVE_WORKFLOW_STATUSES = ["pending", "running", "suspended"]
|
|
43
|
-
async function runBulkWorkflowPool(items, concurrencyLimit, operation) {
|
|
44
|
-
const results = [];
|
|
45
|
-
for (let batchStart = 0;batchStart < items.length; batchStart += concurrencyLimit) {
|
|
46
|
-
const batchItems = items.slice(batchStart, batchStart + concurrencyLimit);
|
|
47
|
-
results.push(...await Promise.all(batchItems.map(async (item) => {
|
|
48
|
-
try {
|
|
49
|
-
return { item, status: "fulfilled", value: await operation(item) };
|
|
50
|
-
} catch (reason) {
|
|
51
|
-
return { item, status: "rejected", reason };
|
|
52
|
-
}
|
|
53
|
-
})));
|
|
54
|
-
}
|
|
55
|
-
return results;
|
|
56
|
-
}
|
|
25
|
+
const ACTIVE_WORKFLOW_STATUSES = ["pending", "running", "suspended"];
|
|
57
26
|
export async function purge(internals, filter, cleanupWaiters) {
|
|
58
27
|
return purgeInternal(internals, filter, { expiredOnly: !1, now: internals.options.getNow() }, cleanupWaiters);
|
|
59
28
|
}
|
|
@@ -95,138 +64,10 @@ async function runBulkCancellation(internals, filter, options = {}) {
|
|
|
95
64
|
export async function cancelAll(internals, filter, options = {}) {
|
|
96
65
|
return runBulkCancellation(internals, filter, options);
|
|
97
66
|
}
|
|
98
|
-
async function runBulkFailedWorkflowRetry(internals, filter, options = {}) {
|
|
99
|
-
options = normalizeBulkOperationOptions(options);
|
|
100
|
-
assertScopedBulkWorkflowFilter(filter);
|
|
101
|
-
const actionableFilter = buildActionableBulkWorkflowFilter(internals, filter, FAILED_WORKFLOW_STATUSES), preparation = await prepareBulkOperation(internals, "retry-failed", actionableFilter, filter, {}, options);
|
|
102
|
-
if (options.dryRun === !0)
|
|
103
|
-
return preparation.preview;
|
|
104
|
-
validateBulkConfirmation(options, preparation);
|
|
105
|
-
const bulkConcurrency = resolveBulkOperationConcurrency(options);
|
|
106
|
-
let retried = 0;
|
|
107
|
-
const errors = [], retryResults = await runBulkWorkflowPool(preparation.workflowIds, bulkConcurrency, async (workflowId) => {
|
|
108
|
-
await retryFailedWorkflow(internals, workflowId);
|
|
109
|
-
return { status: "retried" };
|
|
110
|
-
});
|
|
111
|
-
for (const retryResult of retryResults) {
|
|
112
|
-
if (retryResult.status === "rejected") {
|
|
113
|
-
errors.push(toBulkOperationError(internals, retryResult.item, retryResult.reason));
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
retried += 1;
|
|
117
|
-
}
|
|
118
|
-
const result = { retried, failed: errors.length, errors };
|
|
119
|
-
if (!shouldPersistBulkAudit(options))
|
|
120
|
-
return result;
|
|
121
|
-
return withBulkAuditEvent(internals, preparation, options, result, retried);
|
|
122
|
-
}
|
|
123
67
|
export async function retryFailedAll(internals, filter, options = {}) {
|
|
124
68
|
assertLeaseHeldForEngineWork(internals);
|
|
125
69
|
return runBulkFailedWorkflowRetry(internals, filter, options);
|
|
126
70
|
}
|
|
127
|
-
async function retryFailedWorkflow(internals, workflowId) {
|
|
128
|
-
const state = await loadWorkflowState(internals, workflowId);
|
|
129
|
-
if (state === null)
|
|
130
|
-
throw Error("Workflow no longer exists");
|
|
131
|
-
if (state.status !== "failed")
|
|
132
|
-
throw Error(`Workflow is ${state.status}, not failed`);
|
|
133
|
-
if (await internals.storage.get(KEYS.checkpoint(workflowId)) !== null) {
|
|
134
|
-
await reactivateFailedWorkflowFromCheckpoint(internals, state);
|
|
135
|
-
await internals.engine.resume(workflowId);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
await internals.engine.start(state.type, state.input, {
|
|
139
|
-
id: workflowId,
|
|
140
|
-
onTerminalConflict: "start-new",
|
|
141
|
-
...state.tags !== void 0 ? { tags: state.tags } : {}
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
async function reactivateFailedWorkflowFromCheckpoint(internals, state) {
|
|
145
|
-
const reactivated = await runSerializedWorkflowStateWrite(internals, state.id, async () => reactivateFailedWorkflowFromCheckpointSerialized(internals, state.id));
|
|
146
|
-
if (reactivated.terminalCleanupTimerId !== void 0)
|
|
147
|
-
await internals.scheduler.cancel(reactivated.terminalCleanupTimerId, state.id);
|
|
148
|
-
}
|
|
149
|
-
async function reactivateFailedWorkflowFromCheckpointSerialized(internals, workflowId) {
|
|
150
|
-
let lastConcurrencyStateKey;
|
|
151
|
-
for (let attempt = 0;attempt < CHECKPOINT_RETRY_CONCURRENCY_ADMISSION_MAX_ATTEMPTS; attempt += 1) {
|
|
152
|
-
const currentStateBytes = await internals.storage.get(KEYS.workflow(workflowId));
|
|
153
|
-
if (currentStateBytes === null)
|
|
154
|
-
throw Error("Workflow no longer exists");
|
|
155
|
-
const currentState = decodeWorkflowState(currentStateBytes);
|
|
156
|
-
if (currentState.status !== "failed")
|
|
157
|
-
throw Error(`Workflow is ${currentState.status}, not failed`);
|
|
158
|
-
const currentCheckpointBytes = await internals.storage.get(KEYS.checkpoint(workflowId));
|
|
159
|
-
if (currentCheckpointBytes === null)
|
|
160
|
-
throw Error("Checkpoint no longer exists");
|
|
161
|
-
const checkpoint = deserializeCheckpoint(currentCheckpointBytes), registration = internals.registrations.get(currentState.type);
|
|
162
|
-
if (registration === void 0)
|
|
163
|
-
throw Error(`No workflow registered with name "${currentState.type}" (needed to retry "${workflowId}")`);
|
|
164
|
-
const concurrencyStartOperations = registration.concurrency === void 0 ? void 0 : await buildWorkflowConcurrencyStartOperations(internals, currentState.type, workflowId, currentState.input, registration.concurrency);
|
|
165
|
-
lastConcurrencyStateKey = concurrencyStartOperations?.stateKey ?? lastConcurrencyStateKey;
|
|
166
|
-
const reactivatedState = buildReactivatedWorkflowState(internals, currentState), currentAttributes = await loadSearchAttributes(internals, workflowId), operations = [
|
|
167
|
-
...buildTerminalWorkflowIndexOperations(currentState, reactivatedState),
|
|
168
|
-
{ type: "put", key: KEYS.workflow(workflowId), value: encode(reactivatedState) },
|
|
169
|
-
...buildWorkflowVisibilityIndexTransition(workflowId, currentState, reactivatedState).batchOps,
|
|
170
|
-
...buildRetrySearchAttributeOperations(workflowId, currentAttributes, checkpoint.searchAttributes),
|
|
171
|
-
...reactivatedState.executionDeadline === void 0 ? [] : buildTimerBatchOperations({
|
|
172
|
-
id: `deadline:${workflowId}`,
|
|
173
|
-
workflowId,
|
|
174
|
-
fireAt: reactivatedState.executionDeadline,
|
|
175
|
-
kind: "execution-deadline"
|
|
176
|
-
}),
|
|
177
|
-
...concurrencyStartOperations?.operations ?? []
|
|
178
|
-
], conditions = concurrencyStartOperations?.conditions ?? [];
|
|
179
|
-
if (await commitFailedWorkflowReactivation(internals, workflowId, operations, conditions))
|
|
180
|
-
return {
|
|
181
|
-
terminalCleanupTimerId: currentState.terminalCleanupToken === void 0 ? void 0 : createTerminalCleanupTimerId(!1, currentState.terminalCleanupToken)
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
throw Error(`Workflow concurrency admission for "${lastConcurrencyStateKey ?? workflowId}" changed too many times while retrying failed workflow "${workflowId}"`);
|
|
185
|
-
}
|
|
186
|
-
async function commitFailedWorkflowReactivation(internals, workflowId, operations, conditions) {
|
|
187
|
-
const claimFold = await prepareWorkflowClaimFold(internals, workflowId);
|
|
188
|
-
if (claimFold === void 0) {
|
|
189
|
-
if (conditions.length > 0)
|
|
190
|
-
requireStorageCapability(internals.storage, "conditionalBatch", "retry failed workflow");
|
|
191
|
-
return commitFencedEngineWriteAllowingPreconditionFailure(internals, workflowId, operations, conditions);
|
|
192
|
-
}
|
|
193
|
-
const result = await commitWithWorkflowClaimFold(internals, claimFold, operations, conditions, "retry failed workflow claim acquisition");
|
|
194
|
-
if (result.status === "committed")
|
|
195
|
-
return !0;
|
|
196
|
-
return result.claimConflict ? throwWorkflowClaimUnavailable(internals, workflowId) : !1;
|
|
197
|
-
}
|
|
198
|
-
function buildReactivatedWorkflowState(internals, state) {
|
|
199
|
-
const reactivatedState = {
|
|
200
|
-
...state,
|
|
201
|
-
status: "running",
|
|
202
|
-
updatedAt: internals.options.getNow()
|
|
203
|
-
};
|
|
204
|
-
delete reactivatedState.error;
|
|
205
|
-
delete reactivatedState.errorStack;
|
|
206
|
-
delete reactivatedState.failureCategory;
|
|
207
|
-
delete reactivatedState.result;
|
|
208
|
-
delete reactivatedState.terminationReason;
|
|
209
|
-
delete reactivatedState.terminalCleanupToken;
|
|
210
|
-
return reactivatedState;
|
|
211
|
-
}
|
|
212
|
-
async function loadSearchAttributes(internals, workflowId) {
|
|
213
|
-
const attributeBytes = await internals.storage.get(KEYS.attribute(workflowId));
|
|
214
|
-
if (attributeBytes === null)
|
|
215
|
-
return {};
|
|
216
|
-
return decode(attributeBytes);
|
|
217
|
-
}
|
|
218
|
-
function buildRetrySearchAttributeOperations(workflowId, currentAttributes, checkpointAttributes) {
|
|
219
|
-
const operations = buildIndexOperations(workflowId, currentAttributes, checkpointAttributes);
|
|
220
|
-
if (Object.keys(checkpointAttributes).length === 0)
|
|
221
|
-
operations.push({ type: "delete", key: KEYS.attribute(workflowId) });
|
|
222
|
-
else
|
|
223
|
-
operations.push({
|
|
224
|
-
type: "put",
|
|
225
|
-
key: KEYS.attribute(workflowId),
|
|
226
|
-
value: encode(checkpointAttributes)
|
|
227
|
-
});
|
|
228
|
-
return operations;
|
|
229
|
-
}
|
|
230
71
|
export async function signalAll(internals, filter, name, payload, maybeOptions) {
|
|
231
72
|
const options = normalizeBulkOperationOptions(maybeOptions ?? {});
|
|
232
73
|
assertScopedBulkWorkflowFilter(filter);
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
createReviewOperationCallbacks,
|
|
17
17
|
handleScheduleTimerForEngine
|
|
18
18
|
} from "./callback-creators-schedule.js";
|
|
19
|
+
import { resolveExecutableRegistrationForRevision } from "./dynamic-source-execution.js";
|
|
19
20
|
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
20
21
|
import { guardTerminalWorkflow, guardTerminalWorkflowAfterCoordinatedRequest } from "./guards.js";
|
|
21
22
|
import { getInternals } from "./internals.js";
|
|
@@ -122,7 +123,7 @@ export function createTimeOperationCallbacks(engine) {
|
|
|
122
123
|
loadWorkflowState: (workflowId) => loadWorkflowState(getInternals(engine), workflowId),
|
|
123
124
|
failWorkflow: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine)),
|
|
124
125
|
runSerializedWorkflowStateWrite: (workflowId, writeOperation) => runSerializedWorkflowStateWrite(getInternals(engine), workflowId, writeOperation),
|
|
125
|
-
beginWorkflowExecution: (workflowId, workflowExecutionToken, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, registration) => beginWorkflowExecution(getInternals(engine), workflowId, workflowExecutionToken, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, registration, createLifecycleCallbacks(engine)),
|
|
126
|
+
beginWorkflowExecution: (workflowId, workflowExecutionToken, workflowType, revision, input, checkpoint, executionDeadline, executionStateOwnerId, registration) => beginWorkflowExecution(getInternals(engine), workflowId, workflowExecutionToken, workflowType, revision, input, checkpoint, executionDeadline, executionStateOwnerId, registration, createLifecycleCallbacks(engine)),
|
|
126
127
|
workflowVersionTupleFromState: (state) => workflowVersionTupleFromState(getInternals(engine), state, createLifecycleCallbacks(engine)),
|
|
127
128
|
setWorkflowStartHeaders: (workflowId, headers) => setWorkflowStartHeaders(getInternals(engine), workflowId, headers, createLifecycleCallbacks(engine)),
|
|
128
129
|
loadWorkflowStartHeaders: (workflowId) => loadWorkflowStartHeaders(getInternals(engine), workflowId, createLifecycleCallbacks(engine)),
|
|
@@ -131,7 +132,8 @@ export function createTimeOperationCallbacks(engine) {
|
|
|
131
132
|
runWorkflowFinalizer: (workflowId, timerId) => runWorkflowFinalizer(getInternals(engine), workflowId, timerId, createTerminationCallbacks(engine)),
|
|
132
133
|
handleScheduleTimer: (entry) => handleScheduleTimerForEngine(engine, entry),
|
|
133
134
|
timeout: (workflowId) => engine.timeout(workflowId),
|
|
134
|
-
handleCleanupError: (source, error, workflowId) => createTerminationCallbacks(engine).handleCleanupError(source, error, workflowId)
|
|
135
|
+
handleCleanupError: (source, error, workflowId) => createTerminationCallbacks(engine).handleCleanupError(source, error, workflowId),
|
|
136
|
+
resolveExecutableRegistrationForRevision: (type, revision) => resolveExecutableRegistrationForRevision(engine, getInternals(engine), type, revision)
|
|
135
137
|
};
|
|
136
138
|
}
|
|
137
139
|
export function createUpdateCallbacks(engine) {
|
|
@@ -3,6 +3,10 @@ import {
|
|
|
3
3
|
broadcast as broadcastFromInternals,
|
|
4
4
|
forwardEventToHandle as forwardEventToHandleFromBroadcast
|
|
5
5
|
} from "./broadcast.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveExecutableRegistration,
|
|
8
|
+
resolveExecutableRegistrationForRevision
|
|
9
|
+
} from "./dynamic-source-execution.js";
|
|
6
10
|
import { createWorkflowHandleWithResultPromise } from "./handle-result.js";
|
|
7
11
|
import {
|
|
8
12
|
hasLocalCheckpointOwnership,
|
|
@@ -79,7 +83,11 @@ export function createLifecycleCallbacks(engine) {
|
|
|
79
83
|
failWorkflowForUnavailableServices: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system"),
|
|
80
84
|
failWorkflowForRecoveryHook: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system"),
|
|
81
85
|
failWorkflowForCheckpointDecodeError: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system"),
|
|
82
|
-
failWorkflowForVersionMismatch: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system")
|
|
86
|
+
failWorkflowForVersionMismatch: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system"),
|
|
87
|
+
resolveExecutableRegistration: (type, onRevisionChosen) => resolveExecutableRegistration(engine, getInternals(engine), type, onRevisionChosen),
|
|
88
|
+
failWorkflowForUnavailableDynamicSource: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system"),
|
|
89
|
+
resolveExecutableRegistrationForRevision: (type, revision) => resolveExecutableRegistrationForRevision(engine, getInternals(engine), type, revision),
|
|
90
|
+
failWorkflowForRevisionUnavailable: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system")
|
|
83
91
|
};
|
|
84
92
|
}
|
|
85
93
|
export function createTerminationCallbacksWith(engine, handleScheduledWorkflowTerminal) {
|
|
@@ -20,8 +20,8 @@ import { feedOperationResult } from "./strategy-helpers.js";
|
|
|
20
20
|
import { ensureTerminalCleanupTracked, failWorkflow, handleCleanupError } from "./termination.js";
|
|
21
21
|
export function createScheduleCallbacks(engine) {
|
|
22
22
|
return {
|
|
23
|
-
startWorkflow: async (type, input, options, additionalStartOperations) => {
|
|
24
|
-
await startWorkflow(getInternals(engine), type, input, options, additionalStartOperations, createLifecycleCallbacks(engine));
|
|
23
|
+
startWorkflow: async (type, input, options, additionalStartOperations, revisionOverride) => {
|
|
24
|
+
await startWorkflow(getInternals(engine), type, input, options, additionalStartOperations, createLifecycleCallbacks(engine), void 0, revisionOverride);
|
|
25
25
|
},
|
|
26
26
|
loadWorkflowState: (workflowId) => loadWorkflowState(getInternals(engine), workflowId),
|
|
27
27
|
cancelWorkflow: (workflowId) => engine.cancel(workflowId),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { restoreWorkflowCatalog, WorkflowCatalog } from "../catalog/index.js";
|
|
2
2
|
import { buildWorkflowManifestForType } from "../registry-workflow-manifest.js";
|
|
3
3
|
import { dispatchCatalogInstallAndActivatedEvents } from "./catalog-events.js";
|
|
4
|
+
import { resolveOrphanedCatalogTombstones } from "./catalog-tombstone-recovery.js";
|
|
4
5
|
import { EngineDisposedError } from "./errors.js";
|
|
5
6
|
import { getInternals } from "./internals.js";
|
|
6
7
|
export { getWorkflowCatalog } from "./internals.js";
|
|
@@ -44,6 +45,7 @@ export async function ensureWorkflowCatalogReady(engine) {
|
|
|
44
45
|
if (internals.disposed)
|
|
45
46
|
throw new EngineDisposedError;
|
|
46
47
|
if (!internals.catalogRestored) {
|
|
48
|
+
await resolveOrphanedCatalogTombstones(internals.storage);
|
|
47
49
|
internals.workflowCatalog = new WorkflowCatalog(internals.storage, await restoreWorkflowCatalog(internals.storage));
|
|
48
50
|
internals.catalogRestored = !0;
|
|
49
51
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { type WorkflowRevisionReferenceCounts } from '../catalog/index.ts';
|
|
16
16
|
import type { Engine } from './index.ts';
|
|
17
17
|
import { type EngineInternals } from './internals.ts';
|
|
18
|
+
type RegistrationEntry = EngineInternals['registrations'] extends Map<string, infer Entry> ? Entry : never;
|
|
18
19
|
/**
|
|
19
20
|
* Reserve one in-flight-start slot against `type`'s currently active
|
|
20
21
|
* revision (if any), returning the revision reserved (or `undefined` when
|
|
@@ -25,15 +26,70 @@ import { type EngineInternals } from './internals.ts';
|
|
|
25
26
|
* caller and passed back to {@link releaseInFlightStart} in its `finally` —
|
|
26
27
|
* never re-resolved, so a concurrent activation moving the pointer
|
|
27
28
|
* mid-start cannot decrement a different revision than was incremented.
|
|
29
|
+
*
|
|
30
|
+
* `revisionOverride` (WFT-15/16) is the revision `resolveExecutableRegistration()`
|
|
31
|
+
* already resolved for a `registerSource()`-registered `type` — passed
|
|
32
|
+
* through instead of re-deriving via `catalog.resolveActive(type)`, which
|
|
33
|
+
* would read `undefined` for a dynamic source with no catalog active
|
|
34
|
+
* pointer set (a source can resolve and install without ever being
|
|
35
|
+
* `engine.workflows.activate()`-d), making `inFlightStarts` diagnostics
|
|
36
|
+
* silently miss a lazy run in flight. `undefined` (the default) preserves
|
|
37
|
+
* the eager-path behavior byte-for-byte.
|
|
38
|
+
*/
|
|
39
|
+
export declare function reserveInFlightStart(internals: EngineInternals, type: string, revisionOverride?: string): string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* `startWorkflow`'s combined "resolve `type`, reserve an `inFlightStarts`
|
|
42
|
+
* slot for the resolved revision" step. Reserves EARLY — before the loader
|
|
43
|
+
* is awaited, via `resolve()`'s `onRevisionChosen` hook — for a lazy type,
|
|
44
|
+
* closing the window where a concurrent `removeWorkflowRevision()` could
|
|
45
|
+
* observe zero references against a revision this call's own source load
|
|
46
|
+
* is about to durably (re)install; reserves afterward for an eager type,
|
|
47
|
+
* exactly as before WFT-15/16 (that path never invokes the hook). If the
|
|
48
|
+
* loader then fails — `resolve()` rejects AFTER the hook already fired — the
|
|
49
|
+
* early reservation is released here before rethrowing: `startWorkflow`
|
|
50
|
+
* only assigns its own `inFlightRevision` on a successful return, so its
|
|
51
|
+
* `finally` releases nothing on this path and the increment would otherwise
|
|
52
|
+
* leak forever, permanently reporting the revision non-`removable`.
|
|
53
|
+
*
|
|
54
|
+
* `resolvedRevision` is the raw value `resolve()` itself returned —
|
|
55
|
+
* `undefined` for an eager registration, the resolved candidate for a
|
|
56
|
+
* dynamic source — distinct from `inFlightRevision`, which for an eager
|
|
57
|
+
* type falls back to the catalog's cached ACTIVE pointer (see
|
|
58
|
+
* {@link reserveInFlightStart}). A caller that needs "the exact revision of
|
|
59
|
+
* the code this process is about to run" (WFT-17's `WorkflowState.revision`)
|
|
60
|
+
* must use `resolvedRevision`, not `inFlightRevision` — the two intentionally
|
|
61
|
+
* diverge for an eager type under a multi-engine deployment where this
|
|
62
|
+
* process's own registration lags the durable active pointer.
|
|
28
63
|
*/
|
|
29
|
-
export declare function
|
|
64
|
+
export declare function resolveAndReserveExecutableRegistration(internals: EngineInternals, type: string, resolve: (type: string, onRevisionChosen?: (revision: string) => void) => Promise<{
|
|
65
|
+
entry: RegistrationEntry;
|
|
66
|
+
revision: string | undefined;
|
|
67
|
+
}>): Promise<{
|
|
68
|
+
registration: RegistrationEntry;
|
|
69
|
+
inFlightRevision: string | undefined;
|
|
70
|
+
resolvedRevision: string | undefined;
|
|
71
|
+
}>;
|
|
30
72
|
/** Release the slot {@link reserveInFlightStart} reserved; a no-op when `revision` is `undefined`. */
|
|
31
73
|
export declare function releaseInFlightStart(internals: EngineInternals, type: string, revision: string | undefined): void;
|
|
32
74
|
/**
|
|
33
75
|
* Count every in-process reference this batch wires to a real signal
|
|
34
76
|
* against `(name, revision)`. `registeredDefinitions` and `inFlightStarts`
|
|
35
|
-
* are
|
|
36
|
-
*
|
|
77
|
+
* are in-memory, constant-time lookups; `nonTerminalRuns` (WFT-17 — see
|
|
78
|
+
* {@link countNonTerminalRunsForRevision}) is an UNBOUNDED, full `wf:`-prefix
|
|
79
|
+
* durable scan — the same shape `groupActiveWorkflowsByType` already uses
|
|
80
|
+
* elsewhere for this class of diagnostic, so it's consistent with how this
|
|
81
|
+
* codebase already pays for this, but it is O(n) in the total number of
|
|
82
|
+
* workflow records, not bounded by `(name, revision)`. `removeWorkflowRevision()`
|
|
83
|
+
* calls this TWICE per removal attempt (its own pre-check, and
|
|
84
|
+
* {@link finalizeRevisionRemoval}'s post-check) — worth knowing before
|
|
85
|
+
* wiring `removeWorkflowRevision()` into an automated cleanup path on a
|
|
86
|
+
* large durable store, rather than the operator-triggered use this batch
|
|
87
|
+
* assumes. `pinnedSchedules` is ALSO an unbounded, full `schedule:`-prefix
|
|
88
|
+
* durable scan (WFT-20 — see {@link countPinnedSchedulesForRevision}), so
|
|
89
|
+
* this function now pays for two bounded-but-unbounded-in-store-size scans
|
|
90
|
+
* per call. The remaining two fields of {@link WorkflowRevisionReferenceCounts}
|
|
91
|
+
* stay `0` — the dispatch ledger and execution realms are out of this
|
|
92
|
+
* batch's scope.
|
|
37
93
|
*/
|
|
38
94
|
export declare function countWorkflowRevisionReferences(engine: Engine, name: string, revision: string): Promise<WorkflowRevisionReferenceCounts>;
|
|
39
95
|
/**
|
|
@@ -76,6 +132,39 @@ export type WorkflowCatalogRemovalResult = Readonly<{
|
|
|
76
132
|
* compare-and-swap loses to a concurrent writer — the caller may re-read
|
|
77
133
|
* and retry. Dispatches `catalog:revision-removed` on success.
|
|
78
134
|
*
|
|
135
|
+
* Checks references BOTH before and after the durable delete (WFT-17,
|
|
136
|
+
* Codex review on PR #958). The pre-check above is an OPTIMIZATION — it
|
|
137
|
+
* refuses upfront in the common case, avoiding a needless delete-then-restore
|
|
138
|
+
* round trip — but is not, on its own, the guarantee: a concurrent
|
|
139
|
+
* `engine.start()` on a DIFFERENT process could read the entry as still
|
|
140
|
+
* installed, and commit a NEW run pinned to `revision`, in the narrow
|
|
141
|
+
* window between this function's pre-check scan and its own delete CAS
|
|
142
|
+
* landing; the pre-check's zero-references snapshot would then be stale
|
|
143
|
+
* by the time the delete actually commits. The POST-check closes this: it
|
|
144
|
+
* is safe to trust because `start-commit.ts`'s own start batch fences on
|
|
145
|
+
* this exact catalog entry's bytes (`buildCatalogEntryStartPrecondition`)
|
|
146
|
+
* — `conditionalBatch`'s precondition is evaluated against LIVE storage
|
|
147
|
+
* state at commit time, so ANY start whose commit lands after this
|
|
148
|
+
* function's delete necessarily loses its own CAS and never creates the
|
|
149
|
+
* reference at all. A nonzero post-delete count can therefore only be a
|
|
150
|
+
* run that committed BEFORE the delete — this function restores the entry
|
|
151
|
+
* and reports `'referenced'` rather than leaving a real, still-referenced
|
|
152
|
+
* run pinned to a revision the catalog no longer carries.
|
|
153
|
+
*
|
|
154
|
+
* `catalog.remove()`'s delete and `finalizeRevisionRemoval`'s restore-or-
|
|
155
|
+
* finalize used to be two SEPARATE commits (WFT-17/18, Codex review on PR
|
|
156
|
+
* #958): a process crash between them left the revision durably uninstalled
|
|
157
|
+
* with NO durable record of what was deleted, recoverable by no one. Both
|
|
158
|
+
* halves of `removeCatalogEntry` (the delete) now land in ONE
|
|
159
|
+
* `conditionalBatch` with a `catalog-tombstone:<name>:<revision>` record
|
|
160
|
+
* (the exact deleted bytes) — see `core/catalog/removal.ts`'s module doc —
|
|
161
|
+
* so this function's own restore-or-finalize below is itself a SECOND
|
|
162
|
+
* atomic operation (not a bare `install()`/no-op), and a crash between the
|
|
163
|
+
* two commits leaves a durable tombstone ANY process can resolve, not just
|
|
164
|
+
* this one. `catalog-tombstone-recovery.ts`'s boot-time sweep and this
|
|
165
|
+
* function's own targeted pre-check (below) both close that residual
|
|
166
|
+
* window; see that module's doc for the full mechanism.
|
|
167
|
+
*
|
|
79
168
|
* @example
|
|
80
169
|
* ```ts
|
|
81
170
|
* import { Engine, removeWorkflowRevision, workflow } from '@lostgradient/weft';
|
|
@@ -111,6 +200,20 @@ export type WorkflowRevisionDiagnostics = Readonly<{
|
|
|
111
200
|
activeRevision?: string;
|
|
112
201
|
references: WorkflowRevisionReferenceCounts;
|
|
113
202
|
removable: boolean;
|
|
203
|
+
/**
|
|
204
|
+
* Dynamic-source load diagnostics (WFT-15/16), present only when `name`
|
|
205
|
+
* has an entry in `internals.sources.byName` — i.e. `name` has ever been
|
|
206
|
+
* `registerSource()`-registered on this engine. `undefined` for a purely
|
|
207
|
+
* eager name, or a dynamic source this process has never heard of.
|
|
208
|
+
*/
|
|
209
|
+
source?: {
|
|
210
|
+
kind: import('../source/index.ts').WorkflowSourceKind;
|
|
211
|
+
requestedRevision: string;
|
|
212
|
+
state: import('./source-runtime-state.ts').SourceLoadState;
|
|
213
|
+
loadDurationMs?: number;
|
|
214
|
+
lastFailureCategory?: import('../types/identity.ts').FailureCategory;
|
|
215
|
+
waiterCount: number;
|
|
216
|
+
};
|
|
114
217
|
}>;
|
|
115
218
|
/**
|
|
116
219
|
* Bounded diagnostics for one `(name, revision)`: whether it is installed,
|
|
@@ -130,3 +233,4 @@ export type WorkflowRevisionDiagnostics = Readonly<{
|
|
|
130
233
|
* ```
|
|
131
234
|
*/
|
|
132
235
|
export declare function getWorkflowRevisionDiagnostics(engine: Engine, name: string, revision: string): Promise<WorkflowRevisionDiagnostics>;
|
|
236
|
+
export {};
|
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
import {
|
|
2
2
|
decrementNestedRevisionCount,
|
|
3
|
+
finalizeCatalogTombstone,
|
|
3
4
|
incrementNestedRevisionCount,
|
|
4
5
|
readNestedRevisionCount,
|
|
6
|
+
restoreCatalogEntryFromTombstone,
|
|
5
7
|
totalWorkflowRevisionReferences
|
|
6
8
|
} from "../catalog/index.js";
|
|
7
9
|
import { WorkflowRevisionRemovedEvent } from "../events/catalog-events.js";
|
|
8
10
|
import { ensureWorkflowCatalogReady, getWorkflowCatalog } from "./catalog-readiness.js";
|
|
11
|
+
import { resolveCatalogTombstoneIfPresent } from "./catalog-tombstone-recovery.js";
|
|
9
12
|
import { getInternals } from "./internals.js";
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
import { countNonTerminalRunsForRevision } from "./nonterminal-revision-count.js";
|
|
14
|
+
import { countPinnedSchedulesForRevision } from "./pinned-schedule-revision-count.js";
|
|
15
|
+
import { readSourceLoadDiagnostics, readSourceWaiterCount } from "./source-diagnostics.js";
|
|
16
|
+
export function reserveInFlightStart(internals, type, revisionOverride) {
|
|
17
|
+
const revision = revisionOverride ?? internals.workflowCatalog?.resolveActive(type)?.revision;
|
|
12
18
|
if (revision !== void 0)
|
|
13
19
|
incrementNestedRevisionCount(internals.inFlightStartsByRevision, type, revision);
|
|
14
20
|
return revision;
|
|
15
21
|
}
|
|
22
|
+
export async function resolveAndReserveExecutableRegistration(internals, type, resolve) {
|
|
23
|
+
let earlyReservation;
|
|
24
|
+
try {
|
|
25
|
+
const { entry: registration, revision } = await resolve(type, (chosen) => {
|
|
26
|
+
earlyReservation = reserveInFlightStart(internals, type, chosen);
|
|
27
|
+
}), inFlightRevision = earlyReservation !== void 0 ? earlyReservation : reserveInFlightStart(internals, type, revision);
|
|
28
|
+
return { registration, inFlightRevision, resolvedRevision: revision };
|
|
29
|
+
} catch (error) {
|
|
30
|
+
releaseInFlightStart(internals, type, earlyReservation);
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
16
34
|
export function releaseInFlightStart(internals, type, revision) {
|
|
17
35
|
if (revision !== void 0)
|
|
18
36
|
decrementNestedRevisionCount(internals.inFlightStartsByRevision, type, revision);
|
|
19
37
|
}
|
|
20
38
|
export async function countWorkflowRevisionReferences(engine, name, revision) {
|
|
21
|
-
const internals = getInternals(engine);
|
|
39
|
+
const internals = getInternals(engine), nonTerminalRuns = await countNonTerminalRunsForRevision(internals.storage, name, revision), pinnedSchedules = await countPinnedSchedulesForRevision(internals.storage, name, revision);
|
|
22
40
|
return {
|
|
23
41
|
registeredDefinitions: internals.registeredCatalogRevisions.get(name) === revision ? 1 : 0,
|
|
24
42
|
inFlightStarts: readNestedRevisionCount(internals.inFlightStartsByRevision, name, revision),
|
|
25
|
-
nonTerminalRuns
|
|
26
|
-
pinnedSchedules
|
|
43
|
+
nonTerminalRuns,
|
|
44
|
+
pinnedSchedules,
|
|
27
45
|
pendingDispatches: 0,
|
|
28
46
|
activeExecutionRealms: 0,
|
|
29
47
|
retainedRecoveryRecords: 0
|
|
@@ -31,20 +49,20 @@ export async function countWorkflowRevisionReferences(engine, name, revision) {
|
|
|
31
49
|
}
|
|
32
50
|
export async function removeWorkflowRevision(engine, name, revision) {
|
|
33
51
|
await ensureWorkflowCatalogReady(engine);
|
|
34
|
-
const catalog = getWorkflowCatalog(engine);
|
|
52
|
+
const catalog = getWorkflowCatalog(engine), internals = getInternals(engine);
|
|
53
|
+
await resolveCatalogTombstoneIfPresent(internals.storage, name, revision);
|
|
35
54
|
if (!await catalog.hasInstalled(name, revision))
|
|
36
55
|
return { removed: !1, reason: "not-found" };
|
|
37
56
|
const active = await catalog.resolveActiveDurable(name);
|
|
38
57
|
if (active !== void 0 && active.revision === revision)
|
|
39
58
|
return { removed: !1, reason: "active", activeRevision: active.revision };
|
|
40
|
-
const
|
|
41
|
-
if (totalWorkflowRevisionReferences(
|
|
42
|
-
return { removed: !1, reason: "referenced", references };
|
|
59
|
+
const preReferences = await countWorkflowRevisionReferences(engine, name, revision);
|
|
60
|
+
if (totalWorkflowRevisionReferences(preReferences) > 0)
|
|
61
|
+
return { removed: !1, reason: "referenced", references: preReferences };
|
|
43
62
|
const result = await catalog.remove(name, revision);
|
|
44
63
|
switch (result.outcome) {
|
|
45
64
|
case "removed":
|
|
46
|
-
engine
|
|
47
|
-
return { removed: !0 };
|
|
65
|
+
return finalizeRevisionRemoval(engine, name, revision, result.tombstoneBytes);
|
|
48
66
|
case "not-found":
|
|
49
67
|
return { removed: !1, reason: "not-found" };
|
|
50
68
|
case "active":
|
|
@@ -55,9 +73,19 @@ export async function removeWorkflowRevision(engine, name, revision) {
|
|
|
55
73
|
throw Error(`Unknown workflow catalog removal outcome: ${String(result)}`);
|
|
56
74
|
}
|
|
57
75
|
}
|
|
76
|
+
async function finalizeRevisionRemoval(engine, name, revision, tombstoneBytes) {
|
|
77
|
+
const internals = getInternals(engine), postReferences = await countWorkflowRevisionReferences(engine, name, revision);
|
|
78
|
+
if (totalWorkflowRevisionReferences(postReferences) > 0) {
|
|
79
|
+
await restoreCatalogEntryFromTombstone(internals.storage, name, revision, tombstoneBytes);
|
|
80
|
+
return { removed: !1, reason: "referenced", references: postReferences };
|
|
81
|
+
}
|
|
82
|
+
await finalizeCatalogTombstone(internals.storage, name, revision, tombstoneBytes);
|
|
83
|
+
engine.dispatchEvent(new WorkflowRevisionRemovedEvent(name, revision));
|
|
84
|
+
return { removed: !0 };
|
|
85
|
+
}
|
|
58
86
|
export async function getWorkflowRevisionDiagnostics(engine, name, revision) {
|
|
59
87
|
await ensureWorkflowCatalogReady(engine);
|
|
60
|
-
const catalog = getWorkflowCatalog(engine), installed = await catalog.hasInstalled(name, revision), activePointer = await catalog.resolveActiveDurable(name), active = activePointer !== void 0 && activePointer.revision === revision, references = await countWorkflowRevisionReferences(engine, name, revision), removable = installed && !active && totalWorkflowRevisionReferences(references) === 0;
|
|
88
|
+
const catalog = getWorkflowCatalog(engine), installed = await catalog.hasInstalled(name, revision), activePointer = await catalog.resolveActiveDurable(name), active = activePointer !== void 0 && activePointer.revision === revision, references = await countWorkflowRevisionReferences(engine, name, revision), removable = installed && !active && totalWorkflowRevisionReferences(references) === 0, source = buildSourceDiagnostics(getInternals(engine), name, revision);
|
|
61
89
|
return {
|
|
62
90
|
name,
|
|
63
91
|
revision,
|
|
@@ -65,6 +93,31 @@ export async function getWorkflowRevisionDiagnostics(engine, name, revision) {
|
|
|
65
93
|
active,
|
|
66
94
|
...activePointer !== void 0 ? { activeRevision: activePointer.revision } : {},
|
|
67
95
|
references,
|
|
68
|
-
removable
|
|
96
|
+
removable,
|
|
97
|
+
...source !== void 0 ? { source } : {}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function resolveDiagnosticsSourceKind(diagnostics, registeredRevisions, revision) {
|
|
101
|
+
if (diagnostics !== void 0)
|
|
102
|
+
return diagnostics.kind;
|
|
103
|
+
const registered = registeredRevisions.get(revision);
|
|
104
|
+
return registered === void 0 ? "module" : registered.descriptor.kind;
|
|
105
|
+
}
|
|
106
|
+
function buildSourceDiagnostics(internals, name, revision) {
|
|
107
|
+
const registeredRevisions = internals.sources.byName.get(name);
|
|
108
|
+
if (registeredRevisions === void 0)
|
|
109
|
+
return;
|
|
110
|
+
const diagnostics = readSourceLoadDiagnostics(internals, name, revision);
|
|
111
|
+
return {
|
|
112
|
+
kind: resolveDiagnosticsSourceKind(diagnostics, registeredRevisions, revision),
|
|
113
|
+
requestedRevision: revision,
|
|
114
|
+
state: diagnostics === void 0 ? "idle" : diagnostics.state,
|
|
115
|
+
...diagnostics?.loadDurationMs !== void 0 && {
|
|
116
|
+
loadDurationMs: diagnostics.loadDurationMs
|
|
117
|
+
},
|
|
118
|
+
...diagnostics?.lastFailureCategory !== void 0 && {
|
|
119
|
+
lastFailureCategory: diagnostics.lastFailureCategory
|
|
120
|
+
},
|
|
121
|
+
waiterCount: readSourceWaiterCount(internals, name, revision)
|
|
69
122
|
};
|
|
70
123
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves durable `catalog-tombstone:<name>:<revision>` records left
|
|
3
|
+
* behind by {@link import('../catalog/removal.ts').removeCatalogEntry}
|
|
4
|
+
* (WFT-17/18, Codex review on PR #958) — a tombstone exists only in the
|
|
5
|
+
* brief window between its delete-and-tombstone commit and the SAME call's
|
|
6
|
+
* own resolution (restore or finalize), normally sub-millisecond. A
|
|
7
|
+
* tombstone that outlives that window is an ORPHAN: the process that
|
|
8
|
+
* created it crashed (or otherwise failed) before resolving it. This
|
|
9
|
+
* module makes that recoverable by ANY process, not just the one that
|
|
10
|
+
* crashed — the exact gap the tombstone design closes (see
|
|
11
|
+
* `core/catalog/removal.ts`'s module doc for the full mechanism).
|
|
12
|
+
*
|
|
13
|
+
* Deliberately lives in `core/engine/`, not alongside the tombstone
|
|
14
|
+
* primitives in `core/catalog/`: resolving an orphan needs a FRESH
|
|
15
|
+
* reference count, and the only reference signal meaningful for a
|
|
16
|
+
* crashed peer's tombstone is the durable one —
|
|
17
|
+
* `nonterminal-revision-count.ts`'s `wf:`-prefix scan (in-process signals
|
|
18
|
+
* like `registeredDefinitions`/`inFlightStarts` are per-engine-instance and
|
|
19
|
+
* say nothing about what a DIFFERENT, crashed process was doing).
|
|
20
|
+
* `core/catalog/**` never imports from `core/engine/**` — a directional
|
|
21
|
+
* boundary `check-import-cycles.ts` enforces — so this orchestration has to
|
|
22
|
+
* live on the engine side of that line.
|
|
23
|
+
*
|
|
24
|
+
* Two call sites: `catalog-readiness.ts`'s boot-time sweep (every
|
|
25
|
+
* orphaned tombstone in the whole store, run once per engine instance
|
|
26
|
+
* before recovery or any new start can observe stale state) and
|
|
27
|
+
* `catalog-removal.ts`'s `removeWorkflowRevision` (a single, targeted
|
|
28
|
+
* check for the EXACT `(name, revision)` key it is about to act on, since
|
|
29
|
+
* a long-lived engine could observe a peer crash mid-lifetime, after its
|
|
30
|
+
* own boot sweep already ran).
|
|
31
|
+
*
|
|
32
|
+
* @module core/engine/catalog-tombstone-recovery
|
|
33
|
+
*/
|
|
34
|
+
import { type Storage } from '../../storage/interface.ts';
|
|
35
|
+
/**
|
|
36
|
+
* Sweep every `catalog-tombstone:` record in `storage` and resolve each
|
|
37
|
+
* one. Bounded by the number of orphaned tombstones actually present —
|
|
38
|
+
* normally zero, so this is a cheap prefix scan that yields nothing to
|
|
39
|
+
* iterate; only non-trivial after a real crash left orphans behind. Called
|
|
40
|
+
* once per engine instance, at catalog-boot time
|
|
41
|
+
* (`catalog-readiness.ts`), before recovery's own preflight or any fresh
|
|
42
|
+
* start can observe a revision this sweep would otherwise still be
|
|
43
|
+
* resolving.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveOrphanedCatalogTombstones(storage: Storage): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Targeted check for ONE `(name, revision)` tombstone — used by
|
|
48
|
+
* `removeWorkflowRevision` immediately before it acts, so a peer crash
|
|
49
|
+
* that happened after this engine's own boot-time sweep already ran (a
|
|
50
|
+
* long-lived engine) does not leave a stale tombstone permanently blocking
|
|
51
|
+
* every future removal attempt on that exact key (`removeCatalogEntry`'s
|
|
52
|
+
* own CAS requires the tombstone key absent). A no-op when no tombstone is
|
|
53
|
+
* present for this exact key — the overwhelmingly common case, costing one
|
|
54
|
+
* `storage.get`.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveCatalogTombstoneIfPresent(storage: Storage, name: string, revision: string): Promise<void>;
|