@orkestrel/workflow 0.0.17 → 0.0.19

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.
@@ -1,16 +1,17 @@
1
+ import { arrayShape, attempt, cloneJSONRecord, cloneJSONValue, compileGuard, createContract, integerShape, isArray, isBoolean, isContractError, isError, isFiniteNumber, isFunction, isInstance, isInteger, isJSONValue, isNonEmptyString, isObject, isRecord, isString, literalShape, objectShape, optionalShape, rawShape, stringShape } from "@orkestrel/contract";
1
2
  import { createAbort, isAbortSignal, linkSignal } from "@orkestrel/abort";
2
- import { arrayShape, attempt, cloneJSONRecord, cloneJSONValue, compileGuard, createContract, integerShape, isArray, isBoolean, isContractError, isFiniteNumber, isFunction, isInteger, isJSONValue, isNonEmptyString, isObject, isRecord, literalShape, objectShape, optionalShape, rawShape, stringShape } from "@orkestrel/contract";
3
3
  import { createDatabase, createMemoryDriver } from "@orkestrel/database";
4
4
  import { Emitter } from "@orkestrel/emitter";
5
5
  import { createTimeout } from "@orkestrel/timeout";
6
6
  import { createQueue } from "@orkestrel/queue";
7
7
  //#region src/core/errors.ts
8
8
  /**
9
- * Represents an error raised by the workflow runtime.
9
+ * Represents an error the workflow runtime raises for an operation it refuses — a
10
+ * {@link WorkflowErrorCode} (`TRANSITION`, `RESTORE`, `MUTATION`, `SCHEDULE`, or `INVARIANT`)
11
+ * beside an optional `context` naming the node or the parameter at fault.
10
12
  *
11
13
  * @remarks
12
- * Carries a {@link WorkflowErrorCode} and an optional `context` bag naming the
13
- * offending node id / status / parameter. Raised for an illegal lifecycle transition
14
+ * Raised for an illegal lifecycle transition
14
15
  * (`TRANSITION`), a structurally invalid {@link import('./types.js').WorkflowSnapshot}
15
16
  * boundary (`RESTORE`), a refused structural/activity edit (`MUTATION`), a host
16
17
  * schedule refused before arming because the caller's `signal` is not a native
@@ -43,18 +44,18 @@ var WorkflowError = class extends Error {
43
44
  * ```
44
45
  */
45
46
  function isWorkflowError(value) {
46
- try {
47
- return value instanceof WorkflowError;
48
- } catch {
49
- return false;
50
- }
47
+ return isInstance(value, WorkflowError);
51
48
  }
52
49
  //#endregion
53
50
  //#region src/core/constants.ts
54
- /** Names the default {@link import('./types.js').WorkflowDefinition.bail} — graceful (continue on a leaf failure). */
51
+ /**
52
+ * Names the default {@link import('./types.js').WorkflowDefinition.bail}, `false` — the graceful
53
+ * policy that records a leaf failure and finishes every phase.
54
+ */
55
55
  var DEFAULT_BAIL = false;
56
56
  /**
57
- * Lists every {@link LifecycleStatus} value, frozen — the vocabulary every tier draws from.
57
+ * Lists every {@link LifecycleStatus} value, frozen — the vocabulary every tier draws from,
58
+ * in the order `pending`, `running`, `completed`, `failed`, `skipped`, `stopped`.
58
59
  *
59
60
  * @remarks
60
61
  * Ordered pending → running → terminal (`completed` / `failed` / `skipped` /
@@ -70,8 +71,8 @@ var LIFECYCLE_STATUSES = Object.freeze([
70
71
  "stopped"
71
72
  ]);
72
73
  /**
73
- * Lists the {@link LifecycleStatus} values that are TERMINAL — a node in one of these will
74
- * not transition further, frozen.
74
+ * Lists the terminal {@link LifecycleStatus} values, frozen — `completed`, `failed`, `skipped`,
75
+ * and `stopped`, each a state a node never transitions out of.
75
76
  *
76
77
  * @remarks
77
78
  * The source of truth behind {@link import('./helpers.js').isTerminalStatus}.
@@ -116,30 +117,32 @@ var TASK_TRANSITIONS = Object.freeze({
116
117
  /**
117
118
  * Names the default per-phase task concurrency the {@link import('./factories.js').createWorkflowRunner}
118
119
  * runner applies when a {@link import('./types.js').PhaseDefinition} omits its `concurrency`
119
- * throttle — a cap that is effectively unbounded for any realistic phase.
120
+ * throttle — `1024`, a cap that is effectively unbounded for any realistic phase.
120
121
  *
121
122
  * @remarks
122
- * The determinism principle fixes that a phase's tasks run CONCURRENTLY; `concurrency` is
123
+ * The determinism principle fixes that a phase's tasks run concurrently; `concurrency` is
123
124
  * only an optional resource throttle (max-in-flight). With none declared, the runner runs
124
125
  * all of a phase's tasks at once — modelled as this finite cap so the value flows straight
125
126
  * into the substrate {@link import('./types.js').RunnerInterface}'s `concurrency` (which
126
127
  * expects a positive integer) without a special unbounded branch. No realistic phase
127
128
  * declares enough tasks to reach it, so it behaves as "run them all".
128
129
  *
129
- * WHY `1024` and not a huge sentinel like `1_000_000`: the backing `@orkestrel/queue` Runner
130
- * EAGERLY spawns one parked worker loop per concurrency unit AT CONSTRUCTION, so this default
130
+ * why `1024` and not a huge sentinel like `1_000_000`: the backing `@orkestrel/queue` Runner
131
+ * eagerly spawns one parked worker loop per concurrency unit at construction, so this default
131
132
  * must be a value whose eager allocation cost is negligible for every default-concurrency
132
133
  * phase — a million-unit default meant ~1e6 promise/closure allocations per such phase. A
133
- * phase may still DECLARE a larger explicit `concurrency` and pays that allocation knowingly.
134
+ * phase may still declare a larger explicit `concurrency` and pays that allocation knowingly.
134
135
  */
135
136
  var DEFAULT_PHASE_CONCURRENCY = 1024;
136
137
  /**
137
- * Names the largest delay representable by the host timer APIs without overflow or clamping.
138
+ * Names the largest delay representable by the host timer APIs without overflow or clamping,
139
+ * `2_147_483_647` milliseconds.
138
140
  */
139
141
  var MAX_TIMER_MS = 2147483647;
140
142
  /**
141
143
  * Lists the {@link WorkflowEventMap} / {@link PhaseEventMap} events that make a durable observer
142
- * re-persist the live tree, frozen.
144
+ * re-persist the live tree, frozen — `start`, `complete`, `fail`, `skip`, `stop`, `move`, and
145
+ * `update`.
143
146
  *
144
147
  * @remarks
145
148
  * The two maps carry the same event names, so one list serves both tiers. It is the source of
@@ -158,7 +161,8 @@ var PERSISTED_NODE_EVENTS = Object.freeze([
158
161
  "update"
159
162
  ]);
160
163
  /**
161
- * Lists the {@link TaskEventMap} events that make a durable observer re-persist the live tree, frozen.
164
+ * Lists the {@link TaskEventMap} events that make a durable observer re-persist the live tree,
165
+ * frozen — `start`, `complete`, `fail`, `skip`, `stop`, `report`, and `pulse`.
162
166
  *
163
167
  * @remarks
164
168
  * The leaf counterpart of {@link PERSISTED_NODE_EVENTS}, and the source of truth behind the task
@@ -229,8 +233,8 @@ function isTaskFailure(value) {
229
233
  * The discriminator behind the overloaded
230
234
  * {@link import('./types.js').WorkflowRunnerInterface.execute}: a
231
235
  * {@link import('./types.js').WorkflowInterface} is the only one of the two carrying `destroyed`
232
- * (RUNTIME-ONLY, never a field on the pure-JSON
233
- * {@link import('./types.js').WorkflowDefinition}) AND a callable `snapshot`. Requiring both is
236
+ * (runtime-only, never a field on the pure-JSON
237
+ * {@link import('./types.js').WorkflowDefinition}) and a callable `snapshot`. Requiring both is
234
238
  * sturdier than `destroyed` alone — a definition could coincidentally carry a `destroyed` field as
235
239
  * arbitrary data, and pairing it with a function-typed `snapshot` narrows to the actual entity
236
240
  * shape without an `as`. It reads a live class instance, so it tests object identity rather than a
@@ -269,14 +273,14 @@ function isWorkflowInterface(value) {
269
273
  */
270
274
  function isOwnedWorkflowSnapshot(value) {
271
275
  try {
272
- if (!isRecord(value) || !Object.keys(value).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "phases" || key === "created" || key === "updated") || !isNonEmptyString(value.id) || !isNonEmptyString(value.name) || value.description !== void 0 && typeof value.description !== "string" || !isLifecycleStatus(value.status) || value.override !== void 0 && value.override !== "completed" && value.override !== "skipped" && value.override !== "stopped" || !isBoolean(value.bail) || !isArray(value.phases) || !isFiniteNumber(value.created) || value.created < 0 || !isFiniteNumber(value.updated) || value.updated < value.created) return false;
276
+ if (!isRecord(value) || !Object.keys(value).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "phases" || key === "created" || key === "updated") || !isNonEmptyString(value.id) || !isNonEmptyString(value.name) || value.description !== void 0 && !isString(value.description) || !isLifecycleStatus(value.status) || value.override !== void 0 && value.override !== "completed" && value.override !== "skipped" && value.override !== "stopped" || !isBoolean(value.bail) || !isArray(value.phases) || !isFiniteNumber(value.created) || value.created < 0 || !isFiniteNumber(value.updated) || value.updated < value.created) return false;
273
277
  const phaseIds = /* @__PURE__ */ new Set();
274
278
  const derivations = [];
275
279
  let frontier = false;
276
280
  let running = false;
277
281
  let vacuous = true;
278
282
  for (const phase of value.phases) {
279
- if (!isRecord(phase) || !Object.keys(phase).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "concurrency" || key === "tasks") || !isNonEmptyString(phase.id) || phaseIds.has(phase.id) || !isNonEmptyString(phase.name) || phase.description !== void 0 && typeof phase.description !== "string" || !isLifecycleStatus(phase.status) || phase.override !== void 0 && phase.override !== "skipped" && phase.override !== "stopped" || !isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return false;
283
+ if (!isRecord(phase) || !Object.keys(phase).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "concurrency" || key === "tasks") || !isNonEmptyString(phase.id) || phaseIds.has(phase.id) || !isNonEmptyString(phase.name) || phase.description !== void 0 && !isString(phase.description) || !isLifecycleStatus(phase.status) || phase.override !== void 0 && phase.override !== "skipped" && phase.override !== "stopped" || !isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return false;
280
284
  const forced = phase.override === "skipped" || phase.override === "stopped";
281
285
  const started = phase.status === "running" || phase.status === "completed" || phase.status === "failed";
282
286
  if (!forced && frontier && started || phase.status === "running" && running) return false;
@@ -287,7 +291,7 @@ function isOwnedWorkflowSnapshot(value) {
287
291
  const statuses = [];
288
292
  if (phase.tasks.length > 0) vacuous = false;
289
293
  for (const task of phase.tasks) {
290
- if (!isRecord(task) || !Object.keys(task).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "result" || key === "metadata" || key === "attempts" || key === "behavior" || key === "retries" || key === "timeout" || key === "activity") || !isNonEmptyString(task.id) || taskIds.has(task.id) || !isNonEmptyString(task.name) || task.description !== void 0 && typeof task.description !== "string" || !isLifecycleStatus(task.status) || !isRecord(task.metadata) || !isJSONValue(task.metadata) || !isInteger(task.attempts) || task.attempts < 0 || task.behavior !== void 0 && !isNonEmptyString(task.behavior) || task.retries !== void 0 && (!isInteger(task.retries) || task.retries < 0) || task.timeout !== void 0 && (!isInteger(task.timeout) || task.timeout < 0 || task.timeout > 2147483647)) return false;
294
+ if (!isRecord(task) || !Object.keys(task).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "result" || key === "metadata" || key === "attempts" || key === "behavior" || key === "retries" || key === "timeout" || key === "activity") || !isNonEmptyString(task.id) || taskIds.has(task.id) || !isNonEmptyString(task.name) || task.description !== void 0 && !isString(task.description) || !isLifecycleStatus(task.status) || !isRecord(task.metadata) || !isJSONValue(task.metadata) || !isInteger(task.attempts) || task.attempts < 0 || task.behavior !== void 0 && !isNonEmptyString(task.behavior) || task.retries !== void 0 && (!isInteger(task.retries) || task.retries < 0) || task.timeout !== void 0 && (!isInteger(task.timeout) || task.timeout < 0 || task.timeout > 2147483647)) return false;
291
295
  const budget = (task.retries ?? 0) + 1;
292
296
  if (task.attempts > budget || task.status === "pending" && task.attempts >= budget) return false;
293
297
  if (!(task.activity === void 0 || isTaskActivity(task.activity))) return false;
@@ -422,7 +426,7 @@ function isTaskActivityInput(value) {
422
426
  * Tests whether an unknown value is valid persisted task activity.
423
427
  *
424
428
  * @remarks
425
- * The persisted counterpart of {@link isTaskActivityInput}: the same frame plus the REQUIRED
429
+ * The persisted counterpart of {@link isTaskActivityInput}: the same frame plus the required
426
430
  * `operations`, `constraints`, and a finite non-negative `updated` stamp, because a stored frame
427
431
  * has already been accepted and normalized. Total — a hostile prototype or accessor answers
428
432
  * `false` rather than throwing.
@@ -494,11 +498,11 @@ function captureWorkflowOptions(options) {
494
498
  });
495
499
  }
