@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
@@ -0,0 +1,57 @@
1
+ export class WorkflowSourceLoadStartedEvent extends Event {
2
+ static type = "workflow-source:load-started";
3
+ workflowType;
4
+ revision;
5
+ kind;
6
+ constructor(workflowType, revision, kind) {
7
+ super(WorkflowSourceLoadStartedEvent.type);
8
+ this.workflowType = workflowType;
9
+ this.revision = revision;
10
+ this.kind = kind;
11
+ }
12
+ }
13
+
14
+ export class WorkflowSourceLoadReadyEvent extends Event {
15
+ static type = "workflow-source:load-ready";
16
+ workflowType;
17
+ revision;
18
+ kind;
19
+ loadDurationMs;
20
+ constructor(workflowType, revision, kind, loadDurationMs) {
21
+ super(WorkflowSourceLoadReadyEvent.type);
22
+ this.workflowType = workflowType;
23
+ this.revision = revision;
24
+ this.kind = kind;
25
+ this.loadDurationMs = loadDurationMs;
26
+ }
27
+ }
28
+
29
+ export class WorkflowSourceLoadFailedEvent extends Event {
30
+ static type = "workflow-source:load-failed";
31
+ workflowType;
32
+ revision;
33
+ kind;
34
+ loadDurationMs;
35
+ failureCategory;
36
+ constructor(workflowType, revision, kind, loadDurationMs, failureCategory) {
37
+ super(WorkflowSourceLoadFailedEvent.type);
38
+ this.workflowType = workflowType;
39
+ this.revision = revision;
40
+ this.kind = kind;
41
+ this.loadDurationMs = loadDurationMs;
42
+ this.failureCategory = failureCategory;
43
+ }
44
+ }
45
+
46
+ export class WorkflowSourceLoadCancelledEvent extends Event {
47
+ static type = "workflow-source:load-cancelled";
48
+ workflowType;
49
+ revision;
50
+ kind;
51
+ constructor(workflowType, revision, kind) {
52
+ super(WorkflowSourceLoadCancelledEvent.type);
53
+ this.workflowType = workflowType;
54
+ this.revision = revision;
55
+ this.kind = kind;
56
+ }
57
+ }
@@ -85,6 +85,13 @@ export interface ExecutionStrategy extends Disposable, AsyncDisposable {
85
85
  startWorkflow(parameters: {
86
86
  workflowId: string;
87
87
  workflowExecutionToken?: string;
88
+ /**
89
+ * The starting run's persisted `WorkflowState.revision` (WFT-20). Threaded
90
+ * through to the worker strategy's outbound echo/validation; an inline
91
+ * strategy accepts and ignores it — inline execution crosses no trust
92
+ * boundary this field needs to police.
93
+ */
94
+ revision?: string;
88
95
  workflowType: string;
89
96
  input: unknown;
90
97
  checkpoint: ArrayBuffer | Uint8Array;
@@ -11,7 +11,7 @@ import type { SearchAttributeSchema, WorkflowFunction } from './types.ts';
11
11
  import type { WorkflowLogRecord } from './types/workflow-log.ts';
12
12
  /** Capabilities the engine injects into the inline strategy at construction. */
13
13
  export interface InlineExecutionDependencies {
14
- getRegistration: (workflowType: string) => {
14
+ getRegistration: (workflowType: string, workflowId: string) => {
15
15
  handler: WorkflowFunction;
16
16
  version: string;
17
17
  searchAttributes?: SearchAttributeSchema;
@@ -30,7 +30,7 @@ export class InlineExecutionStrategy {
30
30
  this.#messageHandler = handler;
31
31
  }
32
32
  startWorkflow(parameters) {
33
- const registration = this.#dependencies.getRegistration(parameters.workflowType);
33
+ const registration = this.#dependencies.getRegistration(parameters.workflowType, parameters.workflowId);
34
34
  if (!registration) {
35
35
  this.#emit({
36
36
  type: "failed",
@@ -14,6 +14,21 @@ export declare class StartWorkflowValidationError extends WeftError<'StartWorkfl
14
14
  }
15
15
  export declare const assertExclusiveStartWorkflowOptions: (startAt: unknown, startAfter: unknown) => void;
16
16
  export declare const coerceStartWorkflowId: (value: unknown, fieldName: string) => string;
17
+ /**
18
+ * Coerce a caller-supplied `options.id` for an internal REPLAY of a start
19
+ * that was already accepted once before (WFT-95). Deliberately uses the
20
+ * decode-compatible {@link assertDecodableWorkflowId}, not the strict
21
+ * `.`/`..`-rejecting {@link assertValidWorkflowId} that
22
+ * {@link coerceStartWorkflowId} enforces: this path exists only for the three
23
+ * internal callers that replay an id which was already durably admitted
24
+ * before strict admission existed (a drained schedule queued-run, a bulk
25
+ * failed-workflow retry rebuilding from persisted input, or a child-workflow
26
+ * crash-reattach) — see `startWorkflow`'s `skipAdmissionIdCheck` parameter.
27
+ * It must never be reachable from a public start surface (REST, JSON-RPC,
28
+ * `engine.start`, `ctx.startChild`), because that would let a genuinely
29
+ * fresh caller admit `.`/`..` again.
30
+ */
31
+ export declare const coerceReplayWorkflowId: (value: unknown, fieldName: string) => string;
17
32
  /**
18
33
  * Coerce a transport-supplied idempotency key to a non-empty string. The key is
19
34
  * a caller-chosen dedup token (it becomes part of a `start-idem:` storage key),
@@ -1,6 +1,6 @@
1
1
  import { parseDuration } from "./scheduler.js";
2
2
  import { WeftError } from "./weft-error.js";
3
- import { assertValidWorkflowId } from "./workflow-identifiers.js";
3
+ import { assertDecodableWorkflowId, assertValidWorkflowId } from "./workflow-identifiers.js";
4
4
  export const MAX_WORKFLOW_TAGS = 32, MAX_WORKFLOW_TAG_BYTES = 128, MAX_IDEMPOTENCY_KEY_BYTES = 117;
5
5
  const textEncoder = new TextEncoder, EXCLUSIVE_START_WORKFLOW_OPTIONS_ERROR = "Provide only one of startAt or startAfter";
6
6
 
@@ -22,6 +22,16 @@ export const assertExclusiveStartWorkflowOptions = (startAt, startAfter) => {
22
22
  const message = error instanceof Error ? error.message : String(error);
23
23
  throw new StartWorkflowValidationError(message);
24
24
  }
25
+ }, coerceReplayWorkflowId = (value, fieldName) => {
26
+ if (typeof value !== "string")
27
+ throw new StartWorkflowValidationError(`${fieldName} must be a string`);
28
+ try {
29
+ assertDecodableWorkflowId(value, fieldName);
30
+ return value;
31
+ } catch (error) {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ throw new StartWorkflowValidationError(message);
34
+ }
25
35
  }, coerceStartWorkflowIdempotencyKey = (value, fieldName) => {
26
36
  if (typeof value !== "string")
27
37
  throw new StartWorkflowValidationError(`${fieldName} must be a string`);
@@ -199,6 +199,14 @@ export type WorkerInboundMessage = {
199
199
  maxProtocolMessageBytes?: number;
200
200
  workflowId: WorkflowId;
201
201
  workflowExecutionToken?: string;
202
+ /**
203
+ * The dispatching run's persisted `WorkflowState.revision` (WFT-20),
204
+ * when known. Captured worker-side and re-stamped on every outbound
205
+ * message for this workflow's remaining turns (including `resume`
206
+ * turns, which never resend this field) — see
207
+ * `workers/workflow-runner.ts`'s `workflowRevisions` map.
208
+ */
209
+ workflowRevision?: string;
202
210
  workflowType: string;
203
211
  checkpoint: ArrayBuffer;
204
212
  input: unknown;
@@ -236,12 +244,16 @@ export type WorkerOutboundMessage = {
236
244
  workflowId: WorkflowId;
237
245
  checkpoint: ArrayBuffer;
238
246
  operationRequest: OperationRequest | ContextOperationRequest;
247
+ /** Echo of the captured `run.workflowRevision` (WFT-20) — see that field's doc. */
248
+ workflowRevision?: string;
239
249
  } | {
240
250
  type: 'completed';
241
251
  protocolVersion?: number;
242
252
  turnId?: number;
243
253
  workflowId: WorkflowId;
244
254
  result: unknown;
255
+ /** Echo of the captured `run.workflowRevision` (WFT-20) — see that field's doc. */
256
+ workflowRevision?: string;
245
257
  } | {
246
258
  type: 'failed';
247
259
  protocolVersion?: number;
@@ -251,6 +263,8 @@ export type WorkerOutboundMessage = {
251
263
  errorStack?: string;
252
264
  /** Populated when the execution strategy can classify the failure cause. */
253
265
  failureCategory?: FailureCategory;
266
+ /** Echo of the captured `run.workflowRevision` (WFT-20) — see that field's doc. */
267
+ workflowRevision?: string;
254
268
  } | {
255
269
  /**
256
270
  * A `ctx.log` record forwarded from a worker to the engine host's
@@ -23,6 +23,27 @@ export type ScheduleStatus = 'active' | 'paused' | 'cancelled';
23
23
  * ```
24
24
  */
25
25
  export type ScheduleOverlapPolicy = 'skip' | 'queue' | 'cancel-running' | 'allow';
26
+ /**
27
+ * Which persisted workflow revision a schedule's future occurrences resolve
28
+ * against (WFT-20). `'active-at-fire'` (the default) resolves whatever
29
+ * revision is currently active at the moment each occurrence fires — this is
30
+ * the schedule's pre-WFT-20 behavior, unchanged. `'pinned'` captures the
31
+ * revision that would run right now at create/update time
32
+ * ({@link ScheduleMetadata.pinnedRevision}) and forces every future
33
+ * occurrence to resolve against exactly that revision, pausing the schedule
34
+ * (see `guides/workflow-versioning.md`'s "Schedule revision policy" section)
35
+ * if that revision later becomes unavailable rather than silently falling
36
+ * back to whatever is active.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import type { ScheduleRevisionPolicy } from '@lostgradient/weft';
41
+ *
42
+ * const policy: ScheduleRevisionPolicy = 'pinned';
43
+ * void policy;
44
+ * ```
45
+ */
46
+ export type ScheduleRevisionPolicy = 'active-at-fire' | 'pinned';
26
47
  /**
27
48
  * One occurrence waiting behind the active run of a `queue` overlap schedule.
28
49
  * `workflowId` is reserved when the occurrence enters the durable queue and is
@@ -117,12 +138,27 @@ export interface ScheduleOptions {
117
138
  * pre-jitter occurrence timestamp.
118
139
  */
119
140
  jitter?: Duration;
141
+ /**
142
+ * Which revision future occurrences resolve against. Defaults to
143
+ * `'active-at-fire'`. Passing `'pinned'` captures the revision that would
144
+ * run right now — see {@link ScheduleRevisionPolicy}. This is a
145
+ * per-schedule revision override; `StartOptions` (a one-shot
146
+ * `engine.start()` call) has no equivalent per-call revision override.
147
+ */
148
+ revisionPolicy?: ScheduleRevisionPolicy;
120
149
  }
121
150
  /**
122
151
  * Mutable options accepted when updating an existing schedule. Omitted fields
123
152
  * retain their persisted values. Schedule identity, workflow type, and input
124
153
  * are intentionally excluded.
125
154
  *
155
+ * Omitting `revisionPolicy` preserves the schedule's current policy AND its
156
+ * captured {@link ScheduleMetadata.pinnedRevision} unchanged. Passing
157
+ * `revisionPolicy: 'pinned'` — even when the schedule is already pinned —
158
+ * always RE-resolves and re-captures the pin against the revision active
159
+ * right now; it is never a no-op. Passing `revisionPolicy: 'active-at-fire'`
160
+ * clears any previously captured pin.
161
+ *
126
162
  * @example
127
163
  * ```ts
128
164
  * import { Engine, workflow, type ScheduleUpdateOptions } from '@lostgradient/weft';
@@ -135,7 +171,7 @@ export interface ScheduleOptions {
135
171
  * engine[Symbol.dispose]();
136
172
  * ```
137
173
  */
138
- export type ScheduleUpdateOptions = Pick<ScheduleOptions, 'description' | 'overlap' | 'backfill' | 'jitter'>;
174
+ export type ScheduleUpdateOptions = Pick<ScheduleOptions, 'description' | 'overlap' | 'backfill' | 'jitter' | 'revisionPolicy'>;
139
175
  /**
140
176
  * Declarative recurring schedule definition returned by {@link schedule}. Supply
141
177
  * exactly one of `cron` (cron cadence) or `every` (fixed interval).
@@ -168,6 +204,7 @@ export type ScheduleDefinition<TInput = unknown> = ScheduleSpec & {
168
204
  overlapPolicy?: ScheduleOverlapPolicy;
169
205
  backfill?: boolean;
170
206
  jitter?: Duration;
207
+ revisionPolicy?: ScheduleRevisionPolicy;
171
208
  };
172
209
  /**
173
210
  * Create a recurring schedule definition for `engine.schedule(definition)`.
@@ -206,6 +243,20 @@ export interface ScheduleMetadata {
206
243
  backfill: boolean;
207
244
  /** Normalized deterministic jitter window in milliseconds. */
208
245
  jitterMs?: number;
246
+ /**
247
+ * Which revision future occurrences resolve against. Required on every
248
+ * record this package writes; a legacy record persisted before WFT-20
249
+ * decodes as `'active-at-fire'` (see `validation/schedule-revision.ts`) —
250
+ * absence never means "unset," it means "pre-pinning."
251
+ */
252
+ revisionPolicy: ScheduleRevisionPolicy;
253
+ /**
254
+ * The exact revision every future occurrence resolves against, captured at
255
+ * the moment this schedule was created or last switched to (or re-pinned
256
+ * under) `revisionPolicy: 'pinned'`. Present only when `revisionPolicy ===
257
+ * 'pinned'`.
258
+ */
259
+ pinnedRevision?: string;
209
260
  createdAt: number;
210
261
  updatedAt: number;
211
262
  /** Most recent occurrence that started a scheduled workflow. */
@@ -53,6 +53,20 @@ export interface WorkflowState {
53
53
  * detect version drift.
54
54
  */
55
55
  versionTuple: WorkflowVersionTuple;
56
+ /**
57
+ * The exact executable artifact this run started against — the revision
58
+ * of the code actually loaded in this process at admission time (an
59
+ * eager registration's `registeredCatalogRevisions` entry, or a dynamic
60
+ * source's resolved candidate revision), NOT the catalog's cached active
61
+ * pointer. Sibling to {@link versionTuple}: `revision` answers "which
62
+ * artifact" for identity, diagnostics, and pinning; `versionTuple` remains
63
+ * the sole semantic-compatibility axis recovery checks. Every fresh start
64
+ * from this release forward sets it; `undefined` only on a record
65
+ * persisted before this field existed (a pre-upgrade run) — recovery
66
+ * treats that absence as a bounded, explicitly-classified legacy case
67
+ * rather than falling back to the currently active revision.
68
+ */
69
+ revision?: string;
56
70
  /**
57
71
  * Durable token identifying this concrete workflow run. It changes when a
58
72
  * stable workflow id is reused with `onTerminalConflict: 'start-new'`, and
@@ -291,6 +305,8 @@ export interface WorkflowSummary {
291
305
  status: WorkflowStatus;
292
306
  tags?: string[];
293
307
  version: string;
308
+ /** The exact executable artifact this run started against. See {@link WorkflowState.revision}. */
309
+ revision?: string;
294
310
  createdAt: number;
295
311
  updatedAt: number;
296
312
  /** Execution deadline (ms epoch) if set on the workflow. */
@@ -25,7 +25,7 @@
25
25
  * }
26
26
  * ```
27
27
  */
28
- export type WeftErrorCode = 'WorkflowAlreadyExistsError' | 'BulkDeleteRequiresTerminalWorkflowsError' | 'BulkOperationConfirmationError' | 'WorkflowTypeNotRegisteredForRecoveryError' | 'EngineCreateNameMismatchError' | 'EngineDisposedError' | 'EngineDisposalError' | 'WorkflowNotFoundError' | 'WorkflowNotRegisteredError' | 'WorkflowConcurrencyLimitExceededError' | 'WorkflowSuspendNotSupportedError' | 'ActivityResolutionError' | 'BranchTopologyChangedError' | 'PersistedDataIncompatibleError' | 'PersistedDataCorruptError' | 'WorkflowTimeoutError' | 'HttpClientError' | 'WorkerProtocolIncompatibleError' | 'UpdateTimeoutError' | 'UpdateValidationError' | 'WorkflowTerminalError' | 'WorkflowBuilderError' | 'VersionMismatchError' | 'EffectReplayConflictError' | 'ReviewTimeoutError' | 'AtomicStateConflictError' | 'StandardSchemaValidationError' | 'ActivityReconciliationCapabilityError' | 'ActivityReconciliationConflictError' | 'ActivityReconciliationIndeterminateError' | 'DurableActivityScopeError' | 'DurableActivityUnsupportedError' | 'AsyncActivityTokenNotFoundError' | 'ActivityScheduleToCloseTimeoutError' | 'ActivityPerAttemptTimeoutError' | 'PayloadSizeExceededError' | 'StartOrSignalConflictError' | 'WorkflowTeardownPendingError' | 'IdempotencyKeyPurgedError' | 'WorkerManifestBuildError' | 'OwnershipModeMismatchError' | 'ApplicationCommandValidationError' | 'MailboxContentionError' | 'ApplicationDeliveryValidationError' | 'OutboxContentionError' | 'WaitBudgetElapsedError' | 'WorkflowCatalogConflictError' | 'WorkflowRevisionNotInstalledError' | 'WorkflowSourceValidationError';
28
+ export type WeftErrorCode = 'WorkflowAlreadyExistsError' | 'BulkDeleteRequiresTerminalWorkflowsError' | 'BulkOperationConfirmationError' | 'WorkflowTypeNotRegisteredForRecoveryError' | 'EngineCreateNameMismatchError' | 'EngineDisposedError' | 'EngineDisposalError' | 'WorkflowNotFoundError' | 'WorkflowNotRegisteredError' | 'WorkflowConcurrencyLimitExceededError' | 'WorkflowSuspendNotSupportedError' | 'ActivityResolutionError' | 'BranchTopologyChangedError' | 'PersistedDataIncompatibleError' | 'PersistedDataCorruptError' | 'WorkflowTimeoutError' | 'HttpClientError' | 'WorkerProtocolIncompatibleError' | 'UpdateTimeoutError' | 'UpdateValidationError' | 'WorkflowTerminalError' | 'WorkflowBuilderError' | 'VersionMismatchError' | 'EffectReplayConflictError' | 'ReviewTimeoutError' | 'AtomicStateConflictError' | 'StandardSchemaValidationError' | 'ActivityReconciliationCapabilityError' | 'ActivityReconciliationConflictError' | 'ActivityReconciliationIndeterminateError' | 'DurableActivityScopeError' | 'DurableActivityUnsupportedError' | 'AsyncActivityTokenNotFoundError' | 'ActivityScheduleToCloseTimeoutError' | 'ActivityPerAttemptTimeoutError' | 'PayloadSizeExceededError' | 'StartOrSignalConflictError' | 'WorkflowTeardownPendingError' | 'IdempotencyKeyPurgedError' | 'WorkerManifestBuildError' | 'OwnershipModeMismatchError' | 'ApplicationCommandValidationError' | 'MailboxContentionError' | 'ApplicationDeliveryValidationError' | 'OutboxContentionError' | 'WaitBudgetElapsedError' | 'WorkflowCatalogConflictError' | 'WorkflowRevisionNotInstalledError' | 'WorkflowSourceValidationError' | 'WorkflowSourceNotRegisteredError' | 'DynamicWorkflowSourceUnavailableError' | 'WorkflowRevisionUnavailableError';
29
29
  /**
30
30
  * Generic abstract base for all Weft library errors. The `TCode` parameter
31
31
  * makes each subclass's `code` its own literal type; the exported base surface
@@ -55,7 +55,10 @@ const publicWeftErrorCodeMap = {
55
55
  WaitBudgetElapsedError: !0,
56
56
  WorkflowCatalogConflictError: !0,
57
57
  WorkflowRevisionNotInstalledError: !0,
58
- WorkflowSourceValidationError: !0
58
+ WorkflowSourceValidationError: !0,
59
+ WorkflowSourceNotRegisteredError: !0,
60
+ DynamicWorkflowSourceUnavailableError: !0,
61
+ WorkflowRevisionUnavailableError: !0
59
62
  }, PUBLIC_WEFT_ERROR_CODES = new Set(Object.keys(publicWeftErrorCodeMap));
60
63
  export function isWeftError(value) {
61
64
  return value instanceof WeftError;
@@ -11,6 +11,7 @@ export declare class WorkerExecutionStrategy implements ExecutionStrategy {
11
11
  startWorkflow(parameters: {
12
12
  workflowId: string;
13
13
  workflowExecutionToken?: string;
14
+ revision?: string;
14
15
  workflowType: string;
15
16
  input: unknown;
16
17
  checkpoint: ArrayBuffer;
@@ -45,6 +45,7 @@ export class WorkerExecutionStrategy {
45
45
  #messageHandler;
46
46
  #disposed;
47
47
  #nextTurnId;
48
+ #workflowRevisions;
48
49
  constructor(pool, options) {
49
50
  const {
50
51
  workflowTurnTimeoutMs,
@@ -58,6 +59,7 @@ export class WorkerExecutionStrategy {
58
59
  } = options ?? {};
59
60
  this.#pool = pool;
60
61
  this.#ownership = new WorkerExecutionOwnership;
62
+ this.#workflowRevisions = new Map;
61
63
  this.#workerListeners = new WorkerListenerRegistry;
62
64
  this.#checkpointResumeState = new WorkerCheckpointResumeState;
63
65
  this.#workflowTurnTimeoutMs = workflowTurnTimeoutMs;
@@ -81,6 +83,9 @@ export class WorkerExecutionStrategy {
81
83
  pool: this.#pool,
82
84
  emit: (message) => {
83
85
  this.#emit(message);
86
+ },
87
+ forgetWorkflowRevision: (workflowId) => {
88
+ this.#workflowRevisions.delete(workflowId);
84
89
  }
85
90
  });
86
91
  this.#dispatcher = new WorkerExecutionDispatcher({
@@ -97,7 +102,7 @@ export class WorkerExecutionStrategy {
97
102
  },
98
103
  ensureRealmReady: (worker, workflowId) => this.#ensureRealmReady(worker, workflowId),
99
104
  beginTurn: (worker, workflowId, turnId, kind) => {
100
- this.#turnWatchdog.begin(worker, workflowId, turnId, kind);
105
+ this.#turnWatchdog.begin(worker, workflowId, turnId, kind, void 0, this.#workflowRevisions.get(workflowId));
101
106
  },
102
107
  clearTurn: (worker) => {
103
108
  this.#turnWatchdog.clear(worker);
@@ -132,6 +137,10 @@ export class WorkerExecutionStrategy {
132
137
  startWorkflow(parameters) {
133
138
  this.#ownership.resetWorkflow(parameters.workflowId);
134
139
  this.#checkpointResumeState.resetWorkflow(parameters.workflowId);
140
+ if (parameters.revision !== void 0)
141
+ this.#workflowRevisions.set(parameters.workflowId, parameters.revision);
142
+ else
143
+ this.#workflowRevisions.delete(parameters.workflowId);
135
144
  const message = buildRunMessage(parameters, this.#inboundMessageContext());
136
145
  if (!this.#faultHandler.assertHostToWorkerMessageWithinLimit(parameters.workflowId, message))
137
146
  return;
@@ -185,6 +194,7 @@ export class WorkerExecutionStrategy {
185
194
  };
186
195
  }
187
196
  cancelWorkflow(workflowId) {
197
+ this.#workflowRevisions.delete(workflowId);
188
198
  const worker = this.#ownership.getActiveWorker(workflowId);
189
199
  if (worker) {
190
200
  this.#ownership.markCancelled(workflowId);
@@ -246,6 +256,7 @@ export class WorkerExecutionStrategy {
246
256
  this.#workerListeners.detachAll();
247
257
  this.#ownership.clear();
248
258
  this.#checkpointResumeState.clear();
259
+ this.#workflowRevisions.clear();
249
260
  this.#messageHandler = null;
250
261
  }
251
262
  async#handleWorkerMessage(worker, message) {
@@ -279,6 +290,7 @@ export class WorkerExecutionStrategy {
279
290
  return !1;
280
291
  this.#ownership.consumeCancelled(message.workflowId);
281
292
  this.#ownership.deleteParked(message.workflowId);
293
+ this.#workflowRevisions.delete(message.workflowId);
282
294
  this.#releaseActiveWorker(message.workflowId);
283
295
  this.#detachWorkerListenersIfIdle(worker);
284
296
  this.#checkpointResumeState.forgetWorkflowIfClosed(message.workflowId, this.#ownership.isWorkflowClosed(message.workflowId));
@@ -17,6 +17,16 @@ export interface WorkerFaultHandlerDependencies {
17
17
  protocolGuard: WorkerProtocolGuard;
18
18
  pool: WorkerPool;
19
19
  emit: (message: WorkerOutboundMessage) => void;
20
+ /**
21
+ * Forget a workflow's captured revision (WFT-20), mirroring
22
+ * `#settleTerminalWorkerMessage`'s real-terminal-message cleanup. Every
23
+ * fault path here (protocol violation, turn timeout, worker crash, log
24
+ * abuse, realm-ready failure) synthesizes a `failed` message directly
25
+ * rather than routing through that settle path, so without this callback
26
+ * `WorkerExecutionStrategy#workflowRevisions` would keep an entry per
27
+ * faulted workflow ID for the life of the strategy.
28
+ */
29
+ forgetWorkflowRevision: (workflowId: string) => void;
20
30
  }
21
31
  export interface WorkerDiscardOptions {
22
32
  targetWorkflowId?: string;
@@ -71,6 +71,7 @@ export class WorkerFaultHandler {
71
71
  const { ownership, checkpointResumeState } = this.#dependencies;
72
72
  ownership.forgetWorkflow(workflowId);
73
73
  checkpointResumeState.forgetWorkflowIfClosed(workflowId, !0);
74
+ this.#dependencies.forgetWorkflowRevision(workflowId);
74
75
  if (isTarget && options.skipTarget)
75
76
  return;
76
77
  this.#dependencies.emit({
@@ -20,6 +20,7 @@ export interface WorkerInboundMessageContext {
20
20
  export declare function buildRunMessage(parameters: {
21
21
  workflowId: string;
22
22
  workflowExecutionToken?: string;
23
+ revision?: string;
23
24
  workflowType: string;
24
25
  input: unknown;
25
26
  checkpoint: ArrayBuffer;
@@ -8,6 +8,7 @@ export function buildRunMessage(parameters, context) {
8
8
  ...parameters.workflowExecutionToken !== void 0 && {
9
9
  workflowExecutionToken: parameters.workflowExecutionToken
10
10
  },
11
+ ...parameters.revision !== void 0 && { workflowRevision: parameters.revision },
11
12
  workflowType: parameters.workflowType,
12
13
  checkpoint: parameters.checkpoint,
13
14
  input: parameters.input,
@@ -51,6 +51,10 @@ export class WorkerProtocolGuard {
51
51
  throw new WorkerProtocolError("Worker message arrived outside an active turn");
52
52
  if ((message.type === "log" ? void 0 : message.turnId) !== turn.turnId || message.workflowId !== turn.workflowId)
53
53
  throw new WorkerProtocolError("Worker message did not match the active turn");
54
+ if (turn.revision !== void 0) {
55
+ if ((message.type === "log" ? void 0 : message.workflowRevision) !== turn.revision)
56
+ throw new WorkerProtocolError("Worker message revision did not match the active turn");
57
+ }
54
58
  }
55
59
  }
56
60
  function protocolFailure(error, options) {
@@ -59,6 +59,13 @@ export declare function createBoundedWorkerFailureMessage(parameters: {
59
59
  error: string;
60
60
  failureCategory: FailureCategory;
61
61
  turnId?: number;
62
+ /**
63
+ * Echo of the active turn's captured revision (WFT-20). Required whenever the
64
+ * caller has one, so a bounded fallback for an oversized checkpoint/terminal
65
+ * message still passes the host's strict revision check instead of being
66
+ * discarded as a protocol violation.
67
+ */
68
+ workflowRevision?: string;
62
69
  }): WorkerOutboundMessage;
63
70
  export declare function estimateWorkerProtocolMessageBytes(message: unknown): number;
64
71
  export declare function assertWorkerProtocolMessageWithinLimit(message: unknown, maxBytes: number | undefined): number;
@@ -65,7 +65,8 @@ export function createBoundedWorkerFailureMessage(parameters) {
65
65
  ...parameters.turnId === void 0 ? {} : { turnId: parameters.turnId },
66
66
  workflowId: parameters.workflowId,
67
67
  error: truncateForProtocol(parameters.error),
68
- failureCategory: parameters.failureCategory
68
+ failureCategory: parameters.failureCategory,
69
+ ...parameters.workflowRevision === void 0 ? {} : { workflowRevision: parameters.workflowRevision }
69
70
  };
70
71
  }
71
72
  export function estimateWorkerProtocolMessageBytes(message) {
@@ -4,12 +4,19 @@ export interface WorkerTurnState {
4
4
  turnId: number;
5
5
  kind: 'run' | 'resume';
6
6
  timeoutMs: number | undefined;
7
+ /**
8
+ * The workflow's captured revision (WFT-20), when known — carried forward
9
+ * on every turn for this workflow (including `resume`, which never
10
+ * re-supplies it), for `WorkerProtocolGuard` to validate the outbound
11
+ * echo against.
12
+ */
13
+ revision?: string;
7
14
  }
8
15
  export type WorkerTurnTimeoutResolverForTesting = (turn: Pick<WorkerTurnState, 'workflowId' | 'kind'>) => number;
9
16
  export declare class WorkerTurnWatchdog {
10
17
  #private;
11
18
  constructor(timeoutMs: number | undefined, onTimeout: (turn: WorkerTurnState) => void);
12
- begin(worker: Worker, workflowId: string, turnId: number, kind: WorkerTurnState['kind'], timeoutMs?: number | undefined): void;
19
+ begin(worker: Worker, workflowId: string, turnId: number, kind: WorkerTurnState['kind'], timeoutMs?: number | undefined, revision?: string): void;
13
20
  setTimeoutResolverForTesting(resolver: WorkerTurnTimeoutResolverForTesting): void;
14
21
  clear(worker: Worker): void;
15
22
  clearAll(): void;
@@ -7,7 +7,7 @@ export class WorkerTurnWatchdog {
7
7
  this.#timeoutMs = timeoutMs;
8
8
  this.#onTimeout = onTimeout;
9
9
  }
10
- begin(worker, workflowId, turnId, kind, timeoutMs = this.#timeoutMs) {
10
+ begin(worker, workflowId, turnId, kind, timeoutMs = this.#timeoutMs, revision) {
11
11
  this.clear(worker);
12
12
  const resolvedTimeoutMs = this.#timeoutResolverForTesting?.({ workflowId, kind }) ?? timeoutMs, turn = {
13
13
  worker,
@@ -15,7 +15,8 @@ export class WorkerTurnWatchdog {
15
15
  turnId,
16
16
  kind,
17
17
  timeoutMs: resolvedTimeoutMs,
18
- timeout: null
18
+ timeout: null,
19
+ ...revision !== void 0 && { revision }
19
20
  };
20
21
  if (resolvedTimeoutMs !== void 0)
21
22
  turn.timeout = setTimeout(() => {
@@ -1 +1,38 @@
1
+ /**
2
+ * Assert every workflow-id constraint that predates WFT-95: a string,
3
+ * non-empty, at most {@link MAX_WORKFLOW_ID_LENGTH} characters, and free of
4
+ * control characters. Deliberately does NOT reject the exact strings `.` or
5
+ * `..` — those were valid workflow ids before WFT-95 and may already be
6
+ * durably persisted (a schedule id, a persisted `currentWorkflowId`, a
7
+ * queued run's `workflowId`, schedule-run metadata, an `executionStateOwnerId`
8
+ * or `parentWorkflowId`/`restartedFrom.workflowId` on a decoded
9
+ * `WorkflowState`). Decode and schedule-control (lookup, pause, resume,
10
+ * cancel, update) paths must keep accepting them so an upgrade doesn't
11
+ * strand pre-existing data or make a pre-existing schedule/workflow
12
+ * unmanageable; only fresh admission ({@link assertValidWorkflowId}) adds
13
+ * the `.`/`..` rejection.
14
+ *
15
+ * Takes `unknown`, not `string` (WFT-95 review): every caller passes an
16
+ * already-decoded field whose static `WorkflowState`/`ScheduleState` type
17
+ * says `string` but whose runtime shape is untrusted — the storage record
18
+ * could be corrupted. Without an explicit `typeof` guard here, a decoded
19
+ * array of strings would pass (`.length`, iteration, and
20
+ * `containsControlCharacter()`'s per-element `codePointAt()` all succeed on
21
+ * an array too), silently accepting a malformed field instead of dropping
22
+ * it — the same guard `coerceStartWorkflowId()` performed before this
23
+ * predicate existed.
24
+ */
25
+ export declare function assertDecodableWorkflowId(id: unknown, fieldName?: string): asserts id is string;
26
+ /** Whether `id` satisfies {@link assertDecodableWorkflowId}. */
27
+ export declare function isDecodableWorkflowId(id: unknown): boolean;
28
+ /**
29
+ * Whether `id` is exactly `.` or `..` — the two literals
30
+ * {@link assertValidWorkflowId} rejects at fresh admission (WFT-95). Exported
31
+ * so callers that need to recognize "this id is the one strict admission
32
+ * would reject" without re-running the full assertion (for example, to
33
+ * decide whether a caller-facing id MIGHT be a legacy pre-WFT-95 record
34
+ * worth checking storage for) share one definition instead of re-deriving
35
+ * the literal comparison.
36
+ */
37
+ export declare function isReservedWorkflowIdLiteral(id: string): boolean;
1
38
  export declare function assertValidWorkflowId(id: string, fieldName?: string): void;
@@ -9,7 +9,9 @@ function containsControlCharacter(value) {
9
9
  }
10
10
  return !1;
11
11
  }
12
- export function assertValidWorkflowId(id, fieldName = "options.id") {
12
+ export function assertDecodableWorkflowId(id, fieldName = "options.id") {
13
+ if (typeof id !== "string")
14
+ throw Error(`${fieldName} must be a string`);
13
15
  if (id.length === 0)
14
16
  throw Error(`${fieldName} must not be an empty string`);
15
17
  if (id.length > MAX_WORKFLOW_ID_LENGTH)
@@ -17,3 +19,19 @@ export function assertValidWorkflowId(id, fieldName = "options.id") {
17
19
  if (containsControlCharacter(id))
18
20
  throw Error(`${fieldName} must not contain control characters`);
19
21
  }
22
+ export function isDecodableWorkflowId(id) {
23
+ try {
24
+ assertDecodableWorkflowId(id);
25
+ return !0;
26
+ } catch {
27
+ return !1;
28
+ }
29
+ }
30
+ export function isReservedWorkflowIdLiteral(id) {
31
+ return id === "." || id === "..";
32
+ }
33
+ export function assertValidWorkflowId(id, fieldName = "options.id") {
34
+ if (isReservedWorkflowIdLiteral(id))
35
+ throw Error(`${fieldName} must not be "." or ".."`);
36
+ assertDecodableWorkflowId(id, fieldName);
37
+ }
@@ -95,6 +95,11 @@ export function formatVersionCheckReport(report) {
95
95
  lines.push(`${typeReport.type} (${typeReport.storedVersion} \u2192 ${typeReport.registeredVersion}):`);
96
96
  lines.push(` ${typeReport.runningCount} running workflows`);
97
97
  lines.push(` Compatibility: ${typeReport.compatibility}`);
98
+ const revisionEntries = Object.entries(typeReport.revisionCounts);
99
+ if (revisionEntries.length > 0 || typeReport.unpinnedRunningCount > 0) {
100
+ const revisionSummary = revisionEntries.map(([revision, count]) => `${revision} (${count})`).concat(typeReport.unpinnedRunningCount > 0 ? [`unpinned (${typeReport.unpinnedRunningCount})`] : []).join(", ");
101
+ lines.push(` Revisions: ${revisionSummary}`);
102
+ }
98
103
  lines.push("");
99
104
  }
100
105
  switch (report.overallVerdict) {
@@ -148,6 +148,30 @@ export interface WorkflowTypeReport {
148
148
  registeredVersion: string;
149
149
  runningCount: number;
150
150
  compatibility: VersionCompatibility;
151
+ /**
152
+ * Count of active (running/pending) workflows for this type that carry a
153
+ * persisted `WorkflowState.revision`, broken down by that EXACT revision
154
+ * — the exact executable artifact each run started against, distinct
155
+ * from `storedVersion` (the semantic `versionTuple.workflowVersion`,
156
+ * which many revisions can share, e.g. a documentation-only redeploy).
157
+ *
158
+ * Deliberately holds only real, persisted revision values — a dynamic
159
+ * source's `revision` is any non-empty, bounded string with no reserved
160
+ * values, so folding "no persisted revision" into this same map under a
161
+ * sentinel key could collide with a genuinely pinned run that happens to
162
+ * use that literal string. A run with no persisted revision (a
163
+ * pre-revision-pinning record) is counted separately, in
164
+ * {@link WorkflowTypeReport.unpinnedRunningCount}.
165
+ */
166
+ revisionCounts: Record<string, number>;
167
+ /**
168
+ * Count of active (running/pending) workflows for this type with no
169
+ * persisted `WorkflowState.revision` — a record written before
170
+ * per-run revision pinning existed (WFT-17). Kept out of
171
+ * {@link WorkflowTypeReport.revisionCounts} so it can never collide with
172
+ * a genuinely pinned revision that happens to share the same string.
173
+ */
174
+ unpinnedRunningCount: number;
151
175
  }
152
176
  /**
153
177
  * Deployment-safety report produced by `weft version:check`.