@lostgradient/weft 0.24.0 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/conformance.js +5 -2
  3. package/dist/cli/generated/operation-catalog.snapshot.json +95 -1
  4. package/dist/cli/generated/operation-client.generated.d.ts +28 -2
  5. package/dist/cli/generated/operation-client.generated.js +2 -0
  6. package/dist/cli/parse-schedule-arguments.js +15 -3
  7. package/dist/cli/schedule.js +2 -1
  8. package/dist/cli/types.d.ts +2 -1
  9. package/dist/client/http-client-requests.js +6 -9
  10. package/dist/client/start-body.js +1 -0
  11. package/dist/core/catalog/index.d.ts +2 -2
  12. package/dist/core/catalog/index.js +9 -2
  13. package/dist/core/catalog/reference-counts.d.ts +29 -14
  14. package/dist/core/catalog/removal.d.ts +63 -10
  15. package/dist/core/catalog/removal.js +18 -2
  16. package/dist/core/catalog/storage-io.d.ts +23 -0
  17. package/dist/core/catalog/storage-io.js +1 -1
  18. package/dist/core/engine/activity-resolution.js +17 -17
  19. package/dist/core/engine/bulk-operations-purge.d.ts +1 -1
  20. package/dist/core/engine/bulk-operations-purge.js +17 -22
  21. package/dist/core/engine/bulk-operations-retry.d.ts +24 -0
  22. package/dist/core/engine/bulk-operations-retry.js +190 -0
  23. package/dist/core/engine/bulk-operations-shared.d.ts +18 -0
  24. package/dist/core/engine/bulk-operations-shared.js +14 -0
  25. package/dist/core/engine/bulk-operations.d.ts +3 -2
  26. package/dist/core/engine/bulk-operations.js +10 -169
  27. package/dist/core/engine/callback-creators-bundles.js +5 -3
  28. package/dist/core/engine/callback-creators-core.js +9 -1
  29. package/dist/core/engine/callback-creators-schedule.js +2 -2
  30. package/dist/core/engine/catalog-readiness.js +2 -0
  31. package/dist/core/engine/catalog-removal.d.ts +107 -3
  32. package/dist/core/engine/catalog-removal.js +66 -13
  33. package/dist/core/engine/catalog-tombstone-recovery.d.ts +56 -0
  34. package/dist/core/engine/catalog-tombstone-recovery.js +39 -0
  35. package/dist/core/engine/child-workflow.d.ts +13 -1
  36. package/dist/core/engine/child-workflow.js +22 -4
  37. package/dist/core/engine/constraints.js +5 -1
  38. package/dist/core/engine/construction.d.ts +1 -1
  39. package/dist/core/engine/decode-revision.d.ts +32 -0
  40. package/dist/core/engine/decode-revision.js +9 -0
  41. package/dist/core/engine/disposal.d.ts +6 -2
  42. package/dist/core/engine/disposal.js +24 -10
  43. package/dist/core/engine/dynamic-source-errors.d.ts +81 -0
  44. package/dist/core/engine/dynamic-source-errors.js +25 -0
  45. package/dist/core/engine/dynamic-source-execution.d.ts +173 -0
  46. package/dist/core/engine/dynamic-source-execution.js +108 -0
  47. package/dist/core/engine/engine-internal-types.d.ts +16 -0
  48. package/dist/core/engine/engine-workflows-namespace.d.ts +10 -0
  49. package/dist/core/engine/engine-workflows-namespace.js +3 -1
  50. package/dist/core/engine/generation-codec.d.ts +22 -0
  51. package/dist/core/engine/generation-codec.js +2 -0
  52. package/dist/core/engine/index.d.ts +18 -10
  53. package/dist/core/engine/index.js +22 -11
  54. package/dist/core/engine/inline-launch-queue.js +1 -1
  55. package/dist/core/engine/internals.d.ts +26 -30
  56. package/dist/core/engine/lifecycle/checkpoint-launch.d.ts +29 -0
  57. package/dist/core/engine/lifecycle/checkpoint-launch.js +75 -0
  58. package/dist/core/engine/lifecycle/delayed-start-registration.d.ts +30 -0
  59. package/dist/core/engine/lifecycle/delayed-start-registration.js +11 -0
  60. package/dist/core/engine/lifecycle/fork-helpers.d.ts +33 -1
  61. package/dist/core/engine/lifecycle/fork-helpers.js +3 -1
  62. package/dist/core/engine/lifecycle/recovery-isolation.d.ts +20 -0
  63. package/dist/core/engine/lifecycle/recovery-isolation.js +26 -0
  64. package/dist/core/engine/lifecycle/recovery-revision-groups.d.ts +75 -0
  65. package/dist/core/engine/lifecycle/recovery-revision-groups.js +64 -0
  66. package/dist/core/engine/lifecycle/resume-body.d.ts +44 -0
  67. package/dist/core/engine/lifecycle/resume-body.js +211 -0
  68. package/dist/core/engine/lifecycle/resume-generation-guard.d.ts +58 -0
  69. package/dist/core/engine/lifecycle/resume-generation-guard.js +11 -0
  70. package/dist/core/engine/lifecycle/resume.d.ts +46 -3
  71. package/dist/core/engine/lifecycle/resume.js +21 -201
  72. package/dist/core/engine/lifecycle/shared.d.ts +43 -0
  73. package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +80 -2
  74. package/dist/core/engine/lifecycle/standalone-claim-acquire.js +20 -4
  75. package/dist/core/engine/lifecycle/start-commit-errors.d.ts +12 -0
  76. package/dist/core/engine/lifecycle/start-commit-errors.js +6 -0
  77. package/dist/core/engine/lifecycle/start-commit.d.ts +24 -7
  78. package/dist/core/engine/lifecycle/start-commit.js +57 -15
  79. package/dist/core/engine/lifecycle/start-exec.d.ts +18 -2
  80. package/dist/core/engine/lifecycle/start-exec.js +7 -5
  81. package/dist/core/engine/lifecycle/start-or-signal-create.js +1 -3
  82. package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +8 -2
  83. package/dist/core/engine/lifecycle/start-or-signal-resolution.js +11 -3
  84. package/dist/core/engine/lifecycle/start-or-signal.js +1 -1
  85. package/dist/core/engine/lifecycle/start-precondition-attribution.d.ts +55 -0
  86. package/dist/core/engine/lifecycle/start-precondition-attribution.js +13 -0
  87. package/dist/core/engine/lifecycle/start-revision-resolution.d.ts +34 -0
  88. package/dist/core/engine/lifecycle/start-revision-resolution.js +11 -0
  89. package/dist/core/engine/lifecycle/start-schedule-timing.d.ts +11 -0
  90. package/dist/core/engine/lifecycle/start-schedule-timing.js +21 -0
  91. package/dist/core/engine/lifecycle/start-state.d.ts +16 -0
  92. package/dist/core/engine/lifecycle/start-state.js +60 -0
  93. package/dist/core/engine/lifecycle/start-terminal-conflict-purge.d.ts +146 -4
  94. package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +33 -4
  95. package/dist/core/engine/lifecycle/start.d.ts +54 -7
  96. package/dist/core/engine/lifecycle/start.js +53 -90
  97. package/dist/core/engine/lifecycle/transition.d.ts +12 -4
  98. package/dist/core/engine/lifecycle/transition.js +40 -95
  99. package/dist/core/engine/lifecycle.d.ts +5 -2
  100. package/dist/core/engine/lifecycle.js +7 -6
  101. package/dist/core/engine/listing.js +15 -1
  102. package/dist/core/engine/memo-durable-activity.js +4 -1
  103. package/dist/core/engine/nonterminal-revision-count.d.ts +25 -0
  104. package/dist/core/engine/nonterminal-revision-count.js +16 -0
  105. package/dist/core/engine/operations-time.d.ts +5 -23
  106. package/dist/core/engine/operations-time.js +13 -11
  107. package/dist/core/engine/ownership-bootstrap.d.ts +1 -1
  108. package/dist/core/engine/pinned-schedule-revision-count.d.ts +26 -0
  109. package/dist/core/engine/pinned-schedule-revision-count.js +19 -0
  110. package/dist/core/engine/pinned-schedule-revision.d.ts +76 -0
  111. package/dist/core/engine/pinned-schedule-revision.js +53 -0
  112. package/dist/core/engine/registration.d.ts +30 -0
  113. package/dist/core/engine/registration.js +17 -2
  114. package/dist/core/engine/retention.js +9 -2
  115. package/dist/core/engine/revision-errors.d.ts +71 -0
  116. package/dist/core/engine/revision-errors.js +13 -0
  117. package/dist/core/engine/schedule-overlap.js +2 -1
  118. package/dist/core/engine/schedule-revision-fire.d.ts +17 -0
  119. package/dist/core/engine/schedule-revision-fire.js +3 -0
  120. package/dist/core/engine/schedule-run.d.ts +10 -0
  121. package/dist/core/engine/schedule-run.js +2 -1
  122. package/dist/core/engine/schedules.d.ts +11 -1
  123. package/dist/core/engine/schedules.js +28 -9
  124. package/dist/core/engine/source-diagnostics.d.ts +76 -0
  125. package/dist/core/engine/source-diagnostics.js +105 -0
  126. package/dist/core/engine/source-registration.js +4 -2
  127. package/dist/core/engine/source-resolution.d.ts +25 -9
  128. package/dist/core/engine/source-resolution.js +49 -14
  129. package/dist/core/engine/source-runtime-state.d.ts +92 -0
  130. package/dist/core/engine/source-runtime-state.js +36 -0
  131. package/dist/core/engine/storage-io.d.ts +19 -2
  132. package/dist/core/engine/storage-io.js +21 -2
  133. package/dist/core/engine/termination/complete.js +1 -1
  134. package/dist/core/engine/termination/finalizer-registration.d.ts +18 -0
  135. package/dist/core/engine/termination/finalizer-registration.js +21 -0
  136. package/dist/core/engine/termination/finalizer.d.ts +16 -19
  137. package/dist/core/engine/termination/finalizer.js +3 -3
  138. package/dist/core/engine/termination/suspend.js +3 -0
  139. package/dist/core/engine/time-operation-callbacks.d.ts +32 -0
  140. package/dist/core/engine/time-operation-callbacks.js +0 -0
  141. package/dist/core/engine/validation/schedule-cadence.d.ts +16 -0
  142. package/dist/core/engine/validation/schedule-cadence.js +20 -0
  143. package/dist/core/engine/validation/schedule-options.d.ts +4 -1
  144. package/dist/core/engine/validation/schedule-options.js +14 -0
  145. package/dist/core/engine/validation/schedule-revision.d.ts +24 -0
  146. package/dist/core/engine/validation/schedule-revision.js +21 -0
  147. package/dist/core/engine/validation/schedule-warnings.d.ts +11 -0
  148. package/dist/core/engine/validation/schedule-warnings.js +5 -0
  149. package/dist/core/engine/validation/schedule.d.ts +29 -4
  150. package/dist/core/engine/validation/schedule.js +19 -36
  151. package/dist/core/engine/validation.js +13 -17
  152. package/dist/core/engine/workflow-claim-reclaim-target.d.ts +1 -1
  153. package/dist/core/engine/workflow-claim-reclaim-target.js +22 -8
  154. package/dist/core/engine/workflow-claim-registry.d.ts +79 -75
  155. package/dist/core/engine/workflow-claim-registry.js +15 -2
  156. package/dist/core/engine/workflow-generation-fence.d.ts +74 -0
  157. package/dist/core/engine/workflow-generation-fence.js +19 -0
  158. package/dist/core/engine/workflow-retention-deadline.d.ts +16 -0
  159. package/dist/core/engine/workflow-retention-deadline.js +33 -0
  160. package/dist/core/events/event-map.d.ts +5 -0
  161. package/dist/core/events/index.d.ts +1 -0
  162. package/dist/core/events/index.js +1 -0
  163. package/dist/core/events/workflow-source-events.d.ts +102 -0
  164. package/dist/core/events/workflow-source-events.js +57 -0
  165. package/dist/core/execution-strategy.d.ts +7 -0
  166. package/dist/core/inline-execution-strategy.context-options.d.ts +1 -1
  167. package/dist/core/inline-execution-strategy.js +1 -1
  168. package/dist/core/start-workflow-validation.d.ts +15 -0
  169. package/dist/core/start-workflow-validation.js +11 -1
  170. package/dist/core/types/checkpoint.d.ts +14 -0
  171. package/dist/core/types/schedules.d.ts +52 -1
  172. package/dist/core/types/state.d.ts +16 -0
  173. package/dist/core/weft-error.d.ts +1 -1
  174. package/dist/core/weft-error.js +4 -1
  175. package/dist/core/worker-execution-strategy.d.ts +1 -0
  176. package/dist/core/worker-execution-strategy.js +13 -1
  177. package/dist/core/worker-fault-handling.d.ts +10 -0
  178. package/dist/core/worker-fault-handling.js +1 -0
  179. package/dist/core/worker-inbound-message.d.ts +1 -0
  180. package/dist/core/worker-inbound-message.js +1 -0
  181. package/dist/core/worker-protocol-guard.js +4 -0
  182. package/dist/core/worker-protocol.d.ts +7 -0
  183. package/dist/core/worker-protocol.js +2 -1
  184. package/dist/core/worker-turn-watchdog.d.ts +8 -1
  185. package/dist/core/worker-turn-watchdog.js +3 -2
  186. package/dist/core/workflow-identifiers.d.ts +37 -0
  187. package/dist/core/workflow-identifiers.js +19 -1
  188. package/dist/diagnostics/format.js +5 -0
  189. package/dist/diagnostics/types.d.ts +24 -0
  190. package/dist/diagnostics/version-check.js +8 -2
  191. package/dist/http.js +2 -2
  192. package/dist/index.d.ts +3 -3
  193. package/dist/index.js +7 -0
  194. package/dist/indexeddb.js +1 -1
  195. package/dist/json-schema.js +3 -3
  196. package/dist/server/fault-to-json-rpc.js +2 -1
  197. package/dist/server/fleet-event-feed.js +6 -6
  198. package/dist/server/index.d.ts +11 -0
  199. package/dist/server/operation-fault.d.ts +2 -0
  200. package/dist/server/operation-fault.js +2 -1
  201. package/dist/server/operations/create-schedule.d.ts +2 -0
  202. package/dist/server/operations/create-schedule.js +10 -6
  203. package/dist/server/operations/get-catalog-diagnostics.d.ts +28 -0
  204. package/dist/server/operations/get-catalog-diagnostics.js +10 -2
  205. package/dist/server/operations/get-task-detail-schema.d.ts +8 -0
  206. package/dist/server/operations/get-task-detail-schema.js +1 -0
  207. package/dist/server/operations/get-task-detail.d.ts +8 -0
  208. package/dist/server/operations/get-task-detail.js +1 -0
  209. package/dist/server/operations/get-task-diagnostics.d.ts +4 -4
  210. package/dist/server/operations/preload-workflow-revision.d.ts +28 -0
  211. package/dist/server/operations/preload-workflow-revision.js +60 -0
  212. package/dist/server/operations/schedule-faults.d.ts +1 -0
  213. package/dist/server/operations/schedule-faults.js +32 -4
  214. package/dist/server/operations/schedule-rest-body.d.ts +1 -0
  215. package/dist/server/operations/schedule-rest-body.js +2 -1
  216. package/dist/server/operations/static-registrations.js +6 -0
  217. package/dist/server/operations/update-schedule.d.ts +2 -0
  218. package/dist/server/operations/update-schedule.js +4 -2
  219. package/dist/server/operations/workflow-catalog-operation-helpers.js +27 -0
  220. package/dist/server/rest-binding.d.ts +11 -0
  221. package/dist/server/runtime/task-dispatch-envelope.d.ts +41 -0
  222. package/dist/server/runtime/task-dispatch-envelope.js +54 -0
  223. package/dist/server/runtime/task-dispatch-revision.d.ts +29 -0
  224. package/dist/server/runtime/task-dispatch-revision.js +13 -0
  225. package/dist/server/runtime/task-dispatch.d.ts +3 -1
  226. package/dist/server/runtime/task-dispatch.js +20 -44
  227. package/dist/server/runtime/task-ledger-recovery.js +1 -1
  228. package/dist/server/runtime/task-ledger-runtime.d.ts +14 -2
  229. package/dist/server/runtime/task-ledger-runtime.js +5 -3
  230. package/dist/server/runtime/task-polling.js +9 -3
  231. package/dist/server/runtime/task-reconciliation.js +2 -1
  232. package/dist/server/runtime/websocket-worker.js +9 -0
  233. package/dist/server/task-ledger-codec.d.ts +28 -0
  234. package/dist/server/task-ledger-codec.js +7 -1
  235. package/dist/server/task-ledger-transition-helpers.js +1 -0
  236. package/dist/server/task-ledger-types.d.ts +9 -0
  237. package/dist/server/task-ledger.d.ts +1 -1
  238. package/dist/server/task-ledger.js +3 -1
  239. package/dist/server/task-queue-types.d.ts +2 -0
  240. package/dist/storage/bun-sql.js +135 -100
  241. package/dist/storage/catalog-keys.d.ts +19 -0
  242. package/dist/storage/catalog-keys.js +3 -1
  243. package/dist/storage/compressed-storage.js +1 -1
  244. package/dist/storage/generation-keys.d.ts +52 -0
  245. package/dist/storage/generation-keys.js +4 -0
  246. package/dist/storage/index.d.ts +76 -63
  247. package/dist/storage/interface.d.ts +24 -230
  248. package/dist/storage/interface.js +1 -1
  249. package/dist/storage/key-prefixes.d.ts +1 -1
  250. package/dist/storage/key-prefixes.js +2 -0
  251. package/dist/storage/lease-keys.d.ts +48 -0
  252. package/dist/storage/lease-keys.js +8 -0
  253. package/dist/storage/lmdb.d.ts +30 -2
  254. package/dist/storage/lmdb.js +1 -1
  255. package/dist/storage/memory.js +1 -1
  256. package/dist/storage/neon.js +3 -3
  257. package/dist/storage/node-sqlite.js +135 -100
  258. package/dist/storage/postgres.js +3 -3
  259. package/dist/storage/resolve.js +1 -1
  260. package/dist/storage/scoped-storage.js +1 -1
  261. package/dist/storage/signal-keys.d.ts +23 -0
  262. package/dist/storage/signal-keys.js +9 -0
  263. package/dist/storage/testing.js +1 -1
  264. package/dist/storage/turso.js +2 -2
  265. package/dist/storage/workflow-lifecycle-keys.d.ts +128 -0
  266. package/dist/storage/workflow-lifecycle-keys.js +46 -0
  267. package/dist/storage/workflow-record-keys.d.ts +98 -0
  268. package/dist/storage/workflow-record-keys.js +47 -0
  269. package/dist/version.d.ts +1 -1
  270. package/dist/version.js +1 -1
  271. package/dist/web-extension.js +1 -1
  272. package/dist/worker/protocol-messages.d.ts +17 -0
  273. package/dist/worker/protocol-schemas.d.ts +32 -0
  274. package/dist/worker/protocol-schemas.js +7 -3
  275. package/dist/worker/protocol-task-result.js +35 -3
  276. package/dist/worker/protocol.js +1 -1
  277. package/dist/worker/registry/types.d.ts +2 -0
  278. package/dist/worker/registry.d.ts +1 -1
  279. package/dist/worker/registry.js +3 -1
  280. package/dist/workers/workflow-runner.d.ts +9 -0
  281. package/dist/workers/workflow-runner.js +9 -2
  282. package/dist/workers/workflow-worker-entry.js +9 -4
  283. package/package.json +2 -2
