@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.
Files changed (283) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/conformance.js +5 -2
  3. package/dist/cli/generated/operation-catalog.snapshot.json +95 -1
  4. package/dist/cli/generated/operation-client.generated.d.ts +28 -2
  5. package/dist/cli/generated/operation-client.generated.js +2 -0
  6. package/dist/cli/parse-schedule-arguments.js +15 -3
  7. package/dist/cli/schedule.js +2 -1
  8. package/dist/cli/types.d.ts +2 -1
  9. package/dist/client/http-client-requests.js +6 -9
  10. package/dist/client/start-body.js +1 -0
  11. package/dist/core/catalog/index.d.ts +2 -2
  12. package/dist/core/catalog/index.js +9 -2
  13. package/dist/core/catalog/reference-counts.d.ts +29 -14
  14. package/dist/core/catalog/removal.d.ts +63 -10
  15. package/dist/core/catalog/removal.js +18 -2
  16. package/dist/core/catalog/storage-io.d.ts +23 -0
  17. package/dist/core/catalog/storage-io.js +1 -1
  18. package/dist/core/engine/activity-resolution.js +17 -17
  19. package/dist/core/engine/bulk-operations-purge.d.ts +1 -1
  20. package/dist/core/engine/bulk-operations-purge.js +17 -22
  21. package/dist/core/engine/bulk-operations-retry.d.ts +24 -0
  22. package/dist/core/engine/bulk-operations-retry.js +190 -0
  23. package/dist/core/engine/bulk-operations-shared.d.ts +18 -0
  24. package/dist/core/engine/bulk-operations-shared.js +14 -0
  25. package/dist/core/engine/bulk-operations.d.ts +3 -2
  26. package/dist/core/engine/bulk-operations.js +10 -169
  27. package/dist/core/engine/callback-creators-bundles.js +5 -3
  28. package/dist/core/engine/callback-creators-core.js +9 -1
  29. package/dist/core/engine/callback-creators-schedule.js +2 -2
  30. package/dist/core/engine/catalog-readiness.js +2 -0
  31. package/dist/core/engine/catalog-removal.d.ts +107 -3
  32. package/dist/core/engine/catalog-removal.js +66 -13
  33. package/dist/core/engine/catalog-tombstone-recovery.d.ts +56 -0
  34. package/dist/core/engine/catalog-tombstone-recovery.js +39 -0
  35. package/dist/core/engine/child-workflow.d.ts +13 -1
  36. package/dist/core/engine/child-workflow.js +22 -4
  37. package/dist/core/engine/constraints.js +5 -1
  38. package/dist/core/engine/construction.d.ts +1 -1
  39. package/dist/core/engine/decode-revision.d.ts +32 -0
  40. package/dist/core/engine/decode-revision.js +9 -0
  41. package/dist/core/engine/disposal.d.ts +6 -2
  42. package/dist/core/engine/disposal.js +24 -10
  43. package/dist/core/engine/dynamic-source-errors.d.ts +81 -0
  44. package/dist/core/engine/dynamic-source-errors.js +25 -0
  45. package/dist/core/engine/dynamic-source-execution.d.ts +173 -0
  46. package/dist/core/engine/dynamic-source-execution.js +108 -0
  47. package/dist/core/engine/engine-internal-types.d.ts +16 -0
  48. package/dist/core/engine/engine-workflows-namespace.d.ts +10 -0
  49. package/dist/core/engine/engine-workflows-namespace.js +3 -1
  50. package/dist/core/engine/generation-codec.d.ts +22 -0
  51. package/dist/core/engine/generation-codec.js +2 -0
  52. package/dist/core/engine/index.d.ts +18 -10
  53. package/dist/core/engine/index.js +22 -11
  54. package/dist/core/engine/inline-launch-queue.js +1 -1
  55. package/dist/core/engine/internals.d.ts +26 -30
  56. package/dist/core/engine/lifecycle/checkpoint-launch.d.ts +29 -0
  57. package/dist/core/engine/lifecycle/checkpoint-launch.js +75 -0
  58. package/dist/core/engine/lifecycle/delayed-start-registration.d.ts +30 -0
  59. package/dist/core/engine/lifecycle/delayed-start-registration.js +11 -0
  60. package/dist/core/engine/lifecycle/fork-helpers.d.ts +33 -1
  61. package/dist/core/engine/lifecycle/fork-helpers.js +3 -1
  62. package/dist/core/engine/lifecycle/recovery-isolation.d.ts +20 -0
  63. package/dist/core/engine/lifecycle/recovery-isolation.js +26 -0
  64. package/dist/core/engine/lifecycle/recovery-revision-groups.d.ts +75 -0
  65. package/dist/core/engine/lifecycle/recovery-revision-groups.js +64 -0
  66. package/dist/core/engine/lifecycle/resume-body.d.ts +44 -0
  67. package/dist/core/engine/lifecycle/resume-body.js +211 -0
  68. package/dist/core/engine/lifecycle/resume-generation-guard.d.ts +58 -0
  69. package/dist/core/engine/lifecycle/resume-generation-guard.js +11 -0
  70. package/dist/core/engine/lifecycle/resume.d.ts +46 -3
  71. package/dist/core/engine/lifecycle/resume.js +21 -201
  72. package/dist/core/engine/lifecycle/shared.d.ts +43 -0
  73. package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +80 -2
  74. package/dist/core/engine/lifecycle/standalone-claim-acquire.js +20 -4
  75. package/dist/core/engine/lifecycle/start-commit-errors.d.ts +12 -0
  76. package/dist/core/engine/lifecycle/start-commit-errors.js +6 -0
  77. package/dist/core/engine/lifecycle/start-commit.d.ts +24 -7
  78. package/dist/core/engine/lifecycle/start-commit.js +57 -15
  79. package/dist/core/engine/lifecycle/start-exec.d.ts +18 -2
  80. package/dist/core/engine/lifecycle/start-exec.js +7 -5
  81. package/dist/core/engine/lifecycle/start-or-signal-create.js +1 -3
  82. package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +8 -2
  83. package/dist/core/engine/lifecycle/start-or-signal-resolution.js +11 -3
  84. package/dist/core/engine/lifecycle/start-or-signal.js +1 -1
  85. package/dist/core/engine/lifecycle/start-precondition-attribution.d.ts +55 -0
  86. package/dist/core/engine/lifecycle/start-precondition-attribution.js +13 -0
  87. package/dist/core/engine/lifecycle/start-revision-resolution.d.ts +34 -0
  88. package/dist/core/engine/lifecycle/start-revision-resolution.js +11 -0
  89. package/dist/core/engine/lifecycle/start-schedule-timing.d.ts +11 -0
  90. package/dist/core/engine/lifecycle/start-schedule-timing.js +21 -0
  91. package/dist/core/engine/lifecycle/start-state.d.ts +16 -0
  92. package/dist/core/engine/lifecycle/start-state.js +60 -0
  93. package/dist/core/engine/lifecycle/start-terminal-conflict-purge.d.ts +146 -4
  94. package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +33 -4
  95. package/dist/core/engine/lifecycle/start.d.ts +54 -7
  96. package/dist/core/engine/lifecycle/start.js +53 -90
  97. package/dist/core/engine/lifecycle/transition.d.ts +12 -4
  98. package/dist/core/engine/lifecycle/transition.js +40 -95
  99. package/dist/core/engine/lifecycle.d.ts +5 -2
  100. package/dist/core/engine/lifecycle.js +7 -6
  101. package/dist/core/engine/listing.js +15 -1
  102. package/dist/core/engine/memo-durable-activity.js +4 -1
  103. package/dist/core/engine/nonterminal-revision-count.d.ts +25 -0
  104. package/dist/core/engine/nonterminal-revision-count.js +16 -0
  105. package/dist/core/engine/operations-time.d.ts +5 -23
  106. package/dist/core/engine/operations-time.js +13 -11
  107. package/dist/core/engine/ownership-bootstrap.d.ts +1 -1
  108. package/dist/core/engine/pinned-schedule-revision-count.d.ts +26 -0
  109. package/dist/core/engine/pinned-schedule-revision-count.js +19 -0
  110. package/dist/core/engine/pinned-schedule-revision.d.ts +76 -0
  111. package/dist/core/engine/pinned-schedule-revision.js +53 -0
  112. package/dist/core/engine/registration.d.ts +30 -0
  113. package/dist/core/engine/registration.js +17 -2
  114. package/dist/core/engine/retention.js +9 -2
  115. package/dist/core/engine/revision-errors.d.ts +71 -0
  116. package/dist/core/engine/revision-errors.js +13 -0
  117. package/dist/core/engine/schedule-overlap.js +2 -1
  118. package/dist/core/engine/schedule-revision-fire.d.ts +17 -0
  119. package/dist/core/engine/schedule-revision-fire.js +3 -0
  120. package/dist/core/engine/schedule-run.d.ts +10 -0
  121. package/dist/core/engine/schedule-run.js +2 -1
  122. package/dist/core/engine/schedules.d.ts +11 -1
  123. package/dist/core/engine/schedules.js +28 -9
  124. package/dist/core/engine/source-diagnostics.d.ts +76 -0
  125. package/dist/core/engine/source-diagnostics.js +105 -0
  126. package/dist/core/engine/source-registration.js +4 -2
  127. package/dist/core/engine/source-resolution.d.ts +25 -9
  128. package/dist/core/engine/source-resolution.js +49 -14
  129. package/dist/core/engine/source-runtime-state.d.ts +92 -0
  130. package/dist/core/engine/source-runtime-state.js +36 -0
  131. package/dist/core/engine/storage-io.d.ts +19 -2
  132. package/dist/core/engine/storage-io.js +21 -2
  133. package/dist/core/engine/termination/complete.js +1 -1
  134. package/dist/core/engine/termination/finalizer-registration.d.ts +18 -0
  135. package/dist/core/engine/termination/finalizer-registration.js +21 -0
  136. package/dist/core/engine/termination/finalizer.d.ts +16 -19
  137. package/dist/core/engine/termination/finalizer.js +3 -3
  138. package/dist/core/engine/termination/suspend.js +3 -0
  139. package/dist/core/engine/time-operation-callbacks.d.ts +32 -0
  140. package/dist/core/engine/time-operation-callbacks.js +0 -0
  141. package/dist/core/engine/validation/schedule-cadence.d.ts +16 -0
  142. package/dist/core/engine/validation/schedule-cadence.js +20 -0
  143. package/dist/core/engine/validation/schedule-options.d.ts +4 -1
  144. package/dist/core/engine/validation/schedule-options.js +14 -0
  145. package/dist/core/engine/validation/schedule-revision.d.ts +24 -0
  146. package/dist/core/engine/validation/schedule-revision.js +21 -0
  147. package/dist/core/engine/validation/schedule-warnings.d.ts +11 -0
  148. package/dist/core/engine/validation/schedule-warnings.js +5 -0
  149. package/dist/core/engine/validation/schedule.d.ts +29 -4
  150. package/dist/core/engine/validation/schedule.js +19 -36
  151. package/dist/core/engine/validation.js +13 -17
  152. package/dist/core/engine/workflow-claim-reclaim-target.d.ts +1 -1
  153. package/dist/core/engine/workflow-claim-reclaim-target.js +22 -8
  154. package/dist/core/engine/workflow-claim-registry.d.ts +79 -75
  155. package/dist/core/engine/workflow-claim-registry.js +15 -2
  156. package/dist/core/engine/workflow-generation-fence.d.ts +74 -0
  157. package/dist/core/engine/workflow-generation-fence.js +19 -0
  158. package/dist/core/engine/workflow-retention-deadline.d.ts +16 -0
  159. package/dist/core/engine/workflow-retention-deadline.js +33 -0
  160. package/dist/core/events/event-map.d.ts +5 -0
  161. package/dist/core/events/index.d.ts +1 -0
  162. package/dist/core/events/index.js +1 -0
  163. package/dist/core/events/workflow-source-events.d.ts +102 -0
  164. package/dist/core/events/workflow-source-events.js +57 -0
  165. package/dist/core/execution-strategy.d.ts +7 -0
  166. package/dist/core/inline-execution-strategy.context-options.d.ts +1 -1
  167. package/dist/core/inline-execution-strategy.js +1 -1
  168. package/dist/core/start-workflow-validation.d.ts +15 -0
  169. package/dist/core/start-workflow-validation.js +11 -1
  170. package/dist/core/types/checkpoint.d.ts +14 -0
  171. package/dist/core/types/schedules.d.ts +52 -1
  172. package/dist/core/types/state.d.ts +16 -0
  173. package/dist/core/weft-error.d.ts +1 -1
  174. package/dist/core/weft-error.js +4 -1
  175. package/dist/core/worker-execution-strategy.d.ts +1 -0
  176. package/dist/core/worker-execution-strategy.js +13 -1
  177. package/dist/core/worker-fault-handling.d.ts +10 -0
  178. package/dist/core/worker-fault-handling.js +1 -0
  179. package/dist/core/worker-inbound-message.d.ts +1 -0
  180. package/dist/core/worker-inbound-message.js +1 -0
  181. package/dist/core/worker-protocol-guard.js +4 -0
  182. package/dist/core/worker-protocol.d.ts +7 -0
  183. package/dist/core/worker-protocol.js +2 -1
  184. package/dist/core/worker-turn-watchdog.d.ts +8 -1
  185. package/dist/core/worker-turn-watchdog.js +3 -2
  186. package/dist/core/workflow-identifiers.d.ts +37 -0
  187. package/dist/core/workflow-identifiers.js +19 -1
  188. package/dist/diagnostics/format.js +5 -0
  189. package/dist/diagnostics/types.d.ts +24 -0
  190. package/dist/diagnostics/version-check.js +8 -2
  191. package/dist/http.js +2 -2
  192. package/dist/index.d.ts +3 -3
  193. package/dist/index.js +7 -0
  194. package/dist/indexeddb.js +1 -1
  195. package/dist/json-schema.js +3 -3
  196. package/dist/server/fault-to-json-rpc.js +2 -1
  197. package/dist/server/fleet-event-feed.js +6 -6
  198. package/dist/server/index.d.ts +11 -0
  199. package/dist/server/operation-fault.d.ts +2 -0
  200. package/dist/server/operation-fault.js +2 -1
  201. package/dist/server/operations/create-schedule.d.ts +2 -0
  202. package/dist/server/operations/create-schedule.js +10 -6
  203. package/dist/server/operations/get-catalog-diagnostics.d.ts +28 -0
  204. package/dist/server/operations/get-catalog-diagnostics.js +10 -2
  205. package/dist/server/operations/get-task-detail-schema.d.ts +8 -0
  206. package/dist/server/operations/get-task-detail-schema.js +1 -0
  207. package/dist/server/operations/get-task-detail.d.ts +8 -0
  208. package/dist/server/operations/get-task-detail.js +1 -0
  209. package/dist/server/operations/get-task-diagnostics.d.ts +4 -4
  210. package/dist/server/operations/preload-workflow-revision.d.ts +28 -0
  211. package/dist/server/operations/preload-workflow-revision.js +60 -0
  212. package/dist/server/operations/schedule-faults.d.ts +1 -0
  213. package/dist/server/operations/schedule-faults.js +32 -4
  214. package/dist/server/operations/schedule-rest-body.d.ts +1 -0
  215. package/dist/server/operations/schedule-rest-body.js +2 -1
  216. package/dist/server/operations/static-registrations.js +6 -0
  217. package/dist/server/operations/update-schedule.d.ts +2 -0
  218. package/dist/server/operations/update-schedule.js +4 -2
  219. package/dist/server/operations/workflow-catalog-operation-helpers.js +27 -0
  220. package/dist/server/rest-binding.d.ts +11 -0
  221. package/dist/server/runtime/task-dispatch-envelope.d.ts +41 -0
  222. package/dist/server/runtime/task-dispatch-envelope.js +54 -0
  223. package/dist/server/runtime/task-dispatch-revision.d.ts +29 -0
  224. package/dist/server/runtime/task-dispatch-revision.js +13 -0
  225. package/dist/server/runtime/task-dispatch.d.ts +3 -1
  226. package/dist/server/runtime/task-dispatch.js +20 -44
  227. package/dist/server/runtime/task-ledger-recovery.js +1 -1
  228. package/dist/server/runtime/task-ledger-runtime.d.ts +14 -2
  229. package/dist/server/runtime/task-ledger-runtime.js +5 -3
  230. package/dist/server/runtime/task-polling.js +9 -3
  231. package/dist/server/runtime/task-reconciliation.js +2 -1
  232. package/dist/server/runtime/websocket-worker.js +9 -0
  233. package/dist/server/task-ledger-codec.d.ts +28 -0
  234. package/dist/server/task-ledger-codec.js +7 -1
  235. package/dist/server/task-ledger-transition-helpers.js +1 -0
  236. package/dist/server/task-ledger-types.d.ts +9 -0
  237. package/dist/server/task-ledger.d.ts +1 -1
  238. package/dist/server/task-ledger.js +3 -1
  239. package/dist/server/task-queue-types.d.ts +2 -0
  240. package/dist/storage/bun-sql.js +135 -100
  241. package/dist/storage/catalog-keys.d.ts +19 -0
  242. package/dist/storage/catalog-keys.js +3 -1
  243. package/dist/storage/compressed-storage.js +1 -1
  244. package/dist/storage/generation-keys.d.ts +52 -0
  245. package/dist/storage/generation-keys.js +4 -0
  246. package/dist/storage/index.d.ts +76 -63
  247. package/dist/storage/interface.d.ts +24 -230
  248. package/dist/storage/interface.js +1 -1
  249. package/dist/storage/key-prefixes.d.ts +1 -1
  250. package/dist/storage/key-prefixes.js +2 -0
  251. package/dist/storage/lease-keys.d.ts +48 -0
  252. package/dist/storage/lease-keys.js +8 -0
  253. package/dist/storage/lmdb.d.ts +30 -2
  254. package/dist/storage/lmdb.js +1 -1
  255. package/dist/storage/memory.js +1 -1
  256. package/dist/storage/neon.js +3 -3
  257. package/dist/storage/node-sqlite.js +135 -100
  258. package/dist/storage/postgres.js +3 -3
  259. package/dist/storage/resolve.js +1 -1
  260. package/dist/storage/scoped-storage.js +1 -1
  261. package/dist/storage/signal-keys.d.ts +23 -0
  262. package/dist/storage/signal-keys.js +9 -0
  263. package/dist/storage/testing.js +1 -1
  264. package/dist/storage/turso.js +2 -2
  265. package/dist/storage/workflow-lifecycle-keys.d.ts +128 -0
  266. package/dist/storage/workflow-lifecycle-keys.js +46 -0
  267. package/dist/storage/workflow-record-keys.d.ts +98 -0
  268. package/dist/storage/workflow-record-keys.js +47 -0
  269. package/dist/version.d.ts +1 -1
  270. package/dist/version.js +1 -1
  271. package/dist/web-extension.js +1 -1
  272. package/dist/worker/protocol-messages.d.ts +17 -0
  273. package/dist/worker/protocol-schemas.d.ts +32 -0
  274. package/dist/worker/protocol-schemas.js +7 -3
  275. package/dist/worker/protocol-task-result.js +35 -3
  276. package/dist/worker/protocol.js +1 -1
  277. package/dist/worker/registry/types.d.ts +2 -0
  278. package/dist/worker/registry.d.ts +1 -1
  279. package/dist/worker/registry.js +3 -1
  280. package/dist/workers/workflow-runner.d.ts +9 -0
  281. package/dist/workers/workflow-runner.js +9 -2
  282. package/dist/workers/workflow-worker-entry.js +9 -4
  283. package/package.json +2 -2