496
500
  /**
497
- * Tests whether a {@link LifecycleStatus} is TERMINAL — a node in this state will not
498
- * transition further.
501
+ * Tests whether a {@link LifecycleStatus} is terminal — `completed`, `failed`, `skipped`, or
502
+ * `stopped`, the states a node never transitions out of.
499
503
  *
500
504
  * @remarks
501
- * The ONE terminal check across every tier (AGENTS.md § Design laws, "one concept, one term"):
505
+ * The one terminal check across every tier (AGENTS.md § Design laws, "one concept, one term"):
502
506
  * a task, a phase, and a workflow share the same {@link LifecycleStatus} vocabulary, so a
503
507
  * single predicate covers them — {@link derivePhaseStatus} and {@link deriveWorkflowStatus}
504
508
  * both consult it to tell a settled node from an in-flight one. It reads the terminal set from
@@ -513,12 +517,13 @@ function isTerminalStatus(status) {
513
517
  return TERMINAL_STATUSES.includes(status);
514
518
  }
515
519
  /**
516
- * Tests whether a driving run must stop giving a workflow more work.
520
+ * Tests whether a driving run must stop giving a workflow — or one forced phase of it —
521
+ * more work.
517
522
  *
518
523
  * @remarks
519
524
  * The halt gate a {@link import('./WorkflowRunner.js').WorkflowRunner} consults before starting a
520
525
  * phase, before dispatching a task, and after every cooperative gate. A workflow is halted after
521
- * its derived status is terminal but NOT `completed` — a `bail: true` failure, a caller's own
526
+ * its derived status is terminal but not `completed` — a `bail: true` failure, a caller's own
522
527
  * graceful `stop()`, or a forced `skip`. `completed` is excluded deliberately: a workflow that
523
528
  * completed vacuously is settled, not halted, and the distinction is what keeps the run from
524
529
  * sweeping a finished tree. When a `phase` is supplied, its own forced `skipped` / `stopped` halts
@@ -541,11 +546,11 @@ function isHalted(workflow, phase) {
541
546
  return isTerminalStatus(status) && status !== "completed" || phase?.status === "skipped" || phase?.status === "stopped";
542
547
  }
543
548
  /**
544
- * Tests whether forcing a workflow `stopped` would still record something.
549
+ * Tests whether forcing a workflow `stopped` would still record the cancellation.
545
550
  *
546
551
  * @remarks
547
552
  * `stop()` is a no-op after a workflow's status becomes terminal, so a run that must record a
548
- * cancellation forces it only while this holds. It is NOT the negation of
553
+ * cancellation forces it only while this holds. It is not the negation of
549
554
  * {@link isTerminalStatus}: `completed` and `skipped` both pass, because a run-level cancel that
550
555
  * lands on a vacuously-completed or fully-skipped tree still records `stopped` as the outcome the
551
556
  * caller asked for. Only an already-`failed` or already-`stopped` workflow has a terminal state
@@ -571,7 +576,7 @@ function isStoppable(workflow) {
571
576
  * @remarks
572
577
  * A run that walked every phase and still derives `pending` executed nothing — zero phases, or
573
578
  * every phase empty — so it is vacuously done and the run settles it `completed`. Gated on
574
- * EXACTLY `pending` so a real `completed`, a `bail: true` `failed`, a `stopped`, or a derived
579
+ * exactly `pending` so a real `completed`, a `bail: true` `failed`, a `stopped`, or a derived
575
580
  * `skipped` is never overridden. The tree-is-empty half of the rule is
576
581
  * {@link WorkflowInterface.complete}'s own guard, which refuses a pending tree that still holds
577
582
  * tasks.
@@ -595,7 +600,7 @@ function isCompletable(workflow) {
595
600
  * running task's folded signal, and only two of them mean "skip this task": the task's own
596
601
  * `signal` (its `stop` / `skip`), and the unit or run signal (a sibling fail-fast under
597
602
  * `bail: true`, or a run-level abort / timeout / budget / `destroy`). A bare per-attempt timeout
598
- * fires NEITHER — it aborts only the deadline portion of the attempt signal — so it stays a
603
+ * fires neither — it aborts only the deadline portion of the attempt signal — so it stays a
599
604
  * retryable failure of that attempt instead of skipping the leaf and losing the recorded fault.
600
605
  * Read fresh at each call so a cancel that lands mid-dispatch is seen.
601
606
  *
@@ -617,7 +622,7 @@ function isSkipping(task, controller, runSignal) {
617
622
  *
618
623
  * @remarks
619
624
  * A retried task is re-dispatched while an earlier attempt's handler may still be resolving, so
620
- * every settlement path re-checks ownership before touching the leaf. Ownership needs BOTH
625
+ * every settlement path re-checks ownership before touching the leaf. Ownership needs both
621
626
  * halves: the run-local `owners` ledger must still name this attempt, and the live task's own
622
627
  * `attempts` tally must still match it. A superseded attempt reads `false` and returns without
623
628
  * recording anything, so a late resolution can never overwrite the newer attempt's outcome.
@@ -638,14 +643,14 @@ function ownsAttempt(owners, task, attempt) {
638
643
  return owners.get(task.id) === attempt && task.attempts === attempt;
639
644
  }
640
645
  /**
641
- * Derives a phase's status from its tasks' statuses (tasks are concurrent, so this
642
- * is an order-insensitive reduction).
646
+ * Derives a phase's status from its tasks' statuses, the most severe terminal status winning
647
+ * (tasks are concurrent, so this is an order-insensitive reduction).
643
648
  *
644
649
  * @remarks
645
650
  * The truth table (most-severe terminal wins; `bail`-agnostic — a phase surfaces a
646
651
  * task failure as `failed` so the workflow's `bail` policy can decide):
647
652
  * - no tasks ⇒ `pending`.
648
- * - any task `running`, OR a mix of started-and-unsettled tasks (some non-`pending`
653
+ * - any task `running`, or a mix of started-and-unsettled tasks (some non-`pending`
649
654
  * but not all terminal) ⇒ `running`.
650
655
  * - every task `pending` ⇒ `pending`.
651
656
  * - all terminal: any `failed` ⇒ `failed`; else any `stopped` ⇒ `stopped`; else any
@@ -669,23 +674,24 @@ function derivePhaseStatus(tasks) {
669
674
  }
670
675
  /**
671
676
  * Derives a workflow's status from its phases' {@link PhaseDerivation}s — each phase's status
672
- * paired with the EFFECTIVE `bail` it ran under (`phase.bail ?? workflow.bail`) — so the
673
- * failure outcome is PER-PHASE-bail-aware (phases are sequential, but the derivation is an
674
- * order-insensitive reduction over the settled set).
677
+ * paired with the effective `bail` it ran under (`phase.bail ?? workflow.bail`) — so the
678
+ * failure outcome is aware of each phase's own policy, and `failed` is reachable only where
679
+ * that policy is `true` (phases are sequential, but the derivation is an order-insensitive
680
+ * reduction over the settled set).
675
681
  *
676
682
  * @remarks
677
683
  * `bail` is a per-phase override, so it is carried on each
678
- * {@link PhaseDerivation} rather than passed as one scalar. It is the ONLY axis that changes
684
+ * {@link PhaseDerivation} rather than passed as one scalar. It is the only axis that changes
679
685
  * the failure outcome, decided per phase:
680
686
  * - **A `failed` phase whose effective `bail` is `true` (halt)** propagates ⇒ the workflow is
681
687
  * `failed` (the database-transaction halt) — even when the workflow default is graceful.
682
- * - **A `failed` phase whose effective `bail` is `false` (graceful)** is DATA, not a workflow
683
- * failure — it folds into completion like a settled phase. A graceful failed phase NEVER
688
+ * - **A `failed` phase whose effective `bail` is `false` (graceful)** is data, not a workflow
689
+ * failure — it folds into completion like a settled phase. A graceful failed phase never
684
690
  * makes the workflow `failed` — even when the workflow default is strict.
685
691
  *
686
692
  * The rest of the table is shared:
687
693
  * - no phases ⇒ `pending`.
688
- * - any phase `running`, OR a mix of started-and-unsettled phases (some non-`pending`
694
+ * - any phase `running`, or a mix of started-and-unsettled phases (some non-`pending`
689
695
  * but not all terminal) ⇒ `running`.
690
696
  * - every phase `pending` ⇒ `pending`.
691
697
  * - all terminal (a `failed` phase counts as terminal here): any `stopped` ⇒ `stopped`; else
@@ -705,18 +711,19 @@ function deriveWorkflowStatus(phases) {
705
711
  return "skipped";
706
712
  }
707
713
  /**
708
- * Derives the PENDING SUFFIX boundary of a positional list of {@link LifecycleStatus}es —
709
- * the index of the first entry in the contiguous trailing run of `pending` entries.
714
+ * Derives the pending-suffix boundary of a positional list of {@link LifecycleStatus}es —
715
+ * the index of the first entry in the contiguous trailing run of `pending` entries, or the
716
+ * list's length where it has none.
710
717
  *
711
718
  * @remarks
712
719
  * The native, hook-free replacement for a runner-installed cursor: a
713
720
  * {@link import('./types.js').WorkflowInterface}'s `add` / `remove` / `move` / `update`
714
721
  * reads this over its live phases' statuses to decide which positions are safe to edit.
715
- * Because entries run SEQUENTIALLY (phases sequential, AGENTS determinism), every
716
- * already-started entry forms a contiguous LEADING prefix and every still-`pending`
722
+ * Because entries run sequentially (phases sequential, AGENTS determinism), every
723
+ * already-started entry forms a contiguous leading prefix and every still-`pending`
717
724
  * entry forms the trailing suffix — so the boundary is the count of leading
718
725
  * non-`pending` entries: the index of the first `pending` entry, or the full length when
719
- * none is `pending` (nothing is safely editable). A `pending` container's entries are ALL
726
+ * none is `pending` (nothing is safely editable). A `pending` container's entries are all
720
727
  * `pending`, so the boundary is `0` and every position is naturally accepted — callers
721
728
  * need no special case for that.
722
729
  *
@@ -752,15 +759,16 @@ function canTransitionTask(from, to) {
752
759
  return TASK_TRANSITIONS[from].includes(to);
753
760
  }
754
761
  /**
755
- * Resolves a task's runtime silence window against its workflow default.
762
+ * Resolves a task's runtime silence window against its workflow default, to a host-safe
763
+ * `1..MAX_TIMER_MS` window or to `undefined` where the task disables it.
756
764
  *
757
765
  * @param value - The task-level override; any present non-positive or non-finite value disables
758
766
  * @param fallback - The workflow-level default
759
767
  * @returns A host-safe effective window (`1..MAX_TIMER_MS`), or `undefined`
760
768
  */
761
769
  function resolveTaskSilence(value, fallback) {
762
- if (value !== void 0) return Number.isFinite(value) && value > 0 && value <= 2147483647 ? value : void 0;
763
- return fallback !== void 0 && Number.isFinite(fallback) && fallback > 0 && fallback <= 2147483647 ? fallback : void 0;
770
+ if (value !== void 0) return isFiniteNumber(value) && value > 0 && value <= 2147483647 ? value : void 0;
771
+ return fallback !== void 0 && isFiniteNumber(fallback) && fallback > 0 && fallback <= 2147483647 ? fallback : void 0;
764
772
  }
765
773
  /**
766
774
  * Boxes a value as a {@link Success} — the graceful outcome half of a {@link Result}.
@@ -806,8 +814,8 @@ function failure(error) {
806
814
  */
