@dudousxd/nestjs-durable-core 0.63.0 → 0.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -168,6 +168,34 @@ declare class InMemoryAdmissionBackend implements AdmissionBackend {
168
168
  * wire contract — the adonis + python SDKs build the byte-identical shape (design §7.8), and the
169
169
  * golden fixtures in `<repo>/fixtures/wire/` pin the exact bytes all three SDKs must agree on.
170
170
  */
171
+ /**
172
+ * One workflow a worker ANNOUNCES it can execute — the richer form of a `workflows` name.
173
+ *
174
+ * The rule is "announce only what you can run": a process publishes a registration for a workflow
175
+ * whose body it holds and whose queue it consumes. It never publishes one for a workflow it merely
176
+ * knows how to *route to* (an engine's `registerRemote`), because that claim says nothing about a
177
+ * live executor existing — which is precisely the ambiguity an aggregate registry has to remove.
178
+ *
179
+ * Every field but `name` is optional because the SDKs hold different amounts of this. A worker that
180
+ * knows only names (the Python `@worker.workflow("x")` form, or any pre-registrations SDK) publishes
181
+ * bare `workflows` names instead and is still a first-class announcer — see {@link WorkerDescriptor.registrations}.
182
+ * A reader must therefore treat an absent field as "not stated", never as a value: the aggregate in
183
+ * `./announced` never invents a version, a group or an origin that nobody announced.
184
+ */
185
+ interface WorkflowRegistration {
186
+ /** Registered workflow name — the `workflow` a {@link WorkflowTask} carries. */
187
+ name: string;
188
+ /** Registered version, when the announcer knows one. Absent = unversioned announcement. */
189
+ version?: string;
190
+ /** The routing token this workflow's turns are dispatched to (the queue this worker consumes for
191
+ * it). Absent = the announcer did not say; a reader must NOT assume the by-convention token. */
192
+ group?: string;
193
+ /** Capabilities a worker must advertise to run it (design §7.5), as the announcer declares them. */
194
+ requires?: string[];
195
+ /** The package that DECLARED the workflow (see `WorkflowRun.origin`). Absent = unknown; nothing
196
+ * ever substitutes a default, because a wrong origin is worse than no origin. */
197
+ origin?: string;
198
+ }
171
199
  /**
172
200
  * A worker's (or control-plane's) advertised identity, wire-protocol support, feature capabilities
173
201
  * and registered handlers. Published on startup + on change (design §7.2) and consumed by
@@ -200,6 +228,19 @@ interface WorkerDescriptor {
200
228
  workflows: string[];
201
229
  /** Registered step handler names → routing targets. Order-insensitive (a set). */
202
230
  steps: string[];
231
+ /**
232
+ * The richer, per-workflow ANNOUNCEMENT of what this instance can execute (name + version + group
233
+ * + requires + origin). OPTIONAL and purely additive: an SDK that has only names keeps publishing
234
+ * {@link workflows} and is aggregated all the same, just unversioned. An SDK that publishes this
235
+ * SHOULD also list the same names in {@link workflows}, so a reader that predates this field still
236
+ * sees the workflow at all.
237
+ *
238
+ * It rides the descriptor rather than a registry of its own so it inherits the whole liveness
239
+ * story for free: the descriptor key is written with the heartbeat's TTL and refreshed by the same
240
+ * beat, so an announcement from a worker that has since died EXPIRES instead of offering a choice
241
+ * that fails at run time.
242
+ */
243
+ registrations?: WorkflowRegistration[];
203
244
  /** Optional routing partition (queue/group sharding). */
204
245
  partition?: string;
205
246
  /** Optional tenant namespace this instance serves. */
@@ -252,7 +293,13 @@ declare function isLegacyDescriptor(raw: RawWorkerDescriptor | WorkerDescriptor)
252
293
  * - absent `workflows`/`steps` → `[]`, absent `sdk` → an `unknown` marker, absent `startedAt` → `0`.
253
294
  *
254
295
  * The undefined-vs-`[]` distinction is deliberate: an absent field means "legacy, doesn't advertise
255
- * this axis"; an explicit empty array means "modern SDK that genuinely advertises nothing".
296
+ * this axis"; an explicit empty array means "modern SDK that genuinely advertises nothing". The same
297
+ * reasoning keeps `registrations` ABSENT when it was absent (rather than defaulting to `[]`): "this
298
+ * SDK does not announce registrations" and "this SDK announces none" are different facts, and only
299
+ * the second one licenses a reader to conclude the worker serves nothing. (No SDK publishes `[]`
300
+ * today — {@link describeWorker} and the Python builder both OMIT the field when there is nothing to
301
+ * announce, so a worker with no workflows keeps its pre-announcement bytes and ETag — but a reader
302
+ * must not collapse the two cases on that basis.)
256
303
  */
257
304
  declare function normalizeDescriptor(raw: RawWorkerDescriptor | WorkerDescriptor): WorkerDescriptor;
258
305
  /**
@@ -292,6 +339,35 @@ declare function heartbeatStatus(descriptor: RawWorkerDescriptor | WorkerDescrip
292
339
  ts?: number;
293
340
  status?: WorkerLifecycle;
294
341
  }): HeartbeatStatus;
342
+ /**
343
+ * Build a worker's own {@link WorkerDescriptor} in the exact §7.1 shape every SDK publishes — the
344
+ * single builder both TS advertisers use (the BullMQ transport's in-app worker and the standalone
345
+ * `runRedisWorker`), so two processes in the same fleet cannot drift into two dialects of the same
346
+ * wire.
347
+ *
348
+ * Set-valued fields are sorted here (not just in the hash projection) so the published BYTES are
349
+ * stable across registration order too — a re-registration in a different order must not look like a
350
+ * change to a reader diffing the raw value. `partition` and `namespace` are omitted when empty, and
351
+ * `namespace` additionally when it is `default`, keeping a single-tenant deployment byte-identical
352
+ * to the un-namespaced scheme. `capabilities` is the canonical v1 baseline this protocol guarantees.
353
+ */
354
+ declare function describeWorker(opts: {
355
+ instanceId: string;
356
+ runtime: 'node' | 'python';
357
+ sdk: {
358
+ name: string;
359
+ version: string;
360
+ };
361
+ steps: string[];
362
+ workflows?: string[] | undefined;
363
+ /** What this instance announces it can EXECUTE — see {@link WorkflowRegistration}. Omitted from
364
+ * the descriptor entirely when absent or empty, so a worker with nothing to announce keeps the
365
+ * pre-registrations bytes (and therefore the pre-registrations hash). */
366
+ registrations?: readonly WorkflowRegistration[] | undefined;
367
+ startedAt: number;
368
+ partition?: string | undefined;
369
+ namespace?: string | undefined;
370
+ }): WorkerDescriptor;
295
371
 
296
372
  /**
297
373
  * Capability- & protocol-aware dispatch planning (design §7.5/§7.6). Sits BETWEEN the pure handshake
@@ -1463,6 +1539,19 @@ interface Transport {
1463
1539
  * a run can dispatch to `group` or must park `blocked`.
1464
1540
  */
1465
1541
  readWorkerDescriptors?(group: string): Promise<WorkerDescriptor[]>;
1542
+ /**
1543
+ * Every live {@link WorkerDescriptor} in the deployment, across all groups — the read behind
1544
+ * {@link WorkflowEngine.announcedWorkflows}. Optional, same as {@link readWorkerDescriptors}: a
1545
+ * transport that cannot introspect the advertisement keyspace returns nothing and the registry is
1546
+ * simply empty.
1547
+ *
1548
+ * Deliberately NOT `listWorkerGroups()` + a read per group: a worker publishes the SAME descriptor
1549
+ * under every token it consumes, so per-group reads cost one round trip per group to re-fetch bytes
1550
+ * the caller already has. One pass over the advertisement keyspace, de-duplicated by `instanceId`,
1551
+ * answers with one scan and no per-group fan-out — and it also sees an instance whose groups the
1552
+ * caller could not have enumerated.
1553
+ */
1554
+ readAllWorkerDescriptors?(): Promise<WorkerDescriptor[]>;
1466
1555
  /**
1467
1556
  * DB-less tenant worker → control plane: publish a {@link StartRunMessage} requesting a new run.
1468
1557
  * Optional — only transports that carry the hosted-control-plane protocol (P4) implement this.
@@ -1615,6 +1704,19 @@ type BackoffStrategy = 'fixed' | 'exp';
1615
1704
  interface ChildCallOptions {
1616
1705
  /** Deterministic child run id; defaults to one derived from the parent run id + call position. */
1617
1706
  childId?: string | undefined;
1707
+ /**
1708
+ * Pin the child to this EXACT registered version instead of the newest one — the same contract as
1709
+ * `StartOptions.version`, reached from inside a workflow body: an unregistered version fails the
1710
+ * child's start rather than quietly running a different body, and a version can only be pinned on a
1711
+ * workflow that is really registered (not on the synthesized remote/convention routes). A `ctx.child`
1712
+ * whose pinned start fails surfaces it as a failed child on the parent's waiter; a fire-and-forget
1713
+ * `ctx.startChild` buffers it until the parent joins the id.
1714
+ *
1715
+ * Replay-safe as long as you pass a CONSTANT (`{ version: '2' }`), which is the point — it names the
1716
+ * body you meant. Computing it from a live registry read at call time would make the body
1717
+ * non-deterministic, exactly like reading the clock.
1718
+ */
1719
+ version?: string | undefined;
1618
1720
  /**
1619
1721
  * Dispatch priority for a REMOTE child workflow — stamped on the child run and carried onto every
1620
1722
  * {@link WorkflowTask} dispatched to advance it, so an urgent child can jump ahead of enqueued
@@ -2288,6 +2390,253 @@ declare class EventAccumulators {
2288
2390
  private batch;
2289
2391
  }
2290
2392
 
2393
+ /**
2394
+ * The announced workflow registry (design §7.9) — the aggregate answer to "what workflows exist in
2395
+ * this deployment?", built ONLY from what live workers say about themselves.
2396
+ *
2397
+ * ## Why this is announced, not inferred
2398
+ *
2399
+ * `WorkflowEngine.workflowBody(name, version)` answers for the process that asks, and a missing body
2400
+ * is ambiguous by design: it means "not registered here", but equally "registered via
2401
+ * `registerRemote` against another SDK" or "a group this pod resolves by convention against a live
2402
+ * worker". A picker built on that inference would offer different options depending on which replica
2403
+ * served the request. So the registry is not derived from any engine's registry: a worker publishes
2404
+ * what it can execute ({@link WorkerDescriptor.registrations}, or bare `workflows` names), and this
2405
+ * module folds those statements together. Every entry is therefore a fact somebody asserted, and the
2406
+ * aggregate is the same on every pod because every pod reads the same published statements.
2407
+ *
2408
+ * The announcer is always the process that CONSUMES the queue, never the engine that dispatches to
2409
+ * it — including when the two share a process (the co-located in-app worker announces; the engine
2410
+ * beside it does not). One consequence is worth stating plainly rather than leaving to be
2411
+ * rediscovered: a pure operator (`store` with no `connection`) runs its bodies inline, consumes no
2412
+ * queue, and therefore announces NOTHING. Its workflows are real and startable through its own
2413
+ * `start()`, but they are not in this registry, because nothing about them is externally addressable
2414
+ * for a picker to point at.
2415
+ *
2416
+ * ## Liveness
2417
+ *
2418
+ * There is no expiry logic here because there is no state here. An announcement lives on the
2419
+ * descriptor key, which is written with the worker-heartbeat TTL and refreshed by the same beat; a
2420
+ * worker that dies stops refreshing and its key expires, taking its announcements with it. The
2421
+ * resolution of that liveness is therefore the TTL (35s against a 10s beat), so an entry can name a
2422
+ * worker that died within the last beat window — the same staleness the capability router already
2423
+ * accepts when it reads descriptors to decide whether to dispatch. A caller that needs a stronger
2424
+ * guarantee should not seek it here: dispatch to a dead worker parks the run `blocked`/`pending` on
2425
+ * its queue and the existing worker-health surface reports it. What this design DOES rule out is the
2426
+ * unbounded failure — an announcement that outlives its worker forever.
2427
+ *
2428
+ * ## Disagreement
2429
+ *
2430
+ * Two live workers can announce the same `name@version` with different groups, origins or capability
2431
+ * demands. The aggregate keeps EVERY distinct claim and reports the axes they differ on
2432
+ * ({@link AnnouncedWorkflow.disagreements}); it never merges them into one value and never picks a
2433
+ * winner, because either choice would make a caller act on a claim nobody made. Silence is not a
2434
+ * claim: a worker that announced no origin does not disagree with one that did.
2435
+ *
2436
+ * ## Steps are deliberately out of scope
2437
+ *
2438
+ * There is no equivalent registry for steps, and this is not an omission to be fixed later. A step is
2439
+ * not addressable from outside a run: it is identified by its `(runId, seq)` position in one
2440
+ * workflow's history, `ctx.step` is only callable from inside a replaying body, and no engine entry
2441
+ * point starts a step on its own. "Call this step" is therefore not something the engine can do, so a
2442
+ * picker offering steps would be offering an operation that does not exist. Step HANDLER names are
2443
+ * still advertised (`WorkerDescriptor.steps`) — that is a routing/capability fact used to decide
2444
+ * whether a dispatched step can be run, not an invocable catalog.
2445
+ */
2446
+
2447
+ /** One live worker's claim about one workflow: the registration plus who announced it. */
2448
+ interface WorkflowAnnouncement extends WorkflowRegistration {
2449
+ /** The announcing worker's instance id. */
2450
+ instanceId: string;
2451
+ /** The announcing worker's runtime — how a Python-served workflow is told from a Node-served one. */
2452
+ runtime: 'node' | 'python';
2453
+ }
2454
+ /** An axis on which the live announcers of one workflow do not agree. `values` holds every distinct
2455
+ * DECLARED value (sorted); an announcer that stated nothing on the axis contributes nothing. */
2456
+ interface Disagreement {
2457
+ axis: 'group' | 'origin' | 'requires';
2458
+ values: string[];
2459
+ }
2460
+ /** One workflow the live fleet announces, folded across every worker that announced it. */
2461
+ interface AnnouncedWorkflow {
2462
+ /** `name@version`, or the bare `name` when no announcer stated a version. Stable sort key. */
2463
+ key: string;
2464
+ name: string;
2465
+ /** Absent when no announcer stated one — never inferred from another announcer's version. */
2466
+ version?: string;
2467
+ /** Every distinct routing group announced (sorted). Empty = nobody stated one. */
2468
+ groups: string[];
2469
+ /** Every distinct declaring package announced (sorted). Empty = nobody stated one. */
2470
+ origins: string[];
2471
+ /** Every distinct capability demand announced, each de-duplicated + sorted. Empty = nobody stated
2472
+ * one. Two entries here means two workers demand different capabilities for the same version. */
2473
+ requires: string[][];
2474
+ /** Runtimes of the live announcers (sorted, de-duplicated). */
2475
+ runtimes: ('node' | 'python')[];
2476
+ /** Instance ids of the live workers announcing it (sorted, de-duplicated). Its length is how many
2477
+ * workers can currently run this workflow — `1` is a single point of failure, and it is never 0
2478
+ * (an entry exists only because somebody announced it). */
2479
+ instances: string[];
2480
+ /** The axes the announcers differ on, empty when they speak with one voice. A caller that must
2481
+ * pick a single target has to resolve these itself — the registry refuses to guess. */
2482
+ disagreements: Disagreement[];
2483
+ }
2484
+ /**
2485
+ * What one descriptor announces. `registrations` is authoritative when present; any `workflows` name
2486
+ * it does not cover is still announced as a bare, unversioned claim, so a worker that describes some
2487
+ * of its workflows richly and others by name alone is reported in full rather than in part.
2488
+ *
2489
+ * A descriptor with neither field announces nothing — including the control plane's own descriptor,
2490
+ * which is a router, not an executor.
2491
+ */
2492
+ declare function announcementsOf(raw: RawWorkerDescriptor | WorkerDescriptor): WorkflowAnnouncement[];
2493
+ /**
2494
+ * Fold every live descriptor's announcements into one entry per `name@version`, sorted by key.
2495
+ *
2496
+ * Pure: it holds nothing and caches nothing, so no pod ever carries a growing table of the fleet's
2497
+ * registrations — the caller reads the live descriptors it already knows how to read, folds them
2498
+ * here, and drops the result. The work is O(descriptors × announcements) per call, on demand.
2499
+ *
2500
+ * The same instance announcing the same workflow twice (a worker publishes its descriptor under
2501
+ * every routing token it consumes, so the same bytes arrive several times) folds to ONE announcer:
2502
+ * `instances` is a set, so it counts workers, not keys.
2503
+ */
2504
+ declare function aggregateAnnouncements(descriptors: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): AnnouncedWorkflow[];
2505
+
2506
+ /**
2507
+ * Bilateral compatibility negotiation between two handshake descriptors (design §7.3/§7.4). Pure
2508
+ * logic: given a local and a remote {@link WorkerDescriptor}, compute the negotiated session (highest
2509
+ * common protocol major + capability intersection) and classify it into one of three outcomes with a
2510
+ * precise, structured reason — never a bare boolean (design §7.6).
2511
+ */
2512
+
2513
+ /**
2514
+ * The three compatibility outcomes (design §7.4):
2515
+ * - `compatible` — protocol ranges intersect + full capability parity → dispatch freely.
2516
+ * - `degraded` — ranges intersect but a capability is missing on one side → dispatch, but route
2517
+ * capability-requiring work only to capable workers (soft warning).
2518
+ * - `incompatible` — no protocol-range overlap → do NOT dispatch; red flag with the exact reason.
2519
+ */
2520
+ type NegotiationOutcome = 'compatible' | 'degraded' | 'incompatible';
2521
+ /** Structured failure/warning payload — carries the precise delta so a diagnostics event can render
2522
+ * it on the timeline + dashboard health panel (design §7.6). Never a bare boolean. */
2523
+ interface NegotiationReason {
2524
+ /** Stable machine code for alerting/telescope. */
2525
+ code: 'protocol.incompatible' | 'capability.unavailable';
2526
+ /** Human-readable one-liner (dashboard/telescope copy). */
2527
+ message: string;
2528
+ /** The exact delta: which protocol ranges failed to overlap, or which capabilities are missing. */
2529
+ detail: {
2530
+ localRange?: [number, number];
2531
+ remoteRange?: [number, number];
2532
+ /** Capabilities the local side advertises that the remote lacks. */
2533
+ missingOnRemote?: string[];
2534
+ /** Capabilities the remote side advertises that the local lacks. */
2535
+ missingOnLocal?: string[];
2536
+ /** Explicitly-required capabilities not satisfied by the negotiated session. */
2537
+ missingRequired?: string[];
2538
+ };
2539
+ }
2540
+ /** The negotiated session + full delta between two sides. Carries both normalized descriptors so a
2541
+ * downstream diagnostics event can attach them wholesale (design §7.6). */
2542
+ interface NegotiationResult {
2543
+ outcome: NegotiationOutcome;
2544
+ /** Highest common protocol major, or `null` when ranges do not overlap (incompatible). */
2545
+ negotiatedProtocol: number | null;
2546
+ protocol: {
2547
+ localRange: [number, number];
2548
+ remoteRange: [number, number];
2549
+ };
2550
+ capabilities: {
2551
+ /** Intersection — the capabilities the negotiated session can rely on everywhere. */
2552
+ shared: string[];
2553
+ /** Local advertises, remote lacks. */
2554
+ missingOnRemote: string[];
2555
+ /** Remote advertises, local lacks. */
2556
+ missingOnLocal: string[];
2557
+ };
2558
+ /** Present for `degraded` + `incompatible`; absent for `compatible`. */
2559
+ reason?: NegotiationReason;
2560
+ local: WorkerDescriptor;
2561
+ remote: WorkerDescriptor;
2562
+ }
2563
+ interface NegotiateOptions {
2564
+ /**
2565
+ * Capabilities the local side needs the negotiated session to provide. A required capability the
2566
+ * remote lacks degrades the session (dispatch generally, but capability-requiring runs park via
2567
+ * the router, design §7.5) — it never, on its own, makes the pair *incompatible*: only a protocol
2568
+ * range gap does that (design §7.4).
2569
+ */
2570
+ required?: string[];
2571
+ }
2572
+ /**
2573
+ * Negotiate compatibility between `local` and `remote` (design §7.3/§7.4). Accepts raw/partial
2574
+ * descriptors (a missing `protocol`/`capabilities` is normalized to the legacy-v1 baseline, so a
2575
+ * legacy peer negotiates as **assume-compatible**, design §7.7).
2576
+ *
2577
+ * Outcome logic:
2578
+ * 1. no protocol-range overlap → `incompatible` (`protocol.incompatible`);
2579
+ * 2. else if a `required` capability is missing on the remote → `degraded` (`capability.unavailable`);
2580
+ * 3. else if the capability sets differ at all (either direction) → `degraded`;
2581
+ * 4. else full parity → `compatible`.
2582
+ *
2583
+ * **Bilateral-symmetric** on outcome for the parity path: `negotiate(a, b).outcome ===
2584
+ * negotiate(b, a).outcome` (the `missingOnLocal`/`missingOnRemote` delta swaps). `required` is the
2585
+ * caller's own need and intentionally asymmetric.
2586
+ */
2587
+ declare function negotiate(localRaw: RawWorkerDescriptor | WorkerDescriptor, remoteRaw: RawWorkerDescriptor | WorkerDescriptor, opts?: NegotiateOptions): NegotiationResult;
2588
+
2589
+ /**
2590
+ * Capability-aware routing (design §7.5). A workflow/step handler may declare
2591
+ * `requires: ['saga', 'search-attr-v2']`; the control-plane dispatches only to workers whose
2592
+ * descriptor advertises every required capability. If no live capable worker exists the run must
2593
+ * **park as `blocked`** with a precise reason (visible in the dashboard) — never a silent hang.
2594
+ *
2595
+ * Pure logic: no transport, no store. Consumers (the dispatcher/provider) feed live descriptors in.
2596
+ */
2597
+
2598
+ /** A handler's capability demand — either a bare list, or anything carrying a `requires` array
2599
+ * (a workflow/step ref). An absent/empty list means "runs anywhere" (design §7.5). */
2600
+ type CapabilityRequirement = string[] | {
2601
+ requires?: string[];
2602
+ };
2603
+ /** Normalize the two accepted shapes to a de-duplicated list of required capability names. */
2604
+ declare function requiredCapabilities(handler: CapabilityRequirement): string[];
2605
+ /**
2606
+ * Can `worker` run a handler with these requirements? True iff the worker's (normalized) descriptor
2607
+ * advertises **every** required capability. A legacy worker (no `capabilities` field) is normalized
2608
+ * to the v1 baseline first (design §7.7), so it can still run baseline-only work.
2609
+ */
2610
+ declare function canRoute(handler: CapabilityRequirement, worker: RawWorkerDescriptor | WorkerDescriptor): boolean;
2611
+ /**
2612
+ * The capabilities a handler requires that `worker` does NOT advertise — the precise routing delta.
2613
+ * Empty iff {@link canRoute} is true.
2614
+ */
2615
+ declare function missingCapabilities(handler: CapabilityRequirement, worker: RawWorkerDescriptor | WorkerDescriptor): string[];
2616
+ /** Filter a fleet down to the workers that can run a handler (design §7.5). */
2617
+ declare function capableWorkers<T extends RawWorkerDescriptor | WorkerDescriptor>(handler: CapabilityRequirement, workers: readonly T[]): T[];
2618
+ /** A run is dispatchable — at least one live worker can execute the handler. */
2619
+ interface RoutableResolution {
2620
+ status: 'routable';
2621
+ /** The normalized subset of the fleet that can run it. */
2622
+ workers: WorkerDescriptor[];
2623
+ requires: string[];
2624
+ }
2625
+ /** No live capable worker — the run parks as `blocked` with a human reason (design §7.5). */
2626
+ interface BlockedResolution {
2627
+ status: 'blocked';
2628
+ /** Exactly the dashboard copy: `blocked: no compatible worker (requires <caps>)`. */
2629
+ reason: string;
2630
+ requires: string[];
2631
+ }
2632
+ type RoutingResolution = RoutableResolution | BlockedResolution;
2633
+ /**
2634
+ * Decide whether a handler can be dispatched to the given live fleet (design §7.5). Returns
2635
+ * `routable` with the capable subset, or `blocked` with a precise reason when nobody can run it —
2636
+ * so the run parks visibly instead of hanging silently. An empty fleet always blocks.
2637
+ */
2638
+ declare function resolveRouting(handler: CapabilityRequirement, workers: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): RoutingResolution;
2639
+
2291
2640
  type WorkflowFn = (ctx: InternalWorkflowCtx, input: unknown) => Promise<unknown>;
2292
2641
  /**
2293
2642
  * Options for {@link WorkflowEngine.start}.
@@ -2298,6 +2647,26 @@ type WorkflowFn = (ctx: InternalWorkflowCtx, input: unknown) => Promise<unknown>
2298
2647
  * another lib's, and an origin filter would quietly lie.
2299
2648
  */
2300
2649
  interface StartOptions {
2650
+ /**
2651
+ * Start THIS registered version instead of the newest one. Omit (the default) and the run starts on
2652
+ * the newest registered version, exactly as it always has — this option only ever narrows.
2653
+ *
2654
+ * Resolved against the by-name@version registry, so it pins anything explicitly registered:
2655
+ * {@link WorkflowEngine.register}, {@link WorkflowEngine.registerRemote}, {@link WorkflowEngine.remote}.
2656
+ * A version that is NOT registered throws before a run row is created and NEVER falls back to
2657
+ * `latest` — a silent fallback is the exact failure this option exists to prevent (a caller that
2658
+ * asked for `name@3` and got `name@4` has no way to tell, and its run is already going).
2659
+ *
2660
+ * NOT available on the two SYNTHESIZED registration paths, which exist precisely because nothing is
2661
+ * registered: a child inheriting its remote ancestor's routing (see `findInheritedRegistration`),
2662
+ * and convention routing to a live worker group of the same name (`resolveRemoteByConvention`).
2663
+ * Both INVENT the version they stamp — the ancestor's, and the `'1'` convention default — as a
2664
+ * routing placeholder; neither is evidence that the worker has a body at the version you asked for.
2665
+ * A pinned start that reaches them therefore throws rather than pretending to have honoured the pin.
2666
+ * To pin a cross-SDK workflow, register it for real — `engine.remote('name', { version: '2', group })`
2667
+ * — which puts `name@2` in the registry and pins like any other registration.
2668
+ */
2669
+ version?: string | undefined;
2301
2670
  /** Run-scoped tags, merged with the workflow's static `@Workflow({ tags })` onto the run. */
2302
2671
  tags?: string[] | undefined;
2303
2672
  /** Typed, queryable run data stamped on the run (e.g. `{ amount: 200, tier: 'pro' }`). */
@@ -2645,6 +3014,10 @@ declare class WorkflowEngine {
2645
3014
  * remote (a `registerRemote` / synthesized child carries only a throwing stub, never handed out).
2646
3015
  */
2647
3016
  workflowBody(name: string, version: string): WorkflowFn | undefined;
3017
+ /** The registered versions of `name`, for the pinned-start error — "2 is not registered" is a
3018
+ * guess-the-typo error without them, and the answer (which versions this deploy actually carries)
3019
+ * is right here. `none` when the name is unknown entirely. */
3020
+ private registeredVersions;
2648
3021
  /**
2649
3022
  * Register a workflow whose body runs in another SDK (e.g. Python). The engine owns the run exactly
2650
3023
  * as for a TS workflow — it persists checkpoints, recovers, runs timers — but advances it by handing
@@ -2800,7 +3173,32 @@ declare class WorkflowEngine {
2800
3173
  /**
2801
3174
  * Cancel in-flight runs that have outlived their workflow's `executionTimeout`. Call it from the
2802
3175
  * timer poller alongside {@link resumeDueTimers}. A timed-out run is moved to `cancelled` with an
2803
- * `execution_timeout` error (terminal, so a late step result can't resurrect it).
3176
+ * `execution_timeout` error (terminal, so a late step result can't resurrect it), AND its children
3177
+ * are cascaded exactly as an explicit {@link cancel} cascades them.
3178
+ *
3179
+ * WHY THE TERMINAL WRITE IS DIRECT, not a call to {@link cancel}. Two reasons, both still standing:
3180
+ * the run must land with the `execution_timeout` error `cancel` knows nothing about (`cancel` writes
3181
+ * a plain `cancelled`, and the code is what a dashboard/alert distinguishes a timeout by), and this
3182
+ * is a SCAN — it walks every in-flight run of every timed workflow on every poller tick, so it must
3183
+ * not pay `cancel`'s per-run re-read and singleton/control-plane work on runs it isn't cancelling.
3184
+ * The cascade below is charged only to the runs that ACTUALLY timed out, which are rare by
3185
+ * construction; the scan itself is unchanged.
3186
+ *
3187
+ * The subtree, though, has no reason to differ, and before this it did: a timed-out parent's
3188
+ * children kept running with the parent gone and nothing pointing at them — a silent orphan you
3189
+ * could only find by reading the runs table by hand. {@link cancelChildren} is the SAME recursive
3190
+ * walk `cancel` uses, so children of children go too, and it inherits that path's two guards:
3191
+ * `cancel` returns early on a run that is already `completed`/`cancelled`/`dead` (a child that beat
3192
+ * the sweep to the finish is never clobbered, and a re-cancel is a no-op), and it writes the
3193
+ * parent's terminal status BEFORE recursing, so a cyclic parent→child graph terminates on the
3194
+ * second visit instead of looping. Two pollers sweeping the same run concurrently therefore write
3195
+ * the same terminal state twice and cascade into already-cancelled children — idempotent, not
3196
+ * corrupting. The one window it does NOT close is the pre-existing check-then-write race an
3197
+ * explicit `cancel` has always had (a child that completes between `cancel`'s read and its write);
3198
+ * closing that needs a compare-and-set in the store contract, not a change here.
3199
+ *
3200
+ * Cascade failures are swallowed per subtree for the same reason `cancelChildren` swallows them per
3201
+ * child: one unreachable child must not abort the sweep and leave every LATER timed-out run alive.
2804
3202
  */
2805
3203
  sweepTimeouts(now?: number): Promise<void>;
2806
3204
  /**
@@ -3064,6 +3462,10 @@ declare class WorkflowEngine {
3064
3462
  * `child:<id>` waiter) and fire-and-forget (`ctx.startChild`, found via its `spawn:<id>`
3065
3463
  * checkpoint). Recursive, so a whole subtree is cancelled; the terminal guard in `cancel` stops it
3066
3464
  * at finished / already-cancelled runs (no loops, no re-cancel).
3465
+ *
3466
+ * Two callers, deliberately the same walk: an explicit {@link cancel}, and {@link sweepTimeouts}
3467
+ * when a parent hits its `executionTimeout` — a timed-out parent's subtree dies the same way an
3468
+ * explicitly cancelled one's does, or the children are orphans nobody is holding a reference to.
3067
3469
  */
3068
3470
  private cancelChildren;
3069
3471
  /**
@@ -3090,6 +3492,25 @@ declare class WorkflowEngine {
3090
3492
  * from registrations, shows once its workers beat). Empty when no transport can introspect health
3091
3493
  * (only the BullMQ transport implements `groupHealth`). */
3092
3494
  workerHealth(extra?: string[]): Promise<GroupHealth[]>;
3495
+ /**
3496
+ * What workflows the LIVE fleet announces it can execute — the deployment-wide registry a picker
3497
+ * ("call this existing workflow") needs, and the one question this engine's own registry cannot
3498
+ * answer. {@link workflowBody} answers only for this process, and a missing body is ambiguous: it
3499
+ * means "not registered here", but equally "registered via {@link registerRemote} against another
3500
+ * SDK" or "a group resolved by convention against a worker". An inferred answer would therefore
3501
+ * differ per replica. This one does not: every pod folds the same published announcements.
3502
+ *
3503
+ * Scoped like every other poll surface — an engine WITH a `namespace` sees only workers in it, and
3504
+ * an operator (namespace unset) sees the whole deployment ("ver tudo = ausência de namespace"). A
3505
+ * descriptor that states no namespace is `default`, matching how a single-tenant fleet publishes.
3506
+ *
3507
+ * Cost: one advertisement scan per CALL, on demand. Nothing is added to the poll loop and nothing
3508
+ * is retained — no pod holds a table of the fleet's registrations. Empty when no transport can
3509
+ * introspect the advertisement keyspace (a pure in-process transport advertises nothing).
3510
+ *
3511
+ * Workflows only, and deliberately: see the "steps are out of scope" note in `handshake/announced`.
3512
+ */
3513
+ announcedWorkflows(): Promise<AnnouncedWorkflow[]>;
3093
3514
  /**
3094
3515
  * The routing groups of every in-flight run's still-`pending` remote step — the queues where work is
3095
3516
  * dispatched and awaiting a worker. Folded into {@link workerHealth} so a step waiting on a group
@@ -3177,8 +3598,22 @@ declare class WorkflowEngine {
3177
3598
  * cancel flag stays too, re-derived from the status on any later re-drive regardless.
3178
3599
  */
3179
3600
  private compensateAndCancel;
3180
- /** The run body, lease held + renewed by {@link execute}. */
3601
+ /**
3602
+ * The run body, lease held + renewed by {@link execute}, wrapped in the registered
3603
+ * {@link runDurableExecution} wrappers so an observability scope covers ONE WHOLE TURN.
3604
+ *
3605
+ * The wrap goes here — around the entire turn — rather than tightly around the `fn(ctx, input)`
3606
+ * call, because the store writes that bracket the body (the `pending` → `running` transition, the
3607
+ * step checkpoints, the terminal `settleRun` update) are exactly the queries an operator wants
3608
+ * correlated to the turn that issued them. Narrowing the wrap to the body would leave them
3609
+ * uncorrelated for the sake of a purity that buys nothing.
3610
+ *
3611
+ * Only the TS path is wrapped. {@link runRemoteExecution} does not execute a body at all — it
3612
+ * dispatches a workflow task and awaits the worker's decision — so a scope opened there would
3613
+ * describe a dispatch, not an execution, and would mislead anyone reading the trace.
3614
+ */
3181
3615
  private runExecution;
3616
+ private runExecutionTurn;
3182
3617
  /**
3183
3618
  * Undo every registered saga compensation in strict reverse order — the shared unwind both the
3184
3619
  * run-failure path (`runExecution`'s catch) and the compensating-cancel path
@@ -3537,138 +3972,68 @@ declare class ContinueAsNew extends Error {
3537
3972
  }
3538
3973
 
3539
3974
  /**
3540
- * Bilateral compatibility negotiation between two handshake descriptors (design §7.3/§7.4). Pure
3541
- * logic: given a local and a remote {@link WorkerDescriptor}, compute the negotiated session (highest
3542
- * common protocol major + capability intersection) and classify it into one of three outcomes with a
3543
- * precise, structured reason — never a bare boolean (design §7.6).
3544
- */
3545
-
3546
- /**
3547
- * The three compatibility outcomes (design §7.4):
3548
- * - `compatible` — protocol ranges intersect + full capability parity → dispatch freely.
3549
- * - `degraded` — ranges intersect but a capability is missing on one side → dispatch, but route
3550
- * capability-requiring work only to capable workers (soft warning).
3551
- * - `incompatible` — no protocol-range overlap → do NOT dispatch; red flag with the exact reason.
3552
- */
3553
- type NegotiationOutcome = 'compatible' | 'degraded' | 'incompatible';
3554
- /** Structured failure/warning payload — carries the precise delta so a diagnostics event can render
3555
- * it on the timeline + dashboard health panel (design §7.6). Never a bare boolean. */
3556
- interface NegotiationReason {
3557
- /** Stable machine code for alerting/telescope. */
3558
- code: 'protocol.incompatible' | 'capability.unavailable';
3559
- /** Human-readable one-liner (dashboard/telescope copy). */
3560
- message: string;
3561
- /** The exact delta: which protocol ranges failed to overlap, or which capabilities are missing. */
3562
- detail: {
3563
- localRange?: [number, number];
3564
- remoteRange?: [number, number];
3565
- /** Capabilities the local side advertises that the remote lacks. */
3566
- missingOnRemote?: string[];
3567
- /** Capabilities the remote side advertises that the local lacks. */
3568
- missingOnLocal?: string[];
3569
- /** Explicitly-required capabilities not satisfied by the negotiated session. */
3570
- missingRequired?: string[];
3571
- };
3572
- }
3573
- /** The negotiated session + full delta between two sides. Carries both normalized descriptors so a
3574
- * downstream diagnostics event can attach them wholesale (design §7.6). */
3575
- interface NegotiationResult {
3576
- outcome: NegotiationOutcome;
3577
- /** Highest common protocol major, or `null` when ranges do not overlap (incompatible). */
3578
- negotiatedProtocol: number | null;
3579
- protocol: {
3580
- localRange: [number, number];
3581
- remoteRange: [number, number];
3582
- };
3583
- capabilities: {
3584
- /** Intersection — the capabilities the negotiated session can rely on everywhere. */
3585
- shared: string[];
3586
- /** Local advertises, remote lacks. */
3587
- missingOnRemote: string[];
3588
- /** Remote advertises, local lacks. */
3589
- missingOnLocal: string[];
3590
- };
3591
- /** Present for `degraded` + `incompatible`; absent for `compatible`. */
3592
- reason?: NegotiationReason;
3593
- local: WorkerDescriptor;
3594
- remote: WorkerDescriptor;
3595
- }
3596
- interface NegotiateOptions {
3597
- /**
3598
- * Capabilities the local side needs the negotiated session to provide. A required capability the
3599
- * remote lacks degrades the session (dispatch generally, but capability-requiring runs park via
3600
- * the router, design §7.5) — it never, on its own, makes the pair *incompatible*: only a protocol
3601
- * range gap does that (design §7.4).
3602
- */
3603
- required?: string[];
3604
- }
3605
- /**
3606
- * Negotiate compatibility between `local` and `remote` (design §7.3/§7.4). Accepts raw/partial
3607
- * descriptors (a missing `protocol`/`capabilities` is normalized to the legacy-v1 baseline, so a
3608
- * legacy peer negotiates as **assume-compatible**, design §7.7).
3975
+ * Wrappers around the **real execution** of a unit of durable work — a workflow turn, or a step
3976
+ * handler body. Distinct from {@link StepInterceptor} (`engine.use`), which only sees the in-process
3977
+ * `localStep` primitives (`ctx.now`, `ctx.sideEffect`, `ctx.task`'s dispatch step, saga
3978
+ * compensations): a user's `ctx.step` is ALWAYS dispatched, so its handler runs inside a transport
3979
+ * callback or a worker process where no engine is in scope at all.
3609
3980
  *
3610
- * Outcome logic:
3611
- * 1. no protocol-range overlap → `incompatible` (`protocol.incompatible`);
3612
- * 2. else if a `required` capability is missing on the remote → `degraded` (`capability.unavailable`);
3613
- * 3. else if the capability sets differ at all (either direction) → `degraded`;
3614
- * 4. else full parity → `compatible`.
3615
- *
3616
- * **Bilateral-symmetric** on outcome for the parity path: `negotiate(a, b).outcome ===
3617
- * negotiate(b, a).outcome` (the `missingOnLocal`/`missingOnRemote` delta swaps). `required` is the
3618
- * caller's own need and intentionally asymmetric.
3619
- */
3620
- declare function negotiate(localRaw: RawWorkerDescriptor | WorkerDescriptor, remoteRaw: RawWorkerDescriptor | WorkerDescriptor, opts?: NegotiateOptions): NegotiationResult;
3621
-
3622
- /**
3623
- * Capability-aware routing (design §7.5). A workflow/step handler may declare
3624
- * `requires: ['saga', 'search-attr-v2']`; the control-plane dispatches only to workers whose
3625
- * descriptor advertises every required capability. If no live capable worker exists the run must
3626
- * **park as `blocked`** with a precise reason (visible in the dashboard) — never a silent hang.
3981
+ * That is exactly why this registry is process-level rather than per-engine. `runStepHandler` is a
3982
+ * free function every transport shares, and a thin worker (`DurableModule` with a `connection` and
3983
+ * no `store`) holds no `WorkflowEngine` to hang a hook off. A per-engine registry could not reach
3984
+ * either, so the seam that covers every deployment shape has to sit above the engine.
3627
3985
  *
3628
- * Pure logic: no transport, no store. Consumers (the dispatcher/provider) feed live descriptors in.
3986
+ * The intended consumer is an observability bridge (Telescope, OTel, a logger) that needs to
3987
+ * establish an ambient scope — an AsyncLocalStorage batch, a span — that the executing code records
3988
+ * into. It is deliberately NOT a transform point: see {@link runDurableExecution}.
3629
3989
  */
3630
-
3631
- /** A handler's capability demand — either a bare list, or anything carrying a `requires` array
3632
- * (a workflow/step ref). An absent/empty list means "runs anywhere" (design §7.5). */
3633
- type CapabilityRequirement = string[] | {
3634
- requires?: string[];
3635
- };
3636
- /** Normalize the two accepted shapes to a de-duplicated list of required capability names. */
3637
- declare function requiredCapabilities(handler: CapabilityRequirement): string[];
3990
+ /** The unit of durable work a wrapper is being invoked around. */
3991
+ interface DurableExecution {
3992
+ /** `'workflow'` = one turn of a run's body; `'step'` = one step handler body. */
3993
+ readonly unit: 'workflow' | 'step';
3994
+ readonly runId: string;
3995
+ /** The workflow name. Present on a turn; a dispatched step task does not carry it on the wire. */
3996
+ readonly workflow: string | undefined;
3997
+ /** The step name, for `unit: 'step'`. Undefined for a turn. */
3998
+ readonly name: string | undefined;
3999
+ /** The step's logical position within the run, for `unit: 'step'`. */
4000
+ readonly seq: number | undefined;
4001
+ /** 1-based dispatch attempt, when the task carries one. */
4002
+ readonly attempt: number | undefined;
4003
+ }
3638
4004
  /**
3639
- * Can `worker` run a handler with these requirements? True iff the worker's (normalized) descriptor
3640
- * advertises **every** required capability. A legacy worker (no `capabilities` field) is normalized
3641
- * to the v1 baseline first (design §7.7), so it can still run baseline-only work.
4005
+ * Wraps one unit of durable work. Call `next()` to run the body (or the next wrapper). Whatever the
4006
+ * wrapper returns or throws is discarded — see {@link runDurableExecution} for why.
3642
4007
  */
3643
- declare function canRoute(handler: CapabilityRequirement, worker: RawWorkerDescriptor | WorkerDescriptor): boolean;
4008
+ type DurableExecutionWrapper = (execution: DurableExecution, next: () => Promise<unknown>) => Promise<unknown>;
3644
4009
  /**
3645
- * The capabilities a handler requires that `worker` does NOT advertise — the precise routing delta.
3646
- * Empty iff {@link canRoute} is true.
4010
+ * Register a {@link DurableExecutionWrapper}. Returns an unregister function.
4011
+ *
4012
+ * Registering is the ONLY cost an unused hook imposes: with an empty registry
4013
+ * {@link runDurableExecution} returns the body's own promise without allocating a chain, so a host
4014
+ * that wires no observability pays nothing at all on the execution path.
3647
4015
  */
3648
- declare function missingCapabilities(handler: CapabilityRequirement, worker: RawWorkerDescriptor | WorkerDescriptor): string[];
3649
- /** Filter a fleet down to the workers that can run a handler (design §7.5). */
3650
- declare function capableWorkers<T extends RawWorkerDescriptor | WorkerDescriptor>(handler: CapabilityRequirement, workers: readonly T[]): T[];
3651
- /** A run is dispatchable — at least one live worker can execute the handler. */
3652
- interface RoutableResolution {
3653
- status: 'routable';
3654
- /** The normalized subset of the fleet that can run it. */
3655
- workers: WorkerDescriptor[];
3656
- requires: string[];
3657
- }
3658
- /** No live capable worker — the run parks as `blocked` with a human reason (design §7.5). */
3659
- interface BlockedResolution {
3660
- status: 'blocked';
3661
- /** Exactly the dashboard copy: `blocked: no compatible worker (requires <caps>)`. */
3662
- reason: string;
3663
- requires: string[];
3664
- }
3665
- type RoutingResolution = RoutableResolution | BlockedResolution;
4016
+ declare function useDurableExecution(wrapper: DurableExecutionWrapper): () => void;
4017
+ /** Drop every registered wrapper. For tests that must not leak a wrapper into the next file. */
4018
+ declare function clearDurableExecutionWrappers(): void;
4019
+ /** Whether any wrapper is registered — lets a hot path skip building an execution descriptor. */
4020
+ declare function hasDurableExecutionWrappers(): boolean;
3666
4021
  /**
3667
- * Decide whether a handler can be dispatched to the given live fleet (design §7.5). Returns
3668
- * `routable` with the capable subset, or `blocked` with a precise reason when nobody can run it —
3669
- * so the run parks visibly instead of hanging silently. An empty fleet always blocks.
4022
+ * Run `body` inside every registered wrapper and settle with **the body's own outcome**.
4023
+ *
4024
+ * The wrappers' return values and throws are deliberately ignored. An observer that misbehaves —
4025
+ * returns early without calling `next()` is the one case we cannot paper over, but one that throws
4026
+ * *after* the body settled, or swallows the body's error and returns normally, is common enough in
4027
+ * telemetry code — must not be able to change what the workflow saw. A wrapper that turned a failed
4028
+ * step into a completed one would corrupt the run's history and its checkpoints, which is a far
4029
+ * worse failure than a missing trace. This is the same reasoning the bullmq/schedule Telescope
4030
+ * watchers apply when they re-throw the host's error rather than swallowing it, pushed down into
4031
+ * the seam so every wrapper gets it for free instead of having to remember.
4032
+ *
4033
+ * The one unrecoverable case is a wrapper that never calls `next()`: the body did not run, so there
4034
+ * is no outcome to return and we throw rather than invent one.
3670
4035
  */
3671
- declare function resolveRouting(handler: CapabilityRequirement, workers: readonly (RawWorkerDescriptor | WorkerDescriptor)[]): RoutingResolution;
4036
+ declare function runDurableExecution<T>(execution: DurableExecution, body: () => Promise<T>): Promise<T>;
3672
4037
 
3673
4038
  /** Canonical step id — the stable identity of a step within a run, used for dedupe and
3674
4039
  * correlation. The format is part of the cross-language wire contract (Python builds the same). */
@@ -4345,4 +4710,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
4345
4710
  onControl(handler: (msg: ControlMessage) => void): void;
4346
4711
  }
4347
4712
 
4348
- export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DispatchDiagnostics, type DispatchPlan, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, canRoute, capableWorkers, classifyWaiterToken, collectMetrics, controlPlaneDescriptor, createStepLogger, currentStep, currentWorkflowCtx, debug, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, parseDuration, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, warn, workflowName };
4713
+ export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AnnouncedWorkflow, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type Disagreement, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, type WorkflowAnnouncement, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRegistration, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, aggregateAnnouncements, announcementsOf, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, canRoute, capableWorkers, classifyWaiterToken, clearDurableExecutionWrappers, collectMetrics, controlPlaneDescriptor, createStepLogger, currentStep, currentWorkflowCtx, debug, describeWorker, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, parseDuration, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runDurableExecution, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, useDurableExecution, warn, workflowName };