@@ -1,4 +1,3 @@
1
- import { isJSONValue } from "../../core/json.js";
2
1
  import { buildWorkerExecutionIdentity } from "../../worker/manifest/execution-identity.js";
3
2
  import { evictOldestAffinityEntries } from "../runtime-helpers.js";
4
3
  import {
@@ -7,9 +6,14 @@ import {
7
6
  } from "../task-ledger-transitions.js";
8
7
  import {
9
8
  decodeRemoteTaskRecord,
10
- REMOTE_TASK_RECORD_VERSION,
11
9
  taskLedgerKey
12
10
  } from "../task-ledger.js";
11
+ import {
12
+ assertFreshDispatchOperationIdAdmissible,
13
+ awaitTaskLedgerRecoveryReady,
14
+ buildCreateQueuedInput
15
+ } from "./task-dispatch-envelope.js";
16
+ import { assertDispatchTargetsFreshRevision } from "./task-dispatch-revision.js";
13
17
  import { commitTaskLedgerTransition } from "./task-ledger-runtime.js";
14
18
  import {
15
19
  recordTaskBacklogMetric,
@@ -27,7 +31,7 @@ export function scheduleDelayedDispatch(context, options, task, delay) {
27
31
  return;
28
32
  const timer = setTimeout(() => {
29
33
  context.pendingTimers.delete(timer);
30
- dispatchTaskImpl(context, options, task).catch((err) => console.error(`[weft] Delayed redispatch failed for "${task.operationId}":`, err));
34
+ dispatchTaskImpl(context, options, task, { redispatch: !0 }).catch((err) => console.error(`[weft] Delayed redispatch failed for "${task.operationId}":`, err));
31
35
  }, delay);
32
36
  context.pendingTimers.add(timer);
33
37
  }
@@ -65,33 +69,6 @@ function recordDispatchOutcome(context, task, workerId) {
65
69
  operationIds.add(task.operationId);
66
70
  context.operationToWorkflow.set(task.operationId, task.workflowId);
67
71
  }
68
- function buildOptionalCreateQueuedFields(task) {
69
- return {
70
- ...task.workflowId !== void 0 ? { workflowId: task.workflowId } : {},
71
- ...task.workflowExecutionToken !== void 0 ? { workflowExecutionToken: task.workflowExecutionToken } : {},
72
- ...task.priority !== void 0 ? { priority: task.priority } : {},
73
- ...task.fairShareKey !== void 0 ? { fairShareKey: task.fairShareKey } : {},
74
- ...task.sticky && task.workflowId !== void 0 ? { stickyWorkflowId: task.workflowId } : {},
75
- ...task.retryPolicy !== void 0 ? { retryPolicy: task.retryPolicy } : {}
76
- };
77
- }
78
- function buildCreateQueuedInput(task, queue, visibilityTimeout) {
79
- const input = task.input === void 0 ? null : task.input;
80
- if (!isJSONValue(input))
81
- throw Error(`TaskDispatch for operation "${task.operationId}" has a non-JSON-serializable "input" \u2014 the durable task ledger requires JSON-safe input.`);
82
- return {
83
- recordVersion: REMOTE_TASK_RECORD_VERSION,
84
- operationId: task.operationId,
85
- workflowType: task.workflowType,
86
- activityName: task.activityName,
87
- queue,
88
- input,
89
- headers: task.headers ?? {},
90
- visibilityTimeoutMilliseconds: visibilityTimeout,
91
- createdAt: Date.now(),
92
- ...buildOptionalCreateQueuedFields(task)
93
- };
94
- }
95
72
  function buildCreateThenClaimTransition(createInput, claimInput) {
96
73
  return (current, now) => {
97
74
  let queuedRecord;
@@ -114,7 +91,7 @@ function bareActivityName(qualifiedActivityName) {
114
91
  const dotIndex = qualifiedActivityName.indexOf(".");
115
92
  return dotIndex === -1 ? qualifiedActivityName : qualifiedActivityName.slice(dotIndex + 1);
116
93
  }
117
- async function selectAndReserveWorker(context, options, task, queue, visibilityTimeout) {
94
+ async function selectAndReserveWorker(context, options, task, queue, visibilityTimeout, revisionFenceConditions) {
118
95
  const routingOptions = buildRoutingOptions(context, task, queue), worker = context.registry.findWorker(task.activityName, routingOptions);
119
96
  if (!worker)
120
97
  return !1;
@@ -128,7 +105,7 @@ async function selectAndReserveWorker(context, options, task, queue, visibilityT
128
105
  workflowType: task.workflowType,
129
106
  activityName: bareActivityName(task.activityName)
130
107
  });
131
- context.registry.assignTask(worker.id, task.operationId, visibilityTimeout, task.fairShareKey, attemptToken);
108
+ context.registry.assignTask(worker.id, task.operationId, visibilityTimeout, task.fairShareKey, attemptToken, task.workflowRevision);
132
109
  let result;
133
110
  try {
134
111
  result = await commitTaskLedgerTransition(options.engine.storage, task.operationId, buildCreateThenClaimTransition(createInput, {
@@ -136,7 +113,7 @@ async function selectAndReserveWorker(context, options, task, queue, visibilityT
136
113
  workerSessionId: worker.id,
137
114
  ...executionIdentity !== void 0 ? { executionIdentity } : {},
138
115
  leaseDurationMilliseconds: visibilityTimeout
139
- }), 1);
116
+ }), 1, revisionFenceConditions);
140
117
  } catch (error) {
141
118
  context.registry.releaseReservation(task.operationId);
142
119
  throw error;
@@ -159,6 +136,7 @@ async function selectAndReserveWorker(context, options, task, queue, visibilityT
159
136
  ...task.workflowExecutionToken !== void 0 && {
160
137
  workflowExecutionToken: task.workflowExecutionToken
161
138
  },
139
+ ...task.workflowRevision !== void 0 && { workflowRevision: task.workflowRevision },
162
140
  ...task.headers ? { headers: task.headers } : {}
163
141
  }));
164
142
  recordTaskQueueLatencyMetric(context.metricsCollector, {
@@ -169,11 +147,11 @@ async function selectAndReserveWorker(context, options, task, queue, visibilityT
169
147
  recordDispatchOutcome(context, task, worker.id);
170
148
  return !0;
171
149
  }
172
- async function enqueueTaskForLongPoll(context, options, task, queue, visibilityTimeout, resolvedPriority) {
150
+ async function enqueueTaskForLongPoll(context, options, task, queue, visibilityTimeout, resolvedPriority, revisionFenceConditions) {
173
151
  const storage = options.engine.storage, createInput = buildCreateQueuedInput(task, queue, visibilityTimeout), rawExisting = await storage.get(taskLedgerKey(task.operationId)), current = decodeRemoteTaskRecord(rawExisting);
174
152
  let queuedRecord;
175
153
  if (current === null) {
176
- const result = await commitTaskLedgerTransition(storage, task.operationId, (freshCurrent, now) => createQueued(freshCurrent, createInput, now), 1);
154
+ const result = await commitTaskLedgerTransition(storage, task.operationId, (freshCurrent, now) => createQueued(freshCurrent, createInput, now), 1, revisionFenceConditions);
177
155
  if (!result.ok) {
178
156
  const raced = decodeRemoteTaskRecord(await storage.get(taskLedgerKey(task.operationId)));
179
157
  if (raced === null || raced.state !== "queued")
@@ -194,6 +172,7 @@ async function enqueueTaskForLongPoll(context, options, task, queue, visibilityT
194
172
  visibilityTimeout,
195
173
  workflowId: task.workflowId,
196
174
  workflowExecutionToken: task.workflowExecutionToken,
175
+ workflowRevision: queuedRecord.workflowRevision,
197
176
  firstQueuedAt: queuedRecord.firstQueuedAt,
198
177
  lastQueuedAt: queuedRecord.lastQueuedAt,
199
178
  lastDispatchedAt: queuedRecord.lastDispatchedAt,
@@ -206,23 +185,20 @@ async function enqueueTaskForLongPoll(context, options, task, queue, visibilityT
206
185
  recordTaskBacklogMetric(context.metricsCollector, context.taskQueue);
207
186
  return enqueued;
208
187
  }
209
- export async function dispatchTaskImpl(context, options, task) {
210
- try {
211
- await context.taskLedgerRecovery.ready;
212
- } catch (error) {
213
- throw Error(`Cannot dispatch task "${task.operationId}" \u2014 startup task-ledger recovery failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
214
- }
188
+ export async function dispatchTaskImpl(context, options, task, { redispatch = !1 } = {}) {
189
+ await awaitTaskLedgerRecoveryReady(context, task);
190
+ assertFreshDispatchOperationIdAdmissible(task, redispatch);
215
191
  if (!task.workflowType)
216
192
  throw Error(`TaskDispatch for operation "${task.operationId}" is missing required field "workflowType".`);
217
- const dotIndex = task.activityName.indexOf(".");
193
+ const revisionFenceConditions = await assertDispatchTargetsFreshRevision(options, task), dotIndex = task.activityName.indexOf(".");
218
194
  if (dotIndex !== -1 && task.activityName.slice(0, dotIndex) !== task.workflowType)
219
195
  throw Error(`TaskDispatch for operation "${task.operationId}" has activityName "${task.activityName}" whose qualifier does not match workflowType "${task.workflowType}".`);
220
196
  const queue = task.queue ?? "default", visibilityTimeout = clampVisibilityTimeout(task.visibilityTimeout), resolvedPriority = resolveTaskPriority(context, options, task);
221
197
  if (!validateTaskEnvelope(context, task))
222
198
  return !1;
223
- if (await selectAndReserveWorker(context, options, task, queue, visibilityTimeout))
199
+ if (await selectAndReserveWorker(context, options, task, queue, visibilityTimeout, revisionFenceConditions))
224
200
  return !0;
225
- return enqueueTaskForLongPoll(context, options, task, queue, visibilityTimeout, resolvedPriority);
201
+ return enqueueTaskForLongPoll(context, options, task, queue, visibilityTimeout, resolvedPriority, revisionFenceConditions);
226
202
  }
227
203
  export function cancelTask(context, operationId) {
228
204
  const task = context.registry.getTask(operationId);
@@ -16,7 +16,7 @@ function rebuildWorkflowIndex(context, operationId, workflowId) {
16
16
  }
17
17
  function rehydrateWorkerOwnership(context, record, now) {
18
18
  const remaining = Math.max(0, record.leaseDeadline - now);
19
- context.registry.assignTask(record.workerSessionId, record.operationId, remaining, record.fairShareKey, record.attemptToken);
19
+ context.registry.assignTask(record.workerSessionId, record.operationId, remaining, record.fairShareKey, record.attemptToken, record.workflowRevision);
20
20
  const tracked = context.registry.getWorkerTasks(record.workerSessionId).find((task) => task.operationId === record.operationId);
21
21
  if (tracked)
22
22
  tracked.visibilityTimeout = record.visibilityTimeoutMilliseconds;
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * @module server/runtime/task-ledger-runtime
18
18
  */
19
- import { type Storage } from '../../storage/interface.ts';
19
+ import { type ConditionalBatchCondition, type Storage } from '../../storage/interface.ts';
20
20
  import type { TaskLedgerPreconditionResult, TaskLedgerTransitionResult } from '../task-ledger-transitions.ts';
21
21
  import { type RemoteTaskRecord } from '../task-ledger.ts';
22
22
  export type TaskLedgerCommitResult<T extends RemoteTaskRecord> = Readonly<{
@@ -46,7 +46,19 @@ export type TaskLedgerDeleteResult = Readonly<{
46
46
  * reflects reality and re-attempting the identical transition cannot change
47
47
  * the outcome.
48
48
  */
49
- export declare function commitTaskLedgerTransition<T extends RemoteTaskRecord>(storage: Storage, operationId: string, transitionFn: (current: RemoteTaskRecord | null, now: number) => TaskLedgerTransitionResult<T>, maxAttempts?: number): Promise<TaskLedgerCommitResult<T>>;
49
+ export declare function commitTaskLedgerTransition<T extends RemoteTaskRecord>(storage: Storage, operationId: string, transitionFn: (current: RemoteTaskRecord | null, now: number) => TaskLedgerTransitionResult<T>, maxAttempts?: number,
50
+ /**
51
+ * Extra same-transaction preconditions to fence the commit on — for
52
+ * example, "the target workflow's persisted state is still exactly the
53
+ * bytes read at dispatch time" (WFT-20's revision-staleness gate). Checked
54
+ * in the SAME `conditionalBatch` call as the ledger key's own CAS, so a
55
+ * concurrent write to any of these keys between read and commit loses the
56
+ * whole batch atomically, closing the gap a standalone pre-check
57
+ * (read-then-later-write, with unrelated work in between) cannot close.
58
+ * Re-supplied by the caller on each retry attempt since the caller, not
59
+ * this loop, owns re-reading whatever these conditions guard.
60
+ */
61
+ additionalConditions?: readonly ConditionalBatchCondition[]): Promise<TaskLedgerCommitResult<T>>;
50
62
  /**
51
63
  * Read the current ledger record, check a delete-only precondition (WFT-24
52
64
  * retention: {@link canDeleteRetainedTerminalTask}), and conditionally delete
@@ -1,16 +1,18 @@
1
- import { storageConditionalBatch } from "../../storage/interface.js";
1
+ import {
2
+ storageConditionalBatch
3
+ } from "../../storage/interface.js";
2
4
  import {
3
5
  decodeRemoteTaskRecord,
4
6
  encodeRemoteTaskRecord,
5
7
  taskLedgerKey
6
8
  } from "../task-ledger.js";
7
- export async function commitTaskLedgerTransition(storage, operationId, transitionFn, maxAttempts = 1) {
9
+ export async function commitTaskLedgerTransition(storage, operationId, transitionFn, maxAttempts = 1, additionalConditions = []) {
8
10
  const key = taskLedgerKey(operationId);
9
11
  for (let attempt = 1;attempt <= maxAttempts; attempt++) {
10
12
  const rawExisting = await storage.get(key), current = decodeRemoteTaskRecord(rawExisting), result = transitionFn(current, Date.now());
11
13
  if (!result.ok)
12
14
  return result;
13
- if (await storageConditionalBatch(storage, [{ key, expectedValue: rawExisting }], [{ type: "put", key, value: encodeRemoteTaskRecord(result.nextRecord) }]))
15
+ if (await storageConditionalBatch(storage, [{ key, expectedValue: rawExisting }, ...additionalConditions], [{ type: "put", key, value: encodeRemoteTaskRecord(result.nextRecord) }]))
14
16
  return { ok: !0, record: result.nextRecord };
15
17
  }
16
18
  return {
@@ -58,7 +58,8 @@ function validateTaskResultBody(body) {
58
58
  workerId: typeof body.workerId === "string" ? body.workerId : void 0,
59
59
  value: body.value,
60
60
  error: typeof body.error === "string" ? body.error : void 0,
61
- attemptToken: rawAttemptToken
61
+ attemptToken: rawAttemptToken,
62
+ workflowRevision: typeof body.workflowRevision === "string" ? body.workflowRevision : void 0
62
63
  };
63
64
  }
64
65
  function isLongPollCompletionAuthorized(record, validated) {
@@ -68,7 +69,11 @@ function isLongPollCompletionAuthorized(record, validated) {
68
69
  return !1;
69
70
  if (validated.workerId === void 0 || record.workerSessionId !== validated.workerId)
70
71
  return !1;
71
- return validated.attemptToken === record.attemptToken;
72
+ if (validated.attemptToken !== record.attemptToken)
73
+ return !1;
74
+ if (record.workflowRevision !== void 0 && validated.workflowRevision !== record.workflowRevision)
75
+ return !1;
76
+ return !0;
72
77
  }
73
78
  async function applyTaskResult(context, options, result) {
74
79
  const { operationId, status, value, error } = result, committed = await commitTaskLedgerCompletion(options.engine.storage, {
@@ -155,7 +160,8 @@ export async function handleTaskPollRequest(context, options, request, url, prin
155
160
  attemptToken: claim.attemptToken,
156
161
  ...task.workflowExecutionToken !== void 0 && {
157
162
  workflowExecutionToken: task.workflowExecutionToken
158
- }
163
+ },
164
+ ...task.workflowRevision !== void 0 && { workflowRevision: task.workflowRevision }
159
165
  });
160
166
  }
161
167
  return new Response(null, { status: 204 });
@@ -65,7 +65,8 @@ export function taskDispatchFromLedgerRecord(record) {
65
65
  input: record.input,
66
66
  queue: record.queue,
67
67
  visibilityTimeout: record.visibilityTimeoutMilliseconds,
68
- workflowExecutionToken: record.workflowExecutionToken
68
+ workflowExecutionToken: record.workflowExecutionToken,
69
+ workflowRevision: record.workflowRevision
69
70
  };
70
71
  if (record.workflowId !== void 0)
71
72
  taskDispatch.workflowId = record.workflowId;
@@ -47,6 +47,15 @@ function onTaskResultMessage(context, options, ws, message, cleanupWorkflowIndex
47
47
  });
48
48
  return;
49
49
  }
50
+ const inFlightTask = context.registry.getTask(operationId);
51
+ if (inFlightTask?.workflowRevision !== void 0 && message.workflowRevision !== inFlightTask.workflowRevision) {
52
+ sendWorkerProtocolMessage(ws, {
53
+ type: "protocolError",
54
+ code: "invalid_message",
55
+ message: `taskResult for operation "${operationId}" rejected \u2014 revision mismatch`
56
+ });
57
+ return;
58
+ }
50
59
  const resolvedStatus = resolveTaskResultStatus(message), payloadError = taskResultPayloadSizeError({
51
60
  status: resolvedStatus,
52
61
  ...message.status === "completed" ? { value: message.value } : { error: message.error }
@@ -15,6 +15,34 @@
15
15
  * @module server/task-ledger-codec
16
16
  */
17
17
  import type { RemoteTaskCancelling, RemoteTaskCompleting, RemoteTaskDeadLettered, RemoteTaskLeased, RemoteTaskQueued, RemoteTaskRecord, RemoteTaskTerminal, RemoteTaskTerminalCancelled, RemoteTaskTerminalResolved, RemoteTaskTerminalRetryExhausted } from './task-ledger-types.ts';
18
+ /**
19
+ * Whether `value` is a valid `workflowRevision` per the durable task
20
+ * ledger's non-empty, bounded identifier contract — the same contract
21
+ * `decodeRemoteTaskRecord` enforces on read. Exported so dispatch-time
22
+ * callers (`task-dispatch.ts`) can reject an invalid caller-supplied
23
+ * revision before it reaches a ledger write, instead of writing a record
24
+ * that later fails to decode.
25
+ */
26
+ export declare function isValidWorkflowRevision(value: unknown): value is string;
27
+ /**
28
+ * Whether `value` is a valid caller-facing task `operationId` — a non-empty,
29
+ * bounded identifier per the ledger's own contract, and not the exact string
30
+ * `.` or `..` (WFT-95). WHATWG URL path normalization collapses `.`/`..`
31
+ * path segments (and their percent-encoded forms) before `handleRequest()`
32
+ * ever sees `url.pathname`, so a REST route with a single trailing
33
+ * `:operationId` segment (e.g. `weft.tasks.get`'s
34
+ * `/v1/tasks/detail/:operationId`) can never address a record whose
35
+ * operationId is literally `.` or `..`. Exported so fresh-dispatch admission
36
+ * (`task-dispatch.ts`'s `dispatchTaskImpl`) can reject an invalid
37
+ * caller-supplied `operationId` before it ever reaches a ledger write,
38
+ * closing that URL-normalization quirk as an enforced admission-time
39
+ * guarantee instead of an assumption. Deliberately NOT applied when
40
+ * redispatching an already-decoded, previously persisted ledger record —
41
+ * see `dispatchTaskImpl`'s `redispatch` option — since such an id was valid
42
+ * under the pre-WFT-95 decode contract and may already be durably
43
+ * persisted. Does not reject an id that merely contains a dot character.
44
+ */
45
+ export declare function isValidOperationId(value: unknown): value is string;
18
46
  /** Bounded header map: at most {@link MAX_TASK_HEADER_COUNT} entries, each key/value at most {@link MAX_TASK_HEADER_VALUE_BYTES}. */
19
47
  export declare function isValidTaskHeaders(value: unknown): value is Readonly<Record<string, string>>;
20
48
  export declare function isRemoteTaskQueued(value: unknown): value is RemoteTaskQueued;
@@ -14,6 +14,12 @@ function isRecord(value) {
14
14
  function isBoundedIdentifier(value, maxBytes = MAX_TASK_IDENTIFIER_BYTES) {
15
15
  return typeof value === "string" && value.length > 0 && utf8ByteLength(value) <= maxBytes;
16
16
  }
17
+ export function isValidWorkflowRevision(value) {
18
+ return isBoundedIdentifier(value);
19
+ }
20
+ export function isValidOperationId(value) {
21
+ return isBoundedIdentifier(value) && value !== "." && value !== "..";
22
+ }
17
23
  function isBoundedOptionalIdentifier(value, maxBytes = MAX_TASK_IDENTIFIER_BYTES) {
18
24
  return value === void 0 || isBoundedIdentifier(value, maxBytes);
19
25
  }
@@ -86,7 +92,7 @@ function isOptionalExecutionIdentity(value) {
86
92
  return value === void 0 || isValidExecutionIdentity(value);
87
93
  }
88
94
  function isValidTaskIdentityFields(value) {
89
- return value.recordVersion === 1 && isBoundedIdentifier(value.operationId) && isBoundedOptionalIdentifier(value.workflowId) && isBoundedIdentifier(value.workflowType) && isBoundedOptionalIdentifier(value.workflowExecutionToken) && isBoundedIdentifier(value.activityName) && isBoundedIdentifier(value.queue);
95
+ return value.recordVersion === 1 && isBoundedIdentifier(value.operationId) && isBoundedOptionalIdentifier(value.workflowId) && isBoundedIdentifier(value.workflowType) && isBoundedOptionalIdentifier(value.workflowExecutionToken) && isBoundedOptionalIdentifier(value.workflowRevision) && isBoundedIdentifier(value.activityName) && isBoundedIdentifier(value.queue);
90
96
  }
91
97
  function isValidTaskRoutingFields(value) {
92
98
  return isOptionalFiniteNumber(value.priority) && isBoundedOptionalIdentifier(value.fairShareKey) && isBoundedOptionalIdentifier(value.stickyWorkflowId) && isValidExecutionRequirement(value.executionRequirement);
@@ -5,6 +5,7 @@ export function pickBase(record) {
5
5
  ...record.workflowId !== void 0 ? { workflowId: record.workflowId } : {},
6
6
  workflowType: record.workflowType,
7
7
  ...record.workflowExecutionToken !== void 0 ? { workflowExecutionToken: record.workflowExecutionToken } : {},
8
+ ...record.workflowRevision !== void 0 ? { workflowRevision: record.workflowRevision } : {},
8
9
  activityName: record.activityName,
9
10
  queue: record.queue,
10
11
  input: record.input,
@@ -48,6 +48,15 @@ export type RemoteTaskBase = Readonly<{
48
48
  workflowId?: string;
49
49
  workflowType: string;
50
50
  workflowExecutionToken?: string;
51
+ /**
52
+ * The dispatching workflow run's persisted revision (WFT-20), when the
53
+ * `TaskDispatch` caller supplied one. Same optionality rationale as
54
+ * `workflowExecutionToken` above — NOT the same field as
55
+ * `executionRequirement.workflowRevision` (a client-declared routing
56
+ * constraint carried by `WorkerExecutionRequirementInput`, unrelated to
57
+ * this plain wire echo/validation field).
58
+ */
59
+ workflowRevision?: string;
51
60
  activityName: string;
52
61
  queue: string;
53
62
  input: JSONValue;
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @module server/task-ledger
12
12
  */
13
- export { decodeRemoteTaskRecord, encodeRemoteTaskRecord, isRemoteTaskCancelling, isRemoteTaskCompleting, isRemoteTaskDeadLettered, isRemoteTaskLeased, isRemoteTaskQueued, isRemoteTaskRecord, isRemoteTaskTerminal, isRemoteTaskTerminalCancelled, isRemoteTaskTerminalResolved, isRemoteTaskTerminalRetryExhausted, isValidTaskHeaders, } from './task-ledger-codec.ts';
13
+ export { decodeRemoteTaskRecord, encodeRemoteTaskRecord, isRemoteTaskCancelling, isRemoteTaskCompleting, isRemoteTaskDeadLettered, isRemoteTaskLeased, isRemoteTaskQueued, isRemoteTaskRecord, isRemoteTaskTerminal, isRemoteTaskTerminalCancelled, isRemoteTaskTerminalResolved, isRemoteTaskTerminalRetryExhausted, isValidOperationId, isValidTaskHeaders, isValidWorkflowRevision, } from './task-ledger-codec.ts';
14
14
  export { taskLedgerKey } from './task-ledger-keys.ts';
15
15
  export { MAX_TASK_HEADER_COUNT, MAX_TASK_HEADER_VALUE_BYTES, MAX_TASK_IDENTIFIER_BYTES, MAX_TASK_REASON_BYTES, utf8ByteLength, } from './task-ledger-limits.ts';
16
16
  export { REMOTE_TASK_RECORD_VERSION } from './task-ledger-types.ts';
@@ -11,7 +11,9 @@ export {
11
11
  isRemoteTaskTerminalCancelled,
12
12
  isRemoteTaskTerminalResolved,
13
13
  isRemoteTaskTerminalRetryExhausted,
14
- isValidTaskHeaders
14
+ isValidOperationId,
15
+ isValidTaskHeaders,
16
+ isValidWorkflowRevision
15
17
  } from "./task-ledger-codec.js";
16
18
  export { taskLedgerKey } from "./task-ledger-keys.js";
17
19
  export {
@@ -15,6 +15,8 @@ export interface PendingTask extends TaskLifecycleFields {
15
15
  workflowId?: string | undefined;
16
16
  /** Durable token for the workflow run that dispatched this activity, when known. */
17
17
  workflowExecutionToken?: string | undefined;
18
+ /** The dispatching workflow run's persisted revision (WFT-20), when known. */
19
+ workflowRevision?: string | undefined;
18
20
  /**
19
21
  * Task priority. Higher values are dequeued first. Tasks with equal priority
20
22
  * maintain FIFO order. Default: 0. Agent workflow tasks use priority 10.