@lostgradient/weft 0.12.0 → 0.14.0

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 (226) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/api.d.ts +2 -0
  3. package/dist/cli/api.js +3 -4
  4. package/dist/cli/generated/operation-catalog.snapshot.json +508 -12
  5. package/dist/cli/generated/operation-client.generated.d.ts +118 -10
  6. package/dist/cli/generated/operation-client.generated.js +74 -1
  7. package/dist/cli/help-text.d.ts +3 -3
  8. package/dist/cli/help-text.js +4 -4
  9. package/dist/cli/operation-catalog-snapshot.d.ts +12 -3
  10. package/dist/cli/operation-catalog-snapshot.js +14 -5
  11. package/dist/cli/operation-client-runtime.d.ts +29 -0
  12. package/dist/cli/schedule.js +1 -3
  13. package/dist/cli/serve-registrations.d.ts +3 -3
  14. package/dist/cli/serve-registrations.js +2 -2
  15. package/dist/cli/shutdown.d.ts +11 -0
  16. package/dist/cli/shutdown.js +25 -0
  17. package/dist/cli/validate.d.ts +1 -1
  18. package/dist/cli/version-check.d.ts +1 -1
  19. package/dist/cli/version-check.js +1 -1
  20. package/dist/cli-main.js +10 -29
  21. package/dist/client/client-storage.d.ts +24 -0
  22. package/dist/client/client-storage.js +14 -0
  23. package/dist/client/handle-delegation.d.ts +3 -3
  24. package/dist/client/handle-delegation.js +2 -2
  25. package/dist/client/http-client-requests.d.ts +5 -3
  26. package/dist/client/http-client-requests.js +20 -2
  27. package/dist/client/http-client-storage.d.ts +3 -0
  28. package/dist/client/http-client-storage.js +82 -0
  29. package/dist/client/http-client.d.ts +9 -6
  30. package/dist/client/http-client.js +13 -18
  31. package/dist/client/http-operations.d.ts +3 -1
  32. package/dist/client/http-operations.js +77 -1
  33. package/dist/client/http-request.d.ts +20 -14
  34. package/dist/client/http-request.js +21 -31
  35. package/dist/client/in-process-operations.d.ts +2 -2
  36. package/dist/client/in-process-operations.js +2 -2
  37. package/dist/client/index.d.ts +5 -1
  38. package/dist/client/index.js +13 -2
  39. package/dist/client/interface.d.ts +26 -26
  40. package/dist/client/local.d.ts +9 -6
  41. package/dist/client/local.js +9 -4
  42. package/dist/client/search-params.js +10 -6
  43. package/dist/client/start-body.d.ts +3 -1
  44. package/dist/client/start-body.js +9 -0
  45. package/dist/core/bulk-workflow-filter.d.ts +2 -1
  46. package/dist/core/bulk-workflow-filter.js +5 -1
  47. package/dist/core/context/parallel-operations.d.ts +0 -2
  48. package/dist/core/context/parallel-operations.js +0 -7
  49. package/dist/core/engine/async-activity-records.d.ts +12 -1
  50. package/dist/core/engine/async-activity-records.js +81 -2
  51. package/dist/core/engine/bulk-operations-purge.js +16 -1
  52. package/dist/core/engine/bulk-operations-shared.js +2 -0
  53. package/dist/core/engine/callback-checkpoint-persistence.d.ts +2 -0
  54. package/dist/core/engine/callback-checkpoint-persistence.js +6 -3
  55. package/dist/core/engine/callback-creators-schedule.d.ts +2 -1
  56. package/dist/core/engine/callback-creators-schedule.js +3 -3
  57. package/dist/core/engine/callback-creators.js +4 -22
  58. package/dist/core/engine/checkpoint-io.d.ts +1 -1
  59. package/dist/core/engine/checkpoint-reads.js +11 -1
  60. package/dist/core/engine/child-workflow.js +15 -6
  61. package/dist/core/engine/construction.js +3 -0
  62. package/dist/core/engine/disposal.js +2 -0
  63. package/dist/core/engine/engine-internal-types.d.ts +4 -1
  64. package/dist/core/engine/fenced-write.d.ts +5 -3
  65. package/dist/core/engine/finalizer-status.d.ts +10 -0
  66. package/dist/core/engine/finalizer-status.js +82 -0
  67. package/dist/core/engine/index.d.ts +26 -2
  68. package/dist/core/engine/index.js +71 -8
  69. package/dist/core/engine/internals.d.ts +3 -0
  70. package/dist/core/engine/lease-health.d.ts +64 -0
  71. package/dist/core/engine/lease-health.js +0 -0
  72. package/dist/core/engine/lease-manager.d.ts +4 -2
  73. package/dist/core/engine/lease-manager.js +32 -4
  74. package/dist/core/engine/lifecycle/recovered-services.js +0 -2
  75. package/dist/core/engine/lifecycle/shared.d.ts +11 -8
  76. package/dist/core/engine/lifecycle/start-batch.js +12 -0
  77. package/dist/core/engine/lifecycle/start.d.ts +1 -1
  78. package/dist/core/engine/lifecycle/start.js +34 -5
  79. package/dist/core/engine/operations-coordination.d.ts +1 -12
  80. package/dist/core/engine/operations-coordination.js +58 -11
  81. package/dist/core/engine/operations-speculate.d.ts +1 -1
  82. package/dist/core/engine/operations-speculate.js +19 -5
  83. package/dist/core/engine/parallel-dispatch.d.ts +1 -1
  84. package/dist/core/engine/parallel-dispatch.js +1 -1
  85. package/dist/core/engine/registration.js +14 -0
  86. package/dist/core/engine/review-list-entries.d.ts +1 -1
  87. package/dist/core/engine/review-list-entries.js +6 -30
  88. package/dist/core/engine/schedule-handle.d.ts +4 -4
  89. package/dist/core/engine/schedule-handle.js +2 -2
  90. package/dist/core/engine/schedule-overlap.d.ts +11 -0
  91. package/dist/core/engine/schedule-overlap.js +50 -0
  92. package/dist/core/engine/schedule-run.d.ts +11 -3
  93. package/dist/core/engine/schedule-run.js +27 -4
  94. package/dist/core/engine/schedule-timer.js +10 -1
  95. package/dist/core/engine/schedules.d.ts +5 -4
  96. package/dist/core/engine/schedules.js +62 -56
  97. package/dist/core/engine/state-utilities.js +1 -1
  98. package/dist/core/engine/storage-io.d.ts +12 -0
  99. package/dist/core/engine/storage-io.js +49 -10
  100. package/dist/core/engine/termination/finalizer-claim.d.ts +8 -6
  101. package/dist/core/engine/termination/finalizer-claim.js +2 -1
  102. package/dist/core/engine/termination/finalizer.js +10 -14
  103. package/dist/core/engine/timeline-coordinator-constants.d.ts +3 -0
  104. package/dist/core/engine/timeline-coordinator-constants.js +1 -0
  105. package/dist/core/engine/timeline-coordinator-detail.d.ts +13 -0
  106. package/dist/core/engine/timeline-coordinator-detail.js +76 -0
  107. package/dist/core/engine/validation/schedule-options.d.ts +8 -1
  108. package/dist/core/engine/validation/schedule-options.js +18 -0
  109. package/dist/core/engine/validation/schedule.d.ts +1 -1
  110. package/dist/core/engine/validation/schedule.js +27 -3
  111. package/dist/core/engine/validation.js +66 -2
  112. package/dist/core/engine/workflow-state-stream.d.ts +5 -1
  113. package/dist/core/engine/workflow-state-stream.js +37 -14
  114. package/dist/core/events/schedule-events.d.ts +3 -4
  115. package/dist/core/events/workflow-events.d.ts +2 -1
  116. package/dist/core/list-filter-validation.d.ts +3 -0
  117. package/dist/core/list-filter-validation.js +11 -0
  118. package/dist/core/registry-snapshot.d.ts +32 -0
  119. package/dist/core/registry-snapshot.js +28 -0
  120. package/dist/core/review/index.d.ts +91 -4
  121. package/dist/core/review/index.js +29 -0
  122. package/dist/core/types/activity.d.ts +52 -5
  123. package/dist/core/types/bulk.d.ts +1 -0
  124. package/dist/core/types/list-options.d.ts +6 -0
  125. package/dist/core/types/options.d.ts +6 -5
  126. package/dist/core/types/reviews.d.ts +2 -2
  127. package/dist/core/types/schedules.d.ts +45 -2
  128. package/dist/core/types/services-resolution.d.ts +5 -5
  129. package/dist/core/types/state.d.ts +64 -0
  130. package/dist/core/types/workflow-log.d.ts +3 -2
  131. package/dist/core/types/workflow-observability.d.ts +50 -0
  132. package/dist/core/types/workflow-observability.js +0 -0
  133. package/dist/core/types/workflow-registry.d.ts +7 -0
  134. package/dist/core/types.d.ts +1 -0
  135. package/dist/core/types.js +1 -0
  136. package/dist/diagnostics/validate.d.ts +9 -30
  137. package/dist/diagnostics/validate.js +83 -21
  138. package/dist/diagnostics/version-check.d.ts +2 -2
  139. package/dist/http.js +2 -2
  140. package/dist/index.d.ts +4 -3
  141. package/dist/indexeddb.js +1 -1
  142. package/dist/server/access-policy-metadata.d.ts +92 -0
  143. package/dist/server/access-policy-metadata.js +78 -0
  144. package/dist/server/fault-to-http.d.ts +3 -7
  145. package/dist/server/fault-to-http.js +2 -34
  146. package/dist/server/handler/index.js +1 -1
  147. package/dist/server/handler/route-dispatch.d.ts +10 -3
  148. package/dist/server/handler/route-dispatch.js +19 -4
  149. package/dist/server/index.d.ts +1 -1
  150. package/dist/server/index.js +1 -0
  151. package/dist/server/interactive-operations.js +2 -0
  152. package/dist/server/openapi-error-responses.js +41 -4
  153. package/dist/server/openapi.d.ts +3 -1
  154. package/dist/server/openapi.js +4 -0
  155. package/dist/server/openrpc-document-schema.d.ts +64 -0
  156. package/dist/server/openrpc-document-schema.js +6 -26
  157. package/dist/server/openrpc.d.ts +4 -0
  158. package/dist/server/openrpc.js +9 -33
  159. package/dist/server/operation-catalog/index.d.ts +1 -1
  160. package/dist/server/operation-catalog/raise-fault.d.ts +2 -1
  161. package/dist/server/operation-catalog/types.d.ts +2 -1
  162. package/dist/server/operation-fault.d.ts +22 -8
  163. package/dist/server/operation-fault.js +65 -13
  164. package/dist/server/operation-registry.d.ts +1 -1
  165. package/dist/server/operations/aggregate-workflows.d.ts +6 -0
  166. package/dist/server/operations/aggregate-workflows.js +3 -2
  167. package/dist/server/operations/async-activity.d.ts +14 -0
  168. package/dist/server/operations/async-activity.js +88 -4
  169. package/dist/server/operations/bulk-filter-helpers.d.ts +3 -0
  170. package/dist/server/operations/bulk-filter-helpers.js +27 -3
  171. package/dist/server/operations/bulk-mutate-workflow-tags.d.ts +6 -0
  172. package/dist/server/operations/bulk-signal-workflows.d.ts +6 -0
  173. package/dist/server/operations/get-stream-chunks.js +2 -7
  174. package/dist/server/operations/get-system-lease.d.ts +19 -0
  175. package/dist/server/operations/get-system-lease.js +48 -0
  176. package/dist/server/operations/get-workflow-observability.d.ts +24 -0
  177. package/dist/server/operations/get-workflow-observability.js +80 -0
  178. package/dist/server/operations/get-workflow-result.js +2 -2
  179. package/dist/server/operations/get-workflow.d.ts +2 -2
  180. package/dist/server/operations/list-filter-query-extractor.js +15 -0
  181. package/dist/server/operations/list-reviews.js +2 -19
  182. package/dist/server/operations/list-workflows.d.ts +6 -0
  183. package/dist/server/operations/list-workflows.js +12 -3
  184. package/dist/server/operations/operation-helpers.d.ts +9 -18
  185. package/dist/server/operations/operation-helpers.js +5 -23
  186. package/dist/server/operations/purge-workflows.d.ts +3 -0
  187. package/dist/server/operations/recover-all.js +1 -9
  188. package/dist/server/operations/single-workflow-control-operation.d.ts +2 -1
  189. package/dist/server/operations/single-workflow-tag-mutation.js +2 -2
  190. package/dist/server/operations/sse-stream.js +2 -7
  191. package/dist/server/operations/storage-capabilities.d.ts +34 -0
  192. package/dist/server/operations/storage-capabilities.js +38 -0
  193. package/dist/server/operations/storage.js +8 -17
  194. package/dist/server/operations/stream-workflow-sse.js +3 -9
  195. package/dist/server/operations/update-schedule.d.ts +8 -0
  196. package/dist/server/operations/update-schedule.js +70 -7
  197. package/dist/server/operations/workflow-events-sse.js +2 -2
  198. package/dist/server/rest-binding.d.ts +7 -6
  199. package/dist/server/rest-bindings.d.ts +2 -13
  200. package/dist/server/rest-bindings.js +19 -10
  201. package/dist/server/task-queue.d.ts +7 -5
  202. package/dist/storage/bounded-ndjson-response.d.ts +5 -0
  203. package/dist/storage/bounded-ndjson-response.js +33 -0
  204. package/dist/storage/capabilities.d.ts +1 -1
  205. package/dist/storage/cloudflare-value-codec.d.ts +5 -3
  206. package/dist/storage/cloudflare-value-codec.js +1 -1
  207. package/dist/storage/compressed-storage.js +1 -1
  208. package/dist/storage/http.js +5 -37
  209. package/dist/storage/index.d.ts +7 -1
  210. package/dist/storage/interface.d.ts +16 -4
  211. package/dist/storage/interface.js +1 -1
  212. package/dist/storage/key-prefixes.d.ts +1 -1
  213. package/dist/storage/key-prefixes.js +4 -0
  214. package/dist/storage/lmdb.js +1 -1
  215. package/dist/storage/memory.js +1 -1
  216. package/dist/storage/neon.d.ts +3 -16
  217. package/dist/storage/neon.js +4 -4
  218. package/dist/storage/postgres.js +4 -4
  219. package/dist/storage/resolve.js +1 -1
  220. package/dist/storage/scoped-storage.js +1 -1
  221. package/dist/storage/testing.js +1 -1
  222. package/dist/storage/turso.js +2 -2
  223. package/dist/version.d.ts +1 -1
  224. package/dist/version.js +1 -1
  225. package/dist/web-extension.js +1 -1
  226. package/package.json +1 -1