@@ -1,7 +1,11 @@
1
1
  import { KEYS } from "../../storage/interface.js";
2
2
  import { decode } from "../codec.js";
3
- import { WorkflowNotRegisteredError } from "./errors.js";
4
3
  import { commitFencedEngineWrite } from "./fenced-write.js";
4
+ import {
5
+ buildPinnedRevisionWriteOptions,
6
+ resolveScheduleCreationRevision,
7
+ resolveScheduleRevisionForPin
8
+ } from "./pinned-schedule-revision.js";
5
9
  import { ScheduleHandle } from "./schedule-handle.js";
6
10
  import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
7
11
  import { getNextScheduleOccurrence } from "./schedule-occurrence.js";
@@ -31,9 +35,7 @@ import {
31
35
  export { startScheduledRun } from "./schedule-run.js";
32
36
  export { handleScheduleTimer } from "./schedule-timer.js";
33
37
  export async function schedule(internals, type, input, spec, options) {
34
- if (!internals.registrations.has(type))
35
- throw new WorkflowNotRegisteredError(type);
36
- const normalizedSpec = normalizeScheduleSpec(spec), normalizedOptions = normalizeScheduleOptions(options), scheduleId = normalizedOptions.id ?? crypto.randomUUID();
38
+ const normalizedSpec = normalizeScheduleSpec(spec), normalizedOptions = normalizeScheduleOptions(options), scheduleId = normalizedOptions.id ?? crypto.randomUUID(), revisionPolicy = normalizedOptions.revisionPolicy ?? "active-at-fire", pinnedRevision = await resolveScheduleCreationRevision(internals, type, revisionPolicy);
37
39
  if (internals.pendingScheduleCreations.has(scheduleId))
38
40
  throw Error(`Schedule with id "${scheduleId}" already exists`);
39
41
  internals.pendingScheduleCreations.add(scheduleId);
@@ -52,13 +54,15 @@ export async function schedule(internals, type, input, spec, options) {
52
54
  overlap: normalizedOptions.overlap,
53
55
  backfill: normalizedOptions.backfill,
54
56
  ...normalizedOptions.jitterMs !== void 0 && { jitterMs: normalizedOptions.jitterMs },
57
+ revisionPolicy,
58
+ ...pinnedRevision !== void 0 && { pinnedRevision },
55
59
  createdAt: now,
56
60
  updatedAt: now,
57
61
  nextFireAt: getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now),
58
62
  missedFireCount: 0,
59
63
  queuedRuns: []
60
64
  };
61
- await writeScheduleState(internals, state);
65
+ await writeScheduleState(internals, state, await buildPinnedRevisionWriteOptions(internals, type, pinnedRevision));
62
66
  return new ScheduleHandle(scheduleId, internals.engine);
63
67
  } finally {
64
68
  internals.pendingScheduleCreations.delete(scheduleId);
@@ -152,18 +156,33 @@ export async function updateSchedule(internals, scheduleId, newSpec, options) {
152
156
  const state = await requireScheduleState(internals, normalizedScheduleId), now = internals.options.getNow(), {
153
157
  cronExpression: _droppedCron,
154
158
  intervalMs: _droppedInterval,
155
- ...stateWithoutCadence
156
- } = state, cadenceFields = normalizedSpec.kind === "interval" ? { intervalMs: normalizedSpec.intervalMs } : { cronExpression: normalizedSpec.cronExpression }, anchorFields = normalizedSpec.kind === "interval" ? { createdAt: now } : {}, updatedState = {
157
- ...stateWithoutCadence,
159
+ pinnedRevision: _droppedPinnedRevision,
160
+ ...stateWithoutCadenceOrPin
161
+ } = state, cadenceFields = normalizedSpec.kind === "interval" ? { intervalMs: normalizedSpec.intervalMs } : { cronExpression: normalizedSpec.cronExpression }, anchorFields = normalizedSpec.kind === "interval" ? { createdAt: now } : {};
162
+ let pinnedRevision, revisionFields;
163
+ if (normalizedOptions.revisionPolicy === void 0)
164
+ revisionFields = {
165
+ revisionPolicy: state.revisionPolicy,
166
+ ...state.pinnedRevision !== void 0 && { pinnedRevision: state.pinnedRevision }
167
+ };
168
+ else if (normalizedOptions.revisionPolicy === "pinned") {
169
+ pinnedRevision = await resolveScheduleRevisionForPin(internals.engine, internals, state.workflowType);
170
+ revisionFields = { revisionPolicy: "pinned", pinnedRevision };
171
+ } else
172
+ revisionFields = { revisionPolicy: "active-at-fire" };
173
+ const updatedState = {
174
+ ...stateWithoutCadenceOrPin,
158
175
  ...normalizedOptions,
159
176
  ...cadenceFields,
160
177
  ...anchorFields,
178
+ ...revisionFields,
161
179
  updatedAt: now,
162
180
  nextFireAt: state.status === "cancelled" ? null : getNextScheduleOccurrence({ ...cadenceFields, createdAt: now }, now)
163
181
  };
164
182
  await writeScheduleState(internals, updatedState, {
165
183
  includeTimer: state.status === "active",
166
- replaceTimerFrom: state
184
+ replaceTimerFrom: state,
185
+ ...await buildPinnedRevisionWriteOptions(internals, state.workflowType, pinnedRevision)
167
186
  });
168
187
  });
