@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,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
KEYS
|
|
2
|
+
KEYS,
|
|
3
|
+
storageValuesEqual
|
|
3
4
|
} from "../../storage/interface.js";
|
|
4
5
|
import { encode } from "../codec.js";
|
|
5
6
|
import { buildTimerBatchOperations } from "../scheduler.js";
|
|
@@ -8,7 +9,10 @@ import {
|
|
|
8
9
|
clearPendingAtomicWorkflowCommitSideEffects,
|
|
9
10
|
takePendingAtomicWorkflowCommitSideEffects
|
|
10
11
|
} from "./checkpoint-side-effects.js";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
commitFencedEngineWrite,
|
|
14
|
+
commitFencedEngineWriteAllowingPreconditionFailure
|
|
15
|
+
} from "./fenced-write.js";
|
|
12
16
|
import { getWorkflowExecutionStartedAt } from "./handles.js";
|
|
13
17
|
import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
14
18
|
import { createScheduleTimerId, decodeWorkflowStartHeaders } from "./state-utilities.js";
|
|
@@ -150,8 +154,23 @@ export async function writeScheduleState(internals, state, options) {
|
|
|
150
154
|
], includeTimer = options?.includeTimer ?? state.status === "active";
|
|
151
155
|
operations.push(...buildScheduleTimerReplacementOperations(state, includeTimer, options?.replaceTimerFrom));
|
|
152
156
|
operations.push(...options?.additionalOperations ?? []);
|
|
157
|
+
const extraConditions = options?.extraConditions ?? [];
|
|
158
|
+
if (extraConditions.length > 0) {
|
|
159
|
+
await commitScheduleStateWithExtraConditions(internals, state, operations, extraConditions, options?.onExtraConditionsLost);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
153
162
|
await commitFencedEngineWrite(internals, null, operations, [], () => Error(`Schedule state commit for schedule "${state.id}" lost its precondition.`));
|
|
154
163
|
}
|
|
164
|
+
async function commitScheduleStateWithExtraConditions(internals, state, operations, extraConditions, onExtraConditionsLost) {
|
|
165
|
+
if (await commitFencedEngineWriteAllowingPreconditionFailure(internals, null, operations, extraConditions))
|
|
166
|
+
return;
|
|
167
|
+
for (const condition of extraConditions) {
|
|
168
|
+
const currentValue = await internals.storage.get(condition.key);
|
|
169
|
+
if (!storageValuesEqual(currentValue, condition.expectedValue))
|
|
170
|
+
throw onExtraConditionsLost?.() ?? Error(`Schedule state commit for schedule "${state.id}" lost its precondition.`);
|
|
171
|
+
}
|
|
172
|
+
throw Error(`Schedule state commit for schedule "${state.id}" lost its precondition.`);
|
|
173
|
+
}
|
|
155
174
|
export async function loadWorkflowStartHeaders(internals, workflowId) {
|
|
156
175
|
const bytes = await internals.storage.get(KEYS.workflowHeaders(workflowId));
|
|
157
176
|
if (!bytes)
|
|
@@ -177,7 +177,7 @@ async function releaseWorkflowClaimAfterTerminalSettlement(internals, workflowId
|
|
|
177
177
|
if (registry.currentEpoch(workflowId) !== capturedEpoch)
|
|
178
178
|
return;
|
|
179
179
|
try {
|
|
180
|
-
await registry.release(workflowId);
|
|
180
|
+
await registry.release(workflowId, capturedEpoch);
|
|
181
181
|
} catch {}
|
|
182
182
|
}
|
|
183
183
|
export async function completeWorkflow(internals, workflowId, result, callbacks) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EngineInternals } from '../internals.ts';
|
|
2
|
+
import type { RunnableFinalizer } from './finalizer-activity.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve `type`'s `finalizer` against the workflow instance's own EXACT
|
|
5
|
+
* pinned `revision` (WFT-17/WFT-19), awaiting a full dynamic-source resolve
|
|
6
|
+
* when it is `registerSource()`-registered but unresolved on this process —
|
|
7
|
+
* {@link getResolvedDynamicRegistration}'s sync-only lookup cannot close
|
|
8
|
+
* that gap. Without this, a type never started/resumed/recovered here after
|
|
9
|
+
* a fresh restart (only torn down) rearms its `wf-teardown:` timer forever,
|
|
10
|
+
* since nothing else triggers the resolve `lastResolvedRevisionByName`
|
|
11
|
+
* needs. Returns `undefined` (rearm) when `type` has no registration at
|
|
12
|
+
* all, when the pinned revision cannot be resolved on this process
|
|
13
|
+
* ({@link WorkflowRevisionUnavailableError}), or when the resolve itself
|
|
14
|
+
* fails ({@link DynamicWorkflowSourceUnavailableError}) — a load failure
|
|
15
|
+
* here doesn't fail the (already terminal) workflow, it just defers to the
|
|
16
|
+
* next self-heal attempt.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveFinalizerRegistration(internals: EngineInternals, type: string, revision: string | undefined): Promise<RunnableFinalizer | undefined>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DynamicWorkflowSourceUnavailableError } from "../dynamic-source-errors.js";
|
|
2
|
+
import {
|
|
3
|
+
getResolvedDynamicRegistration,
|
|
4
|
+
resolveExecutableRegistrationForRevision
|
|
5
|
+
} from "../dynamic-source-execution.js";
|
|
6
|
+
import { WorkflowRevisionUnavailableError } from "../revision-errors.js";
|
|
7
|
+
export async function resolveFinalizerRegistration(internals, type, revision) {
|
|
8
|
+
const eagerOrAlreadyResolved = getResolvedDynamicRegistration(internals, type, revision)?.finalizer;
|
|
9
|
+
if (eagerOrAlreadyResolved !== void 0)
|
|
10
|
+
return eagerOrAlreadyResolved;
|
|
11
|
+
if (!internals.sources.byName.has(type))
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
const { entry } = await resolveExecutableRegistrationForRevision(internals.engine, internals, type, revision);
|
|
15
|
+
return entry.finalizer;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error instanceof DynamicWorkflowSourceUnavailableError || error instanceof WorkflowRevisionUnavailableError)
|
|
18
|
+
return;
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -3,29 +3,26 @@
|
|
|
3
3
|
* Dispatched by the scheduler when a `wf-teardown:` timer fires, it drives a
|
|
4
4
|
* workflow's definition-level `finalizer` to durable completion after a
|
|
5
5
|
* `cancelled`/`timed-out` terminal — claiming the durable teardown marker, running
|
|
6
|
-
* the finalizer activity (via {@link runFinalizerActivity}), and then clearing,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* the finalizer activity (via {@link runFinalizerActivity}), and then clearing, backing
|
|
7
|
+
* off, or dead-lettering based on the outcome. The byte-level claim mechanics (CAS,
|
|
8
|
+
* settle, re-arm, dead-letter, stale horizon, backoff) live in `./finalizer-claim.ts`;
|
|
9
|
+
* this module is the orchestration that decides which to call.
|
|
10
10
|
*
|
|
11
|
-
* Concurrency model — a single durable, TIME-based claim:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* be re-driven concurrently, which is why workflow finalizers must be idempotent (a
|
|
22
|
-
* contract of the #446 design).
|
|
11
|
+
* Concurrency model — a single durable, TIME-based claim: the durable `teardownOwed`
|
|
12
|
+
* marker carries a `{ status, attempts, token, claimedAt }` claim ({@link TeardownClaim}).
|
|
13
|
+
* A holder fenced-CAS's `owed → running` (stamping `claimedAt`) before running, and
|
|
14
|
+
* settle-CAS's the exact `running` bytes it wrote when clearing or rescheduling — so a
|
|
15
|
+
* concurrent reclaimer can never clobber a fresher claim. Liveness is decided purely by
|
|
16
|
+
* the clock: a `running` claim is reclaimable once `claimedAt` is older than
|
|
17
|
+
* {@link teardownStaleThresholdMs}. There is NO in-memory liveness set and NO epoch in
|
|
18
|
+
* the record; crash recovery is an ordinary stale-claim retry driven by the timer that
|
|
19
|
+
* survived the terminal batch — the tradeoff is a finalizer running past the stale
|
|
20
|
+
* threshold may be re-driven concurrently, which is why finalizers must be idempotent.
|
|
23
21
|
*
|
|
24
22
|
* Self-heal invariant: every exit that does NOT settle the claim (a lost claim CAS, a
|
|
25
23
|
* presumed-live `running` claim, a shutdown-aborted attempt, or a missing registration)
|
|
26
|
-
* re-arms a future `wf-teardown:` timer before returning
|
|
27
|
-
*
|
|
28
|
-
* re-arm would strand the marker with no timer to re-drive it.
|
|
24
|
+
* re-arms a future `wf-teardown:` timer before returning — the scheduler deletes the fired
|
|
25
|
+
* timer on return, so a non-settling exit that forgot to re-arm strands the marker.
|
|
29
26
|
*
|
|
30
27
|
* @module core/engine/termination/finalizer
|
|
31
28
|
*/
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
teardownStaleThresholdMs,
|
|
19
19
|
teardownTimerOperations
|
|
20
20
|
} from "./finalizer-claim.js";
|
|
21
|
+
import { resolveFinalizerRegistration } from "./finalizer-registration.js";
|
|
21
22
|
export { teardownStaleThresholdMs } from "./finalizer-claim.js";
|
|
22
23
|
const TERMINAL_STATUSES_OWED_TEARDOWN = new Set([
|
|
23
24
|
"cancelled",
|
|
@@ -53,10 +54,9 @@ async function resolveTeardownDrive(internals, workflowId, token, callbacks) {
|
|
|
53
54
|
const state = await callbacks.loadWorkflowState(workflowId);
|
|
54
55
|
if (state === null || !TERMINAL_STATUSES_OWED_TEARDOWN.has(state.status))
|
|
55
56
|
return clearOrRearm(internals, workflowId, token, markerBytes);
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
57
|
+
const finalizer = await resolveFinalizerRegistration(internals, state.type, state.revision);
|
|
58
|
+
if (finalizer === void 0)
|
|
58
59
|
return { kind: "rearm", token };
|
|
59
|
-
const finalizer = registeredFinalizer;
|
|
60
60
|
if (!runningClaimIsStale(internals, claim, finalizer))
|
|
61
61
|
return { kind: "rearm", token };
|
|
62
62
|
const finalizerStateBytes = await internals.storage.get(KEYS.finalizerState(workflowId));
|
|
@@ -6,6 +6,7 @@ import { dropQueuedInlineWorkflowStart } from "../inline-launch-queue.js";
|
|
|
6
6
|
import { buildWorkflowVisibilityIndexTransition } from "../workflow-indexes.js";
|
|
7
7
|
import { evictSuspendedWorkflowWaiters } from "./cleanup.js";
|
|
8
8
|
export async function suspendWorkflow(internals, workflowId, callbacks) {
|
|
9
|
+
const capturedClaimEpochForForget = internals.workflowClaimRegistry?.currentEpoch(workflowId) ?? null;
|
|
9
10
|
if (!await callbacks.runSerializedWorkflowStateWrite(workflowId, async () => {
|
|
10
11
|
const state = await callbacks.loadWorkflowState(workflowId);
|
|
11
12
|
if (!state || state.status !== "running")
|
|
@@ -26,6 +27,8 @@ export async function suspendWorkflow(internals, workflowId, callbacks) {
|
|
|
26
27
|
return !0;
|
|
27
28
|
}))
|
|
28
29
|
return;
|
|
30
|
+
if (internals.workflowClaimRegistry !== null && capturedClaimEpochForForget !== null && internals.workflowClaimRegistry.currentEpoch(workflowId) === capturedClaimEpochForForget)
|
|
31
|
+
internals.workflowClaimRegistry.forgetLocalClaim(workflowId);
|
|
29
32
|
const event = new WorkflowSuspendedEvent(workflowId);
|
|
30
33
|
callbacks.dispatchEvent(event);
|
|
31
34
|
callbacks.forwardEventToHandle(workflowId, event);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {@link TimeOperationCallbacks} — the callback bundle `operations-time.ts`'s
|
|
3
|
+
* timer-fire handlers (delayed start, sleep, deadline, schedule) take. Split
|
|
4
|
+
* out of that file, which has no headroom under the repository's 500-line
|
|
5
|
+
* implementation-file ceiling for this type inline.
|
|
6
|
+
*
|
|
7
|
+
* @module core/engine/time-operation-callbacks
|
|
8
|
+
*/
|
|
9
|
+
import type { Checkpoint, Duration, TimerEntry, WorkflowState } from '../types.ts';
|
|
10
|
+
import type { WorkflowVersionTuple } from '../workflow-version-tuple.ts';
|
|
11
|
+
import type { ExecutableRegistration } from './dynamic-source-execution.ts';
|
|
12
|
+
type RegistrationEntry = ExecutableRegistration['entry'];
|
|
13
|
+
export type TimeOperationCallbacks = {
|
|
14
|
+
completeOperation: (workflowId: string, value: unknown) => void;
|
|
15
|
+
dispatchEvent: (event: Event) => void;
|
|
16
|
+
loadWorkflowState: (workflowId: string) => Promise<WorkflowState | null>;
|
|
17
|
+
failWorkflow: (workflowId: string, error: Error) => Promise<void>;
|
|
18
|
+
runSerializedWorkflowStateWrite: <Result>(workflowId: string, writeOperation: () => Promise<Result>) => Promise<Result>;
|
|
19
|
+
beginWorkflowExecution: (workflowId: string, workflowExecutionToken: string | undefined, workflowType: string, revision: string | undefined, input: unknown, checkpoint: Checkpoint, executionDeadline: number | undefined, executionStateOwnerId: string, registration: RegistrationEntry) => void;
|
|
20
|
+
workflowVersionTupleFromState: (state: WorkflowState) => WorkflowVersionTuple;
|
|
21
|
+
setWorkflowStartHeaders: (workflowId: string, headers: Map<string, string> | undefined) => void;
|
|
22
|
+
loadWorkflowStartHeaders: (workflowId: string) => Promise<Map<string, string> | undefined>;
|
|
23
|
+
parseStartOptionDuration: (value: Duration, fieldName: 'options.executionTimeout' | 'options.startAfter') => number;
|
|
24
|
+
runDeferredTerminalCleanup: (workflowId: string, timerId: string) => Promise<void>;
|
|
25
|
+
runWorkflowFinalizer: (workflowId: string, timerId: string) => Promise<void>;
|
|
26
|
+
handleScheduleTimer: (entry: TimerEntry) => Promise<void>;
|
|
27
|
+
timeout: (workflowId: string) => Promise<void>;
|
|
28
|
+
handleCleanupError: (source: string, error: unknown, workflowId: string) => void;
|
|
29
|
+
/** Resolve `type` against an EXACT pinned `WorkflowState.revision` (WFT-17, `undefined` for a legacy record) — never the catalog's active pointer. Used by a delayed-start fire, matching `resumeWorkflowFromStorage()`. */
|
|
30
|
+
resolveExecutableRegistrationForRevision: (type: string, revision: string | undefined) => Promise<ExecutableRegistration>;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode a persisted schedule record's cadence fields (`cronExpression` XOR
|
|
3
|
+
* `intervalMs`). Split out of `schedule.ts` purely to stay under this
|
|
4
|
+
* repository's 500-line-per-file ceiling (the same precedent
|
|
5
|
+
* `lifecycle/start.ts`/`decode-revision.ts` set for WFT-17/18) — there is no
|
|
6
|
+
* behavioral reason to import this module directly instead of `schedule.ts`,
|
|
7
|
+
* which re-exports nothing from here because `decodeScheduleCadence` is
|
|
8
|
+
* consumed only by `decodeScheduleIdentityFields` in that file.
|
|
9
|
+
*
|
|
10
|
+
* @module core/engine/validation/schedule-cadence
|
|
11
|
+
*/
|
|
12
|
+
/** Decode the cadence fields (`cronExpression` XOR `intervalMs`) of a persisted schedule record, or `null` (and warn) on a malformed record. */
|
|
13
|
+
export declare function decodeScheduleCadence(decoded: Record<string, unknown>, scheduleId: string): {
|
|
14
|
+
cronExpression?: string;
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
} | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { parseCronExpression } from "../../schedule.js";
|
|
2
|
+
import { rejectInvalidScheduleRecord } from "./schedule-warnings.js";
|
|
3
|
+
export function decodeScheduleCadence(decoded, scheduleId) {
|
|
4
|
+
const { cronExpression, intervalMs } = decoded, hasCron = cronExpression !== void 0, hasInterval = intervalMs !== void 0;
|
|
5
|
+
if (hasCron === hasInterval)
|
|
6
|
+
return rejectInvalidScheduleRecord(scheduleId, "with conflicting or missing cadence (expected exactly one of cronExpression or intervalMs)");
|
|
7
|
+
if (hasInterval) {
|
|
8
|
+
if (typeof intervalMs !== "number" || !Number.isSafeInteger(intervalMs) || intervalMs <= 0)
|
|
9
|
+
return rejectInvalidScheduleRecord(scheduleId, "with invalid intervalMs");
|
|
10
|
+
return { intervalMs };
|
|
11
|
+
}
|
|
12
|
+
if (typeof cronExpression !== "string")
|
|
13
|
+
return rejectInvalidScheduleRecord(scheduleId, "with invalid cronExpression");
|
|
14
|
+
try {
|
|
15
|
+
parseCronExpression(cronExpression);
|
|
16
|
+
} catch {
|
|
17
|
+
return rejectInvalidScheduleRecord(scheduleId, "with invalid cronExpression");
|
|
18
|
+
}
|
|
19
|
+
return { cronExpression };
|
|
20
|
+
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import type { ScheduleOptions, ScheduleOverlapPolicy, ScheduleUpdateOptions } from '../../types.ts';
|
|
1
|
+
import type { ScheduleOptions, ScheduleOverlapPolicy, ScheduleRevisionPolicy, ScheduleUpdateOptions } from '../../types.ts';
|
|
2
2
|
export declare const SCHEDULE_OVERLAP_POLICIES: Set<ScheduleOverlapPolicy>;
|
|
3
|
+
export declare const SCHEDULE_REVISION_POLICIES: Set<ScheduleRevisionPolicy>;
|
|
3
4
|
type NormalizedScheduleOptions = Required<Pick<ScheduleOptions, 'overlap' | 'backfill'>> & {
|
|
4
5
|
id?: string;
|
|
5
6
|
description?: string;
|
|
6
7
|
jitterMs?: number;
|
|
8
|
+
revisionPolicy?: ScheduleRevisionPolicy;
|
|
7
9
|
};
|
|
8
10
|
type NormalizedScheduleUpdateOptions = {
|
|
9
11
|
description?: string;
|
|
10
12
|
overlap?: ScheduleOverlapPolicy;
|
|
11
13
|
backfill?: boolean;
|
|
12
14
|
jitterMs?: number;
|
|
15
|
+
revisionPolicy?: ScheduleRevisionPolicy;
|
|
13
16
|
};
|
|
14
17
|
export declare function normalizeScheduleOptions(options: ScheduleOptions | undefined): NormalizedScheduleOptions;
|
|
15
18
|
export declare function normalizeScheduleUpdateOptions(options: ScheduleUpdateOptions | undefined): NormalizedScheduleUpdateOptions;
|
|
@@ -5,6 +5,9 @@ export const SCHEDULE_OVERLAP_POLICIES = new Set([
|
|
|
5
5
|
"queue",
|
|
6
6
|
"cancel-running",
|
|
7
7
|
"allow"
|
|
8
|
+
]), SCHEDULE_REVISION_POLICIES = new Set([
|
|
9
|
+
"active-at-fire",
|
|
10
|
+
"pinned"
|
|
8
11
|
]);
|
|
9
12
|
export function normalizeScheduleOptions(options) {
|
|
10
13
|
if (options === void 0)
|
|
@@ -20,6 +23,9 @@ export function normalizeScheduleOptions(options) {
|
|
|
20
23
|
},
|
|
21
24
|
...options.jitter !== void 0 && {
|
|
22
25
|
jitterMs: normalizeScheduleJitter(options.jitter, "options.jitter")
|
|
26
|
+
},
|
|
27
|
+
...options.revisionPolicy !== void 0 && {
|
|
28
|
+
revisionPolicy: normalizeScheduleRevisionPolicy(options.revisionPolicy, "options.revisionPolicy")
|
|
23
29
|
}
|
|
24
30
|
};
|
|
25
31
|
}
|
|
@@ -38,9 +44,17 @@ export function normalizeScheduleUpdateOptions(options) {
|
|
|
38
44
|
},
|
|
39
45
|
...options.jitter !== void 0 && {
|
|
40
46
|
jitterMs: normalizeScheduleJitter(options.jitter, "options.jitter")
|
|
47
|
+
},
|
|
48
|
+
...options.revisionPolicy !== void 0 && {
|
|
49
|
+
revisionPolicy: normalizeScheduleRevisionPolicy(options.revisionPolicy, "options.revisionPolicy")
|
|
41
50
|
}
|
|
42
51
|
};
|
|
43
52
|
}
|
|
53
|
+
function normalizeScheduleRevisionPolicy(value, fieldName) {
|
|
54
|
+
if (!SCHEDULE_REVISION_POLICIES.has(value))
|
|
55
|
+
throw Error(`${fieldName} must be one of ${[...SCHEDULE_REVISION_POLICIES].join(", ")}`);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
44
58
|
function normalizeScheduleDescription(description) {
|
|
45
59
|
if (typeof description !== "string")
|
|
46
60
|
throw Error("options.description must be a string when provided");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode a persisted schedule record's revision-policy fields
|
|
3
|
+
* (`revisionPolicy` / `pinnedRevision`, WFT-20). Split out of `schedule.ts`
|
|
4
|
+
* purely to stay under this repository's 500-line-per-file ceiling — see
|
|
5
|
+
* that file's own precedent (`lifecycle/start.ts`/`decode-revision.ts` for
|
|
6
|
+
* WFT-17/18) — there is no behavioral reason to import this module directly
|
|
7
|
+
* instead of `schedule.ts`, which composes it into `decodeScheduleState`.
|
|
8
|
+
*
|
|
9
|
+
* @module core/engine/validation/schedule-revision
|
|
10
|
+
*/
|
|
11
|
+
import type { ScheduleState } from '../../types.ts';
|
|
12
|
+
type ScheduleRevisionFields = Pick<ScheduleState, 'revisionPolicy' | 'pinnedRevision'>;
|
|
13
|
+
/**
|
|
14
|
+
* Decode `revisionPolicy`/`pinnedRevision` from a persisted schedule record.
|
|
15
|
+
* A record with NO `revisionPolicy` field predates WFT-20 and decodes as
|
|
16
|
+
* `'active-at-fire'` — the same "absent means legacy, not corrupt" treatment
|
|
17
|
+
* `WorkflowState.revision` got (WFT-17/18) — never rejected as malformed. An
|
|
18
|
+
* explicit but unrecognized `revisionPolicy` string, or a `pinnedRevision`
|
|
19
|
+
* present while `revisionPolicy !== 'pinned'` (or absent while it IS
|
|
20
|
+
* `'pinned'`), both reject the whole record via
|
|
21
|
+
* {@link rejectInvalidScheduleRecord} rather than silently coercing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function decodeScheduleRevisionPolicyFields(decoded: Record<string, unknown>, scheduleId: string): ScheduleRevisionFields | null;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SCHEDULE_REVISION_POLICIES } from "./schedule-options.js";
|
|
2
|
+
import { rejectInvalidScheduleRecord } from "./schedule-warnings.js";
|
|
3
|
+
export function decodeScheduleRevisionPolicyFields(decoded, scheduleId) {
|
|
4
|
+
const rawRevisionPolicy = decoded.revisionPolicy;
|
|
5
|
+
if (rawRevisionPolicy === void 0) {
|
|
6
|
+
if (decoded.pinnedRevision !== void 0)
|
|
7
|
+
return rejectInvalidScheduleRecord(scheduleId, "with a pinnedRevision but no revisionPolicy");
|
|
8
|
+
return { revisionPolicy: "active-at-fire" };
|
|
9
|
+
}
|
|
10
|
+
if (!SCHEDULE_REVISION_POLICIES.has(rawRevisionPolicy))
|
|
11
|
+
return rejectInvalidScheduleRecord(scheduleId, "with invalid revisionPolicy");
|
|
12
|
+
const revisionPolicy = rawRevisionPolicy, pinnedRevision = decoded.pinnedRevision, hasPinnedRevision = pinnedRevision !== void 0;
|
|
13
|
+
if (revisionPolicy === "pinned") {
|
|
14
|
+
if (!hasPinnedRevision || typeof pinnedRevision !== "string" || pinnedRevision.length === 0)
|
|
15
|
+
return rejectInvalidScheduleRecord(scheduleId, 'with revisionPolicy "pinned" but a missing or invalid pinnedRevision');
|
|
16
|
+
return { revisionPolicy, pinnedRevision };
|
|
17
|
+
}
|
|
18
|
+
if (hasPinnedRevision)
|
|
19
|
+
return rejectInvalidScheduleRecord(scheduleId, 'with a pinnedRevision but revisionPolicy is not "pinned"');
|
|
20
|
+
return { revisionPolicy };
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared "ignore this malformed persisted schedule record" warning
|
|
3
|
+
* helper, split into its own leaf module so both `schedule.ts` and its
|
|
4
|
+
* decode helpers (`schedule-cadence.ts`, `schedule-revision.ts`) can import
|
|
5
|
+
* it without an import cycle — `schedule.ts` re-exports it so every existing
|
|
6
|
+
* caller keeps importing it from there.
|
|
7
|
+
*
|
|
8
|
+
* @module core/engine/validation/schedule-warnings
|
|
9
|
+
*/
|
|
10
|
+
/** Warn and return `null` for a persisted schedule record that failed decode validation. */
|
|
11
|
+
export declare function rejectInvalidScheduleRecord(scheduleId: string | undefined, message: string): null;
|
|
@@ -1,11 +1,37 @@
|
|
|
1
|
-
import type { ScheduleFilter, ScheduleOverlapPolicy, ScheduleSpec, ScheduleState, ScheduleStatus } from '../../types.ts';
|
|
2
|
-
export { normalizeScheduleOptions, normalizeScheduleUpdateOptions, SCHEDULE_OVERLAP_POLICIES, } from './schedule-options.ts';
|
|
1
|
+
import type { ScheduleFilter, ScheduleOverlapPolicy, ScheduleRevisionPolicy, ScheduleSpec, ScheduleState, ScheduleStatus } from '../../types.ts';
|
|
2
|
+
export { normalizeScheduleOptions, normalizeScheduleUpdateOptions, SCHEDULE_OVERLAP_POLICIES, SCHEDULE_REVISION_POLICIES, } from './schedule-options.ts';
|
|
3
|
+
export { rejectInvalidScheduleRecord } from './schedule-warnings.ts';
|
|
3
4
|
export declare const SCHEDULE_STATUSES: Set<ScheduleStatus>;
|
|
4
5
|
export declare function isValidScheduleTimestamp(value: unknown): value is number;
|
|
5
6
|
export declare function isValidScheduleStatus(value: unknown): value is ScheduleStatus;
|
|
6
7
|
export declare function isValidScheduleOverlapPolicy(value: unknown): value is ScheduleOverlapPolicy;
|
|
8
|
+
export declare function isValidScheduleRevisionPolicy(value: unknown): value is ScheduleRevisionPolicy;
|
|
9
|
+
/**
|
|
10
|
+
* Whether `value` is a schedule-related workflow id that decoded persisted
|
|
11
|
+
* data may still carry. Deliberately reuses the pre-WFT-95 decode predicate
|
|
12
|
+
* (not the fresh-admission `.`/`..` rejection), because every caller of this
|
|
13
|
+
* function — `decodeScheduleIdentityFields`, `decodeScheduleRunMetadata`,
|
|
14
|
+
* and the persisted `currentWorkflowId`/`queuedRuns[].workflowId` fields in
|
|
15
|
+
* this module — reads already-persisted data. A schedule created before
|
|
16
|
+
* WFT-95 with `id: '.'` or `'..'` must remain decodable and keep firing
|
|
17
|
+
* after upgrade.
|
|
18
|
+
*/
|
|
7
19
|
export declare function isValidScheduleIdentifier(value: unknown): value is string;
|
|
8
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Coerce a caller-supplied `scheduleId` used to look up or control an
|
|
22
|
+
* already-persisted schedule — `getSchedule`, `pauseSchedule`,
|
|
23
|
+
* `resumeSchedule`, `cancelSchedule`, and `updateSchedule` all call this,
|
|
24
|
+
* never schedule creation (that admission path is
|
|
25
|
+
* `normalizeScheduleOptions`'s direct `coerceStartWorkflowId(options.id, …)`
|
|
26
|
+
* call, which keeps the strict `.`/`..` rejection). Deliberately uses the
|
|
27
|
+
* decode-compatible `assertDecodableWorkflowId`, not the admission-only
|
|
28
|
+
* `.`/`..` rejection (WFT-95 review): a schedule created before that
|
|
29
|
+
* rejection landed may already carry `id: '.'` or `'..'`, and callers must
|
|
30
|
+
* still be able to inspect, pause, resume, cancel, or update it instead of
|
|
31
|
+
* having every control operation reject a schedule they can't otherwise
|
|
32
|
+
* reach.
|
|
33
|
+
*/
|
|
34
|
+
export declare function coerceScheduleId(scheduleId: unknown, fieldName: string): string;
|
|
9
35
|
/**
|
|
10
36
|
* A recurrence specification normalized into the discriminated cadence the
|
|
11
37
|
* engine persists. Interval periods are resolved to whole milliseconds.
|
|
@@ -24,7 +50,6 @@ export type NormalizedScheduleSpec = {
|
|
|
24
50
|
*/
|
|
25
51
|
export declare function normalizeScheduleSpec(spec: string | ScheduleSpec): NormalizedScheduleSpec;
|
|
26
52
|
export declare function normalizeScheduleFilter(filter: ScheduleFilter | undefined): ScheduleFilter | undefined;
|
|
27
|
-
export declare function rejectInvalidScheduleRecord(scheduleId: string | undefined, message: string): null;
|
|
28
53
|
export declare function decodeScheduleIdentityFields(decoded: Record<string, unknown>): (Pick<ScheduleState, 'id' | 'workflowType' | 'status' | 'overlap'> & {
|
|
29
54
|
cronExpression?: string;
|
|
30
55
|
intervalMs?: number;
|
|
@@ -2,13 +2,18 @@ import { decode } from "../../codec.js";
|
|
|
2
2
|
import { isRecord } from "../../debug-output.js";
|
|
3
3
|
import { parseCronExpression } from "../../schedule.js";
|
|
4
4
|
import { parseDuration } from "../../scheduler.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { assertDecodableWorkflowId, isDecodableWorkflowId } from "../../workflow-identifiers.js";
|
|
6
|
+
import { decodeScheduleCadence } from "./schedule-cadence.js";
|
|
7
|
+
import { SCHEDULE_OVERLAP_POLICIES, SCHEDULE_REVISION_POLICIES } from "./schedule-options.js";
|
|
8
|
+
import { decodeScheduleRevisionPolicyFields } from "./schedule-revision.js";
|
|
9
|
+
import { rejectInvalidScheduleRecord } from "./schedule-warnings.js";
|
|
7
10
|
export {
|
|
8
11
|
normalizeScheduleOptions,
|
|
9
12
|
normalizeScheduleUpdateOptions,
|
|
10
|
-
SCHEDULE_OVERLAP_POLICIES
|
|
13
|
+
SCHEDULE_OVERLAP_POLICIES,
|
|
14
|
+
SCHEDULE_REVISION_POLICIES
|
|
11
15
|
} from "./schedule-options.js";
|
|
16
|
+
export { rejectInvalidScheduleRecord } from "./schedule-warnings.js";
|
|
12
17
|
export const SCHEDULE_STATUSES = new Set(["active", "paused", "cancelled"]);
|
|
13
18
|
export function isValidScheduleTimestamp(value) {
|
|
14
19
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
@@ -19,18 +24,15 @@ export function isValidScheduleStatus(value) {
|
|
|
19
24
|
export function isValidScheduleOverlapPolicy(value) {
|
|
20
25
|
return typeof value === "string" && SCHEDULE_OVERLAP_POLICIES.has(value);
|
|
21
26
|
}
|
|
27
|
+
export function isValidScheduleRevisionPolicy(value) {
|
|
28
|
+
return typeof value === "string" && SCHEDULE_REVISION_POLICIES.has(value);
|
|
29
|
+
}
|
|
22
30
|
export function isValidScheduleIdentifier(value) {
|
|
23
|
-
|
|
24
|
-
return !1;
|
|
25
|
-
try {
|
|
26
|
-
coerceStartWorkflowId(value, "schedule id");
|
|
27
|
-
return !0;
|
|
28
|
-
} catch {
|
|
29
|
-
return !1;
|
|
30
|
-
}
|
|
31
|
+
return typeof value === "string" && isDecodableWorkflowId(value);
|
|
31
32
|
}
|
|
32
33
|
export function coerceScheduleId(scheduleId, fieldName) {
|
|
33
|
-
|
|
34
|
+
assertDecodableWorkflowId(scheduleId, fieldName);
|
|
35
|
+
return scheduleId;
|
|
34
36
|
}
|
|
35
37
|
function normalizeIntervalEvery(every) {
|
|
36
38
|
if (typeof every !== "string" && typeof every !== "number")
|
|
@@ -94,29 +96,6 @@ export function normalizeScheduleFilter(filter) {
|
|
|
94
96
|
validateScheduleFilterBound(filter.offset, "offset");
|
|
95
97
|
return filter;
|
|
96
98
|
}
|
|
97
|
-
export function rejectInvalidScheduleRecord(scheduleId, message) {
|
|
98
|
-
const prefix = scheduleId === void 0 ? "[weft] Ignoring malformed schedule record" : `[weft] Ignoring malformed schedule "${scheduleId}"`;
|
|
99
|
-
console.warn(`${prefix} ${message}.`);
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
function decodeScheduleCadence(decoded, scheduleId) {
|
|
103
|
-
const { cronExpression, intervalMs } = decoded, hasCron = cronExpression !== void 0, hasInterval = intervalMs !== void 0;
|
|
104
|
-
if (hasCron === hasInterval)
|
|
105
|
-
return rejectInvalidScheduleRecord(scheduleId, "with conflicting or missing cadence (expected exactly one of cronExpression or intervalMs)");
|
|
106
|
-
if (hasInterval) {
|
|
107
|
-
if (typeof intervalMs !== "number" || !Number.isSafeInteger(intervalMs) || intervalMs <= 0)
|
|
108
|
-
return rejectInvalidScheduleRecord(scheduleId, "with invalid intervalMs");
|
|
109
|
-
return { intervalMs };
|
|
110
|
-
}
|
|
111
|
-
if (typeof cronExpression !== "string")
|
|
112
|
-
return rejectInvalidScheduleRecord(scheduleId, "with invalid cronExpression");
|
|
113
|
-
try {
|
|
114
|
-
parseCronExpression(cronExpression);
|
|
115
|
-
} catch {
|
|
116
|
-
return rejectInvalidScheduleRecord(scheduleId, "with invalid cronExpression");
|
|
117
|
-
}
|
|
118
|
-
return { cronExpression };
|
|
119
|
-
}
|
|
120
99
|
export function decodeScheduleIdentityFields(decoded) {
|
|
121
100
|
const id = decoded.id;
|
|
122
101
|
if (!isValidScheduleIdentifier(id))
|
|
@@ -307,9 +286,13 @@ export function decodeScheduleState(bytes) {
|
|
|
307
286
|
const runtime = decodeScheduleRuntimeFields(decoded, identity.id);
|
|
308
287
|
if (!runtime)
|
|
309
288
|
return null;
|
|
289
|
+
const revisionFields = decodeScheduleRevisionPolicyFields(decoded, identity.id);
|
|
290
|
+
if (!revisionFields)
|
|
291
|
+
return null;
|
|
310
292
|
return {
|
|
311
293
|
...identity,
|
|
312
294
|
input: decoded.input,
|
|
313
|
-
...runtime
|
|
295
|
+
...runtime,
|
|
296
|
+
...revisionFields
|
|
314
297
|
};
|
|
315
298
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { decode } from "../codec.js";
|
|
2
2
|
import { isRecord } from "../debug-output.js";
|
|
3
3
|
import { normalizeFailureCategory } from "../failure-categories.js";
|
|
4
|
-
import {
|
|
4
|
+
import { parseStartWorkflowDuration } from "../start-workflow-validation.js";
|
|
5
5
|
import { DEFAULT_WORKFLOW_VERSION } from "../versioning.js";
|
|
6
|
+
import { isDecodableWorkflowId } from "../workflow-identifiers.js";
|
|
6
7
|
import { isWorkflowTagArray } from "../workflow-tags.js";
|
|
8
|
+
import { sanitizeDecodedRevision } from "./decode-revision.js";
|
|
7
9
|
import {
|
|
8
10
|
MAX_TIMELINE_COORDINATOR_DETAILS,
|
|
9
11
|
MAX_TIMELINE_DETAIL_STRING_LENGTH
|
|
@@ -30,6 +32,7 @@ const WORKFLOW_STATE_FIELD_NAMES = new Set(defineCompleteWorkflowStateFieldNames
|
|
|
30
32
|
"failureCategory",
|
|
31
33
|
"terminationReason",
|
|
32
34
|
"versionTuple",
|
|
35
|
+
"revision",
|
|
33
36
|
"workflowExecutionToken",
|
|
34
37
|
"executionStateOwnerId",
|
|
35
38
|
"parentWorkflowId",
|
|
@@ -131,24 +134,18 @@ export function decodeWorkflowState(bytes) {
|
|
|
131
134
|
else
|
|
132
135
|
state.failureCategory = normalizedFailureCategory;
|
|
133
136
|
}
|
|
134
|
-
if (state.executionStateOwnerId !== void 0)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
console.warn(`[weft] Decoded workflow state for "${state.id}" has an invalid executionStateOwnerId field; falling back to the workflow id as the execution owner. This usually indicates corruption or tampering of the storage record.`);
|
|
139
|
-
delete state.executionStateOwnerId;
|
|
140
|
-
}
|
|
137
|
+
if (state.executionStateOwnerId !== void 0 && !isDecodableWorkflowId(state.executionStateOwnerId)) {
|
|
138
|
+
console.warn(`[weft] Decoded workflow state for "${state.id}" has an invalid executionStateOwnerId field; falling back to the workflow id as the execution owner. This usually indicates corruption or tampering of the storage record.`);
|
|
139
|
+
delete state.executionStateOwnerId;
|
|
140
|
+
}
|
|
141
141
|
sanitizeDecodedParentLineage(state);
|
|
142
142
|
sanitizeDecodedRestartLineage(state);
|
|
143
|
+
sanitizeDecodedRevision(state);
|
|
143
144
|
return decodedRecord === void 0 ? state : stripUnknownWorkflowStateFields(state, decodedRecord);
|
|
144
145
|
}
|
|
145
146
|
function sanitizeDecodedParentLineage(state) {
|
|
146
|
-
if (state.parentWorkflowId !== void 0)
|
|
147
|
-
|
|
148
|
-
coerceStartWorkflowId(state.parentWorkflowId, "parentWorkflowId");
|
|
149
|
-
} catch {
|
|
150
|
-
delete state.parentWorkflowId;
|
|
151
|
-
}
|
|
147
|
+
if (state.parentWorkflowId !== void 0 && !isDecodableWorkflowId(state.parentWorkflowId))
|
|
148
|
+
delete state.parentWorkflowId;
|
|
152
149
|
if (typeof state.parentWorkflowExecutionToken !== "string" || state.parentWorkflowExecutionToken.length === 0 || state.parentWorkflowId === void 0)
|
|
153
150
|
delete state.parentWorkflowExecutionToken;
|
|
154
151
|
}
|
|
@@ -158,9 +155,8 @@ function sanitizeDecodedRestartLineage(state) {
|
|
|
158
155
|
delete state.restartedFrom;
|
|
159
156
|
return;
|
|
160
157
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
} catch {
|
|
158
|
+
const restartWorkflowId = restartedFrom.workflowId;
|
|
159
|
+
if (typeof restartWorkflowId !== "string" || !isDecodableWorkflowId(restartWorkflowId)) {
|
|
164
160
|
delete state.restartedFrom;
|
|
165
161
|
return;
|
|
166
162
|
}
|
|
@@ -104,4 +104,4 @@ export declare function createWorkflowClaimReclaimTarget(registry: WorkflowClaim
|
|
|
104
104
|
* every existing caller/test that does not care about mixed workflow-type
|
|
105
105
|
* fleets keeps working unchanged.
|
|
106
106
|
*/
|
|
107
|
-
isTypeRegistered?: (workflowType: string) => boolean): WorkflowClaimReclaimTargetHandle;
|
|
107
|
+
isTypeRegistered?: (workflowType: string, revision: string | undefined) => boolean): WorkflowClaimReclaimTargetHandle;
|