807
815
  function errorToMessage(error) {
808
816
  try {
809
- const message = error instanceof Error ? error.message : String(error);
810
- return typeof message === "string" && message.length > 0 ? message : "unknown failure";
817
+ const message = isError(error) ? error.message : String(error);
818
+ return isString(message) && message.length > 0 ? message : "unknown failure";
811
819
  } catch {
812
820
  return "unknown failure";
813
821
  }
@@ -819,7 +827,7 @@ function errorToMessage(error) {
819
827
  *
820
828
  * @remarks
821
829
  * The shared leaf behind {@link import('./phases/Phase.js').Phase} and
822
- * {@link import('./Workflow.js').Workflow}'s own `#failure` — each gathers ITS tier's
830
+ * {@link import('./Workflow.js').Workflow}'s own `#failure` — each gathers its tier's
823
831
  * results (a phase's own settled tasks, a workflow's flattened `results()`) and feeds
824
832
  * them here; the tier-local method keeps the invariant throw (a derived `failed`
825
833
  * status means a failing result exists) because throwing on `undefined` is
@@ -859,7 +867,7 @@ function buildWorkflowContext(node) {
859
867
  * Builds a {@link PhaseContext} — a phase's own identity plus a back-reference to its
860
868
  * workflow — from the parent {@link WorkflowContext} and the phase node's identity.
861
869
  *
862
- * @param workflow - The parent workflow context (the lineage pointer UP the tree)
870
+ * @param workflow - The parent workflow context (the lineage pointer up the tree)
863
871
  * @param node - The phase's identity (`id` / `name` / optional `description`)
864
872
  * @returns The {@link PhaseContext}
865
873
  */
@@ -874,7 +882,7 @@ function buildPhaseContext(workflow, node) {
874
882
  * (and, transitively, its workflow) — from the parent {@link PhaseContext} and the task
875
883
  * node's identity.
876
884
  *
877
- * @param phase - The parent phase context (carrying the full lineage UP the tree)
885
+ * @param phase - The parent phase context (carrying the full lineage up the tree)
878
886
  * @param node - The task's identity (`id` / `name` / optional `description`)
879
887
  * @returns The {@link TaskContext}
880
888
  */
@@ -885,9 +893,9 @@ function buildTaskContext(phase, node) {
885
893
  });
886
894
  }
887
895
  /**
888
- * Converts a {@link WorkflowDefinition} into an INITIAL {@link WorkflowSnapshot} — every
889
- * node `pending`, no results, empty metadata — so the live W-b tree has ONE construction
890
- * path (snapshot-driven) for both a fresh build and a restore.
896
+ * Converts a {@link WorkflowDefinition} into an initial {@link WorkflowSnapshot} — every
897
+ * node `pending`, no results, empty metadata — so the live W-b tree has one construction
898
+ * path, snapshot-driven, for a fresh build and for a restore alike.
891
899
  *
892
900
  * @remarks
893
901
  * The structural fields (`id` / `name` / `description` + the ordered phases / tasks)
@@ -896,20 +904,20 @@ function buildTaskContext(phase, node) {
896
904
  * `retries` / `timeout` (persisted on the {@link TaskSnapshot}, like `bail` / `concurrency`,
897
905
  * so a restore + a {@link import('./types.js').WorkflowOptions.functions} registry resumes
898
906
  * real work). The `bail` policy carries over — at the
899
- * workflow tier AND, per phase, the
900
- * EFFECTIVE policy (`phase.bail ?? workflowBail`) on each {@link PhaseSnapshot} — so the seeded
907
+ * workflow tier and, per phase, the
908
+ * effective policy (`phase.bail ?? workflowBail`) on each {@link PhaseSnapshot} — so the seeded
901
909
  * snapshot is self-contained; a fresh seed has no `override`. `created` / `updated` are stamped at that point.
902
910
  * {@link import('./factories.js').createWorkflow} builds from this.
903
911
  *
904
- * The optional `bail` override is the EFFECTIVE workflow policy the tree will run under
912
+ * The optional `bail` override is the effective workflow policy the tree will run under
905
913
  * (`createWorkflow` / the runner resolve `options.bail ?? definition.bail ?? DEFAULT_BAIL` and
906
- * pass it here), so an `options.bail` override reaches BOTH the workflow tier AND the
914
+ * pass it here), so an `options.bail` override reaches both the workflow tier and the
907
915
  * inheritance default of every phase that declares no `bail` of its own — otherwise the
908
916
  * per-phase seeds would silently ignore the override. Omitted ⇒ the definition's own `bail`
909
917
  * (defaulting to the graceful {@link import('./constants.js').DEFAULT_BAIL}).
910
918
  *
911
919
  * @param definition - The workflow definition to seed from
912
- * @param bail - The EFFECTIVE workflow bail to seed both tiers with (defaults to the definition's)
920
+ * @param bail - The effective workflow bail to seed both tiers with (defaults to the definition's)
913
921
  * @returns An initial, all-`pending` {@link WorkflowSnapshot}
914
922
  */
915
923
  function definitionToSnapshot(definition, bail) {
@@ -931,7 +939,7 @@ function definitionToSnapshot(definition, bail) {
931
939
  * {@link PhaseSnapshot} — the per-phase step of {@link definitionToSnapshot}.
932
940
  *
933
941
  * @remarks
934
- * The snapshot persists the EFFECTIVE failure policy this phase runs under: the phase's own
942
+ * The snapshot persists the effective failure policy this phase runs under: the phase's own
935
943
  * `bail` when it declares one, else the `workflowBail` it inherits — so a restore reinstates
936
944
  * the same per-phase policy without a silent default (`effectiveBail = phase.bail ?? workflowBail`).
937
945
  * `concurrency` (the resource throttle) carries over verbatim, omitted when undefined.
@@ -1061,7 +1069,7 @@ function recoverWorkflowSnapshot(snapshot) {
1061
1069
  *
1062
1070
  * @remarks
1063
1071
  * The equality rule a lineage check needs: two descriptions match when they are the same value
1064
- * AND that value is either a string or genuine absence. Anything else — a number, an object, a
1072
+ * and that value is either a string or genuine absence. Anything else — a number, an object, a
1065
1073
  * `null` — never matches, even against itself, so a lineage stamped with a non-string description
1066
1074
  * is rejected rather than silently accepted.
1067
1075
  *
@@ -1077,14 +1085,14 @@ function recoverWorkflowSnapshot(snapshot) {
1077
1085
  * ```
1078
1086
  */
1079
1087
  function matchesDescription(left, right) {
1080
- return left === right && (left === void 0 || typeof left === "string");
1088
+ return left === right && (left === void 0 || isString(left));
1081
1089
  }
1082
1090
  /**
1083
1091
  * Tests a result's lineage against its containing snapshot nodes.
1084
1092
  *
1085
1093
  * @remarks
1086
1094
  * The four arguments are the result and the three snapshot nodes it claims to belong to, read
1087
- * from the OUTSIDE in: a {@link TaskResult} is self-describing, so restoring one is only safe
1095
+ * from the outside in: a {@link TaskResult} is self-describing, so restoring one is only safe
1088
1096
  * when every identity it carries agrees with the tree it was found in. It checks the exact key
1089
1097
  * set at each level, that `status` equals the owning task's, and that the `task` / `phase` /
1090
1098
  * `workflow` contexts — including the nested `task.phase.workflow` lineage — carry the same `id`,
@@ -1311,7 +1319,7 @@ function scheduleHost(start, signal) {
1311
1319
  * {@link import('./Scheduler.js').Scheduler}, both Node primitives, and every browser backend's
1312
1320
  * `delay` and macrotask fallback route here, so the timer is armed and cleared in one place. It
1313
1321
  * composes {@link scheduleHost}, which owns listener safety, the cancellation race, the exact
1314
- * caller reason, and once-only settlement. It does NOT validate `ms`: the value passes straight to
1322
+ * caller reason, and once-only settlement. It does not validate `ms`: the value passes straight to
1315
1323
  * the host `setTimeout`, which clamps a negative value or `NaN` to about zero, so an
1316
1324
  * out-of-domain `ms` resumes on the next host turn rather than throwing. Pass a non-negative
1317
1325
  * finite `ms`.
@@ -1334,10 +1342,10 @@ function delayHost(ms, signal) {
1334
1342
  }
1335
1343
  /**
1336
1344
  * Parks until `signal` aborts — a promise-parked wait, never a timer or
1337
- * busy-loop, that NEVER rejects.
1345
+ * busy-loop, that resolves on the abort event and never rejects.
1338
1346
  *
1339
1347
  * @remarks
1340
- * Resolves IMMEDIATELY when `signal` is already aborted; otherwise attaches a one-shot
1348
+ * Resolves immediately when `signal` is already aborted; otherwise attaches a one-shot
1341
1349
  * `abort` listener and resolves when it fires, removing the listener either way. The
1342
1350
  * shared leaf behind the duplicate abort-wiring an execution engine otherwise hand-rolls
1343
1351
  * at every fold point.
@@ -1372,9 +1380,9 @@ function parkSignal(signal) {
1372
1380
  * (`task` / `tasks`, `phase` / `phases`). The `Map`'s insertion order is the single source of
1373
1381
  * positional truth; `add` and `move` rebuild it through the pure
1374
1382
  * {@link import('./helpers.js').insertEntry} / {@link import('./helpers.js').moveEntry} leaves.
1375
- * - **Gated mutation API.** `append` is the build-time wiring path and THROWS on a
1383
+ * - **Gated mutation API.** `append` is the build-time wiring path and throws on a
1376
1384
  * duplicate id; `add` / `remove` / `move` / `update` return a graceful `MUTATION`
1377
- * {@link WorkflowError} failure instead. Gating reads ONLY the target's own existence, `pending`
1385
+ * {@link WorkflowError} failure instead. Gating reads only the target's own existence, `pending`
1378
1386
  * status, id, and bounds — a container's own status is the owning entity's gate, applied before
1379
1387
  * it delegates here.
1380
1388
  * - **Event-free.** A purely structural container; the entity that owns it emits on success.
@@ -1467,13 +1475,13 @@ var Collection = class {
1467
1475
  * browser and Node.
1468
1476
  *
1469
1477
  * @remarks
1470
- * - **Cross-environment.** Uses ONLY `setTimeout` / `clearTimeout` — universally
1478
+ * - **Cross-environment.** Uses only `setTimeout` / `clearTimeout` — universally
1471
1479
  * available. It deliberately avoids env-specific fast paths (`setImmediate`,
1472
1480
  * `scheduler.yield`, `requestAnimationFrame`, `node:timers/promises`,
1473
1481
  * `MessageChannel`); those belong to the environment backends, built with the
1474
1482
  * agent loop that consumes them.
1475
1483
  * - **`yield` is a macrotask host-turn, not a microtask.** `yield()` waits on a
1476
- * `setTimeout(0)`, NOT `queueMicrotask`. A microtask drains before the host
1484
+ * `setTimeout(0)`, not `queueMicrotask`. A microtask drains before the host
1477
1485
  * regains control, so it would not actually let pending I/O, timers, or
1478
1486
  * rendering run — it only defers within the current task. A zero-delay timer is
1479
1487
  * the correct cross-environment "give the host a turn".
@@ -1498,7 +1506,7 @@ var Collection = class {
1498
1506
  var Scheduler = class {
1499
1507
  /**
1500
1508
  * Yields control back to the host so other tasks (I/O, timers, rendering) can
1501
- * run, then resumes — a macrotask turn through `setTimeout(0)` (NOT a microtask,
1509
+ * run, then resumes — a macrotask turn through `setTimeout(0)` (not a microtask,
1502
1510
  * which would resume before the host regains control).
1503
1511
  */
1504
1512
  yield(options) {
@@ -1753,28 +1761,28 @@ var phaseUpdateShape = objectShape({
1753
1761
  //#region src/core/stores/DatabaseWorkflowStore.ts
1754
1762
  /**
1755
1763
  * Implements a {@link WorkflowStoreInterface} backed by one table of the `databases` layer — a
1756
- * workflow's durable run-state IS a row, so persistence reduces to keyed point-access
1764
+ * workflow's durable run state is a row, so persistence reduces to keyed point-access
1757
1765
  * (`get` / `set` / `delete`) over a `TableInterface`, the driver-pluggable twin of the
1758
1766
  * plain-`Map` {@link import('./MemoryWorkflowStore.js').MemoryWorkflowStore}.
1759
1767
  *
1760
1768
  * @remarks
1761
1769
  * The store is driver-agnostic: it holds a single {@link TableInterface} whose backend
1762
1770
  * (memory, JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a
1763
- * JSON / SQLite / IndexedDB backend swaps in WITHOUT touching the runner or the entity tree
1771
+ * JSON / SQLite / IndexedDB backend swaps in without touching the runner or the entity tree
1764
1772
  * — the same seam as `@orkestrel/queue`'s `DatabaseQueueStore`.
1765
1773
  * The driver defaults to memory ({@link import('../factories.js').createDatabaseWorkflowStore}
1766
- * passes `createMemoryDriver()`), so it ALSO works in memory out of the box; you opt into the
1774
+ * passes `createMemoryDriver()`), so it also works in memory out of the box; you opt into the
1767
1775
  * durable plumbing by passing a JSON / SQLite / IndexedDB driver.
1768
1776
  *
1769
- * The {@link WorkflowSnapshot} is stored as ONE OPAQUE JSON COLUMN — the table is a row of
1777
+ * The {@link WorkflowSnapshot} is stored as one opaque JSON column — the table is a row of
1770
1778
  * `{ id; snapshot }` ({@link WorkflowSnapshotRow}), the snapshot the whole JSON blob (a `rawShape`
1771
1779
  * column the factory builds) — exactly as `DatabaseQueueStore` stores its `input`. The snapshot is
1772
- * already a COMPLETE, self-contained, pure-JSON payload, so storing it whole is lossless AND
1780
+ * already a complete, self-contained, pure-JSON payload, so storing it whole is lossless and
1773
1781
  * sidesteps a TS2589 instantiation-depth blow-up: a structured multi-column table would force the
1774
1782
  * contract to `Infer` the deeply-nested snapshot shape (workflow → phases → tasks → results),
1775
1783
  * tripping the compiler — one JSON column keeps the row type flat (`snapshot` reads back as `unknown`).
1776
1784
  *
1777
- * - **`set(snapshot)` upserts under the snapshot's OWN `id`** (no separate id param) — it writes
1785
+ * - **`set(snapshot)` upserts under the snapshot's own `id`** (no separate id param) — it writes
1778
1786
  * the row `{ id: snapshot.id, snapshot }`.
1779
1787
  * - **`get(id)` resolves the stored snapshot for an id**, owning and narrowing the opaque JSON
1780
1788
  * column back to a {@link WorkflowSnapshot} through
@@ -1784,9 +1792,9 @@ var phaseUpdateShape = objectShape({
1784
1792
  * differs from the requested key rejects with normalized `RESTORE` evidence.
1785
1793
  * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
1786
1794
  *
1787
- * UNLIKE the server package's `SessionStoreInterface` there is NO
1795
+ * unlike the server package's `SessionStoreInterface` there is no
1788
1796
  * idle-TTL / eviction — a persisted run-state is durable orchestration state that lives until an
1789
- * explicit `delete`. The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the
1797
+ * explicit `delete`. The public surface is exactly `get` / `set` / `delete` — no extra members (the
1790
1798
  * guide's method bijection with {@link WorkflowStoreInterface}). Restore stays a caller concern: read a
1791
1799
  * snapshot back and rebuild the live tree with {@link import('../factories.js').createRestoredWorkflow}.
1792
1800
  *
@@ -1820,7 +1828,7 @@ var DatabaseWorkflowStore = class {
1820
1828
  if (row === void 0) return void 0;
1821
1829
  return cloneWorkflowSnapshot(row.snapshot, id);
1822
1830
  }
1823
- /** Inserts or replaces under the snapshot's OWN `id` (no separate id param) — the row is `{ id, snapshot }`. */
1831
+ /** Inserts or replaces under the snapshot's own `id` (no separate id param) — the row is `{ id, snapshot }`. */
1824
1832
  async set(snapshot) {
1825
1833
  const owned = cloneWorkflowSnapshot(snapshot);
1826
1834
  await this.#table.set({
@@ -1837,27 +1845,24 @@ var DatabaseWorkflowStore = class {
1837
1845
  //#region src/core/stores/MemoryWorkflowStore.ts
1838
1846
  /**
1839
1847
  * Implements the in-memory {@link WorkflowStoreInterface} — a process-lifetime `Map` of
1840
- * {@link WorkflowSnapshot}s keyed by workflow id, the DEFAULT store
1841
- * {@link import('../factories.js').createMemoryWorkflowStore} builds.
1848
+ * {@link WorkflowSnapshot}s keyed by workflow id, the default store
1849
+ * {@link import('../factories.js').createMemoryWorkflowStore} builds. It expires nothing: a
1850
+ * persisted snapshot lives until an explicit `delete`.
1842
1851
  *
1843
1852
  * @remarks
1844
1853
  * A plain `Map<string, WorkflowSnapshot>` (the snapshot is already pure,
1845
- * self-contained JSON, so no encoding is needed for the memory tier). UNLIKE the server
1846
- * package's `SessionStoreInterface`'s memory store there is
1847
- * NO idle-TTL and NO eviction: a persisted workflow run-state is durable orchestration state
1848
- * that lives until an explicit `delete`, never silently aging out (a run that vanished
1849
- * mid-flight would be a silent data loss, not a freed session). A durable backend (JSON /
1850
- * SQLite / IndexedDB) swaps in through the SAME interface without touching the runner or the
1854
+ * self-contained JSON, so no encoding is needed for the memory tier). A durable backend (JSON /
1855
+ * SQLite / IndexedDB) swaps in through the same interface without touching the runner or the
1851
1856
  * entity tree — its driver-pluggable twin is
1852
1857
  * {@link import('./DatabaseWorkflowStore.js').DatabaseWorkflowStore} (the snapshot as one opaque
1853
1858
  * JSON column), exactly as `@orkestrel/queue`'s `MemoryQueueStore`
1854
1859
  * twins `DatabaseQueueStore`.
1855
1860
  *
1856
1861
  * - **`get` resolves the persisted snapshot for an id**, or `undefined` if none is stored.
1857
- * - **`set` inserts / replaces under the snapshot's OWN `id`** (no separate id param).
1862
+ * - **`set` inserts / replaces under the snapshot's own `id`** (no separate id param).
1858
1863
  * - **`delete` drops a snapshot by id**; an absent id is a no-op (no throw).
1859
1864
  *
1860
- * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the guide's method
1865
+ * The public surface is exactly `get` / `set` / `delete` — no extra members (the guide's method
1861
1866
  * bijection with {@link WorkflowStoreInterface}). Restore is a caller concern: read a snapshot
1862
1867
  * back and rebuild the live tree with {@link import('../factories.js').createRestoredWorkflow}.
1863
1868
  *
@@ -1900,27 +1905,27 @@ var MemoryWorkflowStore = class {
1900
1905
  * - **Guarded transitions.** `start` (→ `running`), then `complete(value)`
1901
1906
  * (→ `completed`, records a {@link import('@orkestrel/contract').Success}), `fail(error)`
1902
1907
  * (→ `failed`, records a {@link import('@orkestrel/contract').Failure}), `skip` (→ `skipped`),
1903
- * `stop` (→ `stopped`). Each consults {@link canTransitionTask} FIRST and throws a
1908
+ * `stop` (→ `stopped`). Each consults {@link canTransitionTask} first and throws a
1904
1909
  * `TRANSITION` {@link WorkflowError} on an illegal move (for example, completing a
1905
1910
  * non-`running` task) — the legal graph is the single source of truth, so the leaf can never
1906
1911
  * reach an impossible state.
1907
1912
  * - **Snapshot fidelity.** A leaf needs no override: `skipped` / `stopped` are explicit terminal
1908
1913
  * statuses, and restore reinstates the leaf directly from {@link TaskSnapshot.status}.
1909
1914
  * - **The cascade.** Every status change records its boxed result (when any), fires the leaf's
1910
- * OWN event, THEN calls the parent phase's `#recompute` (injected at construction) so the
1911
- * transition propagates UP (Task → Phase → Workflow re-derive). The own-event-before-cascade
1912
- * order means an observer sees the CAUSE (this leaf changed) before the EFFECT (the parents
1915
+ * own event, then calls the parent phase's `#recompute` (injected at construction) so the
1916
+ * transition propagates up (Task → Phase → Workflow re-derive). The own-event-before-cascade
1917
+ * order means an observer sees the cause (this leaf changed) before the effect (the parents
1913
1918
  * re-derive) — the project precedent (`Runner.#settle` emits its own `fail` before propagating).
1914
1919
  * - **Observable.** The owned {@link emitter} ({@link TaskEventMap}) fires the
1915
- * matching event strictly AFTER the state change, BEFORE the cascade; the emitter isolates
1920
+ * matching event strictly after the state change, before the cascade; the emitter isolates
1916
1921
  * a listener throw and routes it to its `error` handler (the `error` option), so a buggy
1917
1922
  * observer can never corrupt a transition.
1918
- * - **Declarative config.** `behavior` / `retries` / `timeout` PERSIST in a
1923
+ * - **Declarative config.** `behavior` / `retries` / `timeout` persist in a
1919
1924
  * {@link TaskSnapshot} (like a phase's `bail` / `concurrency`), carried verbatim from the
1920
1925
  * matching {@link import('../types.js').TaskDefinition} / {@link TaskSnapshot} field. `handler`
1921
- * is the RUNTIME-ONLY counterpart — `behavior` resolved ONCE at construction against the
1926
+ * is the runtime-only counterpart — `behavior` resolved once at construction against the
1922
1927
  * workflow-level {@link import('../types.js').WorkflowOptions.functions} registry — and is
1923
- * NEVER persisted; `undefined` when `behavior` is omitted or unregistered. Only omission is a
1928
+ * never persisted; `undefined` when `behavior` is omitted or unregistered. Only omission is a
1924
1929
  * deliberate no-op; unresolved named work is rejected before dispatch.
1925
1930
  */
1926
1931
  var Task = class {
@@ -2078,7 +2083,7 @@ var Task = class {
2078
2083
  }
2079
2084
  fail(error) {
2080
2085
  const origin = error.origin === "handler" || error.origin === "timeout" || error.origin === "recovery" ? error.origin : "handler";
2081
- const message = typeof error.message === "string" && error.message.length > 0 ? error.message : "unknown failure";
2086
+ const message = isString(error.message) && error.message.length > 0 ? error.message : "unknown failure";
2082
2087
  this.#transition("failed");
2083
2088
  this.#finish();
2084
2089
  const result = this.#record("failed", Object.freeze({
@@ -2117,7 +2122,7 @@ var Task = class {
2117
2122
  this.#emitter.emit("report", activity);
2118
2123
  return success(activity);
2119
2124
  } catch (error) {
2120
- return failure(error instanceof WorkflowError ? error : new WorkflowError("MUTATION", "task activity report was refused", { task: this.id }));
2125
+ return failure(isWorkflowError(error) ? error : new WorkflowError("MUTATION", "task activity report was refused", { task: this.id }));
2121
2126
  }
2122
2127
  }
2123
2128
  pulse() {
@@ -2144,12 +2149,12 @@ var Task = class {
2144
2149
  return this.#paused && this.#gate !== void 0 ? this.#gate.promise : Promise.resolve();
2145
2150
  }
2146
2151
  /**
2147
- * Applies a validated declarative patch to SELF (`name` / `description`).
2152
+ * Applies a validated declarative patch to self (`name` / `description`).
2148
2153
  *
2149
2154
  * @remarks
2150
2155
  * Defense-in-depth: the owning
2151
- * {@link import('../types.js').TaskManagerInterface.update} gates FIRST (target
2152
- * exists + `pending`), so this is the second, redundant check — it THROWS a
2156
+ * {@link import('../types.js').TaskManagerInterface.update} gates first (target
2157
+ * exists + `pending`), so this is the second, redundant check — it throws a
2153
2158
  * `MUTATION` {@link WorkflowError} unless this task's own `status` is `pending`.
2154
2159
  *
2155
2160
  * @param value - The {@link TaskUpdate} fields to apply
@@ -2258,10 +2263,10 @@ var Task = class {
2258
2263
  * drift apart.
2259
2264
  * - **Positional store.** `append` adds one live {@link TaskInterface} at the end (the build-time
2260
2265
  * wiring path), `task(id)` looks one up, `tasks()` lists them in positional order, `count` is
2261
- * the tally. A `skip` is a STATUS change on a stored task (never a removal), so order survives
2266
+ * the tally. A `skip` is a status change on a stored task (never a removal), so order survives
2262
2267
  * it; a snapshot RESTORE re-`append`s in the snapshot's order, reproducing it exactly.
2263
2268
  * - **Gated mutation API.** `add` / `remove` / `move` / `update` are the graceful
2264
- * `Result` counterparts to `append`, gating ONLY on the target's OWN existence/status/id/bounds
2269
+ * `Result` counterparts to `append`, gating only on the target's own existence/status/id/bounds
2265
2270
  * — a duplicate id, an absent/non-`pending` target, an out-of-bounds `index`, or a patch that
2266
2271
  * fails {@link taskUpdateShape} validation all fail gracefully with a `MUTATION`
2267
2272
  * {@link WorkflowError} instead of throwing.
@@ -2309,47 +2314,47 @@ var TaskManager = class {
2309
2314
  //#endregion
2310
2315
  //#region src/core/phases/Phase.ts
2311
2316
  /**
2312
- * Implements the live DERIVED state machine (W-b) for one phase — an observable whose
2317
+ * Implements the live derived state machine (W-b) for one phase — an observable whose
2313
2318
  * {@link LifecycleStatus} is computed from its tasks (never set directly) and recomputed
2314
2319
  * reactively as a task transitions (the middle tier of the cascade).
2315
2320
  *
2316
2321
  * @remarks
2317
2322
  * - **Derived status.** `status` is `#override` when one is in force, else
2318
2323
  * {@link derivePhaseStatus} over the live tasks' statuses. `#recompute` (passed to
2319
- * each child {@link Task}) re-derives on every child transition; a CHANGE emits the matching
2320
- * event AND escalates to the workflow (`#escalate`, the upward step of the cascade).
2321
- * - **Override.** `skip` / `stop` FORCE the phase's status (for example, skipping a whole
2322
- * phase), overriding the derived value; the override is PERSISTED in the snapshot's own
2323
- * `override` field and restored DIRECTLY (no divergence guess), so a forced phase round-trips.
2324
+ * each child {@link Task}) re-derives on every child transition; a change emits the matching
2325
+ * event and escalates to the workflow (`#escalate`, the upward step of the cascade).
2326
+ * - **Override.** `skip` / `stop` force the phase's status (for example, skipping a whole
2327
+ * phase), overriding the derived value; the override is persisted in the snapshot's own
2328
+ * `override` field and restored directly (no divergence guess), so a forced phase round-trips.
2324
2329
  * - **Children.** `tasks` is the lean {@link TaskManager} (an accessor + `count`,
2325
2330
  * no batch matrix); built positionally from the snapshot so order survives an interior `skip`.
2326
2331
  * `results()` collects the settled tasks' {@link TaskResult}s (the phase tier of the result
2327
- * tree); `workflow` navigates UP to the live parent.
2332
+ * tree); `workflow` navigates up to the live parent.
2328
2333
  * - **Observable.** The owned {@link emitter} ({@link PhaseEventMap}) fires
2329
2334
  * `start` / `complete` / `fail` / `pause` / `resume` / `skip` / `stop` after the
2330
2335
  * corresponding status or runtime-gate change. Status events fire after the phase recomputes
2331
2336
  * and before it escalates to the workflow, preserving child/phase cause before parent effect.
2332
2337
  * The emitter isolates a listener throw and routes it to its `error` handler (the `error`
2333
2338
  * option); `fail` carries the failing task's {@link TaskResult}.
2334
- * - **Structural API.** `add` / `remove` / `move` / `update` gate BEFORE
2339
+ * - **Structural API.** `add` / `remove` / `move` / `update` gate before
2335
2340
  * delegating to {@link tasks} (the manager gates the target's own existence/status/id/
2336
- * bounds), then emit the matching {@link PhaseEventMap} event on success only. NATIVE
2341
+ * bounds), then emit the matching {@link PhaseEventMap} event on success only. Native
2337
2342
  * gating, purely from this phase's own derived `status` (no runner-installed hook): while
2338
- * `pending`, any valid `index` is accepted; while `running`, `add` accepts ONLY a pure
2343
+ * `pending`, any valid `index` is accepted; while `running`, `add` accepts only a pure
2339
2344
  * append (a live runner subscribed to the `add` event picks it up), and `remove` / `move` /
2340
2345
  * `update` always fail gracefully (the tasks are already handed to the execution
2341
2346
  * substrate); while terminal, everything is refused.
2342
- * - **Patch.** `patch` applies a validated {@link PhaseUpdate} to SELF
2347
+ * - **Patch.** `patch` applies a validated {@link PhaseUpdate} to self
2343
2348
  * (`name` / `description` / `concurrency` / `bail`) — defense-in-depth: it throws a
2344
2349
  * `MUTATION` {@link WorkflowError} unless this phase's own `status` is `pending`, mirroring
2345
2350
  * the owning {@link WorkflowInterface.update}'s gate.
2346
- * - **Minting.** {@link add} MINTS a live {@link Task} from a {@link TaskDefinition}
2347
- * (converts it to a {@link TaskSnapshot}, builds the task wired to THIS phase) — the same
2351
+ * - **Minting.** {@link add} mints a live {@link Task} from a {@link TaskDefinition}
2352
+ * (converts it to a {@link TaskSnapshot}, builds the task wired to this phase) — the same
2348
2353
  * construction path {@link #append} uses at build time, so a live mint and a restored/built
2349
- * task are wired IDENTICALLY. At construction, the workflow-level
2354
+ * task are wired identically. At construction, the workflow-level
2350
2355
  * {@link import('../types.js').WorkflowRegistry} registry (threaded from
2351
2356
  * {@link import('../types.js').WorkflowOptions.functions}) resolves every unique initial `behavior`
2352
- * name ONCE before any task is built; siblings sharing a name receive the exact same captured
2357
+ * name once before any task is built; siblings sharing a name receive the exact same captured
2353
2358
  * runtime {@link import('../types.js').TaskInterface.handler}. A later live {@link add} reads
2354
2359
  * that name once from the retained registry at its own mint moment. An omitted or unregistered
2355
2360
  * `behavior` resolves to no handler; only omission is a no-op, while an unresolved present name makes
@@ -2357,7 +2362,7 @@ var TaskManager = class {
2357
2362
  * - **Runtime lifecycle.** `pause` / `resume` / `wait` mirror the workflow's own
2358
2363
  * quartet, scoped to this phase — a driving
2359
2364
  * {@link import('../types.js').WorkflowRunnerInterface.execute} gates a task's own
2360
- * pre-dispatch on the workflow's gate FIRST, then this phase's gate, WITHOUT touching
2365
+ * pre-dispatch on the workflow's gate first, then this phase's gate, without touching
2361
2366
  * {@link status} — `paused` is runtime-only, never persisted. `skip` / `stop` (this phase's
2362
2367
  * own terminal forcing) always release a parked {@link wait} waiter, mirroring
2363
2368
  * {@link import('../Workflow.js').Workflow.destroy}'s cascade — a permanently-ended phase
@@ -2616,7 +2621,7 @@ var Phase = class {
2616
2621
  * looks one up, `phases()` lists them in positional order, `count` is the tally. A snapshot
2617
2622
  * RESTORE re-`append`s in the snapshot's order, reproducing it exactly.
2618
2623
  * - **Gated mutation API.** `add` / `remove` / `move` / `update` are the graceful
2619
- * `Result` counterparts to `append`, gating ONLY on the target's OWN existence/status/id/bounds
2624
+ * `Result` counterparts to `append`, gating only on the target's own existence/status/id/bounds
2620
2625
  * — a duplicate id, an absent/non-`pending` target, an out-of-bounds `index`, or a patch that
2621
2626
  * fails {@link phaseUpdateShape} validation all fail gracefully with a `MUTATION`
2622
2627
  * {@link WorkflowError} instead of throwing.
@@ -2664,7 +2669,7 @@ var PhaseManager = class {
2664
2669
  //#endregion
2665
2670
  //#region src/core/Workflow.ts
2666
2671
  /**
2667
- * Implements the live DERIVED state machine (W-b) for a whole workflow — the observable ROOT
2672
+ * Implements the live derived state machine (W-b) for a whole workflow — the observable root
2668
2673
  * whose {@link LifecycleStatus} is computed from its phases under the `bail` policy and
2669
2674
  * recomputed reactively as the cascade propagates up from a task transition.
2670
2675
  *
@@ -2675,16 +2680,16 @@ var PhaseManager = class {
2675
2680
  * passes a persisted one). Each child {@link Phase} is wired to escalate to `#recompute`.
2676
2681
  * - **Derived status.** `status` is `#override` when forced, else
2677
2682
  * {@link deriveWorkflowStatus} over the live phases' statuses feeding `bail`. `failed` is
2678
- * reachable ONLY under `bail: true` (a single failed task halts the workflow); under
2683
+ * reachable only under `bail: true` (a single failed task halts the workflow); under
2679
2684
  * `bail: false` a failed phase folds into `completed`. `#recompute` diffs on each phase
2680
- * change; a CHANGE emits.
2681
- * - **Override.** `skip` / `stop` FORCE the status; an executed task-free pending tree
2682
- * may also be force-completed vacuously. The override is PERSISTED in the snapshot's own
2683
- * `override` field and restored DIRECTLY (no divergence guess). The snapshot also persists
2685
+ * change; a change emits.
2686
+ * - **Override.** `skip` / `stop` force the status; an executed task-free pending tree
2687
+ * may also be force-completed vacuously. The override is persisted in the snapshot's own
2688
+ * `override` field and restored directly (no divergence guess). The snapshot also persists
2684
2689
  * `bail`, so a restore re-derives status identically without a silent policy default.
2685
2690
  * - **Result tree.** `results()` flattens every phase's `results()` ({@link collectResults}) — the
2686
- * workflow tier; `phase(id)` + each `phase.task(id)` navigate DOWN, a task's `phase` / `workflow`
2687
- * navigate UP.
2691
+ * workflow tier; `phase(id)` + each `phase.task(id)` navigate down, a task's `phase` / `workflow`
2692
+ * navigate up.
2688
2693
  * - **Snapshot.** `snapshot()` serializes the whole live tree to a {@link WorkflowSnapshot} (pure
2689
2694
  * JSON); {@link import('./factories.js').createRestoredWorkflow} rebuilds an equivalent live tree.
2690
2695
  * - **Observable.** The owned {@link emitter} ({@link WorkflowEventMap}) fires
@@ -2692,17 +2697,17 @@ var PhaseManager = class {
2692
2697
  * corresponding status or runtime-gate change; the emitter isolates a listener throw and
2693
2698
  * routes it to its `error` handler (the `error` option); `fail` carries the failing task's
2694
2699
  * {@link TaskResult}.
2695
- * - **Structural API.** `add` / `remove` / `move` / `update` gate BEFORE
2700
+ * - **Structural API.** `add` / `remove` / `move` / `update` gate before
2696
2701
  * delegating to {@link phases} (the manager gates the target's own existence/status/id/
2697
- * bounds), then emit the matching {@link WorkflowEventMap} event on success only. NATIVE,
2702
+ * bounds), then emit the matching {@link WorkflowEventMap} event on success only. Native,
2698
2703
  * bottom-up gating (no runner-installed hook): refused outright while this workflow's own
2699
- * `status` is terminal; otherwise a target position must fall within the PENDING SUFFIX —
2704
+ * `status` is terminal; otherwise a target position must fall within the pending suffix —
2700
2705
  * the contiguous trailing run of `pending` phases — whose boundary is
2701
2706
  * {@link import('./helpers.js').deriveBoundary} over the live phases' statuses. A `pending`
2702
2707
  * workflow's phases are all `pending`, so the boundary is `0` and every position is
2703
2708
  * naturally accepted.
2704
2709
  * - **Runtime lifecycle.** `pause` / `resume` / `wait` gate execution at the runner's
2705
- * phase/task boundaries WITHOUT touching {@link status} — `paused` is runtime-only, never
2710
+ * phase/task boundaries without touching {@link status} — `paused` is runtime-only, never
2706
2711
  * persisted. `destroy` is a terminal teardown: it `stop`s every non-terminal task and
2707
2712
  * phase (releasing their gates and liveness resources), aborts {@link signal}, forces the
2708
2713
  * workflow `stop` override when needed, releases its parked waiter, and marks
@@ -2991,14 +2996,14 @@ var Workflow = class {
2991
2996
  * mints a live {@link WorkflowInterface} through the same construction path
2992
2997
  * {@link import('./factories.js').createWorkflow} takes (flowing the manager's
2993
2998
  * `functions` registry in) and stores it under `definition.id` — an already-present id
2994
- * OVERWRITES (last write wins). `count` is the map size, `workflow(id)` looks one up,
2999
+ * overwrites (last write wins). `count` is the map size, `workflow(id)` looks one up,
2995
3000
  * `workflows()` lists them in insertion order.
2996
3001
  * - **Durable open / save.** `open(id)` returns an already-registered workflow directly; same-id
2997
3002
  * misses share one hydration. A concurrent `add` wins, while `remove` / `clear` invalidate
2998
3003
  * earlier reads; wrong-key payloads reject with `RESTORE`. `save(id)` captures a registered
2999
3004
  * workflow's snapshot at invocation and serializes same-id writes without coupling other ids.
3000
3005
  * Both remain lenient without a store or registered id.
3001
- * - **Removal.** `remove` drops one by id, or a batch (array overload FIRST) — `true` only when
3006
+ * - **Removal.** `remove` drops one by id, or a batch (array overload first) — `true` only when
3002
3007
  * every id was removed. `clear` empties the registry.
3003
3008
  * - **No active pointer.** Unlike its `ConversationManager` / `WorkspaceManager` twins, there is
3004
3009
  * no `active` / `switch` — nothing in the workflow domain renders "the current workflow".
@@ -3008,7 +3013,7 @@ var Workflow = class {
3008
3013
  * const manager = new WorkflowManager({
3009
3014
  * functions: { compile: async (controller) => `built ${controller.task.id}` },
3010
3015
  * })
3011
- * const workflow = manager.add(definition) // minted, registered, RUNNABLE
3016
+ * const workflow = manager.add(definition) // minted, registered, runnable
3012
3017
  * manager.workflow(workflow.id) // the same workflow
3013
3018
  * manager.count // 1
3014
3019
  * ```
@@ -3234,11 +3239,11 @@ var RunHolder = class {
3234
3239
  * unit it dispatches, handing it the unit's `id`, `input`, the unit's `Abort`
3235
3240
  * handle, the queue attempt's `signal`, and a `spawn` callback that launches a
3236
3241
  * sibling through the same queue.
3237
- * - **Signal.** `signal` is the queue attempt's signal, which ANY-combines the
3242
+ * - **Signal.** `signal` is the queue attempt's signal, which any-combines the
3238
3243
  * unit's own abort, the runner-level abort (the runner aborts every unit), and
3239
3244
  * the per-attempt timeout — so it fires on any of the three. `aborted` and
3240
3245
  * `abort(reason)` delegate to the unit's `Abort` (the cancellation source of
3241
- * truth); because the attempt signal ANY-includes that abort, `abort()` fires
3246
+ * truth); because the attempt signal any-includes that abort, `abort()` fires
3242
3247
  * `signal` too.
3243
3248
  * - **`wait` promise-parks (never a timer).** It resolves the instant the unit's
3244
3249
  * `signal` fires (immediately if already aborted) through a one-shot listener — no
@@ -3298,13 +3303,13 @@ var Controller = class {
3298
3303
  * bounded concurrency, retries, and the per-attempt timeout are all the Queue's —
3299
3304
  * the Runner adds only orchestration (launching, ordering, draining, fail-fast).
3300
3305
  * - **Spawns actually run, results stay ordered (the B2 fix).** Declared inputs and
3301
- * `spawn`ed siblings flow through the SAME `#launch`, which appends the unit's `id`
3306
+ * `spawn`ed siblings flow through the same `#launch`, which appends the unit's `id`
3302
3307
  * to an ordered `#order` list and records its settled value into `#values` by `id`.
3303
3308
  * Results are read back as `#order.map(id => #values.get(id))` — declared first (in
3304
3309
  * input order), then spawns (in spawn order). There is no one-time task snapshot,
3305
3310
  * so a unit spawned mid-handler is run and ordered like any other.
3306
3311
  * - **`execute` awaits the full spawn closure through a count gate.** `#launch` increments
3307
- * an outstanding-unit `#count` BEFORE enqueuing and every settle decrements it,
3312
+ * an outstanding-unit `#count` before enqueuing and every settle decrements it,
3308
3313
  * resolving the `#drained` deferred at zero. Because `spawn` calls `#launch` (so
3309
3314
  * `#count += 1`) before the parent handler returns, the count never reaches zero
3310
3315
  * mid-run — `execute` parks on `#drained` and so awaits the entire transitive
@@ -3316,26 +3321,26 @@ var Controller = class {
3316
3321
  * spawn by a bounded handler can still deadlock — that caveat is the caller's.)
3317
3322
  * - **Per-unit Controller + signal.** Each unit gets a `Controller` carrying its `id`,
3318
3323
  * `input`, the unit's `Abort` (so `aborted` / `abort` delegate to it), and the queue
3319
- * attempt's `signal` (which ANY-combines the unit abort + runner abort + timeout). A
3324
+ * attempt's `signal` (which any-combines the unit abort + runner abort + timeout). A
3320
3325
  * `spawn` callback is injected so `controller.spawn(input)` delegates to `#launch`.
3321
3326
  * - **One-shot + fail-fast.** `execute` runs once (a second call throws). The first
3322
3327
  * unit failure (after its retries) records the error and `abort()`s the run, so every
3323
3328
  * sibling's signal fires; later failures are ignored and `execute` rejects with the
3324
3329
  * first error. A user `abort(reason)` likewise rejects a running `execute`.
3325
3330
  * - **`pause` / `resume` / `stop` ride the backing Queue.** `pause` / `resume`
3326
- * delegate straight to the Queue's own pause/resume (holding/releasing the NEXT
3331
+ * delegate straight to the Queue's own pause/resume (holding/releasing the next
3327
3332
  * dispatch while an in-flight unit finishes); `paused` mirrors the Queue's. `stop` is a
3328
- * GRACEFUL permanent end, distinct from `abort`: still-pending (never-dispatched)
3329
- * units are rejected by the Queue's own stop WITHOUT their handler ever running, and
3333
+ * graceful permanent end, distinct from `abort`: still-pending (never-dispatched)
3334
+ * units are rejected by the Queue's own stop without their handler ever running, and
3330
3335
  * `#settle` reads that fact (`#dispatched`) to treat the rejection as a stop artifact —
3331
3336
  * not a failure, never tripping fail-fast — while an in-flight unit still runs to
3332
- * completion and settles normally. `execute` RESOLVES (never rejects) after every unit
3337
+ * completion and settles normally. `execute` resolves (never rejects) after every unit
3333
3338
  * has settled, with whatever results actually completed.
3334
3339
  * - **Observable.** The owned {@link emitter} ({@link RunnerEventMap}) carries the run
3335
3340
  * lifecycle — `start` / `unit` / `spawn` / `settle` / `fail` / `finish` / `abort` — for
3336
- * fire-and-forget observers. Every event is emitted directly, strictly AFTER the relevant
3341
+ * fire-and-forget observers. Every event is emitted directly, strictly after the relevant
3337
3342
  * launch / settle / drain transition; the emitter isolates a listener throw and routes it
3338
- * to its `error` handler (the `error` option), so a buggy observer can NEVER reorder, throw
3343
+ * to its `error` handler (the `error` option), so a buggy observer can never reorder, throw
3339
3344
  * into, or corrupt the one-shot / fail-fast / spawn-tracking engine: the outstanding-unit
3340
3345
  * count gate stays balanced and fail-fast still fires regardless of what a listener does.
3341
3346
  * Observation is purely a side-channel.
@@ -3394,15 +3399,15 @@ var Runner = class {
3394
3399
  return this.#queue.paused;
3395
3400
  }
3396
3401
  /**
3397
- * Injects one more unit into an IN-FLIGHT `execute` run — a LIVE counterpart to a
3398
- * `Controller.spawn`, called from OUTSIDE any unit's handler.
3402
+ * Injects one more unit into an in-flight `execute` run — a live counterpart to a
3403
+ * `Controller.spawn`, called from outside any unit's handler.
3399
3404
  *
3400
3405
  * @remarks
3401
3406
  * Returns `undefined` synchronously (graceful, non-throwing) unless the
3402
3407
  * runner is mid-`execute` and not yet stopped — covering "never started",
3403
3408
  * "already drained", "aborted", and "destroyed". Otherwise the unit is routed through
3404
- * the SAME backing queue as a declared/`spawn`ed unit through `#launch` — the outstanding-
3405
- * unit count gate increments BEFORE this call returns, so an in-flight `execute`
3409
+ * the same backing queue as a declared/`spawn`ed unit through `#launch` — the outstanding-
3410
+ * unit count gate increments before this call returns, so an in-flight `execute`
3406
3411
  * keeps awaiting it (the drain race: `#running` flips to `false` as the very first
3407
3412
  * step after `execute`'s `await drained.promise` settles, so a `spawn` reaching this
3408
3413
  * method after the run has fully drained is cleanly rejected with `undefined` —
@@ -3462,7 +3467,7 @@ var Runner = class {
3462
3467
  }
3463
3468
  /**
3464
3469
  * Suspends dispatch (resumable): delegates to the backing queue's own
3465
- * `pause`, which holds the NEXT dispatch while any in-flight unit finishes.
3470
+ * `pause`, which holds the next dispatch while any in-flight unit finishes.
3466
3471
  *
3467
3472
  * @remarks
3468
3473
  * A no-op after the runner is `stopped` — a stopped runner has no dispatch left to
@@ -3486,10 +3491,10 @@ var Runner = class {
3486
3491
  this.#queue.resume();
3487
3492
  }
3488
3493
  /**
3489
- * Ends the runner permanently — a GRACEFUL stop, distinct from `abort`.
3494
+ * Ends the runner permanently — a graceful stop, distinct from `abort`.
3490
3495
  * Marks the runner `stopping` + `stopped`, then stops the backing queue: every
3491
- * still-PENDING (never-dispatched) unit is rejected by the queue with its own
3492
- * "queue is stopped" error, WITHOUT running its handler; every already-in-flight unit
3496
+ * still-pending (never-dispatched) unit is rejected by the queue with its own
3497
+ * "queue is stopped" error, without running its handler; every already-in-flight unit
3493
3498
  * keeps running to completion and settles normally. `#settle` reads `#stopping` to
3494
3499
  * classify a never-dispatched unit's rejection as a stop artifact (decrement the count
3495
3500
  * gate, no recorded failure, no fail-fast trip) rather than a genuine failure — a
@@ -3618,21 +3623,21 @@ var Runner = class {
3618
3623
  * Implements the attempt-scoped handle a {@link import('./types.js').WorkflowFunction} receives.
3619
3624
  *
3620
3625
  * @remarks
3621
- * - **A leaf handle, NOT the runner `Controller`.** A workflow task is a leaf of the
3626
+ * - **A leaf handle, not the runner `Controller`.** A workflow task is a leaf of the
3622
3627
  * declarative W-b tree, not a fan-out unit, so it has no `spawn`; its `wait` instead
3623
3628
  * checkpoints the workflow, phase, and task cooperative gates.
3624
- * - **Folded signal.** `signal` is the cancellation folded for THIS attempt: its per-attempt
3629
+ * - **Folded signal.** `signal` is the cancellation folded for this attempt: its per-attempt
3625
3630
  * deadline, task stop/skip, workflow abort/timeout/budget/destroy, or a sibling fail-fast.
3626
3631
  * A handler races its work against it; `aborted` reads it.
3627
3632
  * - **Attempt ownership.** `report` / `pulse` are closures supplied by the runner and refuse
3628
3633
  * after this signal aborts or a retry token supersedes this handle.
3629
3634
  * - **Input + lineage.** `input` is the task's open `metadata` bag (its
3630
3635
  * {@link import('./types.js').TaskInput} payload, `{}` when none); `task` is the full
3631
- * {@link TaskContext}, so `task.phase` / `task.phase.workflow` navigate UP the lineage.
3636
+ * {@link TaskContext}, so `task.phase` / `task.phase.workflow` navigate up the lineage.
3632
3637
  * - **Read-up results.** `results()` returns every settled task's {@link TaskResult} across
3633
3638
  * the phases that have already finished (a closure over the live
3634
3639
  * {@link import('./types.js').WorkflowInterface}), so a `function` task can read an earlier
3635
- * phase's output. Read-only — a task records its OWN outcome by returning / throwing, not
3640
+ * phase's output. Read-only — a task records its own outcome by returning / throwing, not
3636
3641
  * by mutating the tree.
3637
3642
  * - **Event-free.** Like the runner `Controller`, the per-task handle carries no Emitter;
3638
3643
  * observe the W-b entities' own emitters (`task.emitter` / `phase.emitter`) instead.
@@ -3726,11 +3731,12 @@ var TaskController = class {
3726
3731
  //#endregion
3727
3732
  //#region src/core/WorkflowPersistence.ts
3728
3733
  /**
3729
- * Coordinates advanced run-local snapshot persistence with one writer and one coalesced most recent obligation.
3734
+ * Coordinates advanced run-local snapshot persistence with one writer and one coalesced most
3735
+ * recent obligation, normally composed through `execute({ store })` rather than built directly.
3730
3736
  *
3731
3737
  * @remarks
3732
- * Normally composed by `WorkflowRunner.execute({ store })`; exported for hosts that need to
3733
- * coordinate the same required boundaries around their own runner integration.
3738
+ * Exported for hosts that need to coordinate the same required boundaries around their own runner
3739
+ * integration.
3734
3740
  *
3735
3741
  * @example
3736
3742
  * ```ts
@@ -3896,8 +3902,8 @@ var WorkflowPersistence = class {
3896
3902
  //#endregion
3897
3903
  //#region src/core/WorkflowRunner.ts
3898
3904
  /**
3899
- * Implements the thin orchestrator that EXECUTES a live W-b workflow tree by COMPOSING the shipped
3900
- * substrate — phases sequential, tasks concurrent — dispatching each task through its OWN
3905
+ * Implements the thin orchestrator that executes a live W-b workflow tree by composing the shipped
3906
+ * substrate — phases sequential, tasks concurrent — dispatching each task through its own
3901
3907
  * resolved handler under the `bail` policy.
3902
3908
  *
3903
3909
  * @remarks
@@ -3908,33 +3914,33 @@ var WorkflowPersistence = class {
3908
3914
  * timeout / budget / entity `signal` fold through the `@orkestrel/abort` signal contract,
3909
3915
  * {@link createTimeout}, and `AbortSignal.any` (exactly as the agent runtime folds its bounds);
3910
3916
  * pacing is the shipped
3911
- * {@link SchedulerInterface}. The runner writes ZERO concurrency / retry / abort logic of
3917
+ * {@link SchedulerInterface}. The runner writes zero concurrency / retry / abort logic of
3912
3918
  * its own — it only sequences phases, dispatches a task's own handler, and drives the live
3913
3919
  * entity. The workflow layer owns per-task deadlines because timeout settlement must
3914
3920
  * update the live leaf under the phase's `bail` policy before the substrate unit settles.
3915
3921
  * - **Pure engine — no integration registry.** The runner carries no behavior or provider
3916
3922
  * registry: each live {@link TaskInterface} already
3917
3923
  * resolved its own {@link import('./types.js').WorkflowFunction} into
3918
- * {@link import('./types.js').TaskInterface.handler} ONCE at construction (build, restore,
3924
+ * {@link import('./types.js').TaskInterface.handler} once at construction (build, restore,
3919
3925
  * or a live mint all resolve it identically, from {@link WorkflowOptions.functions}), so
3920
3926
  * dispatch is "invoke the task's own handler". Provider, protocol, and tool
3921
3927
  * integrations remain application-owned {@link import('./types.js').WorkflowFunction}s
3922
3928
  * composed into {@link WorkflowOptions.functions}. This module imports none of them.
3923
- * - **Two `execute` forms, one engine.** `execute(definition, options)` BUILDS the live tree
3929
+ * - **Two `execute` forms, one engine.** `execute(definition, options)` builds the live tree
3924
3930
  * from a {@link WorkflowDefinition} (single source of truth for the `behavior` / `concurrency`
3925
- * metadata); `execute(workflow, options)` DRIVES a caller-owned, ALREADY-BUILT
3931
+ * metadata); `execute(workflow, options)` drives a caller-owned, already-built
3926
3932
  * {@link WorkflowInterface} instead — the entity-native control surface
3927
3933
  * (`pause` / `resume` / `add` / `stop` / `destroy` live on the entity itself). Both forms
3928
- * converge on the SAME `#execute` engine: neither reads a `WorkflowDefinition` after the tree
3929
- * exists — `#runTask` reads each task's OWN {@link import('./types.js').TaskInterface.handler}
3930
- * / `retries` / `timeout`, and `#runPhase` reads each phase's OWN
3934
+ * converge on the same `#execute` engine: neither reads a `WorkflowDefinition` after the tree
3935
+ * exists — `#runTask` reads each task's own {@link import('./types.js').TaskInterface.handler}
3936
+ * / `retries` / `timeout`, and `#runPhase` reads each phase's own
3931
3937
  * {@link PhaseInterface.concurrency} / `bail`, so a live `add`-minted phase or task (V5)
3932
- * runs under EXACTLY the same rules as one built from the original definition.
3933
- * - **Phases sequential, tasks concurrent — LIVE continuity.** `#execute` drives the phases in
3934
- * order, RE-READING `workflow.phases.phases()` every iteration (a cursor over the live
3938
+ * runs under exactly the same rules as one built from the original definition.
3939
+ * - **Phases sequential, tasks concurrent — live continuity.** `#execute` drives the phases in
3940
+ * order, re-reading `workflow.phases.phases()` every iteration (a cursor over the live
3935
3941
  * manager, not a one-time snapshot) so a caller's `workflow.add(phaseDefinition)` mid-run is
3936
- * picked up. Within a phase, `#runPhase` subscribes to that phase's `add` event BEFORE
3937
- * capturing its task list, then `spawn`s any task added mid-phase onto the SAME substrate
3942
+ * picked up. Within a phase, `#runPhase` subscribes to that phase's `add` event before
3943
+ * capturing its task list, then `spawn`s any task added mid-phase onto the same substrate
3938
3944
  * Runner (so it is actually dispatched, under the same `concurrency`); a task added too late
3939
3945
  * for `spawn` to accept (the runner already drained) is swept `skip`ped afterward so the
3940
3946
  * phase always reaches a coherent terminal state.
@@ -3943,30 +3949,30 @@ var WorkflowPersistence = class {
3943
3949
  * auto-completes with JSON `null`; a present unresolved name is rejected by the synchronous
3944
3950
  * execution claim and never false-completes.
3945
3951
  * - **`bail` → substrate.** Under `bail: true` (halt) a genuine task failure `fail`s the leaf
3946
- * THEN re-throws, so the substrate Runner fail-fasts — it aborts the in-flight siblings
3952
+ * then re-throws, so the substrate Runner fail-fasts — it aborts the in-flight siblings
3947
3953
  * (their `controller.signal` fires; a mid-flight sibling `skip`s) and rejects the phase run;
3948
3954
  * `#execute` then `skip`s the remaining tasks / phases (the workflow derives `failed`).
3949
- * Under `bail: false` (graceful) a failure `fail`s the leaf and RESOLVES (never throws), so
3955
+ * Under `bail: false` (graceful) a failure `fail`s the leaf and resolves (never throws), so
3950
3956
  * the Runner settles every unit (allSettled) and the run finishes (the workflow derives
3951
3957
  * `completed`, the failure recorded in the result tree).
3952
3958
  * - **Pause / stop / destroy gates.** Workflow, phase, and task gates are checked before
3953
3959
  * dispatch, and a running handler can checkpoint their folded state through
3954
3960
  * {@link import('./types.js').TaskControllerInterface.wait}. Because the substrate acquires
3955
3961
  * concurrency before this handler gate, a paused task occupies one phase slot until resume;
3956
- * already-running siblings continue and its per-attempt timeout keeps counting. A GRACEFUL
3962
+ * already-running siblings continue and its per-attempt timeout keeps counting. A graceful
3957
3963
  * `workflow.stop()` (no signal involved) is caught at
3958
3964
  * those same gates: not-yet-started work is `skip`ped, in-flight work finishes naturally. A
3959
- * HARD `workflow.destroy()` aborts {@link WorkflowInterface.signal}, which `#fold` has folded
3965
+ * hard `workflow.destroy()` aborts {@link WorkflowInterface.signal}, which `#fold` has folded
3960
3966
  * into the run's composed signal — so it cancels the active phase Runner (and every
3961
- * in-flight task) exactly like an external abort / timeout / budget fire. EVERY park on a
3962
- * `wait()` gate is RACED against that same run signal (`#raceWait`, S2) — so a cancel firing
3963
- * WHILE parked unparks the engine promptly instead of hanging until `resume`; the existing
3967
+ * in-flight task) exactly like an external abort / timeout / budget fire. Every park on a
3968
+ * `wait()` gate is raced against that same run signal (`#raceWait`, S2) — so a cancel firing
3969
+ * while parked unparks the engine promptly instead of hanging until `resume`; the existing
3964
3970
  * halt / abort re-checks after the gate then decide the outcome.
3965
3971
  * - **Abort / Timeout / Budget / entity-signal fold.** `#execute` folds the live workflow's
3966
3972
  * own {@link WorkflowInterface.signal}, the run's external `signal`, a
3967
3973
  * {@link TimeoutInterface}, and the `@orkestrel/budget` package's `BudgetInterface`'s
3968
3974
  * `signal` into one `runSignal` (`AbortSignal.any`); a fire aborts the active phase's Runner
3969
- * (cancelling every in-flight task) and HALTS the run — the remaining tasks / phases `skip`
3975
+ * (cancelling every in-flight task) and halts the run — the remaining tasks / phases `skip`
3970
3976
  * and the workflow is force-`stop`ped (settles `stopped`). Each task's
3971
3977
  * {@link TaskController} signal `AbortSignal.any`-combines the substrate per-unit signal with
3972
3978
  * `runSignal`, so a handler observes either cause directly.
@@ -4014,7 +4020,7 @@ var WorkflowRunner = class WorkflowRunner {
4014
4020
  let runSignal;
4015
4021
  let onCancel;
4016
4022
  try {
4017
- timeout = ms !== void 0 && Number.isFinite(ms) && ms > 0 && ms <= 2147483647 ? createTimeout({ ms }) : void 0;
4023
+ timeout = ms !== void 0 && isFiniteNumber(ms) && ms > 0 && ms <= 2147483647 ? createTimeout({ ms }) : void 0;
4018
4024
  timeout?.start();
4019
4025
  budget?.start();
4020
4026
  runSignal = this.#fold(workflow, signal, budget, timeout);
@@ -4147,7 +4153,7 @@ var WorkflowRunner = class WorkflowRunner {
4147
4153
  return;
4148
4154
  }
4149
4155
  const ms = task.timeout;
4150
- const deadline = ms !== void 0 && Number.isFinite(ms) && ms > 0 && ms <= 2147483647 ? createTimeout({ ms }) : void 0;
4156
+ const deadline = ms !== void 0 && isFiniteNumber(ms) && ms > 0 && ms <= 2147483647 ? createTimeout({ ms }) : void 0;
4151
4157
  const signal = this.#taskSignal(task, controller.signal, runSignal, deadline);
4152
4158
  try {
4153
4159
  task.start();
@@ -4301,7 +4307,7 @@ var WorkflowRunner = class WorkflowRunner {
4301
4307
  try {
4302
4308
  if (workflow !== void 0 && isHalted(workflow, phase)) deferred.resolve(void 0);
4303
4309
  const outcome = await Promise.race([wait, deferred.promise]);
4304
- return typeof outcome === "boolean" ? outcome : void 0;
4310
+ return isBoolean(outcome) ? outcome : void 0;
4305
4311
  } finally {
4306
4312
  signal.removeEventListener("abort", onAbort);
4307
4313
  workflow?.emitter.off("skip", onTerminal);
@@ -4384,20 +4390,20 @@ function createWorkflowContract() {
4384
4390
  * Builds the live W-b entity tree from a {@link WorkflowDefinition} — the whole
4385
4391
  * {@link WorkflowInterface} → {@link import('./types.js').PhaseInterface} →
4386
4392
  * {@link import('./types.js').TaskInterface} tree, each level wired with its lineage
4387
- * context, its emitter, and the cascade.
4393
+ * context, its emitter, and the cascade, and every node born `pending`.
4388
4394
  *
4389
4395
  * @remarks
4390
- * The definition is the DECLARATIVE blueprint; this seeds an initial all-`pending`
4396
+ * The definition is the declarative blueprint; this seeds an initial all-`pending`
4391
4397
  * {@link WorkflowSnapshot} from it ({@link definitionToSnapshot}) and constructs the live
4392
4398
  * tree over that one path. The `bail` failure policy resolves to `options.bail`, else the
4393
4399
  * definition's `bail`, else the graceful {@link import('./constants.js').DEFAULT_BAIL}; it
4394
4400
  * feeds {@link import('./helpers.js').deriveWorkflowStatus}. Per-phase / per-task initial
4395
4401
  * listeners + metadata travel through `options.phases[id].on` /
4396
4402
  * `options.phases[id].tasks[id]` (the nested-by-id bag). The W-b tree is the
4397
- * state machine ONLY — it does not execute tasks (W-c drives the transitions).
4403
+ * state machine only — it does not execute tasks (W-c drives the transitions).
4398
4404
  *
4399
4405
  * `options.functions` is the {@link import('./types.js').WorkflowRegistry} registry each live
4400
- * task's `behavior` name resolves against ONCE at construction into its runtime
4406
+ * task's `behavior` name resolves against once at construction into its runtime
4401
4407
  * {@link import('./types.js').TaskInterface.handler}. An omitted name is the deliberate no-op;
4402
4408
  * an unresolved present name remains inspectable but is rejected if execution is attempted.
4403
4409
  *
@@ -4424,14 +4430,14 @@ function createWorkflow(definition, options) {
4424
4430
  * @remarks
4425
4431
  * Seeds an initial all-`pending` {@link WorkflowSnapshot} from the definition and constructs the
4426
4432
  * live {@link WorkflowInterface} over it. `bail` is the caller's own override, forwarded to
4427
- * {@link definitionToSnapshot} so it reaches BOTH tiers: the workflow snapshot AND the inheritance
4433
+ * {@link definitionToSnapshot} so it reaches both tiers: the workflow snapshot and the inheritance
4428
4434
  * default of every phase that declares no `bail` of its own, while a phase declaring one still
4429
4435
  * wins. Omitted, the definition's own `bail` governs, defaulting to the graceful
4430
4436
  * {@link import('./constants.js').DEFAULT_BAIL}.
4431
4437
  *
4432
- * `captured` is forwarded to the entity UNCHANGED — its own `bail` is deliberately not replaced
4438
+ * `captured` is forwarded to the entity unchanged — its own `bail` is deliberately not replaced
4433
4439
  * with the resolved policy, because the snapshot already carries the resolved value at both tiers
4434
- * and an injected one would make `Workflow` read it as an EXPLICIT uniform override and clobber
4440
+ * and an injected one would make `Workflow` read it as an explicit uniform override and clobber
4435
4441
  * the per-phase overrides. Each task's `behavior` / `retries` / `timeout` travel onto the snapshot
4436
4442
  * too, so `captured.functions` resolves every handler identically whether the tree is built fresh
4437
4443
  * or restored. Pass a bag {@link captureWorkflowOptions} already owns: this constructs over it
@@ -4461,11 +4467,11 @@ function createWorkflowTree(definition, captured) {
4461
4467
  *
4462
4468
  * @remarks
4463
4469
  * Round-trip fidelity is paramount: a `snapshot()` → `createRestoredWorkflow()` reproduces the
4464
- * same status at every node (each `#override` restored DIRECTLY from the snapshot's own
4470
+ * same status at every node (each `#override` restored directly from the snapshot's own
4465
4471
  * `override` field, not guessed from a status divergence), the same recorded
4466
4472
  * {@link import('./types.js').TaskResult}s, and the same positional order (an interior
4467
- * `skip` / `remove` survives). The snapshot is SELF-CONTAINED — it persists the `bail`
4468
- * policy it ran under, so the restore re-derives status IDENTICALLY without a silent
4473
+ * `skip` / `remove` survives). The snapshot is self-contained — it persists the `bail`
4474
+ * policy it ran under, so the restore re-derives status identically without a silent
4469
4475
  * default; the snapshot's `bail` is the source of truth, while an explicit `options.bail`
4470
4476
  * still wins when supplied (to deliberately re-run under a different policy). A structurally
4471
4477
  * invalid snapshot (a status — or override — outside the lifecycle vocabulary, or a
@@ -4491,7 +4497,8 @@ function createRestoredWorkflow(snapshot, options) {
4491
4497
  return new Workflow(cloneWorkflowSnapshot(snapshot), captured);
4492
4498
  }
4493
4499
  /**
4494
- * Builds an interrupted workflow back to life at its remaining retry budget.
4500
+ * Builds an interrupted workflow back to life at its remaining retry budget, normalizing a
4501
+ * leaf whose attempts are exhausted into a recovery failure.
4495
4502
  *
4496
4503
  * @remarks
4497
4504
  * Each phase captures every unique initial `behavior` binding once before constructing tasks. Recovery
@@ -4520,18 +4527,17 @@ function createRecoveredWorkflow(snapshot, options) {
4520
4527
  }
4521
4528
  /**
4522
4529
  * Creates the in-memory durable {@link WorkflowStoreInterface} — a process-lifetime
4523
- * {@link MemoryWorkflowStore} persisting {@link WorkflowSnapshot}s by workflow id, the DEFAULT
4524
- * backend behind the W-d persistence seam.
4530
+ * {@link MemoryWorkflowStore} persisting {@link WorkflowSnapshot}s by workflow id, the default
4531
+ * backend behind the W-d persistence seam. It takes no options and expires nothing: a
4532
+ * persisted run state lives until an explicit `delete`.
4525
4533
  *
4526
4534
  * @remarks
4527
4535
  * The snapshot analogue of the server package's `createMemorySessionStore`
4528
- * (and the `createMemoryQueueStore` family), but LEANER — there is no idle-TTL, so no
4529
- * options bag (the smallest interface the capability requires): a persisted run-state lives until
4530
- * an explicit `delete`. This is
4531
- * the zero-plumbing DEFAULT (a plain `Map`); its driver-pluggable twin is
4536
+ * (and the `createMemoryQueueStore` family) is the zero-plumbing default (a plain `Map`); its
4537
+ * driver-pluggable twin is
4532
4538
  * {@link createDatabaseWorkflowStore} (the snapshot as one opaque JSON column over a `databases`
4533
- * table) — for a DURABLE store (run-state surviving a restart) pass it a JSON / SQLite / IndexedDB
4534
- * driver, and it swaps in WITHOUT touching the runner or the entity tree. Restore stays a caller
4539
+ * table) — for a durable store (run-state surviving a restart) pass it a JSON / SQLite / IndexedDB
4540
+ * driver, and it swaps in without touching the runner or the entity tree. Restore stays a caller
4535
4541
  * concern: read a snapshot back and rebuild the live tree with {@link createRestoredWorkflow}.
4536
4542
  *
4537
4543
  * @returns A memory-backed {@link WorkflowStoreInterface}
@@ -4553,22 +4559,22 @@ function createMemoryWorkflowStore() {
4553
4559
  /**
4554
4560
  * Creates a {@link DatabaseWorkflowStore} over any {@link DriverInterface} — the durable,
4555
4561
  * driver-pluggable backing for the W-d persistence seam, the opt-in twin of
4556
- * {@link createMemoryWorkflowStore}.
4562
+ * {@link createMemoryWorkflowStore}. It holds the snapshot as one opaque JSON column, and its
4563
+ * `driver` defaults to memory, so it works before any durable driver is passed.
4557
4564
  *
4558
4565
  * @remarks
4559
- * Builds a one-table database (`snapshots`, keyed by `id`) over the supplied driver, the snapshot
4560
- * held as ONE OPAQUE JSON COLUMN — the column map is `{ id; snapshot }` where `snapshot` is a
4566
+ * Builds a one-table database (`snapshots`, keyed by `id`) over the supplied driver. The column map
4567
+ * is `{ id; snapshot }` where `snapshot` is a
4561
4568
  * `rawShape` (a JSON blob), exactly as `createDatabaseQueueStore` stores its `input`. The
4562
- * snapshot is already a COMPLETE, self-contained, pure-JSON payload, so storing it whole is lossless
4563
- * AND keeps the row type FLAT — a structured multi-column snapshot table would force the contract to
4569
+ * snapshot is already a complete, self-contained, pure-JSON payload, so storing it whole is lossless
4570
+ * and keeps the row type flat — a structured multi-column snapshot table would force the contract to
4564
4571
  * `Infer` the deeply-nested snapshot shape (workflow → phases → tasks → results) and trip TS2589;
4565
4572
  * the opaque column sidesteps it (the column reads back as `unknown`, owned and narrowed on `get` by
4566
4573
  * {@link cloneWorkflowSnapshot}, whose semantic pass is
4567
- * {@link import('./validators.js').isOwnedWorkflowSnapshot}). The `driver` DEFAULTS to
4568
- * {@link createMemoryDriver}, so the store ALSO works in memory out of the box; pass a server
4569
- * `createJSONDriver` / `createSQLiteDriver` (or a browser IndexedDB driver) for a persistent one —
4574
+ * {@link import('./validators.js').isOwnedWorkflowSnapshot}). Pass a server `createJSONDriver` /
4575
+ * `createSQLiteDriver` (or a browser IndexedDB driver) for a persistent one —
4570
4576
  * the durability is the driver's job, the store engine is shared. It swaps in behind
4571
- * {@link WorkflowStoreInterface} WITHOUT touching the runner or the entity tree.
4577
+ * {@link WorkflowStoreInterface} without touching the runner or the entity tree.
4572
4578
  *
4573
4579
  * @param driver - The storage backend the snapshots persist to (defaults to {@link createMemoryDriver})
4574
4580
  * @returns A {@link WorkflowStoreInterface} over the driver
@@ -4596,22 +4602,22 @@ function createDatabaseWorkflowStore(driver = createMemoryDriver()) {
4596
4602
  }).table("snapshots"));
4597
4603
  }
4598
4604
  /**
4599
- * Creates the thin orchestrator — a {@link WorkflowRunnerInterface} — that EXECUTES a live W-b
4600
- * workflow tree by COMPOSING the shipped substrate: phases sequential, tasks concurrent, each
4601
- * task dispatched through its OWN resolved handler under the workflow's `bail` policy.
4605
+ * Creates the thin orchestrator — a {@link WorkflowRunnerInterface} — that executes a live W-b
4606
+ * workflow tree by composing the shipped substrate: phases sequential, tasks concurrent, each
4607
+ * task dispatched through its own resolved handler under the workflow's `bail` policy. The
4608
+ * engine is pure — it carries no behavior or provider registry, and its only option is the
4609
+ * scheduler it paces phase boundaries with.
4602
4610
  *
4603
4611
  * @remarks
4604
- * The runner is a PURE engine — it re-implements no concurrency / retry / abort logic, AND it
4605
- * carries no behavior or provider registry of its own: each live task already
4606
- * resolved its own {@link import('./types.js').WorkflowFunction} into
4607
- * {@link import('./types.js').TaskInterface.handler} ONCE at construction, from the
4612
+ * Each live task already resolved its own {@link import('./types.js').WorkflowFunction} into
4613
+ * {@link import('./types.js').TaskInterface.handler} once at construction, from the
4608
4614
  * {@link WorkflowOptions.functions} registry supplied to `execute` / {@link createWorkflow}.
4609
4615
  * Per-phase bounded concurrency is one {@link createRunner} per phase; `bail` maps onto that
4610
4616
  * Runner's fail-fast (`true` — the first failure aborts the in-flight siblings + skips the
4611
4617
  * rest) vs settle-all (`false` — failures are recorded, the run finishes); the run-level abort
4612
4618
  * / timeout / budget ({@link import('./types.js').WorkflowRunOptions}) fold through
4613
4619
  * `AbortSignal.any` (the agent runtime's pattern); pacing is the shipped scheduler.
4614
- * `execute(definition, options?)` BUILDS the live tree from the definition itself (through
4620
+ * `execute(definition, options?)` builds the live tree from the definition itself (through
4615
4621
  * {@link createWorkflow} — one source of truth, returned in `WorkflowResult.workflow`), drives
4616
4622
  * the live entity (`start` → `complete` / `fail`), and resolves a
4617
4623
  * {@link import('./types.js').WorkflowResult}.
@@ -4625,19 +4631,43 @@ function createDatabaseWorkflowStore(driver = createMemoryDriver()) {
4625
4631
  * See {@link WorkflowRunnerOptions}.
4626
4632
  * @returns A working {@link WorkflowRunnerInterface}
4627
4633
  *
4628
- * @example
4634
+ * @example Author a definition and run it
4629
4635
  * ```ts
4630
4636
  * import { createWorkflowRunner } from '@orkestrel/workflow'
4637
+ * import type { WorkflowDefinition } from '@orkestrel/workflow'
4638
+ *
4639
+ * const definition: WorkflowDefinition = {
4640
+ * id: 'release',
4641
+ * name: 'Release',
4642
+ * phases: [
4643
+ * {
4644
+ * id: 'build',
4645
+ * name: 'Build',
4646
+ * tasks: [
4647
+ * { id: 'compile', name: 'Compile', behavior: 'compile' },
4648
+ * { id: 'lint', name: 'Lint', behavior: 'lint' },
4649
+ * ],
4650
+ * },
4651
+ * {
4652
+ * id: 'ship',
4653
+ * name: 'Ship',
4654
+ * tasks: [{ id: 'publish', name: 'Publish', behavior: 'publish' }],
4655
+ * },
4656
+ * ],
4657
+ * }
4658
+ *
4659
+ * const runner = createWorkflowRunner() // a pure engine — no registries
4631
4660
  *
4632
- * const runner = createWorkflowRunner()
4633
- * const definition = { id: 'w', name: 'W', phases: [{ id: 'p', name: 'P', tasks: [
4634
- * { id: 't', name: 'T', behavior: 'compile' },
4635
- * ] }] }
4636
4661
  * const result = await runner.execute(definition, {
4637
- * functions: { compile: async (controller) => `built ${controller.task.id}` },
4662
+ * functions: {
4663
+ * compile: async (controller) => `built ${controller.task.id}`,
4664
+ * lint: async () => 'clean',
4665
+ * publish: async () => 'published',
4666
+ * },
4638
4667
  * })
4639
4668
  * result.status // 'completed'
4640
- * result.workflow.phase('p')?.task('t')?.status // 'completed'
4669
+ * result.workflow.phase('build')?.task('compile')?.status // 'completed'
4670
+ * result.results // every settled task's TaskResult, in positional order
4641
4671
  * ```
4642
4672
  */
4643
4673
  function createWorkflowRunner(options) {
@@ -4646,15 +4676,17 @@ function createWorkflowRunner(options) {
4646
4676
  /**
4647
4677
  * Creates a {@link WorkflowManagerInterface} — the store-backed registry of
4648
4678
  * {@link WorkflowInterface}s, the additive manager tier mirroring the `@orkestrel/agent`
4649
- * line's `createConversationManager` / `createWorkspaceManager`.
4679
+ * line's `createConversationManager` / `createWorkspaceManager`. The returned registry makes
4680
+ * hydrated named work runnable when `options.functions` is supplied, and leaves it
4681
+ * inspectable when it is not.
4650
4682
  *
4651
4683
  * @remarks
4652
4684
  * `options.functions` flows into every workflow the manager mints (`add`, through
4653
4685
  * {@link createWorkflow}) or hydrates (`open`'s registry-miss path, through
4654
- * {@link createRestoredWorkflow}), so a hydrated workflow is RUNNABLE rather than a dead snapshot
4655
- * mirror. `options.store` is the EXACT analogue of the twins' `store` seam — omitted ⇒ the
4686
+ * {@link createRestoredWorkflow}), so a hydrated workflow is runnable rather than a dead snapshot
4687
+ * mirror. `options.store` is the exact analogue of the twins' `store` seam — omitted ⇒ the
4656
4688
  * manager is registry-only (`open` resolves only what is registered, `save` is a no-op). This
4657
- * is PURELY ADDITIVE: direct {@link WorkflowStoreInterface} use and
4689
+ * is purely additive: direct {@link WorkflowStoreInterface} use and
4658
4690
  * {@link createRestoredWorkflow} remain valid — the manager is one more caller-driven persistence
4659
4691
  * seam, not a replacement.
4660
4692
  *
@@ -4669,7 +4701,7 @@ function createWorkflowRunner(options) {
4669
4701
  * store: createMemoryWorkflowStore(),
4670
4702
  * functions: { compile: async (controller) => `built ${controller.task.id}` },
4671
4703
  * })
4672
- * const workflow = manager.add(definition) // minted, registered, RUNNABLE
4704
+ * const workflow = manager.add(definition) // minted, registered, runnable
4673
4705
  * await manager.save(workflow.id) // persisted to the store
4674
4706
  * const reopened = await manager.open(workflow.id) // already registered — no store hit
4675
4707
  * ```
@@ -4725,12 +4757,13 @@ function createScheduler() {
4725
4757
  }
4726
4758
  /**
4727
4759
  * Creates a thin generic orchestrator that drives declared units — and any they
4728
- * `spawn` — through a bounded-concurrency queue, collecting their results in order.
4760
+ * `spawn` — through a bounded-concurrency queue, collecting their results in order and
4761
+ * failing the run fast on the first genuine unit failure.
4729
4762
  *
4730
4763
  * @remarks
4731
4764
  * The Runner composes the workers `Queue` for backpressure, FIFO ordering, bounded
4732
4765
  * concurrency, retries, and the per-attempt timeout — it adds only orchestration, not
4733
- * a second concurrency engine. `execute(inputs)` runs the unit set ONCE (a second call
4766
+ * a second concurrency engine. `execute(inputs)` runs the unit set once (a second call
4734
4767
  * throws) and resolves the units' results in order: the declared inputs first, then
4735
4768
  * any `spawn`ed siblings in spawn order. Each unit's handler gets a `Controller` — its
4736
4769
  * `id` / `input`, a `signal` that fires on the unit's `abort`, a runner-level `abort`,
@@ -4740,8 +4773,8 @@ function createScheduler() {
4740
4773
  * typed `emitter` surfaces `start` / `unit` / `spawn` / `settle` / `fail` / `finish` / `abort`.
4741
4774
  *
4742
4775
  * Because `spawn` is fire-and-track (the runner awaits the whole spawn closure through an
4743
- * outstanding-unit count, not a one-time snapshot), a handler need NOT await its spawns
4744
- * for them to run — and on a bounded runner do NOT `await` a spawn inline (a slot-holding
4776
+ * outstanding-unit count, not a one-time snapshot), a handler need not await its spawns
4777
+ * for them to run — and on a bounded runner do not `await` a spawn inline (a slot-holding
4745
4778
  * handler awaiting its own spawn can deadlock); fan out and return instead.
4746
4779
  *
4747
4780
  * @typeParam TInput - The work input each unit carries