@lostgradient/weft 0.24.0 → 0.24.1

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 (243) 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.js +14 -20
  20. package/dist/core/engine/bulk-operations-retry.d.ts +9 -0
  21. package/dist/core/engine/bulk-operations-retry.js +177 -0
  22. package/dist/core/engine/bulk-operations-shared.d.ts +18 -0
  23. package/dist/core/engine/bulk-operations-shared.js +14 -0
  24. package/dist/core/engine/bulk-operations.js +8 -167
  25. package/dist/core/engine/callback-creators-bundles.js +4 -2
  26. package/dist/core/engine/callback-creators-core.js +9 -1
  27. package/dist/core/engine/callback-creators-schedule.js +2 -2
  28. package/dist/core/engine/catalog-readiness.js +2 -0
  29. package/dist/core/engine/catalog-removal.d.ts +107 -3
  30. package/dist/core/engine/catalog-removal.js +66 -13
  31. package/dist/core/engine/catalog-tombstone-recovery.d.ts +56 -0
  32. package/dist/core/engine/catalog-tombstone-recovery.js +39 -0
  33. package/dist/core/engine/constraints.js +5 -1
  34. package/dist/core/engine/construction.d.ts +1 -1
  35. package/dist/core/engine/decode-revision.d.ts +32 -0
  36. package/dist/core/engine/decode-revision.js +9 -0
  37. package/dist/core/engine/disposal.d.ts +6 -2
  38. package/dist/core/engine/disposal.js +24 -10
  39. package/dist/core/engine/dynamic-source-errors.d.ts +81 -0
  40. package/dist/core/engine/dynamic-source-errors.js +25 -0
  41. package/dist/core/engine/dynamic-source-execution.d.ts +173 -0
  42. package/dist/core/engine/dynamic-source-execution.js +108 -0
  43. package/dist/core/engine/engine-internal-types.d.ts +16 -0
  44. package/dist/core/engine/engine-workflows-namespace.d.ts +10 -0
  45. package/dist/core/engine/engine-workflows-namespace.js +3 -1
  46. package/dist/core/engine/index.d.ts +18 -10
  47. package/dist/core/engine/index.js +21 -10
  48. package/dist/core/engine/inline-launch-queue.js +1 -1
  49. package/dist/core/engine/internals.d.ts +26 -30
  50. package/dist/core/engine/lifecycle/checkpoint-launch.d.ts +29 -0
  51. package/dist/core/engine/lifecycle/checkpoint-launch.js +75 -0
  52. package/dist/core/engine/lifecycle/delayed-start-registration.d.ts +30 -0
  53. package/dist/core/engine/lifecycle/delayed-start-registration.js +11 -0
  54. package/dist/core/engine/lifecycle/fork-helpers.d.ts +33 -1
  55. package/dist/core/engine/lifecycle/fork-helpers.js +3 -1
  56. package/dist/core/engine/lifecycle/recovery-revision-groups.d.ts +75 -0
  57. package/dist/core/engine/lifecycle/recovery-revision-groups.js +64 -0
  58. package/dist/core/engine/lifecycle/resume-generation-guard.d.ts +58 -0
  59. package/dist/core/engine/lifecycle/resume-generation-guard.js +11 -0
  60. package/dist/core/engine/lifecycle/resume.js +18 -9
  61. package/dist/core/engine/lifecycle/shared.d.ts +43 -0
  62. package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +12 -0
  63. package/dist/core/engine/lifecycle/standalone-claim-acquire.js +6 -0
  64. package/dist/core/engine/lifecycle/start-commit-errors.d.ts +12 -0
  65. package/dist/core/engine/lifecycle/start-commit-errors.js +6 -0
  66. package/dist/core/engine/lifecycle/start-commit.d.ts +22 -7
  67. package/dist/core/engine/lifecycle/start-commit.js +49 -15
  68. package/dist/core/engine/lifecycle/start-exec.d.ts +18 -2
  69. package/dist/core/engine/lifecycle/start-exec.js +7 -5
  70. package/dist/core/engine/lifecycle/start-or-signal-create.js +1 -3
  71. package/dist/core/engine/lifecycle/start-or-signal.js +1 -1
  72. package/dist/core/engine/lifecycle/start-precondition-attribution.d.ts +49 -0
  73. package/dist/core/engine/lifecycle/start-precondition-attribution.js +13 -0
  74. package/dist/core/engine/lifecycle/start-revision-resolution.d.ts +34 -0
  75. package/dist/core/engine/lifecycle/start-revision-resolution.js +11 -0
  76. package/dist/core/engine/lifecycle/start-state.d.ts +16 -0
  77. package/dist/core/engine/lifecycle/start-state.js +60 -0
  78. package/dist/core/engine/lifecycle/start-terminal-conflict-purge.d.ts +66 -3
  79. package/dist/core/engine/lifecycle/start-terminal-conflict-purge.js +13 -3
  80. package/dist/core/engine/lifecycle/start.d.ts +16 -6
  81. package/dist/core/engine/lifecycle/start.js +38 -68
  82. package/dist/core/engine/lifecycle/transition.d.ts +3 -4
  83. package/dist/core/engine/lifecycle/transition.js +30 -80
  84. package/dist/core/engine/lifecycle.d.ts +4 -2
  85. package/dist/core/engine/lifecycle.js +6 -6
  86. package/dist/core/engine/listing.js +15 -1
  87. package/dist/core/engine/memo-durable-activity.js +4 -1
  88. package/dist/core/engine/nonterminal-revision-count.d.ts +25 -0
  89. package/dist/core/engine/nonterminal-revision-count.js +16 -0
  90. package/dist/core/engine/operations-time.d.ts +5 -23
  91. package/dist/core/engine/operations-time.js +13 -11
  92. package/dist/core/engine/ownership-bootstrap.d.ts +1 -1
  93. package/dist/core/engine/pinned-schedule-revision-count.d.ts +26 -0
  94. package/dist/core/engine/pinned-schedule-revision-count.js +19 -0
  95. package/dist/core/engine/pinned-schedule-revision.d.ts +76 -0
  96. package/dist/core/engine/pinned-schedule-revision.js +53 -0
  97. package/dist/core/engine/registration.d.ts +30 -0
  98. package/dist/core/engine/registration.js +17 -2
  99. package/dist/core/engine/retention.js +9 -2
  100. package/dist/core/engine/revision-errors.d.ts +71 -0
  101. package/dist/core/engine/revision-errors.js +13 -0
  102. package/dist/core/engine/schedule-revision-fire.d.ts +17 -0
  103. package/dist/core/engine/schedule-revision-fire.js +3 -0
  104. package/dist/core/engine/schedule-run.js +2 -1
  105. package/dist/core/engine/schedules.d.ts +2 -1
  106. package/dist/core/engine/schedules.js +28 -9
  107. package/dist/core/engine/source-diagnostics.d.ts +76 -0
  108. package/dist/core/engine/source-diagnostics.js +105 -0
  109. package/dist/core/engine/source-registration.js +4 -2
  110. package/dist/core/engine/source-resolution.d.ts +25 -9
  111. package/dist/core/engine/source-resolution.js +49 -14
  112. package/dist/core/engine/source-runtime-state.d.ts +92 -0
  113. package/dist/core/engine/source-runtime-state.js +36 -0
  114. package/dist/core/engine/storage-io.d.ts +19 -2
  115. package/dist/core/engine/storage-io.js +21 -2
  116. package/dist/core/engine/termination/finalizer-registration.d.ts +18 -0
  117. package/dist/core/engine/termination/finalizer-registration.js +21 -0
  118. package/dist/core/engine/termination/finalizer.d.ts +16 -19
  119. package/dist/core/engine/termination/finalizer.js +3 -3
  120. package/dist/core/engine/time-operation-callbacks.d.ts +32 -0
  121. package/dist/core/engine/time-operation-callbacks.js +0 -0
  122. package/dist/core/engine/validation/schedule-cadence.d.ts +16 -0
  123. package/dist/core/engine/validation/schedule-cadence.js +20 -0
  124. package/dist/core/engine/validation/schedule-options.d.ts +4 -1
  125. package/dist/core/engine/validation/schedule-options.js +14 -0
  126. package/dist/core/engine/validation/schedule-revision.d.ts +24 -0
  127. package/dist/core/engine/validation/schedule-revision.js +21 -0
  128. package/dist/core/engine/validation/schedule-warnings.d.ts +11 -0
  129. package/dist/core/engine/validation/schedule-warnings.js +5 -0
  130. package/dist/core/engine/validation/schedule.d.ts +4 -3
  131. package/dist/core/engine/validation/schedule.js +15 -26
  132. package/dist/core/engine/validation.js +3 -0
  133. package/dist/core/engine/workflow-claim-reclaim-target.d.ts +1 -1
  134. package/dist/core/engine/workflow-claim-reclaim-target.js +16 -2
  135. package/dist/core/engine/workflow-retention-deadline.d.ts +16 -0
  136. package/dist/core/engine/workflow-retention-deadline.js +33 -0
  137. package/dist/core/events/event-map.d.ts +5 -0
  138. package/dist/core/events/index.d.ts +1 -0
  139. package/dist/core/events/index.js +1 -0
  140. package/dist/core/events/workflow-source-events.d.ts +102 -0
  141. package/dist/core/events/workflow-source-events.js +57 -0
  142. package/dist/core/execution-strategy.d.ts +7 -0
  143. package/dist/core/inline-execution-strategy.context-options.d.ts +1 -1
  144. package/dist/core/inline-execution-strategy.js +1 -1
  145. package/dist/core/types/checkpoint.d.ts +14 -0
  146. package/dist/core/types/schedules.d.ts +52 -1
  147. package/dist/core/types/state.d.ts +16 -0
  148. package/dist/core/weft-error.d.ts +1 -1
  149. package/dist/core/weft-error.js +4 -1
  150. package/dist/core/worker-execution-strategy.d.ts +1 -0
  151. package/dist/core/worker-execution-strategy.js +13 -1
  152. package/dist/core/worker-fault-handling.d.ts +10 -0
  153. package/dist/core/worker-fault-handling.js +1 -0
  154. package/dist/core/worker-inbound-message.d.ts +1 -0
  155. package/dist/core/worker-inbound-message.js +1 -0
  156. package/dist/core/worker-protocol-guard.js +4 -0
  157. package/dist/core/worker-protocol.d.ts +7 -0
  158. package/dist/core/worker-protocol.js +2 -1
  159. package/dist/core/worker-turn-watchdog.d.ts +8 -1
  160. package/dist/core/worker-turn-watchdog.js +3 -2
  161. package/dist/diagnostics/format.js +5 -0
  162. package/dist/diagnostics/types.d.ts +24 -0
  163. package/dist/diagnostics/version-check.js +8 -2
  164. package/dist/http.js +1 -1
  165. package/dist/index.d.ts +3 -3
  166. package/dist/index.js +7 -0
  167. package/dist/indexeddb.js +1 -1
  168. package/dist/json-schema.js +3 -3
  169. package/dist/server/fault-to-json-rpc.js +2 -1
  170. package/dist/server/index.d.ts +11 -0
  171. package/dist/server/operation-fault.d.ts +2 -0
  172. package/dist/server/operation-fault.js +2 -1
  173. package/dist/server/operations/create-schedule.d.ts +2 -0
  174. package/dist/server/operations/create-schedule.js +10 -6
  175. package/dist/server/operations/get-catalog-diagnostics.d.ts +28 -0
  176. package/dist/server/operations/get-catalog-diagnostics.js +10 -2
  177. package/dist/server/operations/get-task-detail-schema.d.ts +8 -0
  178. package/dist/server/operations/get-task-detail-schema.js +1 -0
  179. package/dist/server/operations/get-task-detail.d.ts +8 -0
  180. package/dist/server/operations/get-task-detail.js +1 -0
  181. package/dist/server/operations/get-task-diagnostics.d.ts +4 -4
  182. package/dist/server/operations/preload-workflow-revision.d.ts +28 -0
  183. package/dist/server/operations/preload-workflow-revision.js +60 -0
  184. package/dist/server/operations/schedule-faults.d.ts +1 -0
  185. package/dist/server/operations/schedule-faults.js +32 -4
  186. package/dist/server/operations/schedule-rest-body.d.ts +1 -0
  187. package/dist/server/operations/schedule-rest-body.js +2 -1
  188. package/dist/server/operations/static-registrations.js +6 -0
  189. package/dist/server/operations/update-schedule.d.ts +2 -0
  190. package/dist/server/operations/update-schedule.js +4 -2
  191. package/dist/server/operations/workflow-catalog-operation-helpers.js +27 -0
  192. package/dist/server/runtime/task-dispatch-envelope.d.ts +14 -0
  193. package/dist/server/runtime/task-dispatch-envelope.js +32 -0
  194. package/dist/server/runtime/task-dispatch-revision.d.ts +29 -0
  195. package/dist/server/runtime/task-dispatch-revision.js +13 -0
  196. package/dist/server/runtime/task-dispatch.js +12 -37
  197. package/dist/server/runtime/task-ledger-recovery.js +1 -1
  198. package/dist/server/runtime/task-ledger-runtime.d.ts +14 -2
  199. package/dist/server/runtime/task-ledger-runtime.js +5 -3
  200. package/dist/server/runtime/task-polling.js +9 -3
  201. package/dist/server/runtime/task-reconciliation.js +2 -1
  202. package/dist/server/runtime/websocket-worker.js +9 -0
  203. package/dist/server/task-ledger-codec.d.ts +9 -0
  204. package/dist/server/task-ledger-codec.js +4 -1
  205. package/dist/server/task-ledger-transition-helpers.js +1 -0
  206. package/dist/server/task-ledger-types.d.ts +9 -0
  207. package/dist/server/task-ledger.d.ts +1 -1
  208. package/dist/server/task-ledger.js +2 -1
  209. package/dist/server/task-queue-types.d.ts +2 -0
  210. package/dist/storage/bun-sql.js +3 -1
  211. package/dist/storage/catalog-keys.d.ts +19 -0
  212. package/dist/storage/catalog-keys.js +3 -1
  213. package/dist/storage/compressed-storage.js +1 -1
  214. package/dist/storage/index.d.ts +3 -1
  215. package/dist/storage/interface.d.ts +2 -0
  216. package/dist/storage/interface.js +1 -1
  217. package/dist/storage/key-prefixes.d.ts +1 -1
  218. package/dist/storage/key-prefixes.js +1 -0
  219. package/dist/storage/lmdb.d.ts +30 -2
  220. package/dist/storage/lmdb.js +1 -1
  221. package/dist/storage/memory.js +1 -1
  222. package/dist/storage/neon.js +2 -2
  223. package/dist/storage/node-sqlite.js +3 -1
  224. package/dist/storage/postgres.js +2 -2
  225. package/dist/storage/resolve.js +1 -1
  226. package/dist/storage/scoped-storage.js +1 -1
  227. package/dist/storage/testing.js +1 -1
  228. package/dist/storage/turso.js +1 -1
  229. package/dist/version.d.ts +1 -1
  230. package/dist/version.js +1 -1
  231. package/dist/web-extension.js +1 -1
  232. package/dist/worker/protocol-messages.d.ts +17 -0
  233. package/dist/worker/protocol-schemas.d.ts +32 -0
  234. package/dist/worker/protocol-schemas.js +7 -3
  235. package/dist/worker/protocol-task-result.js +35 -3
  236. package/dist/worker/protocol.js +1 -1
  237. package/dist/worker/registry/types.d.ts +2 -0
  238. package/dist/worker/registry.d.ts +1 -1
  239. package/dist/worker/registry.js +3 -1
  240. package/dist/workers/workflow-runner.d.ts +9 -0
  241. package/dist/workers/workflow-runner.js +9 -2
  242. package/dist/workers/workflow-worker-entry.js +9 -4
  243. package/package.json +2 -2
