@orkestrel/workflow 0.0.10 → 0.0.12

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,4 +1,4 @@
1
- import { createAbort, linkSignal } from "@orkestrel/abort";
1
+ import { createAbort, isAbortSignal, linkSignal } from "@orkestrel/abort";
2
2
  import { arrayShape, attempt, cloneJSONRecord, cloneJSONValue, compileGuard, createContract, integerShape, isArray, isBoolean, isContractError, isFiniteNumber, isFunction, isInteger, isJSONValue, isNonEmptyString, isRecord, literalShape, objectShape, optionalShape, rawShape, stringShape } from "@orkestrel/contract";
3
3
  import { createDatabase, createMemoryDriver } from "@orkestrel/database";
4
4
  import { Emitter } from "@orkestrel/emitter";
@@ -109,6 +109,204 @@ var DEFAULT_PHASE_CONCURRENCY = 1024;
109
109
  */
110
110
  var MAX_TIMER_MS = 2147483647;
111
111
  //#endregion
112
+ //#region src/core/errors.ts
113
+ /**
114
+ * An error raised by the workflow runtime.
115
+ *
116
+ * @remarks
117
+ * Carries a {@link WorkflowErrorCode} and an optional `context` bag naming the
118
+ * offending node id / status / parameter. Raised for an illegal lifecycle transition
119
+ * (`TRANSITION`), a structurally invalid {@link import('./types.js').WorkflowSnapshot}
120
+ * boundary (`RESTORE`), a refused structural/activity edit (`MUTATION`), or a host
121
+ * schedule refused before arming because the caller's `signal` is not a native
122
+ * `AbortSignal` (`SCHEDULE`, delivered as a rejected promise).
123
+ */
124
+ var WorkflowError = class extends Error {
125
+ code;
126
+ context;
127
+ constructor(code, message, context) {
128
+ super(message);
129
+ this.name = "WorkflowError";
130
+ this.code = code;
131
+ if (context !== void 0) this.context = context;
132
+ }
133
+ };
134
+ /**
135
+ * Narrow an unknown caught value to a {@link WorkflowError}.
136
+ *
137
+ * @param value - The value to test (typically a `catch` binding)
138
+ * @returns `true` when `value` is a {@link WorkflowError}
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * try {
143
+ * task.complete('done')
144
+ * } catch (error) {
145
+ * if (isWorkflowError(error) && error.code === 'TRANSITION') retry()
146
+ * }
147
+ * ```
148
+ */
149
+ function isWorkflowError(value) {
150
+ try {
151
+ return value instanceof WorkflowError;
152
+ } catch {
153
+ return false;
154
+ }
155
+ }
156
+ //#endregion
157
+ //#region src/core/validators.ts
158
+ /** Test the workflow lifecycle vocabulary. */
159
+ function isLifecycleStatus(value) {
160
+ return value === "pending" || value === "running" || value === "completed" || value === "failed" || value === "skipped" || value === "stopped";
161
+ }
162
+ /** Test a normalized persisted task failure. */
163
+ function isTaskFailure(value) {
164
+ try {
165
+ return isRecord(value) && Object.keys(value).every((key) => key === "origin" || key === "message") && (value.origin === "handler" || value.origin === "timeout" || value.origin === "recovery") && isNonEmptyString(value.message);
166
+ } catch {
167
+ return false;
168
+ }
169
+ }
170
+ /**
171
+ * Validate a safe owned JSON graph as a coherent workflow snapshot.
172
+ *
173
+ * @remarks
174
+ * Callers at hostile boundaries use {@link isWorkflowSnapshot}, which owns the
175
+ * graph first so this semantic pass never observes accessors or prototypes.
176
+ */
177
+ function isOwnedWorkflowSnapshot(value) {
178
+ try {
179
+ if (!isRecord(value) || !Object.keys(value).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "phases" || key === "created" || key === "updated") || !isNonEmptyString(value.id) || !isNonEmptyString(value.name) || value.description !== void 0 && typeof value.description !== "string" || !isLifecycleStatus(value.status) || value.override !== void 0 && value.override !== "completed" && value.override !== "skipped" && value.override !== "stopped" || !isBoolean(value.bail) || !isArray(value.phases) || !isFiniteNumber(value.created) || value.created < 0 || !isFiniteNumber(value.updated) || value.updated < value.created) return false;
180
+ const phaseIds = /* @__PURE__ */ new Set();
181
+ const derivations = [];
182
+ let frontier = false;
183
+ let running = false;
184
+ let vacuous = true;
185
+ for (const phase of value.phases) {
186
+ if (!isRecord(phase) || !Object.keys(phase).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "concurrency" || key === "tasks") || !isNonEmptyString(phase.id) || phaseIds.has(phase.id) || !isNonEmptyString(phase.name) || phase.description !== void 0 && typeof phase.description !== "string" || !isLifecycleStatus(phase.status) || phase.override !== void 0 && phase.override !== "skipped" && phase.override !== "stopped" || !isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return false;
187
+ const forced = phase.override === "skipped" || phase.override === "stopped";
188
+ const started = phase.status === "running" || phase.status === "completed" || phase.status === "failed";
189
+ if (!forced && frontier && started || phase.status === "running" && running) return false;
190
+ if (phase.status === "running") running = true;
191
+ if (!forced && (phase.status === "pending" || phase.status === "running" || phase.status === "failed" && phase.bail)) frontier = true;
192
+ phaseIds.add(phase.id);
193
+ const taskIds = /* @__PURE__ */ new Set();
194
+ const statuses = [];
195
+ if (phase.tasks.length > 0) vacuous = false;
196
+ for (const task of phase.tasks) {
197
+ if (!isRecord(task) || !Object.keys(task).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "result" || key === "metadata" || key === "attempts" || key === "run" || key === "retries" || key === "timeout" || key === "activity") || !isNonEmptyString(task.id) || taskIds.has(task.id) || !isNonEmptyString(task.name) || task.description !== void 0 && typeof task.description !== "string" || !isLifecycleStatus(task.status) || !isRecord(task.metadata) || !isJSONValue(task.metadata) || !isInteger(task.attempts) || task.attempts < 0 || task.run !== void 0 && !isNonEmptyString(task.run) || task.retries !== void 0 && (!isInteger(task.retries) || task.retries < 0) || task.timeout !== void 0 && (!isInteger(task.timeout) || task.timeout < 0 || task.timeout > 2147483647)) return false;
198
+ const budget = (task.retries ?? 0) + 1;
199
+ if (task.attempts > budget || task.status === "pending" && task.attempts >= budget) return false;
200
+ if (!(task.activity === void 0 || isTaskActivity(task.activity))) return false;
201
+ if (task.status === "running" || task.status === "completed" || task.status === "failed") {
202
+ if (task.attempts < 1 || task.activity === void 0) return false;
203
+ }
204
+ if (task.status === "pending" && task.activity !== void 0) return false;
205
+ if (task.status === "completed" || task.status === "failed") {
206
+ if (!isTaskResult(task.result, value, phase, task)) return false;
207
+ } else if (task.result !== void 0) return false;
208
+ taskIds.add(task.id);
209
+ statuses.push(task.status);
210
+ }
211
+ const derived = derivePhaseStatus(statuses);
212
+ if (phase.status !== (phase.override ?? derived) || phase.override !== void 0 && phase.status !== phase.override) return false;
213
+ derivations.push({
214
+ status: phase.status,
215
+ bail: phase.bail
216
+ });
217
+ }
218
+ const derived = deriveWorkflowStatus(derivations);
219
+ if (value.override === "completed") return value.status === "completed" && derived === "pending" && vacuous;
220
+ return value.status === (value.override ?? derived);
221
+ } catch {
222
+ return false;
223
+ }
224
+ }
225
+ /** Total hostile-boundary workflow snapshot guard. */
226
+ function isWorkflowSnapshot(value) {
227
+ const cloned = attempt(() => cloneJSONValue(value));
228
+ return cloned.success && isOwnedWorkflowSnapshot(cloned.value);
229
+ }
230
+ /**
231
+ * Test whether an unknown value is a valid whole-frame activity report.
232
+ */
233
+ function isTaskActivityInput(value) {
234
+ try {
235
+ if (!isRecord(value)) return false;
236
+ const prototype = Object.getPrototypeOf(value);
237
+ if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints")) return false;
238
+ const note = value.note;
239
+ const progress = value.progress;
240
+ const operations = value.operations;
241
+ const constraints = value.constraints;
242
+ if (note !== void 0 && !isNonEmptyString(note)) return false;
243
+ if (progress !== void 0) {
244
+ if (!isRecord(progress)) return false;
245
+ const progressPrototype = Object.getPrototypeOf(progress);
246
+ if (progressPrototype !== Object.prototype && progressPrototype !== null || !Object.keys(progress).every((key) => key === "current" || key === "total" || key === "unit")) return false;
247
+ const current = progress.current;
248
+ const total = progress.total;
249
+ const unit = progress.unit;
250
+ if (!isFiniteNumber(current) || current < 0 || total !== void 0 && (!isFiniteNumber(total) || total < current) || unit !== void 0 && !isNonEmptyString(unit)) return false;
251
+ }
252
+ if (operations !== void 0) {
253
+ if (!isArray(operations)) return false;
254
+ const ids = /* @__PURE__ */ new Set();
255
+ for (const operation of operations) {
256
+ if (!isRecord(operation)) return false;
257
+ const operationPrototype = Object.getPrototypeOf(operation);
258
+ if (operationPrototype !== Object.prototype && operationPrototype !== null || !Object.keys(operation).every((key) => key === "id" || key === "name" || key === "started")) return false;
259
+ const id = operation.id;
260
+ const name = operation.name;
261
+ const started = operation.started;
262
+ if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
263
+ ids.add(id);
264
+ }
265
+ }
266
+ if (constraints !== void 0) {
267
+ if (!isArray(constraints)) return false;
268
+ const ids = /* @__PURE__ */ new Set();
269
+ for (const constraint of constraints) {
270
+ if (!isRecord(constraint)) return false;
271
+ const constraintPrototype = Object.getPrototypeOf(constraint);
272
+ if (constraintPrototype !== Object.prototype && constraintPrototype !== null || !Object.keys(constraint).every((key) => key === "id" || key === "name" || key === "started")) return false;
273
+ const id = constraint.id;
274
+ const name = constraint.name;
275
+ const started = constraint.started;
276
+ if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
277
+ ids.add(id);
278
+ }
279
+ }
280
+ return true;
281
+ } catch {
282
+ return false;
283
+ }
284
+ }
285
+ /**
286
+ * Test whether an unknown value is valid persisted task activity.
287
+ */
288
+ function isTaskActivity(value) {
289
+ try {
290
+ if (!isRecord(value)) return false;
291
+ const prototype = Object.getPrototypeOf(value);
292
+ if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints" || key === "updated")) return false;
293
+ const note = value.note;
294
+ const progress = value.progress;
295
+ const operations = value.operations;
296
+ const constraints = value.constraints;
297
+ const updated = value.updated;
298
+ if (operations === void 0 || constraints === void 0 || !isFiniteNumber(updated) || updated < 0) return false;
299
+ return isTaskActivityInput({
300
+ ...note === void 0 ? {} : { note },
301
+ ...progress === void 0 ? {} : { progress },
302
+ operations,
303
+ constraints
304
+ });
305
+ } catch {
306
+ return false;
307
+ }
308
+ }
309
+ //#endregion
112
310
  //#region src/core/helpers.ts