@@ -175,9 +175,12 @@ export interface EngineInternals {
175
175
  pendingNestingDepth: number | undefined;
176
176
  pendingParentHeaders: Map<string, string> | undefined;
177
177
  pendingExecutionStateOwnerId: string | null | undefined;
178
+ pendingParentWorkflowId: string | undefined;
179
+ pendingParentWorkflowExecutionToken: string | undefined;
178
180
  workflowNestingDepths: Map<string, number>;
179
181
  workflowHeaders: Map<string, Map<string, string>>;
180
182
  workflowStateWriteChains: Map<string, Promise<void>>;
183
+ scheduleStateOperationChains: Map<string, Promise<void>>;
181
184
  heartbeatDetails: Map<string, unknown>;
182
185
  /**
183
186
  * Last heartbeat payload PER ACTIVITY STEP, so a retry of a step can read the
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Why a holder lost its lease while running. `deposed` is confirmed ownership
3
+ * loss; `renewal-unconfirmable` means storage failures outlasted the last
4
+ * confirmed lease expiry.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import type { LeaseLostReason } from '@lostgradient/weft';
9
+ *
10
+ * function requiresImmediateHandoff(reason: LeaseLostReason): boolean {
11
+ * return reason === 'deposed';
12
+ * }
13
+ * void requiresImmediateHandoff;
14
+ * ```
15
+ */
16
+ export type LeaseLostReason = 'deposed' | 'renewal-unconfirmable';
17
+ /** Process-local, last-known ownership state exposed through the operator API. */
18
+ export type LeaseManagerHealth = {
19
+ status: 'no-lease';
20
+ holdsLease: false;
21
+ } | {
22
+ status: 'healthy';
23
+ holdsLease: true;
24
+ holderId: string;
25
+ heldSince: number;
26
+ expiresAt: number;
27
+ lastRenewedAt: number;
28
+ fencingEpoch: number;
29
+ } | {
30
+ status: 'contested';
31
+ holdsLease: false;
32
+ lossReason?: LeaseLostReason;
33
+ holderId: string;
34
+ heldSince: number;
35
+ expiresAt: number;
36
+ lastRenewedAt: number;
37
+ fencingEpoch: number;
38
+ };
39
+ /**
40
+ * Public engine ownership-health snapshot returned by
41
+ * {@link Engine.getLeaseHealth} and `weft.system.lease`.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import type { EngineLeaseHealth } from '@lostgradient/weft';
46
+ *
47
+ * function needsOperatorAttention(health: EngineLeaseHealth): boolean {
48
+ * return health.status === 'contested';
49
+ * }
50
+ * void needsOperatorAttention;
51
+ * ```
52
+ */
53
+ export type EngineLeaseHealth = {
54
+ mode: 'none';
55
+ status: 'disabled';
56
+ holdsLease: false;
57
+ } | ({
58
+ mode: 'lease';
59
+ } & LeaseManagerHealth) | {
60
+ mode: 'lease';
61
+ status: 'contested';
62
+ holdsLease: false;
63
+ lossReason: 'deposed';
64
+ };
File without changes
@@ -44,8 +44,8 @@
44
44
  * @module core/engine/lease-manager
45
45
  */
46
46
  import { type Storage } from '../../storage/interface.ts';
47
- /** Why a holder lost its lease — surfaced to {@link LeaseManagerOptions.onLeaseLost}. */
48
- export type LeaseLostReason = 'deposed' | 'renewal-unconfirmable';
47
+ import type { LeaseLostReason, LeaseManagerHealth } from './lease-health.ts';
48
+ export type { LeaseLostReason } from './lease-health.ts';
49
49
  /** Options for {@link createLeaseManager}. */
50
50
  export type LeaseManagerOptions = {
51
51
  storage: Storage;
@@ -102,6 +102,8 @@ export type LeaseManager = {
102
102
  startRenewal(): void;
103
103
  renewOnce(): Promise<void>;
104
104
  currentEpochBytes(): Uint8Array | null;
105
+ /** Return a defensive, synchronous view of this process's last-known lease state. */
106
+ health(): LeaseManagerHealth;
105
107
  release(): Promise<void>;
106
108
  stop(): void;
107
109
  };
@@ -9,12 +9,13 @@ import { EngineLeaseAcquisitionTimeoutError, EngineLeaseCorruptedError } from ".
9
9
  const DEFAULT_ACQUIRE_POLL_INTERVAL_MS = 1000;
10
10
  export function createLeaseManager(options) {
11
11
  const { storage, holderId, getNow, ttlMs, renewIntervalMs, waitTimeoutMs } = options, acquirePollIntervalMs = options.acquirePollIntervalMs ?? DEFAULT_ACQUIRE_POLL_INTERVAL_MS;
12
- let stopped = !1, heldEpoch = null, heldEpochBytes = null, lastHolderBytes = null, renewalInterval = null, leaseLost = !1, inFlightRenewal = null;
12
+ let stopped = !1, heldEpoch = null, heldEpochBytes = null, lastHolderBytes = null, renewalInterval = null, leaseLost = !1, leaseLostReason = null, heldSince = null, lastRenewedAt = null, inFlightRenewal = null;
13
13
  const delay = options.delay ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
14
14
  function reportLeaseLost(reason) {
15
15
  if (leaseLost)
16
16
  return;
17
17
  leaseLost = !0;
18
+ leaseLostReason = reason;
18
19
  options.onLeaseLost?.(reason);
19
20
  }
20
21
  async function readState() {
@@ -27,7 +28,7 @@ export function createLeaseManager(options) {
27
28
  };
28
29
  }
29
30
  async function takeOwnership(expectedEpochBytes, expectedHolderBytes, nextEpoch) {
30
- const expiresAt = getNow() + ttlMs, holderBytes = encodeHolder({ holderId, expiresAt, epoch: nextEpoch }), committed = await storageConditionalBatch(storage, [
31
+ const acquiredAt = getNow(), expiresAt = acquiredAt + ttlMs, holderBytes = encodeHolder({ holderId, expiresAt, epoch: nextEpoch }), committed = await storageConditionalBatch(storage, [
31
32
  { key: KEYS.leaseEpoch(), expectedValue: expectedEpochBytes },
32
33
  { key: KEYS.leaseHolder(), expectedValue: expectedHolderBytes }
33
34
  ], [
@@ -38,6 +39,8 @@ export function createLeaseManager(options) {
38
39
  heldEpoch = nextEpoch;
39
40
  heldEpochBytes = encodeEpoch(nextEpoch);
40
41
  lastHolderBytes = holderBytes;
42
+ heldSince = acquiredAt;
43
+ lastRenewedAt = acquiredAt;
41
44
  }
42
45
  return committed;
43
46
  }
@@ -75,7 +78,7 @@ export function createLeaseManager(options) {
75
78
  async function renewOnce() {
76
79
  if (stopped || leaseLost || heldEpoch === null || lastHolderBytes === null)
77
80
  return;
78
- const expiresAt = getNow() + ttlMs, holderBytes = encodeHolder({ holderId, expiresAt, epoch: heldEpoch });
81
+ const renewedAt = getNow(), expiresAt = renewedAt + ttlMs, holderBytes = encodeHolder({ holderId, expiresAt, epoch: heldEpoch });
79
82
  let committed;
80
83
  try {
81
84
  committed = await storageConditionalBatch(storage, [{ key: KEYS.leaseHolder(), expectedValue: lastHolderBytes }], [{ type: "put", key: KEYS.leaseHolder(), value: holderBytes }]);
@@ -87,6 +90,7 @@ export function createLeaseManager(options) {
87
90
  }
88
91
  if (committed) {
89
92
  lastHolderBytes = holderBytes;
93
+ lastRenewedAt = renewedAt;
90
94
  return;
91
95
  }
92
96
  reportLeaseLost("deposed");
@@ -109,6 +113,30 @@ export function createLeaseManager(options) {
109
113
  function currentEpochBytes() {
110
114
  return heldEpochBytes === null ? null : heldEpochBytes.slice();
111
115
  }
116
+ function health() {
117
+ if (stopped)
118
+ return { status: "no-lease", holdsLease: !1 };
119
+ const holder = lastHolderBytes === null ? null : decodeHolder(lastHolderBytes);
120
+ if (holder === null || heldEpoch === null || heldSince === null || lastRenewedAt === null)
121
+ return { status: "no-lease", holdsLease: !1 };
122
+ const lastKnownLease = {
123
+ holderId: holder.holderId,
124
+ heldSince,
125
+ expiresAt: holder.expiresAt,
126
+ lastRenewedAt,
127
+ fencingEpoch: heldEpoch
128
+ };
129
+ if (leaseLostReason !== null)
130
+ return {
131
+ status: "contested",
132
+ holdsLease: !1,
133
+ lossReason: leaseLostReason,
134
+ ...lastKnownLease
135
+ };
136
+ if (getNow() >= holder.expiresAt)
137
+ return { status: "contested", holdsLease: !1, ...lastKnownLease };
138
+ return { status: "healthy", holdsLease: !0, ...lastKnownLease };
139
+ }
112
140
  function clearRenewal() {
113
141
  if (renewalInterval !== null) {
114
142
  clearInterval(renewalInterval);
@@ -131,5 +159,5 @@ export function createLeaseManager(options) {
131
159
  stopped = !0;
132
160
  clearRenewal();
133
161
  }
134
- return { acquire, startRenewal, renewOnce, currentEpochBytes, release, stop };
162
+ return { acquire, startRenewal, renewOnce, currentEpochBytes, health, release, stop };
135
163
  }
@@ -80,7 +80,5 @@ async function scheduleFromWorkflowState(internals, state) {
80
80
  return metadata;
81
81
  if (metadata.occurrence !== void 0 && scheduleState.nextFireAt === metadata.occurrence)
82
82
  return metadata;
83
- if (metadata.occurrence === void 0 && scheduleState.overlap === "queue" && scheduleState.queuedRuns > 0)
84
- return metadata;
85
83
  return null;
86
84
  }
@@ -58,19 +58,22 @@ export type RecoverAllOptions = {
58
58
  */
59
59
  onRecoveredWorkflow?: (info: RecoveredWorkflowInfo) => void | Promise<void>;
60
60
  /**
61
- * Policy for a recovered workflow whose stored {@link WorkflowVersionTuple}
62
- * (or legacy checkpoint `version`) no longer matches the registered
63
- * {@link WorkflowDefinition.version}. The mismatch is detected before
64
- * `resolveWorkflowServices` and `onRecoveredWorkflow` run for that workflow,
65
- * so a mismatched run never re-provides services or invokes the hook.
61
+ * Policy for a recovered workflow whose persisted
62
+ * {@link WorkflowVersionTuple} or checkpoint `version` no longer matches the
63
+ * registered {@link WorkflowDefinition.version}. Recovery checks both
64
+ * persisted records against the registered version so current workflow-state
65
+ * metadata cannot mask a stale checkpoint version. The mismatch is detected
66
+ * before `resolveWorkflowServices` and `onRecoveredWorkflow` run for that
67
+ * workflow, so a mismatched run never re-provides services or invokes the
68
+ * hook.
66
69
  *
67
70
  * - `'fail-run'` (default): fail only the mismatched run to a terminal
68
71
  * `failed` state with a `system` failure category carrying the
69
72
  * {@link VersionMismatchError} message, then continue recovering its
70
73
  * siblings. The run never advances user workflow code.
71
- * - `'throw'`: preserve the pre-#702 behavior — rethrow the
72
- * {@link VersionMismatchError} out of `recoverAll()` immediately, aborting
73
- * recovery for every workflow not yet processed in this batch.
74
+ * - `'throw'`: use fail-fast recovery — rethrow the
75
+ * {@link VersionMismatchError} out of `recoverAll()` immediately, leaving
76
+ * every workflow not yet processed in this batch unresumed.
74
77
  */
75
78
  versionMismatchPolicy?: 'fail-run' | 'throw';
76
79
  };
@@ -19,6 +19,7 @@ export function buildStartBatchOperations(_internals, workflowId, state, checkpo
19
19
  },
20
20
  ...visibilityIndexOperations,
21
21
  ...buildWorkflowTagIndexOperations(workflowId, void 0, state.tags),
22
+ ...buildChildWorkflowIndexOperations(state),
22
23
  ...buildInitialSearchAttributeOperations(_internals, workflowId, registration, options?.searchAttributes, callbacks),
23
24
  ...buildPerRunScratchOperations(workflowId, options, workflowStartHeaders),
24
25
  ...additionalOperations ?? []
@@ -34,6 +35,17 @@ export function buildStartBatchOperations(_internals, workflowId, state, checkpo
34
35
  operations.push(...buildTimerBatchOperations(delayedStartTimer));
35
36
  return operations;
36
37
  }
38
+ function buildChildWorkflowIndexOperations(state) {
39
+ if (state.parentWorkflowId === void 0)
40
+ return [];
41
+ return [
42
+ {
43
+ type: "put",
44
+ key: KEYS.childWorkflowByParent(state.parentWorkflowId, state.parentWorkflowExecutionToken, state.id),
45
+ value: EMPTY_STORAGE_VALUE
46
+ }
47
+ ];
48
+ }
37
49
  function buildPerRunScratchOperations(workflowId, options, workflowStartHeaders) {
38
50
  const hasHeaders = workflowStartHeaders !== void 0 && workflowStartHeaders.size > 0, hasServices = options?.services !== void 0, operations = [];
39
51
  if (hasHeaders)
@@ -9,5 +9,5 @@ export declare function start(internals: EngineInternals, type: string, input: u
9
9
  export declare function startWorkflow(internals: EngineInternals, type: string, input: unknown, options: StartWorkflowOptions | undefined, additionalStartOperations: BatchOperation[] | undefined, callbacks: LifecycleCallbacks, buildIdempotentStartOperations?: BuildIdempotentStartOperations): Promise<WorkflowHandle>;
10
10
  export declare function resolveScheduledStartAt(internals: EngineInternals, options: StartOptions | undefined, submissionTime: number, callbacks: LifecycleCallbacks): number | undefined;
11
11
  export declare function parseStartOptionDuration(_internals: EngineInternals, duration: Duration, fieldName: 'options.executionTimeout' | 'options.startAfter', _callbacks: LifecycleCallbacks): number;
12
- export declare function createInitialWorkflowState(internals: EngineInternals, workflowId: string, type: string, input: unknown, versionTuple: WorkflowVersionTuple, options: StartOptions | undefined, tags: string[] | undefined, executionStateOwnerId: string | undefined, delayedStartTimer: TimerEntry | undefined, callbacks: LifecycleCallbacks): WorkflowState;
12
+ export declare function createInitialWorkflowState(internals: EngineInternals, workflowId: string, type: string, input: unknown, versionTuple: WorkflowVersionTuple, options: StartOptions | undefined, tags: string[] | undefined, executionStateOwnerId: string | undefined, parentWorkflowId: string | undefined, parentWorkflowExecutionToken: string | undefined, delayedStartTimer: TimerEntry | undefined, callbacks: LifecycleCallbacks): WorkflowState;
13
13
  export declare function createInitialCheckpoint(internals: EngineInternals, workflowId: string, workflowVersion: string, options: StartOptions | undefined, _callbacks: LifecycleCallbacks): Checkpoint;
@@ -38,6 +38,10 @@ function prepareStartWorkflow(internals, options, callbacks) {
38
38
  internals.pendingParentHeaders = void 0;
39
39
  const pendingExecutionStateOwnerId = internals.pendingExecutionStateOwnerId, executionStateOwnerId = pendingExecutionStateOwnerId === null ? void 0 : pendingExecutionStateOwnerId ?? workflowId;
40
40
  internals.pendingExecutionStateOwnerId = void 0;
41
+ const parentWorkflowId = internals.pendingParentWorkflowId;
42
+ internals.pendingParentWorkflowId = void 0;
43
+ const parentWorkflowExecutionToken = internals.pendingParentWorkflowExecutionToken;
44
+ internals.pendingParentWorkflowExecutionToken = void 0;
41
45
  const submissionTime = internals.options.getNow(), scheduledStartAt = resolveScheduledStartAt(internals, options, submissionTime, callbacks), normalizedTags = normalizeStartWorkflowTags(internals, options?.tags, void 0, callbacks), delayedStartTimer = scheduledStartAt !== void 0 && scheduledStartAt > submissionTime ? createDelayedStartTimerEntry(internals, workflowId, scheduledStartAt, options, {
42
46
  parseStartOptionDuration: (value, fieldName) => parseStartOptionDuration(internals, value, fieldName, callbacks)
43
47
  }) : void 0;
@@ -46,6 +50,8 @@ function prepareStartWorkflow(internals, options, callbacks) {
46
50
  callerProvidedId,
47
51
  parentHeaders,
48
52
  executionStateOwnerId,
53
+ parentWorkflowId,
54
+ parentWorkflowExecutionToken,
49
55
  submissionTime,
50
56
  delayedStartTimer,
51
57
  normalizedTags
@@ -70,7 +76,15 @@ export async function startWorkflow(internals, type, input, options, additionalS
70
76
  const workflowConcurrency = registration.concurrency;
71
77
  assertServicesSupportedForMode(internals, options);
72
78
  assertValidOnTerminalConflict(options);
73
- const preparation = prepareStartWorkflow(internals, options, callbacks), { workflowId, callerProvidedId, parentHeaders, executionStateOwnerId, delayedStartTimer } = preparation;
79
+ const preparation = prepareStartWorkflow(internals, options, callbacks), {
80
+ workflowId,
81
+ callerProvidedId,
82
+ parentHeaders,
83
+ executionStateOwnerId,
84
+ parentWorkflowId,
85
+ parentWorkflowExecutionToken,
86
+ delayedStartTimer
87
+ } = preparation;
74
88
  assertDeferSupported(internals, options, Boolean(delayedStartTimer));
75
89
  if (internals.pendingStarts.has(workflowId))
76
90
  throw new WorkflowAlreadyExistsError(workflowId);
@@ -79,7 +93,9 @@ export async function startWorkflow(internals, type, input, options, additionalS
79
93
  try {
80
94
  const terminalRunToPurge = callerProvidedId ? await resolveTerminalConflictForRestart(internals, workflowId, options) : null;
81
95
  assertPayloadWithinLimit(input, internals.options.payloadSizePolicy.maxBytes, "workflow input");
82
- const versionTuple = createWorkflowVersionTuple(internals, registration, callbacks), state = createInitialWorkflowState(internals, workflowId, type, input, versionTuple, options, preparation.normalizedTags, executionStateOwnerId, delayedStartTimer, callbacks), checkpoint = createInitialCheckpoint(internals, workflowId, versionTuple.workflowVersion, options, callbacks), workflowStartHeaders = runWorkflowStartInterceptor(internals, workflowId, type, input, parentHeaders, callbacks), persistedWorkflowStartHeaders = selectPersistedWorkflowStartHeaders(workflowStartHeaders), purgeDeleteOperations = terminalRunToPurge !== null ? await prepareTerminalRunPurge(internals, terminalRunToPurge, callbacks) : void 0;
96
+ const versionTuple = createWorkflowVersionTuple(internals, registration, callbacks), state = createInitialWorkflowState(internals, workflowId, type, input, versionTuple, options, preparation.normalizedTags, executionStateOwnerId, parentWorkflowId, parentWorkflowExecutionToken, delayedStartTimer, callbacks);
97
+ applyRestartLineage(state, terminalRunToPurge);
98
+ const checkpoint = createInitialCheckpoint(internals, workflowId, versionTuple.workflowVersion, options, callbacks), workflowStartHeaders = runWorkflowStartInterceptor(internals, workflowId, type, input, parentHeaders, callbacks), persistedWorkflowStartHeaders = selectPersistedWorkflowStartHeaders(workflowStartHeaders), purgeDeleteOperations = terminalRunToPurge !== null ? await prepareTerminalRunPurge(internals, terminalRunToPurge, callbacks) : void 0;
83
99
  internals.checkpoints.set(workflowId, checkpoint);
84
100
  setWorkflowStartHeaders(internals, workflowId, workflowStartHeaders, callbacks);
85
101
  internals.workflowVersionTuples.set(workflowId, versionTuple);
@@ -121,6 +137,17 @@ export async function startWorkflow(internals, type, input, options, additionalS
121
137
  rollbackTransientStartState(internals, workflowId);
122
138
  }
123
139
  }
140
+ function applyRestartLineage(state, displacedState) {
141
+ if (displacedState === null)
142
+ return;
143
+ state.restartedFrom = {
144
+ workflowId: displacedState.id,
145
+ ...displacedState.workflowExecutionToken !== void 0 && {
146
+ workflowExecutionToken: displacedState.workflowExecutionToken
147
+ },
148
+ replacedAt: state.createdAt
149
+ };
150
+ }
124
151
  export function resolveScheduledStartAt(internals, options, submissionTime, callbacks) {
125
152
  assertExclusiveStartWorkflowOptions(options?.startAt, options?.startAfter);
126
153
  if (options?.startAt !== void 0)
@@ -138,7 +165,7 @@ export function resolveScheduledStartAt(internals, options, submissionTime, call
138
165
  export function parseStartOptionDuration(_internals, duration, fieldName, _callbacks) {
139
166
  return parseStartWorkflowDuration(duration, fieldName);
140
167
  }
141
- function buildInitialIdentitySlice(workflowId, type, input, versionTuple, executionStateOwnerId, delayedStartTimer, now, tags) {
168
+ function buildInitialIdentitySlice(workflowId, type, input, versionTuple, executionStateOwnerId, parentWorkflowId, parentWorkflowExecutionToken, delayedStartTimer, now, tags) {
142
169
  return {
143
170
  id: workflowId,
144
171
  type,
@@ -147,6 +174,8 @@ function buildInitialIdentitySlice(workflowId, type, input, versionTuple, execut
147
174
  versionTuple,
148
175
  workflowExecutionToken: crypto.randomUUID(),
149
176
  ...executionStateOwnerId !== void 0 && { executionStateOwnerId },
177
+ ...parentWorkflowId !== void 0 && { parentWorkflowId },
178
+ ...parentWorkflowExecutionToken !== void 0 && { parentWorkflowExecutionToken },
150
179
  createdAt: now,
151
180
  ...!delayedStartTimer && { startedAt: now },
152
181
  updatedAt: now,
@@ -163,8 +192,8 @@ function resolveInitialExecutionDeadline(internals, options, delayedStartTimer,
163
192
  throw new StartWorkflowValidationError("options.executionTimeout must resolve to a finite, non-negative deadline");
164
193
  }
165
194
  }
166
- export function createInitialWorkflowState(internals, workflowId, type, input, versionTuple, options, tags, executionStateOwnerId, delayedStartTimer, callbacks) {
167
- const now = internals.options.getNow(), state = buildInitialIdentitySlice(workflowId, type, input, versionTuple, executionStateOwnerId, delayedStartTimer, now, tags), executionDeadline = resolveInitialExecutionDeadline(internals, options, delayedStartTimer, now, callbacks);
195
+ export function createInitialWorkflowState(internals, workflowId, type, input, versionTuple, options, tags, executionStateOwnerId, parentWorkflowId, parentWorkflowExecutionToken, delayedStartTimer, callbacks) {
196
+ const now = internals.options.getNow(), state = buildInitialIdentitySlice(workflowId, type, input, versionTuple, executionStateOwnerId, parentWorkflowId, parentWorkflowExecutionToken, delayedStartTimer, now, tags), executionDeadline = resolveInitialExecutionDeadline(internals, options, delayedStartTimer, now, callbacks);
168
197
  if (executionDeadline !== void 0)
169
198
  state.executionDeadline = executionDeadline;
170
199
  return state;
@@ -36,18 +36,7 @@ export declare function processParallelOperation(internals: EngineInternals, wor
36
36
  export declare function processRaceOperation(internals: EngineInternals, workflowId: string, operation: RaceOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
37
37
  export declare function assertValidRaceBranchNames(operation: RaceOperation): void;
38
38
  export declare function wrapRaceWinner(operation: RaceOperation, winnerIndex: number, value: unknown): unknown;
39
- /**
40
- * Execute race branches while giving an already-buffered signal priority over a
41
- * literal zero-duration sleep. The preflight is non-destructive: the selected
42
- * wait-signal branch still returns its deferred-consume envelope, so the owning
43
- * coordinator remains the only place that consumes the durable record.
44
- *
45
- * Positive-duration sleeps deliberately bypass this preflight, including when
46
- * their deadline becomes past-due before dispatch. That keeps ordinary timeout
47
- * races on the existing Promise.race path and limits the stronger ordering
48
- * guarantee to the explicit non-blocking-drain idiom.
49
- */
50
- export declare function executeRaceSubOperations(internals: EngineInternals, workflowId: string, operations: readonly ContextOperationRequest[], execute: (operation: ContextOperationRequest, signal: AbortSignal) => Promise<unknown>, parentSignal?: AbortSignal, wrapWinner?: (winnerIndex: number, value: unknown) => unknown): Promise<unknown>;
39
+ export declare function executeRaceSubOperations(internals: EngineInternals, workflowId: string, operations: readonly ContextOperationRequest[], execute: (operation: ContextOperationRequest, signal: AbortSignal) => Promise<unknown>, parentSignal?: AbortSignal, wrapWinner?: (winnerIndex: number, value: unknown) => unknown, observeWinner?: (winnerIndex: number, error?: unknown) => void): Promise<unknown>;
51
40
  export declare function processRunAllOperation(internals: EngineInternals, workflowId: string, operation: RunAllOperation, callbacks: Pick<CoordinationOperationCallbacks, 'runOperationWithResult'>): Promise<void>;
52
41
  export declare function isConfiguredInlineActivity(fn: Function): fn is RunAllOperation['branches'][string][0] & ActivityFunctionWithMetadata;
53
42
  /**
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isParallelOperationCacheEntry
3
- } from "../context/parallel-operations.js";
3
+ } from "../context/parallel-cache-entry.js";
4
4
  import {
5
5
  executeRunAllBranches,
6
6
  executeRunAllBranchesSettled
@@ -21,6 +21,12 @@ import {
21
21
  untrackWaiterKey
22
22
  } from "./signals.js";
23
23
  import { callActivityFunction } from "./state-utilities.js";
24
+ import {
25
+ parallelTimelineDetails,
26
+ raceTimelineDetails,
27
+ recordTimelineBranches,
28
+ runAllTimelineDetails
29
+ } from "./timeline-coordinator-detail.js";
24
30
  export function assertSupportedSignalBranches(operations) {
25
31
  const seen = new Set, walk = (subOperations) => {
26
32
  for (const subOperation of subOperations)
@@ -74,6 +80,7 @@ export async function processParallelOperation(internals, workflowId, operation,
74
80
  await finalizeFulfilledSlots(slots);
75
81
  const entry = buildEntryFromSlots("all", slots);
76
82
  writePartialEntry(internals, workflowId, operation.step, entry);
83
+ recordTimelineBranches(internals, workflowId, parallelTimelineDetails(operation.operations, slots));
77
84
  if (hasFirstError)
78
85
  throw firstError;
79
86
  return valuesFromSlots(slots);
@@ -111,8 +118,22 @@ export async function processRaceOperation(internals, workflowId, operation, cal
111
118
  return callbacks.runOperationWithResult(workflowId, operation, async () => {
112
119
  assertSupportedSignalBranches(operation.operations);
113
120
  assertValidRaceBranchNames(operation);
114
- const winner = await executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, signal) => callbacks.executeSubOperation(workflowId, subOperation, signal), void 0, (winnerIndex, value) => wrapRaceWinner(operation, winnerIndex, value));
115
- return finalizeAndUnwrap(winner);
121
+ let winnerIndex, winnerError;
122
+ try {
123
+ const winner = await executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, signal) => callbacks.executeSubOperation(workflowId, subOperation, signal), void 0, (selectedIndex, value) => wrapRaceWinner(operation, selectedIndex, value), (selectedIndex, error) => {
124
+ winnerIndex = selectedIndex;
125
+ winnerError = error;
126
+ });
127
+ try {
128
+ return await finalizeAndUnwrap(winner);
129
+ } catch (error) {
130
+ winnerError = error;
131
+ throw error;
132
+ }
133
+ } finally {
134
+ if (winnerIndex !== void 0)
135
+ recordTimelineBranches(internals, workflowId, raceTimelineDetails(operation.operations, winnerIndex, operation.branchNames, winnerError));
136
+ }
116
137
  });
117
138
  }
118
139
  export function assertValidRaceBranchNames(operation) {
@@ -123,21 +144,46 @@ export function wrapRaceWinner(operation, winnerIndex, value) {
123
144
  const key = operation.branchNames?.[winnerIndex];
124
145
  return key === void 0 ? value : createKeyedRaceResultEnvelope(key, value);
125
146
  }
126
- export async function executeRaceSubOperations(internals, workflowId, operations, execute, parentSignal, wrapWinner) {
147
+ function createRaceWinnerObservers(wrapWinner, observeWinner) {
148
+ const observers = {};
149
+ if (wrapWinner !== void 0)
150
+ observers.wrapWinner = wrapWinner;
151
+ if (observeWinner !== void 0)
152
+ observers.observeWinner = observeWinner;
153
+ return observers;
154
+ }
155
+ async function settleRaceBranch(operation, index, signal, execute) {
156
+ try {
157
+ return { index, status: "fulfilled", value: await execute(operation, signal) };
158
+ } catch (error) {
159
+ return { error, index, status: "rejected" };
160
+ }
161
+ }
162
+ function resolveRaceSettlement(settlement, observers) {
163
+ if (settlement.status === "rejected") {
164
+ observers.observeWinner?.(settlement.index, settlement.error);
165
+ throw settlement.error;
166
+ }
167
+ observers.observeWinner?.(settlement.index);
168
+ return observers.wrapWinner === void 0 ? settlement.value : observers.wrapWinner(settlement.index, settlement.value);
169
+ }
170
+ async function executeBufferedRaceWinner(bufferedSignal, signal, execute, observers) {
171
+ const settlement = await settleRaceBranch(bufferedSignal.operation, bufferedSignal.index, signal, execute);
172
+ return resolveRaceSettlement(settlement, observers);
173
+ }
174
+ export async function executeRaceSubOperations(internals, workflowId, operations, execute, parentSignal, wrapWinner, observeWinner) {
127
175
  parentSignal?.throwIfAborted();
128
- const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason);
176
+ const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason), observers = createRaceWinnerObservers(wrapWinner, observeWinner);
129
177
  parentSignal?.addEventListener("abort", abortFromParent, { once: !0 });
130
178
  try {
131
179
  const bufferedSignal = await findBufferedSignalDrainBranch(internals, workflowId, operations);
132
180
  parentSignal?.throwIfAborted();
133
- if (bufferedSignal !== void 0) {
134
- const value = await execute(bufferedSignal.operation, controller.signal);
135
- return wrapWinner === void 0 ? value : wrapWinner(bufferedSignal.index, value);
136
- }
137
- const subOperations = operations.map((operation, index) => execute(operation, controller.signal).then((value) => ({ index, value })));
181
+ if (bufferedSignal !== void 0)
182
+ return await executeBufferedRaceWinner(bufferedSignal, controller.signal, execute, observers);
183
+ const subOperations = operations.map((operation, index) => settleRaceBranch(operation, index, controller.signal, execute));
138
184
  Promise.allSettled(subOperations);
139
185
  const winner = await Promise.race(subOperations);
140
- return wrapWinner === void 0 ? winner.value : wrapWinner(winner.index, winner.value);
186
+ return resolveRaceSettlement(winner, observers);
141
187
  } finally {
142
188
  parentSignal?.removeEventListener("abort", abortFromParent);
143
189
  controller.abort();
@@ -156,6 +202,7 @@ async function findBufferedSignalDrainBranch(internals, workflowId, operations)
156
202
  export async function processRunAllOperation(internals, workflowId, operation, callbacks) {
157
203
  return callbacks.runOperationWithResult(workflowId, operation, async () => {
158
204
  const branchNames = Object.keys(operation.branches), operationIds = branchNames.map((name) => `run-all:${operation.step}:${name}`), resumedSlots = extractResumedSlots(operation.resumedCacheEntry), resumedSlotsByName = mapResumedSlotsByName(resumedSlots, branchNames), branchesToRun = filterBranchesToRun(operation.branches, branchNames, resumedSlotsByName), { outcomes, hasFirstError, firstError } = await executeRunAllBranchesSettled(branchesToRun, (fn, input) => callActivityFunction(fn, input)), slots = mergeRunAllSlots(branchNames, operationIds, resumedSlotsByName, outcomes), entry = buildEntryFromSlots("run-all", slots, branchNames), partialEntryWritten = writePartialEntry(internals, workflowId, operation.step, entry);
205
+ recordTimelineBranches(internals, workflowId, runAllTimelineDetails(branchNames, slots, operation.branches));
159
206
  if (hasFirstError) {
160
207
  assertPartialFailurePersistenceSupported(partialEntryWritten, slots, "ctx.runAll", "worker execution mode");
161
208
  throw firstError;
@@ -12,5 +12,5 @@ export type SpeculateOperationCallbacks = {
12
12
  };
13
13
  export declare function processSpeculateOperation(internals: EngineInternals, workflowId: string, operation: SpeculateOperation, callbacks: SpeculateOperationCallbacks): Promise<void>;
14
14
  export declare function executeSpeculativeBranch(internals: EngineInternals, workflowId: string, operation: SpeculateOperation, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'>): Promise<unknown>;
15
- export declare function driveSpeculativeGenerator(workflowId: string, generator: SpeculativeOperationGenerator, speculativeState: SpeculativeExecutionState, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'>): Promise<unknown>;
15
+ export declare function driveSpeculativeGenerator(workflowId: string, generator: SpeculativeOperationGenerator, speculativeState: SpeculativeExecutionState, callbacks: Pick<SpeculateOperationCallbacks, 'executeSubOperation'>, observeChild?: (operation: ContextOperationRequest, index: number, error?: unknown) => void): Promise<unknown>;
16
16
  export {};
@@ -1,6 +1,10 @@
1
1
  import { finalizeAndUnwrap } from "./deferred-consume-envelope.js";
2
2
  import { assertSupportedSignalBranches } from "./operations-coordination.js";
3
3
  import { SpeculativeExecutionState } from "./speculative-execution-state.js";
4
+ import {
5
+ operationTimelineDetail,
6
+ recordTimelineSpeculation
7
+ } from "./timeline-coordinator-detail.js";
4
8
  export async function processSpeculateOperation(internals, workflowId, operation, callbacks) {
5
9
  return callbacks.runOperationWithResult(workflowId, operation, () => executeSpeculativeBranch(internals, workflowId, operation, callbacks));
6
10
  }
@@ -11,32 +15,42 @@ export async function executeSpeculativeBranch(internals, workflowId, operation,
11
15
  const parentContext = inlineStrategy.getContext(workflowId);
12
16
  if (!parentContext)
13
17
  throw Error(`No active inline context for workflow "${workflowId}"`);
14
- const speculativeContext = parentContext.createSpeculativeChild(), speculativeState = new SpeculativeExecutionState, generator = createSpeculativeOperationGenerator(operation, speculativeContext);
18
+ const speculativeContext = parentContext.createSpeculativeChild(), speculativeState = new SpeculativeExecutionState, generator = createSpeculativeOperationGenerator(operation, speculativeContext), children = [];
15
19
  try {
16
- const result = await driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks);
20
+ const result = await driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks, (child, index, error) => {
21
+ children.push(operationTimelineDetail(child, index, error === void 0 ? "fulfilled" : "rejected", error === void 0 ? {} : { error }));
22
+ });
17
23
  await speculativeState.drainVerifications();
18
24
  parentContext.commitSpeculativeChild(speculativeContext);
25
+ recordTimelineSpeculation(internals, workflowId, children, "committed");
19
26
  return result;
20
27
  } catch (error) {
21
- await speculativeState.rollback();
28
+ try {
29
+ await speculativeState.rollback();
30
+ } finally {
31
+ recordTimelineSpeculation(internals, workflowId, children, "rolled-back");
32
+ }
22
33
  throw error;
23
34
  }
24
35
  }
25
36
  function createSpeculativeOperationGenerator(operation, speculativeContext) {
26
37
  return operation.execute(speculativeContext);
27
38
  }
28
- export async function driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks) {
39
+ export async function driveSpeculativeGenerator(workflowId, generator, speculativeState, callbacks, observeChild) {
40
+ let childIndex = 0;
29
41
  const advance = async (lastResult, errorToThrow) => {
30
42
  const iterationResult = errorToThrow === void 0 ? await generator.next(lastResult) : await generator.throw(errorToThrow);
31
43
  if (iterationResult.done)
32
44
  return iterationResult.value;
33
- const nextOperation = iterationResult.value;
45
+ const nextOperation = iterationResult.value, currentChildIndex = childIndex++;
34
46
  try {
35
47
  if (nextOperation.type === "race" || nextOperation.type === "parallel")
36
48
  assertSupportedSignalBranches(nextOperation.operations);
37
49
  const nextResult = await callbacks.executeSubOperation(workflowId, nextOperation, void 0, speculativeState), finalizedResult = await finalizeAndUnwrap(nextResult);
50
+ observeChild?.(nextOperation, currentChildIndex);
38
51
  return advance(finalizedResult, void 0);
39
52
  } catch (error) {
53
+ observeChild?.(nextOperation, currentChildIndex, error);
40
54
  return advance(lastResult, error instanceof Error ? error : Error(String(error)));
41
55
  }
42
56
  };
@@ -25,7 +25,7 @@
25
25
  * checkpoint-write machinery — see the `parallel-execution.md` guide
26
26
  * for the precise durability contract.
27
27
  */
28
- import type { ParallelBranchSlot, ParallelOperationCacheEntry } from '../context/parallel-operations.ts';
28
+ import type { ParallelBranchSlot, ParallelOperationCacheEntry } from '../context/parallel-cache-entry.ts';
29
29
  export type ParallelDispatchResult = {
30
30
  /** Final slot table — every entry is `fulfilled` or `rejected`. */
31
31
  slots: ParallelBranchSlot[];
@@ -1,4 +1,4 @@
1
- import { createParallelOperationCacheEntry } from "../context/parallel-operations.js";
1
+ import { createParallelOperationCacheEntry } from "../context/parallel-cache-entry.js";
2
2
  export function createFirstRejectionCapture() {
3
3
  return { hasFirstError: !1, firstError: void 0 };
4
4
  }
@@ -59,8 +59,22 @@ function applyOptionalRegistrationFields(entry, registration) {
59
59
  function buildRegistrationEntry(name, registration) {
60
60
  const entry = buildBaseRegistrationEntry(name, registration);
61
61
  applyOptionalRegistrationFields(entry, registration);
62
+ if (isBuilderWorkflowDefinition(registration)) {
63
+ entry.signals = normalizeMessageDefinitions(name, "signal", registration.signals);
64
+ entry.updates = normalizeMessageDefinitions(name, "update", registration.updates);
65
+ entry.queries = normalizeMessageDefinitions(name, "query", registration.queries);
66
+ }
62
67
  return entry;
63
68
  }
69
+ function normalizeMessageDefinitions(workflowType, messageKind, definitions) {
70
+ const normalized = Object.create(null), sortedDefinitions = Object.values(definitions).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
71
+ for (const definition of sortedDefinitions) {
72
+ if (Object.hasOwn(normalized, definition.name))
73
+ throw Error(`Duplicate ${messageKind} runtime name "${definition.name}" in workflow "${workflowType}"`);
74
+ normalized[definition.name] = definition;
75
+ }
76
+ return normalized;
77
+ }
64
78
  function commitWorkflowDefinition(internals, definition, callbacks) {
65
79
  const name = definition.name;
66
80
  validateWorkflowOrActivityName(name, "workflow");
@@ -1,4 +1,4 @@
1
- import type { HumanReviewResult, ReviewRequest } from '../review/index.ts';
1
+ import { type HumanReviewResult, type ReviewRequest } from '../review/index.ts';
2
2
  import type { CompletedReviewEntry, PendingReviewEntry } from '../types.ts';
3
3
  export declare function toPendingReviewEntry(review: ReviewRequest): PendingReviewEntry;
4
4
  export declare function parseStoredReviewRequest(value: Uint8Array): ReviewRequest | null;