@@ -0,0 +1,39 @@
1
+ import { KEYS } from "../../storage/interface.js";
2
+ import { tryDecodeStorageKeyComponent } from "../../storage/key-encoding.js";
3
+ import {
4
+ decodeCatalogEntryRecord,
5
+ finalizeCatalogTombstone,
6
+ restoreCatalogEntryFromTombstone
7
+ } from "../catalog/index.js";
8
+ import { countNonTerminalRunsForRevision } from "./nonterminal-revision-count.js";
9
+ function splitCatalogTombstoneKey(key) {
10
+ const parts = key.split(":");
11
+ if (parts.length !== 3)
12
+ return null;
13
+ const name = tryDecodeStorageKeyComponent(parts[1] ?? ""), revision = tryDecodeStorageKeyComponent(parts[2] ?? "");
14
+ if (name === null || revision === null)
15
+ return null;
16
+ return { name, revision };
17
+ }
18
+ async function resolveCatalogTombstone(storage, name, revision, tombstoneBytes) {
19
+ if (await countNonTerminalRunsForRevision(storage, name, revision) > 0)
20
+ await restoreCatalogEntryFromTombstone(storage, name, revision, tombstoneBytes);
21
+ else
22
+ await finalizeCatalogTombstone(storage, name, revision, tombstoneBytes);
23
+ }
24
+ export async function resolveOrphanedCatalogTombstones(storage) {
25
+ for await (const [key, bytes] of storage.scan(KEYS.catalogTombstonePrefix())) {
26
+ const split = splitCatalogTombstoneKey(key);
27
+ if (split === null)
28
+ throw Error(`The store's catalog tombstone key ("${key}") does not match the expected catalog-tombstone:<name>:<revision> shape. Treating it as absent would risk silently abandoning an in-flight catalog removal, so this fails closed instead. Resolve by operator repair: inspect the stored bytes and, only if certain no removal is actually in flight, delete the key.`);
29
+ await decodeCatalogEntryRecord(key, bytes, split.name, split.revision);
30
+ await resolveCatalogTombstone(storage, split.name, split.revision, bytes);
31
+ }
32
+ }
33
+ export async function resolveCatalogTombstoneIfPresent(storage, name, revision) {
34
+ const tombstoneKey = KEYS.catalogTombstone(name, revision), tombstoneBytes = await storage.get(tombstoneKey);
35
+ if (tombstoneBytes === null)
36
+ return;
37
+ await decodeCatalogEntryRecord(tombstoneKey, tombstoneBytes, name, revision);
38
+ await resolveCatalogTombstone(storage, name, revision, tombstoneBytes);
39
+ }
@@ -1,9 +1,13 @@
1
1
  import { ConstraintViolatedEvent } from "../events.js";