169
188
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Bounded per-`(name, revision)` load-state diagnostics for dynamic
3
+ * workflow sources (WFT-15/16): the `idle -> loading -> ready|failed`
4
+ * state machine, waiter-count bookkeeping, and the `loading -> cancelled`
5
+ * transition. The `record*`/`begin*`/`end*` functions are pure state
6
+ * mutation, individually unit-testable; the `*AndDispatch` wrappers pair
7
+ * each with the matching `workflow-source:load-*` event, gated on the pure
8
+ * function's boolean return so a late, orphaned settle never re-dispatches
9
+ * `ready`/`failed` after the key already moved to `cancelled`, and a fresh
10
+ * attempt for the same key resets cleanly back to `loading` first.
11
+ * `source-resolution.ts` calls only the `*AndDispatch` wrappers.
12
+ *
13
+ * @module core/engine/source-diagnostics
14
+ */
15
+ import type { WorkflowSourceKind } from '../source/index.ts';
16
+ import type { Engine } from './index.ts';
17
+ import type { EngineInternals } from './internals.ts';
18
+ import { type SourceLoadDiagnostics } from './source-runtime-state.ts';
19
+ /** Bounded diagnostics for one `(name, revision)`, or `undefined` when no load was ever attempted. */
20
+ export declare function readSourceLoadDiagnostics(internals: EngineInternals, name: string, revision: string): SourceLoadDiagnostics | undefined;
21
+ /** Record that a fresh shared load for `(name, revision)` just started. */
22
+ export declare function recordSourceLoadStarted(internals: EngineInternals, name: string, revision: string, kind: WorkflowSourceKind, now: number): void;
23
+ /**
24
+ * Record a successful load, UNLESS the key already moved on (a `cancelled`
25
+ * transition from the last waiter releasing, or a fresh attempt already
26
+ * reset it back to `loading` for a NEW load this settle does not belong
27
+ * to). Returns whether the caller should dispatch `WorkflowSourceLoadReadyEvent`.
28
+ */
29
+ export declare function recordSourceLoadReady(internals: EngineInternals, name: string, revision: string, now: number): boolean;
30
+ /** The failed-load counterpart of {@link recordSourceLoadReady}. */
31
+ export declare function recordSourceLoadFailed(internals: EngineInternals, name: string, revision: string, now: number, error: unknown): boolean;
32
+ /** Increment the outstanding-waiter count for `(name, revision)`. Call once per `resolveWorkflowSource()` call, before any await. */
33
+ export declare function beginSourceWaiter(internals: EngineInternals, name: string, revision: string): void;
34
+ /**
35
+ * Decrement the outstanding-waiter count for `(name, revision)`; call in
36
+ * every `resolveWorkflowSource()` call's own `finally`. Returns whether
37
+ * this was the LAST outstanding waiter releasing while the shared load is
38
+ * still `loading` — the caller should transition diagnostics to
39
+ * `cancelled` and dispatch `WorkflowSourceLoadCancelledEvent`.
40
+ */
41
+ export declare function endSourceWaiterAndCheckCancellation(internals: EngineInternals, name: string, revision: string): boolean;
42
+ /**
43
+ * When a new caller joins a still-in-flight shared load whose diagnostics
44
+ * were marked `cancelled` (the last waiter released while the shared
45
+ * promise was still unsettled, and a later caller then joined that SAME
46
+ * orphaned promise instead of starting a fresh one — single-flight never
47
+ * aborts the shared load itself, only per-caller waiter interest), restore
48
+ * the state to `loading` so the eventual settle reaches its normal
49
+ * `ready`/`failed` transition instead of being silently suppressed by
50
+ * `recordSourceLoadReady`/`recordSourceLoadFailed`'s own `state !==
51
+ * 'loading'` guard. Never dispatches a fresh `WorkflowSourceLoadStartedEvent`
52
+ * — the load itself did not restart, only diagnostics visibility into it
53
+ * did. A no-op for any state other than `cancelled` (in particular,
54
+ * `getOrCreateSharedSourceLoad`'s cache-miss branch already sets `loading`
55
+ * itself via {@link recordSourceLoadStartedAndDispatch} for a genuinely new
56
+ * load, so this never fires there).
57
+ */
58
+ export declare function reviveOrphanedSourceLoadDiagnostics(internals: EngineInternals, name: string, revision: string): void;
59
+ /** Current outstanding-waiter count for `(name, revision)`. */
60
+ export declare function readSourceWaiterCount(internals: EngineInternals, name: string, revision: string): number;
61
+ /** Bundles the identity a `source-resolution.ts` call site already has in scope, so the `*AndDispatch` wrappers below take one argument instead of five. */
62
+ export type SourceEventContext = {
63
+ engine: Engine;
64
+ internals: EngineInternals;
65
+ name: string;
66
+ revision: string;
67
+ kind: WorkflowSourceKind;
68
+ };
69
+ /** {@link recordSourceLoadStarted} plus dispatching `WorkflowSourceLoadStartedEvent`. */
70
+ export declare function recordSourceLoadStartedAndDispatch(ctx: SourceEventContext, now: number): void;
71
+ /** {@link recordSourceLoadReady} plus dispatching `WorkflowSourceLoadReadyEvent` when it returns `true`. */
72
+ export declare function recordSourceLoadReadyAndDispatch(ctx: SourceEventContext, now: number): void;
73
+ /** {@link recordSourceLoadFailed} plus dispatching `WorkflowSourceLoadFailedEvent` when it returns `true`. */
74
+ export declare function recordSourceLoadFailedAndDispatch(ctx: SourceEventContext, now: number, error: unknown): void;
75
+ /** {@link endSourceWaiterAndCheckCancellation} plus dispatching `WorkflowSourceLoadCancelledEvent` when it returns `true`. */
76
+ export declare function endSourceWaiterAndDispatchCancellation(engine: Engine, internals: EngineInternals, name: string, revision: string): void;
@@ -0,0 +1,105 @@
1
+ import {
2
+ WorkflowSourceLoadCancelledEvent,
3
+ WorkflowSourceLoadFailedEvent,
4
+ WorkflowSourceLoadReadyEvent,
5
+ WorkflowSourceLoadStartedEvent
6
+ } from "../events/workflow-source-events.js";
7
+ import { classifyErrorAsFailureCategory } from "../failure-categories.js";
8
+ import {
9
+ decrementWaiterCount,
10
+ incrementWaiterCount,
11
+ readWaiterCount
12
+ } from "./source-runtime-state.js";
13
+ function getOrCreateDiagnosticsEntry(internals, name, revision) {
14
+ let byRevision = internals.sources.diagnostics.get(name);
15
+ if (byRevision === void 0) {
16
+ byRevision = new Map;
17
+ internals.sources.diagnostics.set(name, byRevision);
18
+ }
19
+ let entry = byRevision.get(revision);
20
+ if (entry === void 0) {
21
+ entry = {
22
+ state: "idle",
23
+ kind: "module",
24
+ requestedRevision: revision,
25
+ loadStartedAt: void 0,
26
+ loadDurationMs: void 0,
27
+ lastFailureCategory: void 0
28
+ };
29
+ byRevision.set(revision, entry);
30
+ }
31
+ return entry;
32
+ }
33
+ export function readSourceLoadDiagnostics(internals, name, revision) {
34
+ return internals.sources.diagnostics.get(name)?.get(revision);
35
+ }
36
+ export function recordSourceLoadStarted(internals, name, revision, kind, now) {
37
+ const entry = getOrCreateDiagnosticsEntry(internals, name, revision);
38
+ entry.state = "loading";
39
+ entry.kind = kind;
40
+ entry.loadStartedAt = now;
41
+ entry.loadDurationMs = void 0;
42
+ entry.lastFailureCategory = void 0;
43
+ }
44
+ export function recordSourceLoadReady(internals, name, revision, now) {
45
+ const entry = readSourceLoadDiagnostics(internals, name, revision);
46
+ if (entry === void 0 || entry.state !== "loading")
47
+ return !1;
48
+ entry.state = "ready";
49
+ entry.loadDurationMs = entry.loadStartedAt === void 0 ? void 0 : now - entry.loadStartedAt;
50
+ return !0;
51
+ }
52
+ export function recordSourceLoadFailed(internals, name, revision, now, error) {
53
+ const entry = readSourceLoadDiagnostics(internals, name, revision);
54
+ if (entry === void 0 || entry.state !== "loading")
55
+ return !1;
56
+ entry.state = "failed";
57
+ entry.loadDurationMs = entry.loadStartedAt === void 0 ? void 0 : now - entry.loadStartedAt;
58
+ entry.lastFailureCategory = classifyErrorAsFailureCategory(error, {
59
+ defaultErrorCategory: "application"
60
+ });
61
+ return !0;
62
+ }
63
+ export function beginSourceWaiter(internals, name, revision) {
64
+ incrementWaiterCount(internals.sources.waitersByKey, name, revision);
65
+ }
66
+ export function endSourceWaiterAndCheckCancellation(internals, name, revision) {
67
+ if (decrementWaiterCount(internals.sources.waitersByKey, name, revision) > 0)
68
+ return !1;
69
+ const entry = readSourceLoadDiagnostics(internals, name, revision);
70
+ if (entry === void 0 || entry.state !== "loading")
71
+ return !1;
72
+ entry.state = "cancelled";
73
+ return !0;
74
+ }
75
+ export function reviveOrphanedSourceLoadDiagnostics(internals, name, revision) {
76
+ const entry = readSourceLoadDiagnostics(internals, name, revision);
77
+ if (entry === void 0 || entry.state !== "cancelled")
78
+ return;
79
+ entry.state = "loading";
80
+ }
81
+ export function readSourceWaiterCount(internals, name, revision) {
82
+ return readWaiterCount(internals.sources.waitersByKey, name, revision);
83
+ }
84
+ export function recordSourceLoadStartedAndDispatch(ctx, now) {
85
+ recordSourceLoadStarted(ctx.internals, ctx.name, ctx.revision, ctx.kind, now);
86
+ ctx.engine.dispatchEvent(new WorkflowSourceLoadStartedEvent(ctx.name, ctx.revision, ctx.kind));
87
+ }
88
+ export function recordSourceLoadReadyAndDispatch(ctx, now) {
89
+ if (!recordSourceLoadReady(ctx.internals, ctx.name, ctx.revision, now))
90
+ return;
91
+ const loadDurationMs = readSourceLoadDiagnostics(ctx.internals, ctx.name, ctx.revision).loadDurationMs;
92
+ ctx.engine.dispatchEvent(new WorkflowSourceLoadReadyEvent(ctx.name, ctx.revision, ctx.kind, loadDurationMs));
93
+ }
94
+ export function recordSourceLoadFailedAndDispatch(ctx, now, error) {
95
+ if (!recordSourceLoadFailed(ctx.internals, ctx.name, ctx.revision, now, error))
96
+ return;
97
+ const diagnostics = readSourceLoadDiagnostics(ctx.internals, ctx.name, ctx.revision);
98
+ ctx.engine.dispatchEvent(new WorkflowSourceLoadFailedEvent(ctx.name, ctx.revision, ctx.kind, diagnostics.loadDurationMs ?? 0, diagnostics.lastFailureCategory));
99
+ }
100
+ export function endSourceWaiterAndDispatchCancellation(engine, internals, name, revision) {
101
+ if (!endSourceWaiterAndCheckCancellation(internals, name, revision))
102
+ return;
103
+ const kind = readSourceLoadDiagnostics(internals, name, revision)?.kind ?? "module";
104
+ engine.dispatchEvent(new WorkflowSourceLoadCancelledEvent(name, revision, kind));
105
+ }
@@ -14,16 +14,18 @@ function assertWellFormedSourceDescriptor(source) {
14
14
  throw TypeError(`registerSource(): source.descriptor.revision is ${revisionBytes} bytes, exceeding the maximum identifier size of ${MAX_CONTRACT_IDENTIFIER_BYTES}`);
15
15
  }
