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