2
+ import { getResolvedDynamicRegistration } from "./dynamic-source-execution.js";
3
+ function resolveRunningInstanceRevision(internals, workflowId) {
4
+ return internals.workflowTypeByWorkflowId.get(workflowId)?.revision;
5
+ }
2
6
  export async function evaluateConstraints(internals, workflowId, callbacks) {
3
7
  const context = internals.inlineStrategy?.getContext(workflowId);
4
8
  if (!context)
5
9
  return !1;
6
- const constraints = internals.registrations.get(context.workflowType)?.constraints;
10
+ const revision = resolveRunningInstanceRevision(internals, workflowId), constraints = getResolvedDynamicRegistration(internals, context.workflowType, revision)?.constraints;
7
11
  if (!constraints || constraints.length === 0)
8
12
  return !1;
9
13
  const stateSnapshot = {
@@ -50,7 +50,7 @@ export declare function createExecutionStrategyBundle(parameters: {
50
50
  maxNestingDepth: number;
51
51
  development: boolean;
52
52
  broadcastEvents: boolean;
53
- getRegistration: (workflowType: string) => RegistrationEntry | undefined;
53
+ getRegistration: (workflowType: string, workflowId: string) => RegistrationEntry | undefined;
54
54
  /** Live accessor for registered workflow type names (WFT-28); registrations are still empty here. */
55
55
  listRegisteredWorkflowTypes: () => Iterable<string>;
56
56
  getComposedWorkflowInterceptor?: () => ComposedWorkflowInterceptor | null;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Decoded-`revision` sanitization for {@link import('./validation.ts').decodeWorkflowState}.
3
+ * Split out of `validation.ts`, which has no headroom under the repository's
4
+ * 500-line implementation-file ceiling.
5
+ *
6
+ * @module core/engine/decode-revision
7
+ */
8
+ import type { WorkflowState } from '../types.ts';
9
+ /**
10
+ * Normalize a decoded `revision` that is present but not a non-empty,
11
+ * bounded string.
12
+ *
13
+ * Deliberately NEVER drops a present-but-malformed value to `undefined` —
14
+ * `revision` is engine-derived (never user-supplied at the API surface), so
15
+ * an invalid value here indicates storage corruption or tampering, and
16
+ * `undefined` is the SAME signal a genuinely missing `revision` carries
17
+ * (a legitimate pre-revision-pinning record). Recovery treats an
18
+ * `undefined` pin as unambiguous for an eager type or a dynamic source with
19
+ * at most one registered candidate — silently executing that sole
20
+ * candidate against a checkpoint whose true originating revision is
21
+ * actually unknown, rather than rejecting the damaged identity. Instead,
22
+ * a malformed value is replaced with a deterministic, distinctly-prefixed
23
+ * marker derived from the workflow id: still a valid, bounded `string`, but
24
+ * virtually guaranteed not to match any real registered candidate, so the
25
+ * ordinary `resolveExecutableRegistrationForRevision()` "not-registered"
26
+ * path rejects it explicitly (an `unavailable` recovery outcome, isolated
27
+ * to that one run) instead of silently downgrading it to "legacy."
28
+ *
29
+ * Only a genuinely absent `revision` (never persisted at all) returns
30
+ * early, unchanged, above.
31
+ */
32
+ export declare function sanitizeDecodedRevision(state: WorkflowState): void;
@@ -0,0 +1,9 @@
1
+ const MAX_WORKFLOW_REVISION_LENGTH = 512, CORRUPTED_WORKFLOW_REVISION_PREFIX = "weft:corrupted-revision:";
2
+ export function sanitizeDecodedRevision(state) {
3
+ if (state.revision === void 0)
4
+ return;
5
+ if (typeof state.revision === "string" && state.revision.length > 0 && state.revision.length <= MAX_WORKFLOW_REVISION_LENGTH)
6
+ return;
7
+ console.warn(`[weft] Decoded workflow state for "${state.id}" has a malformed revision field; replacing it with a corruption marker so recovery rejects it explicitly as an unavailable pin, rather than silently treating this run as pre-revision-pinning. This usually indicates corruption or tampering of the storage record.`);
8
+ state.revision = `${CORRUPTED_WORKFLOW_REVISION_PREFIX}${state.id}`.slice(0, MAX_WORKFLOW_REVISION_LENGTH);
9
+ }
@@ -4,6 +4,10 @@ import type { EngineInternals } from './internals.ts';
4
4
  * `Engine[Symbol.dispose]` — the operation order is correctness-sensitive
5
5
  * (abort before clearing waiters, dispose strategies before nulling them) and
6
6
  * is preserved exactly. `Engine[Symbol.dispose]` and `[Symbol.asyncDispose]`
7
- * both delegate here.
7
+ * both delegate here. `dispatchEvent` defaults to a no-op so every existing
8
+ * direct-internals test that calls this with one argument keeps compiling
9
+ * and running — only `Engine`'s own two call sites pass the real
10
+ * `engine.dispatchEvent` binding, which is what lets
11
+ * `disposeSourceResolutionState` fire `workflow-source:load-cancelled`.
8
12
  */
9
- export declare function disposeEngine(internals: EngineInternals): void;
13
+ export declare function disposeEngine(internals: EngineInternals, dispatchEvent?: (event: Event) => void): void;
@@ -1,3 +1,4 @@
1
+ import { WorkflowSourceLoadCancelledEvent } from "../events/workflow-source-events.js";
1
2
  import { disposeEngineCleanupInterval } from "./engine-runtime-helpers.js";
2
3
  import { EngineDisposedError } from "./errors.js";
3
4
  import { disposeQueuedInlineWorkflowStarts } from "./inline-launch-queue.js";
@@ -13,15 +14,29 @@ function clearPendingResultPollTimers(internals) {
13
14
  clearTimeout(timer);
14
15
  internals.pendingResultPollTimers.clear();
15
16
  }
16
- function disposeSourceResolutionState(internals) {
17
- for (const controller of internals.sourceResolutionWaiterControllers)
17
+ function rejectPendingResultResolvers(internals) {
18
+ for (const waiter of internals.resultResolvers.values())
19
+ waiter.reject(new EngineDisposedError);
20
+ }
21
+ function disposeSourceResolutionState(internals, dispatchEvent) {
22
+ for (const controller of internals.sources.waiterControllers)
18
23
  controller.abort(new EngineDisposedError);
19
- internals.sourceResolutionWaiterControllers.clear();
20
- internals.sourceResolutionsInFlight.clear();
21
- internals.workflowSourcesByName.clear();
22
- internals.resolvedWorkflowSources.clear();
24
+ const stillLoading = [];
25
+ for (const [name, byRevision] of internals.sources.diagnostics)
26
+ for (const [revision, entry] of byRevision)
27
+ if (entry.state === "loading")
28
+ stillLoading.push({ name, revision, kind: entry.kind });
29
+ internals.sources.waiterControllers.clear();
30
+ internals.sources.resolutionsInFlight.clear();
31
+ internals.sources.byName.clear();
32
+ internals.sources.resolved.clear();
33
+ internals.sources.waitersByKey.clear();
34
+ internals.sources.lastResolvedRevisionByName.clear();
35
+ internals.sources.diagnostics.clear();
36
+ for (const { name, revision, kind } of stillLoading)
37
+ dispatchEvent(new WorkflowSourceLoadCancelledEvent(name, revision, kind));
23
38
  }
24
- export function disposeEngine(internals) {
39
+ export function disposeEngine(internals, dispatchEvent = () => {}) {
25
40
  internals.disposed = !0;
26
41
  internals.alertManager?.[Symbol.dispose]();
27
42
  internals.alertManager = null;
@@ -48,8 +63,7 @@ export function disposeEngine(internals) {
48
63
  disposeSecondInstanceDetection(internals);
49
64
  disposeLeaseManager(internals);
50
65
  internals.handleCache.clear();
51
- for (const waiter of internals.resultResolvers.values())
52
- waiter.reject(new EngineDisposedError);
66
+ rejectPendingResultResolvers(internals);
53
67
  rejectAllSleepTimerAcknowledgements(internals, new EngineDisposedError);
54
68
  internals.resultResolvers.clear();
55
69
  internals.updateWaiters.clear();
@@ -65,7 +79,7 @@ export function disposeEngine(internals) {
65
79
  for (const controller of internals.pendingWebhooks)
66
80
  controller.abort();
67
81
  internals.pendingWebhooks.clear();
68
- disposeSourceResolutionState(internals);
82
+ disposeSourceResolutionState(internals, dispatchEvent);
69
83
  clearPendingResultPollTimers(internals);
70
84
  internals.sleepResolvers.clear();
71
85
  internals.sleepResolversByWorkflow.clear();
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Errors raised by the dynamic workflow source pipeline (WFT-15/16):
3
+ * {@link DynamicWorkflowSourceUnavailableError} at an execution entry point
4
+ * — `start`, `startOrSignal`, `schedule`, `fork`, `resume`, recovery, and
5
+ * bulk-retry — when a registered source's revision is ambiguous or its
6
+ * load fails; {@link WorkflowSourceNotRegisteredError} by
7
+ * `engine.resolveWorkflowSource()` / `engine.workflows.preload()` directly.
8
+ * Split out of `errors.ts`, which is at the repository's 500-line
9
+ * implementation-file ceiling with no headroom for two more documented
10
+ * classes; kept alongside `lease-errors.ts` and `source/errors.ts` as this
11
+ * file family's precedent for a domain-scoped errors module.
12
+ *
13
+ * @module core/engine/dynamic-source-errors
14
+ */
15
+ import { WeftError } from '../weft-error.ts';
16
+ /**
17
+ * Thrown by `engine.resolveWorkflowSource()` (and its
18
+ * `engine.workflows.preload()` alias) when called against a specific
19
+ * `(name, revision)` that `registerSource()` never recorded — a
20
+ * programmer-error contract, not untrusted-input rejection. `revision` is
21
+ * therefore always defined in practice.
22
+ *
23
+ * This is a DIFFERENT case from a workflow `type` with no eager
24
+ * registration AND no dynamic source at all: every execution entry point
25
+ * (`start`, `startOrSignal`, `schedule`, `fork`, `resume`, recovery,
26
+ * bulk-retry) keeps throwing the pre-existing
27
+ * {@link WorkflowNotRegisteredError} for that case instead, preserving the
28
+ * fault classification and wire code callers already depended on before
29
+ * WFT-15/16 — see `dynamic-source-execution.ts`'s
30
+ * `resolveExecutableRegistration()`.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { Engine, WorkflowSourceNotRegisteredError } from '@lostgradient/weft';
35
+ *
36
+ * const engine = new Engine();
37
+ * try {
38
+ * await engine.resolveWorkflowSource('checkout', 'r-never-registered');
39
+ * } catch (err) {
40
+ * if (err instanceof WorkflowSourceNotRegisteredError) {
41
+ * console.error('registerSource() it first:', err.workflowType, err.revision);
42
+ * }
43
+ * }
44
+ * ```
45
+ */
46
+ export declare class WorkflowSourceNotRegisteredError extends WeftError<'WorkflowSourceNotRegisteredError'> {
47
+ readonly workflowType: string;
48
+ readonly revision: string | undefined;
49
+ constructor(workflowType: string, revision?: string);
50
+ }
51
+ /**
52
+ * Thrown when a registered dynamic workflow source cannot be turned into an
53
+ * executable registration: either its load failed (`reason: 'load-failed'`,
54
+ * `cause` carries the underlying error) or which revision to resolve could
55
+ * not be determined (`reason: 'ambiguous-revision'` — two or more revisions
56
+ * are registered for `workflowType` and the catalog has no active pointer
57
+ * naming one of them). Recovery classifies the affected type `unavailable`:
58
+ * only its runs fail, with this error as the `system` failure cause;
59
+ * sibling types continue.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * import { Engine, DynamicWorkflowSourceUnavailableError } from '@lostgradient/weft';
64
+ *
65
+ * const engine = new Engine();
66
+ * try {
67
+ * await engine.start('checkout', { orderId: 'order-1' });
68
+ * } catch (err) {
69
+ * if (err instanceof DynamicWorkflowSourceUnavailableError) {
70
+ * console.error(err.workflowType, err.reason);
71
+ * }
72
+ * }
73
+ * ```
74
+ */
75
+ export declare class DynamicWorkflowSourceUnavailableError extends WeftError<'DynamicWorkflowSourceUnavailableError'> {
76
+ readonly workflowType: string;
77
+ readonly revision: string | undefined;
78
+ readonly reason: 'load-failed' | 'ambiguous-revision';
79
+ readonly cause: unknown;
80
+ constructor(workflowType: string, revision: string | undefined, reason: 'load-failed' | 'ambiguous-revision', cause?: unknown);
81
+ }
@@ -0,0 +1,25 @@
1
+ import { WeftError } from "../weft-error.js";
2
+
3
+ export class WorkflowSourceNotRegisteredError extends WeftError {
4
+ workflowType;
5
+ revision;
6
+ constructor(workflowType, revision) {
7
+ super("WorkflowSourceNotRegisteredError", revision === void 0 ? `No workflow registered with name "${workflowType}"` : `resolveWorkflowSource("${workflowType}", "${revision}") was called before registerSource() ` + "registered this exact (name, revision) \u2014 call engine.registerSource() first.");
8
+ this.workflowType = workflowType;
9
+ this.revision = revision;
10
+ }
11
+ }
12
+
13
+ export class DynamicWorkflowSourceUnavailableError extends WeftError {
14
+ workflowType;
15
+ revision;
16
+ reason;
17
+ cause;
18
+ constructor(workflowType, revision, reason, cause) {
19
+ super("DynamicWorkflowSourceUnavailableError", reason === "ambiguous-revision" ? `Cannot resolve dynamic workflow source "${workflowType}": multiple revisions are registered and none is the catalog active revision. Activate one via engine.workflows.activate(), or register only one revision at a time.` : `Dynamic workflow source "${workflowType}"${revision === void 0 ? "" : ` revision "${revision}"`} failed to load: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
20
+ this.workflowType = workflowType;
21
+ this.revision = revision;
22
+ this.reason = reason;
23
+ this.cause = cause;
24
+ }
25
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * `resolveExecutableRegistration()` — the single async helper every
3
+ * execution entry point that can launch or resume a workflow
4
+ * (`start`/`startOrSignal`/`schedule`/`fork`/`resume`/recovery/bulk-retry)
5
+ * funnels through to turn a workflow `type` into a `RegistrationEntry` it
6
+ * can actually run (WFT-15/16).
7
+ *
8
+ * Fast path: an eagerly `engine.register()`-ed type resolves synchronously
9
+ * from `internals.registrations` and never touches `internals.sources` —
10
+ * this is what makes "starting the eager workflow does not import the lazy
11
+ * module" true. Slow path: a `engine.registerSource()`-registered type is
12
+ * resolved via `resolveWorkflowSourceForExecution()`, which awaits the
13
+ * loader (single-flight, shared with any other concurrent caller for the
14
+ * same key) and installs the result durably before this returns — so no
15
+ * workflow handler ever runs while its definition is still a catalog
16
+ * candidate.
17
+ *
18
+ * @module core/engine/dynamic-source-execution
19
+ */
20
+ import type { Engine } from './index.ts';
21
+ import type { EngineInternals } from './internals.ts';
22
+ type RegistrationEntry = EngineInternals['registrations'] extends Map<string, infer Entry> ? Entry : never;
23
+ /** The outcome of {@link resolveExecutableRegistration}. */
24
+ export type ExecutableRegistration = {
25
+ entry: RegistrationEntry;
26
+ /** The dynamic source revision resolved, or `undefined` for an eager registration. */
27
+ revision: string | undefined;
28
+ };
29
+ /**
30
+ * Turn workflow `type` into an executable {@link RegistrationEntry}, awaiting
31
+ * dynamic-source resolution when `type` is not eagerly registered. Throws
32
+ * {@link WorkflowNotRegisteredError} when `type` is neither an eager
33
+ * registration nor a registered source (the pre-existing error — see the
34
+ * call site below for why); {@link DynamicWorkflowSourceUnavailableError}
35
+ * when a registered source's target revision is ambiguous or its load
36
+ * fails; {@link EngineDisposedError} unwrapped when the engine is disposed
37
+ * during resolution.
38
+ */
39
+ export declare function resolveExecutableRegistration(engine: Engine, internals: EngineInternals, type: string, onRevisionChosen?: (revision: string) => void): Promise<ExecutableRegistration>;
40
+ /**
41
+ * The shared classification `resolveExecutableRegistrationForRevision()`'s
42
+ * throw-vs-proceed decision and the ADR-0002 workflow-lease reclaim-
43
+ * eligibility gate (`index.ts`'s `isWorkflowTypeRegistered`) both delegate
44
+ * to, so the two decisions can never drift out of sync — an engine that
45
+ * cannot locally resolve a run's exact pin must never win that run's claim
46
+ * either (see `workflow-claim-reclaim-target.ts`'s own doc for why that
47
+ * would strand the run under a permanently-failing `onReclaimed` drive).
48
+ *
49
+ * - An eager registration is always resolvable, regardless of `revision`
50
+ * (eager has no ambiguity to pin against).
51
+ * - `type` with no eager registration and no registered source at all is
52
+ * never resolvable.
53
+ * - A dynamic source with `revision === undefined` (legacy/pre-pinning) is
54
+ * resolvable only when it has exactly one registered candidate —
55
+ * unambiguous by construction. Two or more is ambiguous; zero is
56
+ * impossible (an empty `byRevision` map does not exist — the type
57
+ * wouldn't be in `sources.byName` at all).
58
+ * - A dynamic source with a defined `revision` is resolvable exactly when
59
+ * that revision is among this process's currently registered candidates.
60
+ */
61
+ export declare function canResolveRevisionLocally(internals: EngineInternals, type: string, revision: string | undefined): boolean;
62
+ /**
63
+ * Resolve workflow `type` against its EXACT pinned `revision` — the
64
+ * `WorkflowState.revision` a run persisted at start (WFT-17) — instead of
65
+ * whichever revision the catalog currently considers active. This is the
66
+ * resolver `resumeWorkflowFromStorage()` (`lifecycle/resume.ts`) uses for
67
+ * EVERY resume (both `recoverAll()`'s batch and a standalone
68
+ * `engine.resume(id)`), so "recovery never falls back from a missing exact
69
+ * revision to the active revision" (WFT-17/WFT-18's acceptance criterion)
70
+ * holds uniformly, not just inside the batch preflight.
71
+ *
72
+ * Classification:
73
+ * - An eager registration is always resolved from `internals.registrations`
74
+ * regardless of `revision` — eager has no ambiguity to pin against (the
75
+ * process runs whatever code it has loaded), so a stale or absent pin on
76
+ * an eager type is harmless and must not block the common redeploy path.
77
+ * - A dynamic source with `revision === undefined` (a legacy, pre-pinning
78
+ * record) falls through to the ordinary {@link resolveExecutableRegistration}
79
+ * active-pointer resolution when the type has at most one registered
80
+ * candidate (unambiguous by construction), or throws
81
+ * {@link WorkflowRevisionUnavailableError} with `reason: 'legacy-ambiguous'`
82
+ * when it has two or more (there is no way to tell which one the run
83
+ * actually started against).
84
+ * - A dynamic source with a defined `revision` throws
85
+ * {@link WorkflowRevisionUnavailableError} with `reason: 'not-registered'`
86
+ * when that exact revision is not among this process's registered
87
+ * candidates (covers both "never registered at all" and "was the sole
88
+ * candidate, but a different one is registered now") — otherwise it is
89
+ * loaded and installed exactly like {@link resolveExecutableRegistration}'s
90
+ * own dynamic path.
91
+ * - `type` with neither an eager registration nor any registered source
92
+ * throws the pre-existing {@link WorkflowNotRegisteredError}, matching
93
+ * {@link resolveExecutableRegistration}.
94
+ */
95
+ export declare function resolveExecutableRegistrationForRevision(engine: Engine, internals: EngineInternals, type: string, revision: string | undefined): Promise<ExecutableRegistration>;
96
+ /**
97
+ * Wraps `callbacks.resolveExecutableRegistration(type)`, remapping ONLY the
98
+ * "no source registered at all" case (a {@link WorkflowNotRegisteredError})
99
+ * to `notFoundError` — a caller's own, more-specific "no workflow
100
+ * registered" message (naming the workflow ID it was
101
+ * resuming/forking/retrying, for example) instead of the generic one. Every
102
+ * other error (including a source found but unresolvable) propagates
103
+ * unwrapped. Shared by `resume.ts`, `transition.ts`'s `fork()`, and
104
+ * `bulk-operations.ts`'s retry path so each keeps a single, low-complexity
105
+ * call site instead of its own try/catch.
106
+ */
107
+ export declare function resolveExecutableRegistrationOrRenamedNotFound(resolve: (type: string) => Promise<ExecutableRegistration>, type: string, notFoundError: () => Error): Promise<ExecutableRegistration>;
108
+ /**
109
+ * {@link resolveExecutableRegistrationOrRenamedNotFound}, pre-bound to
110
+ * `bulk-operations.ts`'s retry-failed message shape. Resolves against the
111
+ * failed run's own EXACT pinned `revision` (`WorkflowState.revision`, WFT-17)
112
+ * via {@link resolveExecutableRegistrationForRevision} — never the catalog's
113
+ * active pointer — so a bulk retry's pre-reactivation concurrency-admission
114
+ * lookup agrees with the exact-revision resolve the subsequent
115
+ * `engine.resume()` performs. Resolving against the active pointer here
116
+ * instead would let a retry commit the failed->running reactivation batch
117
+ * against the WRONG registration's `concurrency` config, and then — if the
118
+ * pinned revision is unavailable while a different revision is active —
119
+ * only discover that after the reactivation already committed, leaving the
120
+ * run stranded `running` with no generator ever resuming. Kept here (with
121
+ * the bulk of the logic off `bulk-operations.ts`, already at its own
122
+ * oxlint `max-lines` ceiling) rather than inlined at that one call site.
123
+ */
124
+ export declare function resolveExecutableRegistrationForRetry(internals: EngineInternals, type: string, revision: string | undefined, workflowId: string): Promise<ExecutableRegistration>;
125
+ /**
126
+ * Sync-only fallback lookup for PER-INSTANCE call sites that run after a
127
+ * workflow is already executing (or is being swept from storage) and must
128
+ * never trigger a new resolve — finalizer, constraint, retention, and
129
+ * search-attribute-schema resolution (`termination/finalizer-registration.ts`,
130
+ * `constraints.ts`, `workflow-retention-deadline.ts`, `listing.ts`). Reads
131
+ * the eager registration first (an eager type resolves the same regardless
132
+ * of `revision` — see {@link canResolveRevisionLocally}'s doc). Otherwise:
133
+ *
134
+ * - A defined `revision` reads `internals.sources.resolved.get(type)?.get(revision)`
135
+ * directly — the running instance's OWN exact pin — with NO fallback to a
136
+ * different revision when that exact one is not locally resolved (returns
137
+ * `undefined` instead, matching {@link resolveExecutableRegistrationForRevision}'s
138
+ * "never silently substitute a different revision" contract).
139
+ * - `revision === undefined` (a legacy pre-pinning record) falls back to
140
+ * `internals.sources.lastResolvedRevisionByName` ONLY when
141
+ * {@link canResolveRevisionLocally} classifies that as unambiguous —
142
+ * exactly one registered candidate for `type`. With two or more
143
+ * registered candidates this is genuinely ambiguous for a SPECIFIC
144
+ * instance (WFT-19 review round 6, Codex, P1): silently substituting
145
+ * whichever sibling revision this process last resolved could validate
146
+ * `setAttributes()` against a sibling's schema, run a sibling's
147
+ * finalizer, or — worst, since purge is irreversible — purge a run early
148
+ * under a sibling's shorter retention window. Returns `undefined` in that
149
+ * case instead, forcing the caller's async fallback
150
+ * (`resolveExecutableRegistrationForRevision()`) to reach the
151
+ * `legacy-ambiguous` classification and fail closed the same way it
152
+ * already does for resume/recovery. {@link resolveLastKnownDynamicRegistration}
153
+ * is the deliberate exception for the one genuinely TYPE-level caller
154
+ * that wants the permissive last-resolved-wins answer even when
155
+ * ambiguous.
156
+ *
157
+ * Returns `undefined` when no source has a matching entry.
158
+ */
159
+ export declare function getResolvedDynamicRegistration(internals: EngineInternals, type: string, revision: string | undefined): RegistrationEntry | undefined;
160
+ /**
161
+ * TYPE-level, sync-only "whatever this process last resolved" lookup —
162
+ * used ONLY by `retention.ts`'s `resolveWorkflowTypeRetention()`, an
163
+ * across-all-registered-types OVERVIEW API (`getRetentionOverview()`) with
164
+ * no single running instance to pin against, unlike every caller of
165
+ * {@link getResolvedDynamicRegistration} above. Deliberately does NOT fail
166
+ * closed when 2+ candidates are registered for `type`: for an overview,
167
+ * showing the last-resolved candidate's policy is a more useful signal
168
+ * than showing nothing, and — unlike `getResolvedDynamicRegistration`'s
169
+ * per-instance callers — no irreversible action (purge, finalizer run,
170
+ * schema validation) hangs off this call (WFT-19 review round 6, Codex).
171
+ */
172
+ export declare function resolveLastKnownDynamicRegistration(internals: EngineInternals, type: string): RegistrationEntry | undefined;
173
+ export {};
@@ -0,0 +1,108 @@
1
+ import { ensureWorkflowCatalogReady, getWorkflowCatalog } from "./catalog-readiness.js";
2
+ import { DynamicWorkflowSourceUnavailableError } from "./dynamic-source-errors.js";
3
+ import { EngineDisposedError, WorkflowNotRegisteredError } from "./errors.js";
4
+ import { assertConstraintsSupported, buildRegistrationEntry } from "./registration.js";
5
+ import { WorkflowRevisionUnavailableError } from "./revision-errors.js";
6
+ import { resolveWorkflowSourceForExecution } from "./source-resolution.js";
7
+ async function resolveActiveSourceRevision(engine, type, byRevision) {
8
+ await ensureWorkflowCatalogReady(engine);
9
+ const activeRevision = (await getWorkflowCatalog(engine).resolveActiveDurable(type))?.revision;
10
+ if (activeRevision !== void 0 && byRevision.has(activeRevision))
11
+ return activeRevision;
12
+ throw new DynamicWorkflowSourceUnavailableError(type, void 0, "ambiguous-revision");
13
+ }
14
+ export async function resolveExecutableRegistration(engine, internals, type, onRevisionChosen) {
15
+ const eager = internals.registrations.get(type);
16
+ if (eager !== void 0)
17
+ return { entry: eager, revision: void 0 };
18
+ const byRevision = internals.sources.byName.get(type);
19
+ if (byRevision === void 0)
20
+ throw new WorkflowNotRegisteredError(type);
21
+ let revision;
22
+ if (byRevision.size === 1) {
23
+ revision = [...byRevision.keys()][0];
24
+ onRevisionChosen?.(revision);
25
+ } else {
26
+ revision = await resolveActiveSourceRevision(engine, type, byRevision);
27
+ onRevisionChosen?.(revision);
28
+ }
29
+ return loadAndInstallSourceRevision(engine, internals, type, revision);
30
+ }
31
+ async function loadAndInstallSourceRevision(engine, internals, type, revision) {
32
+ try {
33
+ await resolveWorkflowSourceForExecution(engine, type, revision);
34
+ } catch (error) {
35
+ if (error instanceof EngineDisposedError)
36
+ throw error;
37
+ throw new DynamicWorkflowSourceUnavailableError(type, revision, "load-failed", error);
38
+ }
39
+ const resolved = internals.sources.resolved.get(type)?.get(revision);
40
+ if (resolved === void 0)
41
+ throw new DynamicWorkflowSourceUnavailableError(type, revision, "load-failed", Error("Dynamic workflow source resolved without populating a local definition"));
42
+ assertConstraintsSupported(internals, type, resolved.definition);
43
+ const entry = buildRegistrationEntry(type, resolved.definition);
44
+ internals.workflowTypesByHandler.set(resolved.definition.handler, type);
45
+ internals.sources.lastResolvedRevisionByName.set(type, revision);
46
+ return { entry, revision };
47
+ }
48
+ export function canResolveRevisionLocally(internals, type, revision) {
49
+ if (internals.registrations.has(type))
50
+ return !0;
51
+ const byRevision = internals.sources.byName.get(type);
52
+ if (byRevision === void 0)
53
+ return !1;
54
+ if (revision === void 0)
55
+ return byRevision.size === 1;
56
+ return byRevision.has(revision);
57
+ }
58
+ export async function resolveExecutableRegistrationForRevision(engine, internals, type, revision) {
59
+ const eager = internals.registrations.get(type);
60
+ if (eager !== void 0)
61
+ return { entry: eager, revision: void 0 };
62
+ if (internals.sources.byName.get(type) === void 0)
63
+ throw new WorkflowNotRegisteredError(type);
64
+ if (!canResolveRevisionLocally(internals, type, revision)) {
65
+ if (revision === void 0)
66
+ throw new WorkflowRevisionUnavailableError(type, void 0, "legacy-ambiguous");
67
+ throw new WorkflowRevisionUnavailableError(type, revision, "not-registered");
68
+ }
69
+ if (revision === void 0)
70
+ return resolveExecutableRegistration(engine, internals, type);
71
+ return loadAndInstallSourceRevision(engine, internals, type, revision);
72
+ }
73
+ export async function resolveExecutableRegistrationOrRenamedNotFound(resolve, type, notFoundError) {
74
+ return resolve(type).catch((error) => {
75
+ if (error instanceof WorkflowNotRegisteredError)
76
+ throw notFoundError();
77
+ throw error;
78
+ });
79
+ }
80
+ export async function resolveExecutableRegistrationForRetry(internals, type, revision, workflowId) {
81
+ return resolveExecutableRegistrationOrRenamedNotFound((t) => resolveExecutableRegistrationForRevision(internals.engine, internals, t, revision), type, () => Error(`No workflow registered with name "${type}" (needed to retry "${workflowId}")`));
82
+ }
83
+ export function getResolvedDynamicRegistration(internals, type, revision) {
84
+ const eager = internals.registrations.get(type);
85
+ if (eager !== void 0)
86
+ return eager;
87
+ if (revision !== void 0) {
88
+ const resolved = internals.sources.resolved.get(type)?.get(revision);
89
+ if (resolved === void 0)
90
+ return;
91
+ return buildRegistrationEntry(type, resolved.definition);
92
+ }
93
+ if (!canResolveRevisionLocally(internals, type, void 0))
94
+ return;
95
+ return resolveLastKnownDynamicRegistration(internals, type);
96
+ }
97
+ export function resolveLastKnownDynamicRegistration(internals, type) {
98
+ const eager = internals.registrations.get(type);
99
+ if (eager !== void 0)
100
+ return eager;
101
+ const targetRevision = internals.sources.lastResolvedRevisionByName.get(type);
102
+ if (targetRevision === void 0)
103
+ return;
104
+ const resolved = internals.sources.resolved.get(type)?.get(targetRevision);
105
+ if (resolved === void 0)
106
+ return;
107
+ return buildRegistrationEntry(type, resolved.definition);
108
+ }
@@ -3,6 +3,20 @@ import type { ConstraintDefinition } from '../constraint.ts';
3
3
  import type { ExecutionStrategy } from '../execution-strategy.ts';
4
4
  import type { InlineExecutionStrategy } from '../inline-execution-strategy.ts';
5
5
  import type { AnyActivityDefinition, ArchiveAdapter, Checkpoint, DefinitionSchema, EngineOptions, NormalizedHistoryPolicy, NormalizedPayloadSizePolicy, NormalizedRetentionPolicy, QueryDefinition, SearchAttributeSchema, SignalDefinition, UpdateDefinition, WorkflowConcurrencyOptions, WorkflowFunction, WorkflowTimelineEntry } from '../types.ts';
6
+ /**
7
+ * The identity a running workflow instance was launched under, cached by
8
+ * `EngineInternals.workflowTypeByWorkflowId` for the lifetime of its
9
+ * execution (WFT-19). `revision` is the exact `WorkflowState.revision` this
10
+ * instance is pinned to (WFT-17) — `undefined` for an eager registration or
11
+ * a legacy (pre-pinning) record — so a process-local lookup keyed by
12
+ * `workflowId` can resolve THIS instance's own dynamic-source revision
13
+ * rather than falling back to whichever revision this process resolved
14
+ * last.
15
+ */
16
+ export type WorkflowExecutionIdentity = {
17
+ type: string;
18
+ revision: string | undefined;
19
+ };
6
20
  export interface RegistrationEntry {
7
21
  handler: WorkflowFunction;
8
22
  version: string;
@@ -103,6 +117,8 @@ export type QueuedInlineWorkflowExecutionStart = {
103
117
  nestingDepth: number;
104
118
  executionDeadline: number | undefined;
105
119
  executionStateOwnerId: string;
120
+ /** The exact revision this queued start is pinned to (WFT-17/WFT-19). */
121
+ revision: string | undefined;
106
122
  /**
107
123
  * Liveness callback invoked once this queued start has actually begun
108
124
  * executing (its generator has been driven). Set only for `defer: false`
@@ -33,6 +33,7 @@ import { type WorkflowCatalogActivationResult, type WorkflowCatalogActivePointer
33
33
  import type { WorkflowCompatibilityPolicy } from '../contract/compatibility.ts';
34
34
  import type { WorkflowRevisionManifest } from '../contract/types.ts';
35
35
  import type { Engine } from './index.ts';
36
+ import { type ResolveWorkflowSourceOptions } from './source-resolution.ts';
36
37
  /**
37
38
  * Options accepted by {@link EngineWorkflowsNamespace.activate}.
38
39
  *
@@ -125,6 +126,15 @@ export interface EngineWorkflowsNamespace {
125
126
  getRevision(name: string, revision: string): Promise<WorkflowRevisionRecord | null>;
126
127
  /** Every installed revision of `name`, sorted deterministically by revision. */
127
128
  listRevisions(name: string): Promise<readonly WorkflowRevisionRecord[]>;
129
+ /**
130
+ * Load, validate, and install one dynamic workflow source revision
131
+ * previously recorded via `engine.registerSource()` (WFT-15/16). A thin,
132
+ * documented alias for `engine.resolveWorkflowSource()` — identical
133
+ * single-flight, cancellation, and error contract — offered here so
134
+ * deployment tooling that already reaches for `engine.workflows.*` for
135
+ * every other catalog operation does not need a second entry point.
136
+ */
137
+ preload(name: string, revision: string, options?: ResolveWorkflowSourceOptions): Promise<WorkflowRevisionRecord>;
128
138
  }
129
139
  /** Build the `engine.workflows` namespace object for `engine`. */
130
140
  export declare function createEngineWorkflowsNamespace(engine: Engine): EngineWorkflowsNamespace;
@@ -8,6 +8,7 @@ import {
8
8
  isWorkflowCatalogReady
9
9
  } from "./catalog-readiness.js";
10
10
  import { WorkflowNotRegisteredError } from "./errors.js";
11
+ import { resolveWorkflowSource } from "./source-resolution.js";
11
12
  async function ensureCatalogReady(engine) {
12
13
  if (!isWorkflowCatalogReady(engine))
13
14
  await ensureWorkflowCatalogReady(engine);
@@ -44,6 +45,7 @@ export function createEngineWorkflowsNamespace(engine) {
44
45
  activate: (name, revision, options) => activateWorkflowRevision(engine, name, revision, options),
45
46
  getActive: (name) => getActiveWorkflowRevision(engine, name),
46
47
  getRevision: (name, revision) => getInstalledWorkflowRevision(engine, name, revision),
47
- listRevisions: (name) => listInstalledWorkflowRevisions(engine, name)
48
+ listRevisions: (name) => listInstalledWorkflowRevisions(engine, name),
49
+ preload: (name, revision, options) => resolveWorkflowSource(engine, name, revision, options)
48
50
  };
49
51
  }