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