16
16
  export function registerSource(internals, source) {
17
+ if (internals.inlineStrategy === null)
18
+ throw Error('registerSource() is only supported in inline execution mode; a dynamically-loaded definition cannot be shipped to a Worker realm. Use workflowExecutionMode: "inline", or register this workflow eagerly with engine.register() instead.');
17
19
  assertWellFormedSourceDescriptor(source);
18
20
  const { name, revision } = source.descriptor;
19
21
  validateWorkflowOrActivityName(name, "workflow");
20
22
  Object.freeze(source.descriptor);
21
23
  if (internals.registrations.has(name))
22
24
  throw Error(`Cannot registerSource("${name}"): "${name}" is already registered as an eager workflow via engine.register(). A workflow name may not be both eagerly registered and a dynamic source.`);
23
- let byRevision = internals.workflowSourcesByName.get(name);
25
+ let byRevision = internals.sources.byName.get(name);
24
26
  if (byRevision === void 0) {
25
27
  byRevision = new Map;
26
- internals.workflowSourcesByName.set(name, byRevision);
28
+ internals.sources.byName.set(name, byRevision);
27
29
  }
28
30
  const existing = byRevision.get(revision);
29
31
  if (existing === void 0) {
@@ -5,13 +5,12 @@
5
5
  *
6
6
  * Single-flight per `(name, revision)`: the underlying load+validate+install
7
7
  * work is shared by every concurrent caller for the same key via
8
- * `internals.sourceResolutionsInFlight`, but each caller races that shared
8
+ * `internals.sources.resolutionsInFlight`, but each caller races that shared
9
9
  * work against its OWN per-call cancellation interest — a cancelled waiter
10
- * never aborts a load another waiter still needs, and the shared load
11
- * itself is never tied to any individual caller's lifetime. Disposal aborts
12
- * every outstanding waiter (rejecting each pending `resolveWorkflowSource()`
13
- * call) without touching the shared load, which keeps running to whatever
14
- * point it naturally settles.
10
+ * never aborts a load another waiter still needs, and the shared load itself
11
+ * is never tied to any individual caller's lifetime. Disposal aborts every
12
+ * outstanding waiter (rejecting each pending `resolveWorkflowSource()` call)
13
+ * without touching the shared load, which keeps running to its own settle.
15
14
  *
16
15
  * @module core/engine/source-resolution
17
16
  */
@@ -41,12 +40,15 @@ export type ResolveWorkflowSourceOptions = {
41
40
  * {@link WorkflowRevisionRecord} immediately, without invoking the loader
42
41
  * at all, when `(name, revision)` is already durably installed — including
43
42
  * a revision installed by a different process. When that is the case,
44
- * `internals.resolvedWorkflowSources` is NOT populated for this key (there
43
+ * `internals.sources.resolved` is NOT populated for this key (there
45
44
  * is no locally-loaded `WorkflowDefinition` to stash — only the durable
46
45
  * manifest was ever read) even though the catalog itself considers the
47
- * revision installed; a later batch reading `resolvedWorkflowSources` must
46
+ * revision installed; a caller reading `internals.sources.resolved` must
48
47
  * account for that gap rather than assuming every installed revision has a
49
- * live definition available in this process. This fast path still requires
48
+ * live definition available in this process see
49
+ * {@link resolveWorkflowSourceForExecution} for the internal-only variant
50
+ * that closes exactly this gap for callers that need a local definition.
51
+ * This fast path still requires
50
52
  * `registerSource()` to have been called for this exact key first (see
51
53
  * {@link resolveCachedOrHandle}), and still validates a pinned
52
54
  * `workflowVersion`/`contractHash` against the cached manifest — the ONLY
@@ -83,3 +85,17 @@ export type ResolveWorkflowSourceOptions = {
83
85
  * ```
84
86
  */
85
87
  export declare function resolveWorkflowSource(engine: Engine, name: string, revision: string, options?: ResolveWorkflowSourceOptions): Promise<WorkflowRevisionRecord>;
88
+ /**
89
+ * Internal-only variant of {@link resolveWorkflowSource} used exclusively by
90
+ * `resolveExecutableRegistration()` (`dynamic-source-execution.ts`, WFT-15).
91
+ * Identical cancellation, disposal, and single-flight contract, with one
92
+ * difference: a cache hit whose local resolved-definition cache
93
+ * (`internals.sources.resolved`) is still empty for this exact key falls
94
+ * through to a real load instead of returning the cached manifest —
95
+ * closing the cross-process-restart gap {@link resolveWorkflowSource}'s own
96
+ * doc calls out. `catalog.install()` is idempotent on byte-identical
97
+ * content, so re-installing an already-installed manifest never throws.
98
+ * Not exported from the package root — reached only via
99
+ * `resolveExecutableRegistration`.
100
+ */
101
+ export declare function resolveWorkflowSourceForExecution(engine: Engine, name: string, revision: string, options?: ResolveWorkflowSourceOptions): Promise<WorkflowRevisionRecord>;
@@ -9,8 +9,17 @@ import {
9
9
  WorkflowSourceValidationError
10
10
  } from "../source/index.js";
11
11
  import { ensureWorkflowCatalogReady, isWorkflowCatalogReady } from "./catalog-readiness.js";
12
+ import { WorkflowSourceNotRegisteredError } from "./dynamic-source-errors.js";
12
13
  import { EngineDisposedError } from "./errors.js";
13
14
  import { getInternals, getWorkflowCatalog } from "./internals.js";
15
+ import {
16
+ beginSourceWaiter,
17
+ endSourceWaiterAndDispatchCancellation,
18
+ recordSourceLoadFailedAndDispatch,
19
+ recordSourceLoadReadyAndDispatch,
20
+ recordSourceLoadStartedAndDispatch,
21
+ reviveOrphanedSourceLoadDiagnostics
22
+ } from "./source-diagnostics.js";
14
23
  function toAbortError(signal) {
15
24
  const reason = signal.reason;
16
25
  return reason instanceof Error ? reason : Error("resolveWorkflowSource() aborted");
@@ -31,10 +40,10 @@ async function runSharedSourceLoad(engine, internals, name, revision, handle) {
31
40
  throw new EngineDisposedError;
32
41
  const installed = await getWorkflowCatalog(engine).install(outcome.manifest, outcome.definition);
33
42
  if (!internals.disposed) {
34
- let resolvedByRevision = internals.resolvedWorkflowSources.get(name);
43
+ let resolvedByRevision = internals.sources.resolved.get(name);
35
44
  if (resolvedByRevision === void 0) {
36
45
  resolvedByRevision = new Map;
37
- internals.resolvedWorkflowSources.set(name, resolvedByRevision);
46
+ internals.sources.resolved.set(name, resolvedByRevision);
38
47
  }
39
48
  resolvedByRevision.set(revision, {
40
49
  definition: outcome.loadedDefinition,
@@ -44,21 +53,37 @@ async function runSharedSourceLoad(engine, internals, name, revision, handle) {
44
53
  return { manifest: installed.manifest, installedAt: installed.installedAt };
45
54
  }
46
55
  function getOrCreateSharedSourceLoad(engine, internals, name, revision, handle) {
47
- let byRevision = internals.sourceResolutionsInFlight.get(name);
56
+ let byRevision = internals.sources.resolutionsInFlight.get(name);
48
57
  if (byRevision === void 0) {
49
58
  byRevision = new Map;
50
- internals.sourceResolutionsInFlight.set(name, byRevision);
59
+ internals.sources.resolutionsInFlight.set(name, byRevision);
51
60
  }
52
61
  const existing = byRevision.get(revision);
53
- if (existing !== void 0)
62
+ if (existing !== void 0) {
63
+ reviveOrphanedSourceLoadDiagnostics(internals, name, revision);
54
64
  return existing;
55
- const shared = runSharedSourceLoad(engine, internals, name, revision, handle).finally(() => {
56
- const currentByRevision = internals.sourceResolutionsInFlight.get(name);
65
+ }
66
+ const eventContext = {
67
+ engine,
68
+ internals,
69
+ name,
70
+ revision,
71
+ kind: handle.descriptor.kind
72
+ };
73
+ recordSourceLoadStartedAndDispatch(eventContext, internals.options.getNow());
74
+ const shared = runSharedSourceLoad(engine, internals, name, revision, handle).then((record) => {
75
+ recordSourceLoadReadyAndDispatch(eventContext, internals.options.getNow());
76
+ return record;
77
+ }, (error) => {
78
+ recordSourceLoadFailedAndDispatch(eventContext, internals.options.getNow(), error);
79
+ throw error;
80
+ }).finally(() => {
81
+ const currentByRevision = internals.sources.resolutionsInFlight.get(name);
57
82
  if (currentByRevision?.get(revision) !== shared)
58
83
  return;
59
84
  currentByRevision.delete(revision);
60
85
  if (currentByRevision.size === 0)
61
- internals.sourceResolutionsInFlight.delete(name);
86
+ internals.sources.resolutionsInFlight.delete(name);
62
87
  });
63
88
  shared.catch(() => {});
64
89
  byRevision.set(revision, shared);
@@ -72,10 +97,10 @@ function createWaiterAbort(options) {
72
97
  options?.signal?.addEventListener("abort", forwardAbort, { once: !0 });
73
98
  return { controller, detach: () => options?.signal?.removeEventListener("abort", forwardAbort) };
74
99
  }
75
- async function resolveCachedOrHandle(engine, internals, name, revision) {
76
- const handle = internals.workflowSourcesByName.get(name)?.get(revision);
100
+ async function resolveCachedOrHandle(engine, internals, name, revision, requireLocalDefinition) {
101
+ const handle = internals.sources.byName.get(name)?.get(revision);
77
102
  if (handle === void 0)
78
- throw Error(`resolveWorkflowSource("${name}", "${revision}") was called before registerSource() ` + "registered this exact (name, revision) \u2014 call engine.registerSource() first.");
103
+ throw new WorkflowSourceNotRegisteredError(name, revision);
79
104
  if (!isWorkflowCatalogReady(engine))
80
105
  await ensureWorkflowCatalogReady(engine);
81
106
  const existing = await getWorkflowCatalog(engine).resolveEntry(name, revision);
@@ -83,11 +108,19 @@ async function resolveCachedOrHandle(engine, internals, name, revision) {
83
108
  const expectedManifest = buildExpectedManifest(handle.descriptor, existing.manifest), verdict = checkWorkflowCompatibility(expectedManifest, existing.manifest, DEFAULT_WORKFLOW_COMPATIBILITY_POLICY);
84
109
  if (!verdict.compatible)
85
110
  throw new WorkflowSourceValidationError(name, revision, verdict.reasons);
111
+ if (requireLocalDefinition && internals.sources.resolved.get(name)?.get(revision) === void 0)
112
+ return { handle };
86
113
  return { cached: existing };
87
114
  }
88
115
  return { handle };
89
116
  }
90
117
  export async function resolveWorkflowSource(engine, name, revision, options) {
118
+ return resolveWorkflowSourceCore(engine, name, revision, options, !1);
119
+ }
120
+ export async function resolveWorkflowSourceForExecution(engine, name, revision, options) {
121
+ return resolveWorkflowSourceCore(engine, name, revision, options, !0);
122
+ }
123
+ async function resolveWorkflowSourceCore(engine, name, revision, options, requireLocalDefinition) {
91
124
  const internals = getInternals(engine), waiter = createWaiterAbort(options);
92
125
  if (waiter.controller.signal.aborted) {
93
126
  waiter.detach();
@@ -97,9 +130,10 @@ export async function resolveWorkflowSource(engine, name, revision, options) {
97
130
  waiter.detach();
98
131
  throw new EngineDisposedError;
99
132
  }
100
- internals.sourceResolutionWaiterControllers.add(waiter.controller);
133
+ internals.sources.waiterControllers.add(waiter.controller);
134
+ beginSourceWaiter(internals, name, revision);
101
135
  try {
102
- const cachedOrHandle = resolveCachedOrHandle(engine, internals, name, revision), cachedOrHandleAbort = abortRejection(waiter.controller.signal);
136
+ const cachedOrHandle = resolveCachedOrHandle(engine, internals, name, revision, requireLocalDefinition), cachedOrHandleAbort = abortRejection(waiter.controller.signal);
103
137
  cachedOrHandleAbort.catch(() => {});
104
138
  let outcome;
105
139
  try {
@@ -114,6 +148,7 @@ export async function resolveWorkflowSource(engine, name, revision, options) {
114
148
  return await Promise.race([shared, waiterAbort]);
115
149
  } finally {
116
150
  waiter.detach();
117
- internals.sourceResolutionWaiterControllers.delete(waiter.controller);
151
+ internals.sources.waiterControllers.delete(waiter.controller);
152
+ endSourceWaiterAndDispatchCancellation(engine, internals, name, revision);
118
153
  }
119
154
  }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * `EngineInternals.sources` — the single consolidated container for every
3
+ * piece of process-local, in-memory state the dynamic workflow-source
4
+ * pipeline (WFT-13/14, WFT-15/16) needs: registered candidates, in-flight
5
+ * single-flight loads, per-caller waiter controllers, locally-resolved
6
+ * definitions, and bounded per-`(name, revision)` diagnostics.
7
+ *
8
+ * WFT-13/14 originally spread this across five flat `EngineInternals`
9
+ * fields (`workflowSourcesByName`, `sourceResolutionsInFlight`,
10
+ * `sourceResolutionWaiterControllers`, `resolvedWorkflowSources`, plus the
11
+ * `ResolvedSource` type alias). WFT-15/16 folds them into one field —
12
+ * `internals.sources: WorkflowSourceRuntimeState` — both because
13
+ * `internals.ts` and `lifecycle/start.ts` are already at the repository's
14
+ * 500-line implementation-file ceiling (no headroom for four more
15
+ * documented fields) and because the diagnostics state this batch adds
16
+ * (`diagnostics`, `waitersByKey`, `lastResolvedRevisionByName`) is the same
17
+ * kind of per-`(name, revision)` bookkeeping the original four fields
18
+ * already were — one container, one owner.
19
+ *
20
+ * Every consumer reaches this state via `internals.sources.<field>` rather
21
+ * than a flat `internals.<field>` — see `scripts/check-engine-internals-field-access.ts`,
22
+ * which is updated in the same commit to the single `sources` name.
23
+ *
24
+ * @module core/engine/source-runtime-state
25
+ */
26
+ import type { ActivityRegistry } from '../activity-registry.ts';
27
+ import type { WorkflowDefinition } from '../types.ts';
28
+ import type { FailureCategory } from '../types/identity.ts';
29
+ type SourceHandle = import('../source/index.ts').WorkflowSourceHandle;
30
+ type CatalogRevisionRecord = import('../catalog/index.ts').WorkflowRevisionRecord;
31
+ type WorkflowSourceKind = import('../source/index.ts').WorkflowSourceKind;
32
+ /** A successful `resolveWorkflowSource()`'s live, in-process definition and activity registry. */
33
+ export type ResolvedSource = {
34
+ definition: WorkflowDefinition;
35
+ activityRegistry: ActivityRegistry;
36
+ };
37
+ /**
38
+ * Load-state machine for one `(name, revision)`, backing both the
39
+ * `weft.catalog.diagnostics` `source` extension and the
40
+ * `workflow-source:load-*` events. `'idle'` is the type-level starting
41
+ * point before the first `resolveWorkflowSource()` call for a key, and IS
42
+ * observable externally: `weft.catalog.diagnostics` reports it for any
43
+ * `registerSource()`-registered revision that has never had a load start
44
+ * (see `buildSourceDiagnostics()` in `catalog-removal.ts`), and no
45
+ * `workflow-source:load-*` event is emitted for that state.
46
+ */
47
+ export type SourceLoadState = 'idle' | 'loading' | 'ready' | 'failed' | 'cancelled';
48
+ /** Bounded, per-`(name, revision)` diagnostics — no manifest or contract content, only identity and state. */
49
+ export type SourceLoadDiagnostics = {
50
+ state: SourceLoadState;
51
+ kind: WorkflowSourceKind;
52
+ requestedRevision: string;
53
+ loadStartedAt: number | undefined;
54
+ loadDurationMs: number | undefined;
55
+ lastFailureCategory: FailureCategory | undefined;
56
+ };
57
+ /** The consolidated dynamic workflow-source runtime state — see the module doc. */
58
+ export type WorkflowSourceRuntimeState = {
59
+ /** `registerSource()` candidates, keyed name then revision. Registering never invokes the loader. */
60
+ byName: Map<string, Map<string, SourceHandle>>;
61
+ /** In-flight `resolveWorkflowSource` single-flight load per `(name, revision)`. */
62
+ resolutionsInFlight: Map<string, Map<string, Promise<CatalogRevisionRecord>>>;
63
+ /** Per-caller `AbortController`s from in-flight `resolveWorkflowSource()` calls; disposal aborts every waiter without touching the shared load. */
64
+ waiterControllers: Set<AbortController>;
65
+ /**
66
+ * Outstanding-waiter count per `(name, revision)`, keyed name then
67
+ * revision — a flat composite-string key is not safe here, since unlike a
68
+ * workflow `name`, a source `revision` is only byte-length-bounded, not
69
+ * grammar-restricted, so it may contain whatever separator character a
70
+ * flat key would pick. Incremented/decremented inside
71
+ * `resolveWorkflowSource`'s own try/finally. Drives the `loading` ->
72
+ * `cancelled` diagnostics transition: that transition fires only when
73
+ * the LAST outstanding waiter for a key releases while the shared load
74
+ * is still unsettled.
75
+ */
76
+ waitersByKey: Map<string, Map<string, number>>;
77
+ /** A successful `resolveWorkflowSource()`'s live definition, keyed name then revision. */
78
+ resolved: Map<string, Map<string, ResolvedSource>>;
79
+ /** The revision `resolveExecutableRegistration()` most recently resolved for a name — used by the sync-only fallback readers (`finalizer.ts`, `constraints.ts`). */
80
+ lastResolvedRevisionByName: Map<string, string>;
81
+ /** Bounded per-`(name, revision)` load-state diagnostics, keyed name then revision. */
82
+ diagnostics: Map<string, Map<string, SourceLoadDiagnostics>>;
83
+ };
84
+ /** Build an empty {@link WorkflowSourceRuntimeState}. */
85
+ export declare function createWorkflowSourceRuntimeState(): WorkflowSourceRuntimeState;
86
+ /** Read the nested `name -> revision -> value` count at `(name, revision)` from {@link WorkflowSourceRuntimeState.waitersByKey}, defaulting to `0`. */
87
+ export declare function readWaiterCount(waitersByKey: WorkflowSourceRuntimeState['waitersByKey'], name: string, revision: string): number;
88
+ /** Increment the waiter count at `(name, revision)`, creating intermediate maps as needed. Returns the new count. */
89
+ export declare function incrementWaiterCount(waitersByKey: WorkflowSourceRuntimeState['waitersByKey'], name: string, revision: string): number;
90
+ /** Decrement the waiter count at `(name, revision)`, removing empty intermediate maps. Returns the new count (never negative). */
91
+ export declare function decrementWaiterCount(waitersByKey: WorkflowSourceRuntimeState['waitersByKey'], name: string, revision: string): number;
92
+ export {};
@@ -0,0 +1,36 @@
1
+ export function createWorkflowSourceRuntimeState() {
2
+ return {
3
+ byName: new Map,
4
+ resolutionsInFlight: new Map,
5
+ waiterControllers: new Set,
6
+ waitersByKey: new Map,
7
+ resolved: new Map,
8
+ lastResolvedRevisionByName: new Map,
9
+ diagnostics: new Map
10
+ };
11
+ }
12
+ export function readWaiterCount(waitersByKey, name, revision) {
13
+ return waitersByKey.get(name)?.get(revision) ?? 0;
14
+ }
15
+ export function incrementWaiterCount(waitersByKey, name, revision) {
16
+ let byRevision = waitersByKey.get(name);
17
+ if (byRevision === void 0) {
18
+ byRevision = new Map;
19
+ waitersByKey.set(name, byRevision);
20
+ }
21
+ const next = (byRevision.get(revision) ?? 0) + 1;
22
+ byRevision.set(revision, next);
23
+ return next;
24
+ }
25
+ export function decrementWaiterCount(waitersByKey, name, revision) {
26
+ const byRevision = waitersByKey.get(name), current = byRevision?.get(revision) ?? 0, next = Math.max(0, current - 1);
27
+ if (byRevision === void 0)
28
+ return next;
29
+ if (next === 0) {
30
+ byRevision.delete(revision);
31
+ if (byRevision.size === 0)
32
+ waitersByKey.delete(name);
33
+ } else
34
+ byRevision.set(revision, next);
35
+ return next;
36
+ }
@@ -1,4 +1,4 @@
1
- import { type BatchOperation } from '../../storage/interface.ts';
1
+ import { type BatchOperation, type ConditionalBatchCondition } from '../../storage/interface.ts';
2
2
  import type { ScheduleState, WorkflowState } from '../types.ts';
3
3
  import type { EngineInternals } from './internals.ts';
4
4
  import { type WorkflowClaimTransitionFragment } from './workflow-claim-transitions.ts';
@@ -84,11 +84,28 @@ export declare function commitExternalTerminalWorkflowStateOperations(internals:
84
84
  export declare function loadScheduleState(internals: EngineInternals, scheduleId: string): Promise<ScheduleState | null>;
85
85
  /** Load a schedule state. */
86
86
  export declare function requireScheduleState(internals: EngineInternals, scheduleId: string): Promise<ScheduleState>;
87
- /** Persist schedule state and optionally write the next schedule timer. */
87
+ /**
88
+ * Persist schedule state and optionally write the next schedule timer.
89
+ *
90
+ * `extraConditions` (WFT-20) additionally fences the commit on caller-supplied
91
+ * preconditions — used by a `revisionPolicy: 'pinned'` create/update to fence
92
+ * the write on `buildCatalogEntryRevisionCondition(type, pinnedRevision)` so a
93
+ * concurrent `removeWorkflowRevision()` landing between pin capture and this
94
+ * commit cannot let a pin to an already-removed revision durably land. When
95
+ * the commit's `extraConditions` are the ones that lost the CAS,
96
+ * `onExtraConditionsLost` (required whenever `extraConditions` is non-empty)
97
+ * supplies the thrown error instead of this function's own generic
98
+ * lost-precondition error, so the caller sees an actionable, typed failure
99
+ * (e.g. `WorkflowRevisionUnavailableError`) rather than a bare "lost its
100
+ * precondition" message. Every existing caller passes neither option and gets
101
+ * byte-for-byte the pre-WFT-20 commit shape.
102
+ */
88
103
  export declare function writeScheduleState(internals: EngineInternals, state: ScheduleState, options?: {
89
104
  includeTimer?: boolean;
90
105
  replaceTimerFrom?: ScheduleState;
91
106
  additionalOperations?: BatchOperation[];
107
+ extraConditions?: ConditionalBatchCondition[];
108
+ onExtraConditionsLost?: () => Error;
92
109
  }): Promise<void>;
93
110
  /** Load persisted workflow start headers by workflow ID. */
94
111
  export declare function loadWorkflowStartHeaders(internals: EngineInternals, workflowId: string): Promise<Map<string, string> | undefined>;