113
311
  /**
114
312
  * Capture every top-level {@link WorkflowOptions} value exactly once into an owned plain bag.
@@ -582,6 +780,53 @@ function recoverWorkflowSnapshot(snapshot) {
582
780
  updated: now
583
781
  };
584
782
  }
783
+ /** Compare two optional description values. */
784
+ function matchesDescription(left, right) {
785
+ return left === right && (left === void 0 || typeof left === "string");
786
+ }
787
+ /** Test a result's lineage against its containing snapshot nodes. */
788
+ function isTaskResult(value, workflow, phase, task) {
789
+ try {
790
+ if (!isRecord(value) || !isRecord(workflow) || !isRecord(phase) || !isRecord(task) || !Object.keys(value).every((key) => key === "task" || key === "phase" || key === "workflow" || key === "status" || key === "result" || key === "timestamp") || !isLifecycleStatus(value.status) || value.status !== task.status || !isFiniteNumber(value.timestamp) || value.timestamp < 0 || !isRecord(value.task) || !isRecord(value.phase) || !isRecord(value.workflow) || !Object.keys(value.workflow).every((key) => key === "id" || key === "name" || key === "description") || !Object.keys(value.phase).every((key) => key === "id" || key === "name" || key === "description" || key === "workflow") || !Object.keys(value.task).every((key) => key === "id" || key === "name" || key === "description" || key === "phase")) return false;
791
+ if (value.task.id !== task.id || value.task.name !== task.name || !matchesDescription(value.task.description, task.description) || value.phase.id !== phase.id || value.phase.name !== phase.name || !matchesDescription(value.phase.description, phase.description) || value.workflow.id !== workflow.id || value.workflow.name !== workflow.name || !matchesDescription(value.workflow.description, workflow.description)) return false;
792
+ if (!isRecord(value.task.phase) || !isRecord(value.task.phase.workflow) || !isRecord(value.phase.workflow) || !Object.keys(value.task.phase).every((key) => key === "id" || key === "name" || key === "description" || key === "workflow") || !Object.keys(value.task.phase.workflow).every((key) => key === "id" || key === "name" || key === "description") || !Object.keys(value.phase.workflow).every((key) => key === "id" || key === "name" || key === "description")) return false;
793
+ if (value.task.phase.id !== phase.id || value.task.phase.name !== phase.name || !matchesDescription(value.task.phase.description, phase.description) || value.phase.workflow.id !== workflow.id || value.phase.workflow.name !== workflow.name || !matchesDescription(value.phase.workflow.description, workflow.description) || value.task.phase.workflow.id !== workflow.id || value.task.phase.workflow.name !== workflow.name || !matchesDescription(value.task.phase.workflow.description, workflow.description)) return false;
794
+ if (value.status === "completed") return isRecord(value.result) && value.result.success === true && Object.keys(value.result).every((key) => key === "success" || key === "value") && isJSONValue(value.result.value);
795
+ if (value.status === "failed") return isRecord(value.result) && value.result.success === false && Object.keys(value.result).every((key) => key === "success" || key === "error") && isTaskFailure(value.result.error);
796
+ return false;
797
+ } catch {
798
+ return false;
799
+ }
800
+ }
801
+ function hasWorkflowHandlers(workflow, functions) {
802
+ if ("destroyed" in workflow) {
803
+ for (const phase of workflow.phases.phases()) for (const task of phase.tasks.tasks()) if (task.run !== void 0 && !isFunction(task.handler)) return false;
804
+ return true;
805
+ }
806
+ const runs = /* @__PURE__ */ new Set();
807
+ for (const phase of workflow.phases) for (const task of phase.tasks) {
808
+ if (task.run === void 0 || runs.has(task.run)) continue;
809
+ runs.add(task.run);
810
+ if (!isFunction(functions?.[task.run])) return false;
811
+ }
812
+ return true;
813
+ }
814
+ /** Locate the nearest identifiable node for an inconsistent owned snapshot. */
815
+ function workflowSnapshotContext(value) {
816
+ if (!isRecord(value) || !isArray(value.phases)) return void 0;
817
+ for (const phase of value.phases) {
818
+ if (!isRecord(phase)) continue;
819
+ const phaseContext = isNonEmptyString(phase.id) ? { phase: phase.id } : void 0;
820
+ if (!isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return phaseContext;
821
+ for (const task of phase.tasks) {
822
+ if (!isRecord(task)) continue;
823
+ if (task.run !== void 0 && !isNonEmptyString(task.run) || task.retries !== void 0 && (!isInteger(task.retries) || task.retries < 0) || task.timeout !== void 0 && (!isInteger(task.timeout) || task.timeout < 0 || task.timeout > 2147483647) || !isInteger(task.attempts) || task.attempts < 0) return {
824
+ ...phaseContext ?? {},
825
+ ...isNonEmptyString(task.id) ? { task: task.id } : {}
826
+ };
827
+ }
828
+ }
829
+ }
585
830
  /**
586
831
  * Flatten a nested list of per-phase {@link TaskResult} lists into one positional list
587
832
  * — the workflow tier of the result tree, built from each phase's `results()`.
@@ -671,6 +916,16 @@ function createDeferred() {
671
916
  * Schedule one cancellable host operation behind an owned settlement signal.
672
917
  *
673
918
  * @remarks
919
+ * A defined `signal` that is not a native `AbortSignal` is refused before anything is armed, as a
920
+ * rejected promise carrying a {@link import('./errors.js').WorkflowError} with the `SCHEDULE` code.
921
+ * Rejecting rather than throwing keeps every caller on one settlement path, so a backend never has
922
+ * to guard the call itself.
923
+ *
924
+ * The guard is necessary but not sufficient, so linking stays contained. A `Proxy` over a native
925
+ * signal passes the guard and can still make linking throw from a trap, and that escape would be
926
+ * synchronous — the one shape every caller here is built not to expect. Containment turns it into
927
+ * the same `SCHEDULE` rejection, so setup has exactly one failure shape however hostile the input.
928
+ *
674
929
  * The completion and failure paths each own an {@link AbortController}; their native composite is
675
930
  * linked to the optional caller signal before `start` can arm host work. Scheduler backends attach
676
931
  * only to that safe composite, so caller mutation of `addEventListener` or `removeEventListener`
@@ -682,16 +937,18 @@ function createDeferred() {
682
937
  *
683
938
  * @param start - Arm host work and return its cancellation closure
684
939
  * @param signal - Optional caller cancellation signal
685
- * @returns A promise settled exactly once by completion, host failure, or caller abort
940
+ * @returns A promise settled exactly once by an invalid-signal refusal, completion, host failure,
941
+ * or caller abort
686
942
  */
687
943
  function scheduleHost(start, signal) {
944
+ if (signal !== void 0 && !isAbortSignal(signal)) return Promise.reject(new WorkflowError("SCHEDULE", "scheduleHost signal must be an AbortSignal", { signal: typeof signal }));
688
945
  const completion = new AbortController();
689
946
  const failed = new AbortController();
690
947
  let settled;
691
948
  try {
692
949
  settled = linkSignal(AbortSignal.any([completion.signal, failed.signal]), signal);
693
- } catch (error) {
694
- return Promise.reject(error);
950
+ } catch {
951
+ return Promise.reject(new WorkflowError("SCHEDULE", "scheduleHost could not link the caller signal", { signal: typeof signal }));
695
952
  }
696
953
  if (settled.aborted) return Promise.reject(settled.reason);
697
954
  return new Promise((resolve, reject) => {
@@ -814,249 +1071,6 @@ var Scheduler = class {
814
1071
  }
815
1072
  };
816
1073
  //#endregion
817
- //#region src/core/errors.ts
818
- /**
819
- * An error raised by the workflow runtime.
820
- *
821
- * @remarks
822
- * Carries a {@link WorkflowErrorCode} and an optional `context` bag naming the
823
- * offending node id / status. Raised for an illegal lifecycle transition
824
- * (`TRANSITION`), a structurally invalid {@link import('./types.js').WorkflowSnapshot}
825
- * boundary (`RESTORE`), or a refused structural/activity edit (`MUTATION`).
826
- */
827
- var WorkflowError = class extends Error {
828
- code;
829
- context;
830
- constructor(code, message, context) {
831
- super(message);
832
- this.name = "WorkflowError";
833
- this.code = code;
834
- if (context !== void 0) this.context = context;
835
- }
836
- };
837
- /**
838
- * Narrow an unknown caught value to a {@link WorkflowError}.
839
- *
840
- * @param value - The value to test (typically a `catch` binding)
841
- * @returns `true` when `value` is a {@link WorkflowError}
842
- *
843
- * @example
844
- * ```ts
845
- * try {
846
- * task.complete('done')
847
- * } catch (error) {
848
- * if (isWorkflowError(error) && error.code === 'TRANSITION') retry()
849
- * }
850
- * ```
851
- */
852
- function isWorkflowError(value) {
853
- try {
854
- return value instanceof WorkflowError;
855
- } catch {
856
- return false;
857
- }
858
- }
859
- //#endregion
860
- //#region src/core/validators.ts
861
- /** Test the workflow lifecycle vocabulary. */
862
- function isLifecycleStatus(value) {
863
- return value === "pending" || value === "running" || value === "completed" || value === "failed" || value === "skipped" || value === "stopped";
864
- }
865
- /** Test a normalized persisted task failure. */
866
- function isTaskFailure(value) {
867
- try {
868
- return isRecord(value) && Object.keys(value).every((key) => key === "origin" || key === "message") && (value.origin === "handler" || value.origin === "timeout" || value.origin === "recovery") && isNonEmptyString(value.message);
869
- } catch {
870
- return false;
871
- }
872
- }
873
- /** Compare two optional description values. */
874
- function matchesDescription(left, right) {
875
- return left === right && (left === void 0 || typeof left === "string");
876
- }
877
- /** Test a result's lineage against its containing snapshot nodes. */
878
- function isTaskResult(value, workflow, phase, task) {
879
- try {
880
- if (!isRecord(value) || !isRecord(workflow) || !isRecord(phase) || !isRecord(task) || !Object.keys(value).every((key) => key === "task" || key === "phase" || key === "workflow" || key === "status" || key === "result" || key === "timestamp") || !isLifecycleStatus(value.status) || value.status !== task.status || !isFiniteNumber(value.timestamp) || value.timestamp < 0 || !isRecord(value.task) || !isRecord(value.phase) || !isRecord(value.workflow) || !Object.keys(value.workflow).every((key) => key === "id" || key === "name" || key === "description") || !Object.keys(value.phase).every((key) => key === "id" || key === "name" || key === "description" || key === "workflow") || !Object.keys(value.task).every((key) => key === "id" || key === "name" || key === "description" || key === "phase")) return false;
881
- if (value.task.id !== task.id || value.task.name !== task.name || !matchesDescription(value.task.description, task.description) || value.phase.id !== phase.id || value.phase.name !== phase.name || !matchesDescription(value.phase.description, phase.description) || value.workflow.id !== workflow.id || value.workflow.name !== workflow.name || !matchesDescription(value.workflow.description, workflow.description)) return false;
882
- if (!isRecord(value.task.phase) || !isRecord(value.task.phase.workflow) || !isRecord(value.phase.workflow) || !Object.keys(value.task.phase).every((key) => key === "id" || key === "name" || key === "description" || key === "workflow") || !Object.keys(value.task.phase.workflow).every((key) => key === "id" || key === "name" || key === "description") || !Object.keys(value.phase.workflow).every((key) => key === "id" || key === "name" || key === "description")) return false;
883
- if (value.task.phase.id !== phase.id || value.task.phase.name !== phase.name || !matchesDescription(value.task.phase.description, phase.description) || value.phase.workflow.id !== workflow.id || value.phase.workflow.name !== workflow.name || !matchesDescription(value.phase.workflow.description, workflow.description) || value.task.phase.workflow.id !== workflow.id || value.task.phase.workflow.name !== workflow.name || !matchesDescription(value.task.phase.workflow.description, workflow.description)) return false;
884
- if (value.status === "completed") return isRecord(value.result) && value.result.success === true && Object.keys(value.result).every((key) => key === "success" || key === "value") && isJSONValue(value.result.value);
885
- if (value.status === "failed") return isRecord(value.result) && value.result.success === false && Object.keys(value.result).every((key) => key === "success" || key === "error") && isTaskFailure(value.result.error);
886
- return false;
887
- } catch {
888
- return false;
889
- }
890
- }
891
- /**
892
- * Validate a safe owned JSON graph as a coherent workflow snapshot.
893
- *
894
- * @remarks
895
- * Callers at hostile boundaries use {@link isWorkflowSnapshot}, which owns the
896
- * graph first so this semantic pass never observes accessors or prototypes.
897
- */
898
- function isOwnedWorkflowSnapshot(value) {
899
- try {
900
- if (!isRecord(value) || !Object.keys(value).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "phases" || key === "created" || key === "updated") || !isNonEmptyString(value.id) || !isNonEmptyString(value.name) || value.description !== void 0 && typeof value.description !== "string" || !isLifecycleStatus(value.status) || value.override !== void 0 && value.override !== "completed" && value.override !== "skipped" && value.override !== "stopped" || !isBoolean(value.bail) || !isArray(value.phases) || !isFiniteNumber(value.created) || value.created < 0 || !isFiniteNumber(value.updated) || value.updated < value.created) return false;
901
- const phaseIds = /* @__PURE__ */ new Set();
902
- const derivations = [];
903
- let frontier = false;
904
- let running = false;
905
- let vacuous = true;
906
- for (const phase of value.phases) {
907
- if (!isRecord(phase) || !Object.keys(phase).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "override" || key === "bail" || key === "concurrency" || key === "tasks") || !isNonEmptyString(phase.id) || phaseIds.has(phase.id) || !isNonEmptyString(phase.name) || phase.description !== void 0 && typeof phase.description !== "string" || !isLifecycleStatus(phase.status) || phase.override !== void 0 && phase.override !== "skipped" && phase.override !== "stopped" || !isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return false;
908
- const forced = phase.override === "skipped" || phase.override === "stopped";
909
- const started = phase.status === "running" || phase.status === "completed" || phase.status === "failed";
910
- if (!forced && frontier && started || phase.status === "running" && running) return false;
911
- if (phase.status === "running") running = true;
912
- if (!forced && (phase.status === "pending" || phase.status === "running" || phase.status === "failed" && phase.bail)) frontier = true;
913
- phaseIds.add(phase.id);
914
- const taskIds = /* @__PURE__ */ new Set();
915
- const statuses = [];
916
- if (phase.tasks.length > 0) vacuous = false;
917
- for (const task of phase.tasks) {
918
- if (!isRecord(task) || !Object.keys(task).every((key) => key === "id" || key === "name" || key === "description" || key === "status" || key === "result" || key === "metadata" || key === "attempts" || key === "run" || key === "retries" || key === "timeout" || key === "activity") || !isNonEmptyString(task.id) || taskIds.has(task.id) || !isNonEmptyString(task.name) || task.description !== void 0 && typeof task.description !== "string" || !isLifecycleStatus(task.status) || !isRecord(task.metadata) || !isJSONValue(task.metadata) || !isInteger(task.attempts) || task.attempts < 0 || task.run !== void 0 && !isNonEmptyString(task.run) || task.retries !== void 0 && (!isInteger(task.retries) || task.retries < 0) || task.timeout !== void 0 && (!isInteger(task.timeout) || task.timeout < 0 || task.timeout > 2147483647)) return false;
919
- const budget = (task.retries ?? 0) + 1;
920
- if (task.attempts > budget || task.status === "pending" && task.attempts >= budget) return false;
921
- if (!(task.activity === void 0 || isTaskActivity(task.activity))) return false;
922
- if (task.status === "running" || task.status === "completed" || task.status === "failed") {
923
- if (task.attempts < 1 || task.activity === void 0) return false;
924
- }
925
- if (task.status === "pending" && task.activity !== void 0) return false;
926
- if (task.status === "completed" || task.status === "failed") {
927
- if (!isTaskResult(task.result, value, phase, task)) return false;
928
- } else if (task.result !== void 0) return false;
929
- taskIds.add(task.id);
930
- statuses.push(task.status);
931
- }
932
- const derived = derivePhaseStatus(statuses);
933
- if (phase.status !== (phase.override ?? derived) || phase.override !== void 0 && phase.status !== phase.override) return false;
934
- derivations.push({
935
- status: phase.status,
936
- bail: phase.bail
937
- });
938
- }
939
- const derived = deriveWorkflowStatus(derivations);
940
- if (value.override === "completed") return value.status === "completed" && derived === "pending" && vacuous;
941
- return value.status === (value.override ?? derived);
942
- } catch {
943
- return false;
944
- }
945
- }
946
- /** Total hostile-boundary workflow snapshot guard. */
947
- function isWorkflowSnapshot(value) {
948
- const cloned = attempt(() => cloneJSONValue(value));
949
- return cloned.success && isOwnedWorkflowSnapshot(cloned.value);
950
- }
951
- function hasWorkflowHandlers(workflow, functions) {
952
- if ("destroyed" in workflow) {
953
- for (const phase of workflow.phases.phases()) for (const task of phase.tasks.tasks()) if (task.run !== void 0 && !isFunction(task.handler)) return false;
954
- return true;
955
- }
956
- const runs = /* @__PURE__ */ new Set();
957
- for (const phase of workflow.phases) for (const task of phase.tasks) {
958
- if (task.run === void 0 || runs.has(task.run)) continue;
959
- runs.add(task.run);
960
- if (!isFunction(functions?.[task.run])) return false;
961
- }
962
- return true;
963
- }
964
- /** Locate the nearest identifiable node for an inconsistent owned snapshot. */
965
- function workflowSnapshotContext(value) {
966
- if (!isRecord(value) || !isArray(value.phases)) return void 0;
967
- for (const phase of value.phases) {
968
- if (!isRecord(phase)) continue;
969
- const phaseContext = isNonEmptyString(phase.id) ? { phase: phase.id } : void 0;
970
- if (!isBoolean(phase.bail) || phase.concurrency !== void 0 && (!isInteger(phase.concurrency) || phase.concurrency < 1) || !isArray(phase.tasks)) return phaseContext;
971
- for (const task of phase.tasks) {
972
- if (!isRecord(task)) continue;
973
- if (task.run !== void 0 && !isNonEmptyString(task.run) || task.retries !== void 0 && (!isInteger(task.retries) || task.retries < 0) || task.timeout !== void 0 && (!isInteger(task.timeout) || task.timeout < 0 || task.timeout > 2147483647) || !isInteger(task.attempts) || task.attempts < 0) return {
974
- ...phaseContext ?? {},
975
- ...isNonEmptyString(task.id) ? { task: task.id } : {}
976
- };
977
- }
978
- }
979
- }
980
- /**
981
- * Test whether an unknown value is a valid whole-frame activity report.
982
- */
983
- function isTaskActivityInput(value) {
984
- try {
985
- if (!isRecord(value)) return false;
986
- const prototype = Object.getPrototypeOf(value);
987
- if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints")) return false;
988
- const note = value.note;
989
- const progress = value.progress;
990
- const operations = value.operations;
991
- const constraints = value.constraints;
992
- if (note !== void 0 && !isNonEmptyString(note)) return false;
993
- if (progress !== void 0) {
994
- if (!isRecord(progress)) return false;
995
- const progressPrototype = Object.getPrototypeOf(progress);
996
- if (progressPrototype !== Object.prototype && progressPrototype !== null || !Object.keys(progress).every((key) => key === "current" || key === "total" || key === "unit")) return false;
997
- const current = progress.current;
998
- const total = progress.total;
999
- const unit = progress.unit;
1000
- if (!isFiniteNumber(current) || current < 0 || total !== void 0 && (!isFiniteNumber(total) || total < current) || unit !== void 0 && !isNonEmptyString(unit)) return false;
1001
- }
1002
- if (operations !== void 0) {
1003
- if (!isArray(operations)) return false;
1004
- const ids = /* @__PURE__ */ new Set();
1005
- for (const operation of operations) {
1006
- if (!isRecord(operation)) return false;
1007
- const operationPrototype = Object.getPrototypeOf(operation);
1008
- if (operationPrototype !== Object.prototype && operationPrototype !== null || !Object.keys(operation).every((key) => key === "id" || key === "name" || key === "started")) return false;
1009
- const id = operation.id;
1010
- const name = operation.name;
1011
- const started = operation.started;
1012
- if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
1013
- ids.add(id);
1014
- }
1015
- }
1016
- if (constraints !== void 0) {
1017
- if (!isArray(constraints)) return false;
1018
- const ids = /* @__PURE__ */ new Set();
1019
- for (const constraint of constraints) {
1020
- if (!isRecord(constraint)) return false;
1021
- const constraintPrototype = Object.getPrototypeOf(constraint);
1022
- if (constraintPrototype !== Object.prototype && constraintPrototype !== null || !Object.keys(constraint).every((key) => key === "id" || key === "name" || key === "started")) return false;
1023
- const id = constraint.id;
1024
- const name = constraint.name;
1025
- const started = constraint.started;
1026
- if (!isNonEmptyString(id) || !isNonEmptyString(name) || !isFiniteNumber(started) || started < 0 || ids.has(id)) return false;
1027
- ids.add(id);
1028
- }
1029
- }
1030
- return true;
1031
- } catch {
1032
- return false;
1033
- }
1034
- }
1035
- /**
1036
- * Test whether an unknown value is valid persisted task activity.
1037
- */
1038
- function isTaskActivity(value) {
1039
- try {
1040
- if (!isRecord(value)) return false;
1041
- const prototype = Object.getPrototypeOf(value);
1042
- if (prototype !== Object.prototype && prototype !== null || !Object.keys(value).every((key) => key === "note" || key === "progress" || key === "operations" || key === "constraints" || key === "updated")) return false;
1043
- const note = value.note;
1044
- const progress = value.progress;
1045
- const operations = value.operations;
1046
- const constraints = value.constraints;
1047
- const updated = value.updated;
1048
- if (operations === void 0 || constraints === void 0 || !isFiniteNumber(updated) || updated < 0) return false;
1049
- return isTaskActivityInput({
1050
- ...note === void 0 ? {} : { note },
1051
- ...progress === void 0 ? {} : { progress },
1052
- operations,
1053
- constraints
1054
- });
1055
- } catch {
1056
- return false;
1057
- }
1058
- }
1059
- //#endregion
1060
1074
  //#region src/core/cloners.ts
1061
1075
  /**
1062
1076
  * Validate and own a workflow snapshot before live construction.
@@ -1313,18 +1327,18 @@ var phaseUpdateShape = objectShape({
1313
1327
  * idle-TTL / eviction — a persisted run-state is durable orchestration state that lives until an
1314
1328
  * explicit `delete`. The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the
1315
1329
  * §22 method bijection with {@link WorkflowStoreInterface}). Restore stays a caller concern: read a
1316
- * snapshot back and rebuild the live tree with {@link import('../factories.js').restoreWorkflow}.
1330
+ * snapshot back and rebuild the live tree with {@link import('../factories.js').createRestoredWorkflow}.
1317
1331
  *
1318
1332
  * @example
1319
1333
  * ```ts
1320
1334
  * import { createMemoryDriver } from '@orkestrel/database'
1321
- * import { createDatabaseWorkflowStore, createWorkflow, restoreWorkflow } from '@orkestrel/workflow'
1335
+ * import { createDatabaseWorkflowStore, createWorkflow, createRestoredWorkflow } from '@orkestrel/workflow'
1322
1336
  *
1323
1337
  * const store = createDatabaseWorkflowStore(createMemoryDriver()) // a durable driver swaps in here
1324
1338
  * const workflow = createWorkflow(definition)
1325
1339
  * await store.set(workflow.snapshot()) // persist the run state (one JSON column)
1326
1340
  * const snapshot = await store.get(definition.id)
1327
- * const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
1341
+ * const restored = snapshot && createRestoredWorkflow(snapshot) // an identical live tree
1328
1342
  * await store.delete(definition.id) // drop it
1329
1343
  * ```
1330
1344
  */
@@ -1384,17 +1398,17 @@ var DatabaseWorkflowStore = class {
1384
1398
  *
1385
1399
  * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
1386
1400
  * bijection with {@link WorkflowStoreInterface}). Restore is a caller concern: read a snapshot
1387
- * back and rebuild the live tree with {@link import('../factories.js').restoreWorkflow}.
1401
+ * back and rebuild the live tree with {@link import('../factories.js').createRestoredWorkflow}.
1388
1402
  *
1389
1403
  * @example
1390
1404
  * ```ts
1391
- * import { createMemoryWorkflowStore, createWorkflow, restoreWorkflow } from '@orkestrel/workflow'
1405
+ * import { createMemoryWorkflowStore, createWorkflow, createRestoredWorkflow } from '@orkestrel/workflow'
1392
1406
  *
1393
1407
  * const store = createMemoryWorkflowStore()
1394
1408
  * const workflow = createWorkflow(definition)
1395
1409
  * await store.set(workflow.snapshot()) // persist the run state
1396
1410
  * const snapshot = await store.get(definition.id)
1397
- * const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
1411
+ * const restored = snapshot && createRestoredWorkflow(snapshot) // an identical live tree
1398
1412
  * await store.delete(definition.id) // drop it
1399
1413
  * ```
1400
1414
  */
@@ -2236,7 +2250,7 @@ var PhaseManager = class {
2236
2250
  * @remarks
2237
2251
  * - **Construction.** Built from a {@link WorkflowSnapshot} (the unified input —
2238
2252
  * {@link import('./factories.js').createWorkflow} seeds an initial snapshot from a
2239
- * {@link import('./types.js').WorkflowDefinition}, {@link import('./factories.js').restoreWorkflow}
2253
+ * {@link import('./types.js').WorkflowDefinition}, {@link import('./factories.js').createRestoredWorkflow}
2240
2254
  * passes a persisted one). Each child {@link Phase} is wired to escalate to `#recompute`.
2241
2255
  * - **Derived status.** `status` is `#override` when forced, else
2242
2256
  * {@link deriveWorkflowStatus} over the live phases' statuses feeding `bail`. `failed` is
@@ -2251,7 +2265,7 @@ var PhaseManager = class {
2251
2265
  * workflow tier; `phase(id)` + each `phase.task(id)` navigate DOWN, a task's `phase` / `workflow`
2252
2266
  * navigate UP.
2253
2267
  * - **Snapshot.** `snapshot()` serializes the whole live tree to a {@link WorkflowSnapshot} (pure
2254
- * JSON); {@link import('./factories.js').restoreWorkflow} rebuilds an equivalent live tree.
2268
+ * JSON); {@link import('./factories.js').createRestoredWorkflow} rebuilds an equivalent live tree.
2255
2269
  * - **Observable (AGENTS §13).** The owned {@link emitter} ({@link WorkflowEventMap}) fires
2256
2270
  * `start` / `complete` / `fail` / `pause` / `resume` / `skip` / `stop` after the
2257
2271
  * corresponding status or runtime-gate change; the emitter isolates a listener throw and
@@ -2655,7 +2669,7 @@ var WorkflowManager = class {
2655
2669
  if (!this.#owns(id, mutation, generation)) return this.#resolve(id, generation);
2656
2670
  let workflow;
2657
2671
  try {
2658
- workflow = restoreWorkflow(owned, { ...this.#functions === void 0 ? {} : { functions: this.#functions } });
2672
+ workflow = createRestoredWorkflow(owned, { ...this.#functions === void 0 ? {} : { functions: this.#functions } });
2659
2673
  } catch (error) {
2660
2674
  if (!this.#owns(id, mutation, generation)) return this.#resolve(id, generation);
2661
2675
  throw error;
@@ -3964,12 +3978,12 @@ function createWorkflow(definition, options) {
3964
3978
  return new Workflow(definitionToSnapshot(definition, captured.bail ?? definition.bail ?? false), captured);
3965
3979
  }
3966
3980
  /**
3967
- * Rebuild an equivalent live W-b entity tree from a {@link WorkflowSnapshot} — the
3981
+ * Build an equivalent live W-b entity tree from a {@link WorkflowSnapshot} — the
3968
3982
  * inverse of {@link WorkflowInterface.snapshot}, restoring structure + each node's status
3969
3983
  * + recorded results + positional order + the persisted `#override`.
3970
3984
  *
3971
3985
  * @remarks
3972
- * Round-trip fidelity is paramount: a `snapshot()` → `restoreWorkflow()` reproduces the
3986
+ * Round-trip fidelity is paramount: a `snapshot()` → `createRestoredWorkflow()` reproduces the
3973
3987
  * same status at every node (each `#override` restored DIRECTLY from the snapshot's own
3974
3988
  * `override` field, not guessed from a status divergence), the same recorded
3975
3989
  * {@link import('./types.js').TaskResult}s, and the same positional order (an interior
@@ -3989,18 +4003,18 @@ function createWorkflow(definition, options) {
3989
4003
  *
3990
4004
  * @example
3991
4005
  * ```ts
3992
- * import { restoreWorkflow } from '@orkestrel/workflow'
4006
+ * import { createRestoredWorkflow } from '@orkestrel/workflow'
3993
4007
  *
3994
- * const restored = restoreWorkflow(workflow.snapshot()) // bail comes from the snapshot
4008
+ * const restored = createRestoredWorkflow(workflow.snapshot()) // bail comes from the snapshot
3995
4009
  * restored.status === workflow.status // true
3996
4010
  * ```
3997
4011
  */
3998
- function restoreWorkflow(snapshot, options) {
4012
+ function createRestoredWorkflow(snapshot, options) {
3999
4013
  const captured = captureWorkflowOptions(options);
4000
4014
  return new Workflow(cloneWorkflowSnapshot(snapshot), captured);
4001
4015
  }
4002
4016
  /**
4003
- * Rebuild an interrupted workflow at its remaining retry budget.
4017
+ * Build an interrupted workflow back to life at its remaining retry budget.
4004
4018
  *
4005
4019
  * @remarks
4006
4020
  * Each phase captures every unique initial `run` binding once before constructing tasks. Recovery
@@ -4009,9 +4023,17 @@ function restoreWorkflow(snapshot, options) {
4009
4023
  *
4010
4024
  * @param snapshot - The hostile persisted snapshot
4011
4025
  * @param options - Runtime handlers and entity options
4012
- * @returns A recoverable live workflow
4026
+ * @returns A recoverable live {@link WorkflowInterface} root
4027
+ *
4028
+ * @example
4029
+ * ```ts
4030
+ * import { createRecoveredWorkflow } from '@orkestrel/workflow'
4031
+ *
4032
+ * const recovered = createRecoveredWorkflow(snapshot, { functions })
4033
+ * recovered.status // 'pending' — interrupted running work returned to its remaining budget
4034
+ * ```
4013
4035
  */
4014
- function recoverWorkflow(snapshot, options) {
4036
+ function createRecoveredWorkflow(snapshot, options) {
4015
4037
  const captured = captureWorkflowOptions(options);
4016
4038
  const owned = cloneWorkflowSnapshot(snapshot);
4017
4039
  if (owned.override !== void 0 || owned.phases.some((phase) => phase.override !== void 0)) throw new WorkflowError("RESTORE", `workflow '${owned.id}' has a terminal override`, { workflow: owned.id });
@@ -4020,29 +4042,6 @@ function recoverWorkflow(snapshot, options) {
4020
4042
  return workflow;
4021
4043
  }
4022
4044
  /**
4023
- * Assert that a {@link WorkflowSnapshot} carries a `boolean` `bail` — at the workflow tier AND
4024
- * on every phase — and that its every node's status (and its `override`, when present) is drawn
4025
- * from the lifecycle vocabulary, throwing a `RESTORE` {@link WorkflowError} otherwise.
4026
- *
4027
- * @remarks
4028
- * The boundary-narrowing guard (AGENTS §14) for {@link restoreWorkflow}: a snapshot is
4029
- * untrusted JSON, so a status (or an override) outside
4030
- * {@link import('./constants.js').WORKFLOW_STATUSES} /
4031
- * {@link import('./constants.js').PHASE_STATUSES} / {@link import('./constants.js').TASK_STATUSES},
4032
- * a non-boolean `bail` (the workflow's OR any phase's — both are REQUIRED persisted policy), a
4033
- * present-but-invalid phase `concurrency` (not a positive integer), or a present-but-invalid task
4034
- * `run` (an empty string) / `retries` / `timeout` (not a non-negative integer),
4035
- * is rejected loudly (naming the offending node) rather than silently producing a broken tree.
4036
- * The `override` / `concurrency` / `run` / `retries` / `timeout` are optional, so each is only
4037
- * checked WHEN present. Structural shape beyond these fields is the contract's concern; this
4038
- * guards exactly the fields the live state machine reads back.
4039
- *
4040
- * @param snapshot - The snapshot to validate
4041
- */
4042
- function assertSnapshot(snapshot) {
4043
- cloneWorkflowSnapshot(snapshot);
4044
- }
4045
- /**
4046
4045
  * Create the in-memory durable {@link WorkflowStoreInterface} — a process-lifetime
4047
4046
  * {@link MemoryWorkflowStore} persisting {@link WorkflowSnapshot}s by workflow id, the DEFAULT
4048
4047
  * backend behind the W-d persistence seam.
@@ -4055,19 +4054,19 @@ function assertSnapshot(snapshot) {
4055
4054
  * {@link createDatabaseWorkflowStore} (the snapshot as one opaque JSON column over a `databases`
4056
4055
  * table) — for a DURABLE store (run-state surviving a restart) pass it a JSON / SQLite / IndexedDB
4057
4056
  * driver, and it swaps in WITHOUT touching the runner or the entity tree. Restore stays a caller
4058
- * concern: read a snapshot back and rebuild the live tree with {@link restoreWorkflow}.
4057
+ * concern: read a snapshot back and rebuild the live tree with {@link createRestoredWorkflow}.
4059
4058
  *
4060
4059
  * @returns A memory-backed {@link WorkflowStoreInterface}
4061
4060
  *
4062
4061
  * @example
4063
4062
  * ```ts
4064
- * import { createMemoryWorkflowStore, createWorkflow, restoreWorkflow } from '@orkestrel/workflow'
4063
+ * import { createMemoryWorkflowStore, createWorkflow, createRestoredWorkflow } from '@orkestrel/workflow'
4065
4064
  *
4066
4065
  * const store = createMemoryWorkflowStore()
4067
4066
  * const workflow = createWorkflow(definition)
4068
4067
  * await store.set(workflow.snapshot()) // persist the run state
4069
4068
  * const snapshot = await store.get(definition.id)
4070
- * const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
4069
+ * const restored = snapshot && createRestoredWorkflow(snapshot) // an identical live tree
4071
4070
  * ```
4072
4071
  */
4073
4072
  function createMemoryWorkflowStore() {
@@ -4098,13 +4097,13 @@ function createMemoryWorkflowStore() {
4098
4097
  * @example
4099
4098
  * ```ts
4100
4099
  * import { createMemoryDriver } from '@orkestrel/database'
4101
- * import { createDatabaseWorkflowStore, createWorkflow, restoreWorkflow } from '@orkestrel/workflow'
4100
+ * import { createDatabaseWorkflowStore, createWorkflow, createRestoredWorkflow } from '@orkestrel/workflow'
4102
4101
  *
4103
4102
  * const store = createDatabaseWorkflowStore(createMemoryDriver()) // a durable driver swaps in here
4104
4103
  * const workflow = createWorkflow(definition)
4105
4104
  * await store.set(workflow.snapshot()) // persist the run state (one JSON column)
4106
4105
  * const snapshot = await store.get(definition.id)
4107
- * const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
4106
+ * const restored = snapshot && createRestoredWorkflow(snapshot) // an identical live tree
4108
4107
  * ```
4109
4108
  */
4110
4109
  function createDatabaseWorkflowStore(driver = createMemoryDriver()) {
@@ -4173,11 +4172,11 @@ function createWorkflowRunner(options) {
4173
4172
  * @remarks
4174
4173
  * `options.functions` flows into every workflow the manager mints (`add`, via
4175
4174
  * {@link createWorkflow}) or hydrates (`open`'s registry-miss path, via
4176
- * {@link restoreWorkflow}), so a hydrated workflow is RUNNABLE rather than a dead snapshot
4175
+ * {@link createRestoredWorkflow}), so a hydrated workflow is RUNNABLE rather than a dead snapshot
4177
4176
  * mirror. `options.store` is the EXACT analogue of the twins' `store` seam — omitted ⇒ the
4178
4177
  * manager is registry-only (`open` resolves only what is registered, `save` is a no-op). This
4179
4178
  * is PURELY ADDITIVE: direct {@link WorkflowStoreInterface} use and
4180
- * {@link restoreWorkflow} remain valid — the manager is one more caller-driven persistence
4179
+ * {@link createRestoredWorkflow} remain valid — the manager is one more caller-driven persistence
4181
4180
  * seam, not a replacement.
4182
4181
  *
4183
4182
  * @param options - The optional `store` seam and the `functions` registry threaded into every mint/hydrate
@@ -4293,6 +4292,6 @@ function createRunner(options) {
4293
4292
  return new Runner(options);
4294
4293
  }
4295
4294
  //#endregion
4296
- export { Controller, DEFAULT_BAIL, DEFAULT_PHASE_CONCURRENCY, DatabaseWorkflowStore, MAX_TIMER_MS, MemoryWorkflowStore, PHASE_STATUSES, Phase, PhaseManager, Runner, Scheduler, TASK_STATUSES, TASK_TRANSITIONS, TERMINAL_TASK_STATUSES, Task, TaskController, TaskManager, WORKFLOW_STATUSES, Workflow, WorkflowError, WorkflowManager, WorkflowPersistence, WorkflowRunner, assertSnapshot, buildPhaseContext, buildTaskContext, buildWorkflowContext, canTransitionTask, captureWorkflowOptions, cloneTaskActivity, cloneWorkflowSnapshot, collectResults, createDatabaseWorkflowStore, createDeferred, createMemoryWorkflowStore, createRunner, createScheduler, createWorkflow, createWorkflowContract, createWorkflowManager, createWorkflowRunner, definitionToSnapshot, deriveBoundary, derivePhaseStatus, deriveWorkflowStatus, errorToMessage, failure, findFailure, hasWorkflowHandlers, insertEntry, isLifecycleStatus, isOwnedWorkflowSnapshot, isTaskActivity, isTaskActivityInput, isTaskFailure, isTaskResult, isTerminalStatus, isWorkflowError, isWorkflowSnapshot, matchesDescription, moveEntry, parkSignal, phaseDefinitionToSnapshot, phaseShape, phaseUpdateShape, recoverWorkflow, recoverWorkflowSnapshot, resolveTaskSilence, restoreWorkflow, scheduleHost, success, taskDefinitionToSnapshot, taskShape, taskUpdateShape, workflowShape, workflowSnapshotContext };
4295
+ export { Controller, DEFAULT_BAIL, DEFAULT_PHASE_CONCURRENCY, DatabaseWorkflowStore, MAX_TIMER_MS, MemoryWorkflowStore, PHASE_STATUSES, Phase, PhaseManager, Runner, Scheduler, TASK_STATUSES, TASK_TRANSITIONS, TERMINAL_TASK_STATUSES, Task, TaskController, TaskManager, WORKFLOW_STATUSES, Workflow, WorkflowError, WorkflowManager, WorkflowPersistence, WorkflowRunner, buildPhaseContext, buildTaskContext, buildWorkflowContext, canTransitionTask, captureWorkflowOptions, cloneTaskActivity, cloneWorkflowSnapshot, collectResults, createDatabaseWorkflowStore, createDeferred, createMemoryWorkflowStore, createRecoveredWorkflow, createRestoredWorkflow, createRunner, createScheduler, createWorkflow, createWorkflowContract, createWorkflowManager, createWorkflowRunner, definitionToSnapshot, deriveBoundary, derivePhaseStatus, deriveWorkflowStatus, errorToMessage, failure, findFailure, hasWorkflowHandlers, insertEntry, isLifecycleStatus, isOwnedWorkflowSnapshot, isTaskActivity, isTaskActivityInput, isTaskFailure, isTaskResult, isTerminalStatus, isWorkflowError, isWorkflowSnapshot, matchesDescription, moveEntry, parkSignal, phaseDefinitionToSnapshot, phaseShape, phaseUpdateShape, recoverWorkflowSnapshot, resolveTaskSilence, scheduleHost, success, taskDefinitionToSnapshot, taskShape, taskUpdateShape, workflowShape, workflowSnapshotContext };
4297
4296
 
4298
4297
  //# sourceMappingURL=index.js.map