@theokit/sdk 2.26.0 → 2.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c2bdd87: **SE27 — workflow-level `inputSchema` / `outputSchema` (validate the whole-workflow I/O).**
8
+
9
+ `Workflow.create({ ..., inputSchema, outputSchema })` (from `@theokit/sdk/workflow`) now validates the workflow's overall input and final output, closing the SE19 debt (a Workflow carried no top-level schema — only per-step `FnStep` schemas). When `inputSchema` is set, `run(input)` validates `input` BEFORE step 1; a mismatch fails fast with `status: "failed"` and a typed `WorkflowInputError` in `run.error` (no step executes, no silent coerce). When `outputSchema` is set, the terminal `completed` output is validated before `WorkflowRun.output` is populated; a mismatch yields `status: "failed"` with a typed `WorkflowOutputError` (only the `completed` path is checked — suspended/failed runs skip output validation).
10
+
11
+ Both surface as `status: "failed"` (never a throw — consistent with the executor's non-throwing step-error contract). Back-compat: absent schemas ⇒ unchanged. New exports `WorkflowInputError` / `WorkflowOutputError`. `workflowAsTool` (SE19) keeps taking its own `inputSchema` to preserve its structural `{ run }` contract. Mirrors Mastra's `createWorkflow({ inputSchema, outputSchema })`. From the Mastra Workflows comparison (SDK Evolution roadmap SE27).
12
+
13
+ - 09b89ea: **SE28 — `Workflow.stream()` (step-event stream during execution).**
14
+
15
+ `workflow.stream(input, opts?)` (from `@theokit/sdk/workflow`) runs the workflow and emits step-level events as they happen, instead of only the terminal result. It returns a `WorkflowStream` — an async iterator of `WorkflowEvent`s (`step_started` / `step_completed` (with `output`) / `step_failed` (with `error`) / `workflow_suspended` / `workflow_completed`) plus a `result` promise resolving to the SAME terminal `WorkflowRun` `run()` returns (the authoritative outcome — the stream ends when the run terminates).
16
+
17
+ Events fire in execution order for top-level steps (nested `parallel`/`branch`/`foreach` emit as their single wrapping step — coarse-grained by design). This is a STEP-event stream, distinct from the token-delta agent stream deferred in SE24. `run()` is unchanged + authoritative. New public types `WorkflowEvent` + `WorkflowStream`. Mirrors Mastra's `run.stream()` / `stream.result`. From the Mastra Workflows comparison (SDK Evolution roadmap SE28).
18
+
19
+ - f13d499: **SE29 — workflow shared state (`stateSchema` + `state` / `setState`).**
20
+
21
+ Workflow steps can now share values without threading them through every step's input/output. `Workflow.create({ stateSchema, initialState })` (from `@theokit/sdk/workflow`) seeds a shared state; every step's `StepContext` gains `state` (read the current value) and `setState(next)` (update it for subsequent steps). `setState` validates against `stateSchema` when set — a mismatch throws a typed `WorkflowStateError` that fails the step/run (Rule 8); an invalid `initialState` fails the run fast before step 1.
22
+
23
+ State is captured in the `WorkflowSnapshot` (bumped to `_schemaVersion: 2`) and restored on `Workflow.resume` — it survives a suspend→resume round-trip. A pre-SE29 (`_schemaVersion: 1`) snapshot has no state and resumes with `initialState`. Back-compat: no `stateSchema`/`initialState` ⇒ `state` is `undefined` and `setState` is unvalidated. New export `WorkflowStateError`. Mirrors Mastra's workflow `state`/`setState`/`stateSchema`. From the Mastra Workflows comparison (SDK Evolution roadmap SE29).
24
+
25
+ - 8ce8441: **SE30 — workflows-as-steps (`workflowStep`) + `cloneWorkflow`.**
26
+
27
+ `workflowStep(child, { id? })` (from `@theokit/sdk/workflow`) uses a committed `Workflow` as a step inside another workflow: `.then(workflowStep(child))`. The child runs in its OWN executor (own runId, single-flight lock, and step-id space — so nested ids never collide with the parent's); its output becomes the step output. `cloneWorkflow(wf, { id })` returns a new independent `Workflow` with the same committed steps under a new name + a fresh workflowId (clones run independently, distinct observability identity).
28
+
29
+ A non-`completed` child fails the parent step with a typed `WorkflowNestedError`. **Nested suspend/resume is NOT supported in v1** (TheoKit's resume continues AFTER the suspended step, so a nested child would be skipped) — a nested `suspended` fails with a clear message pointing at a top-level suspend; re-running the child on resume (which would re-execute its side effects) is deliberately avoided. ADR 0010. New export `WorkflowNestedError`. Mirrors Mastra's workflows-as-steps + `cloneWorkflow`. From the Mastra Workflows comparison (SDK Evolution roadmap SE30).
30
+
3
31
  ## 2.26.0
4
32
 
5
33
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -4336,7 +4336,7 @@ var init_batch = __esm({
4336
4336
  });
4337
4337
 
4338
4338
  // src/types/workflow.ts
4339
- var WorkflowDuplicateStepIdError, WorkflowAlreadyRunningError, WorkflowSnapshotNotFoundError, WorkflowMaxIterationsExceededError, WorkflowNotSerializableError, WorkflowResumeStepNotFoundError, WorkflowParallelError, WorkflowCompensateNotImplementedError;
4339
+ var WorkflowDuplicateStepIdError, WorkflowInputError, WorkflowOutputError, WorkflowStateError, WorkflowAlreadyRunningError, WorkflowSnapshotNotFoundError, WorkflowMaxIterationsExceededError, WorkflowNotSerializableError, WorkflowResumeStepNotFoundError, WorkflowParallelError, WorkflowCompensateNotImplementedError;
4340
4340
  var init_workflow = __esm({
4341
4341
  "src/types/workflow.ts"() {
4342
4342
  WorkflowDuplicateStepIdError = class extends Error {
@@ -4347,6 +4347,36 @@ var init_workflow = __esm({
4347
4347
  stepId;
4348
4348
  name = "WorkflowDuplicateStepIdError";
4349
4349
  };
4350
+ WorkflowInputError = class extends Error {
4351
+ constructor(workflowName, detail) {
4352
+ super(`Workflow "${workflowName}" input failed schema validation: ${detail}`);
4353
+ this.workflowName = workflowName;
4354
+ this.detail = detail;
4355
+ }
4356
+ workflowName;
4357
+ detail;
4358
+ name = "WorkflowInputError";
4359
+ };
4360
+ WorkflowOutputError = class extends Error {
4361
+ constructor(workflowName, detail) {
4362
+ super(`Workflow "${workflowName}" output failed schema validation: ${detail}`);
4363
+ this.workflowName = workflowName;
4364
+ this.detail = detail;
4365
+ }
4366
+ workflowName;
4367
+ detail;
4368
+ name = "WorkflowOutputError";
4369
+ };
4370
+ WorkflowStateError = class extends Error {
4371
+ constructor(workflowName, detail) {
4372
+ super(`Workflow "${workflowName}" state failed schema validation: ${detail}`);
4373
+ this.workflowName = workflowName;
4374
+ this.detail = detail;
4375
+ }
4376
+ workflowName;
4377
+ detail;
4378
+ name = "WorkflowStateError";
4379
+ };
4350
4380
  WorkflowAlreadyRunningError = class extends Error {
4351
4381
  constructor(workflowName, runId) {
4352
4382
  super(`Workflow "${workflowName}" run "${runId}" already in-flight.`);
@@ -4539,7 +4569,7 @@ var init_snapshot_store = __esm({
4539
4569
  });
4540
4570
 
4541
4571
  // src/internal/workflow/ctx.ts
4542
- function makeStepContext(runId, signal) {
4572
+ function makeStepContext(runId, signal, state4) {
4543
4573
  return {
4544
4574
  runId,
4545
4575
  signal,
@@ -4550,7 +4580,13 @@ function makeStepContext(runId, signal) {
4550
4580
  },
4551
4581
  suspend: async (payload) => {
4552
4582
  throw new WorkflowSuspendedSentinel(payload);
4553
- }
4583
+ },
4584
+ // SE29 — read reflects the current shared state; write goes through the
4585
+ // controller (which validates against `stateSchema`).
4586
+ get state() {
4587
+ return state4.getState();
4588
+ },
4589
+ setState: (next) => state4.setState(next)
4554
4590
  };
4555
4591
  }
4556
4592
  function emit(level, runId, msg, attrs) {
@@ -4603,6 +4639,57 @@ var init_error_shape = __esm({
4603
4639
  }
4604
4640
  });
4605
4641
 
4642
+ // src/internal/workflow/executor-helpers.ts
4643
+ function assembleRun(params) {
4644
+ return {
4645
+ id: params.runId,
4646
+ name: params.name,
4647
+ status: params.status,
4648
+ startedAt: params.startedAt,
4649
+ endedAt: Date.now(),
4650
+ stepResults: params.stepResults,
4651
+ ...params.output !== void 0 ? { output: params.output } : {},
4652
+ ...params.error !== void 0 ? { error: params.error } : {}
4653
+ };
4654
+ }
4655
+ function abortRun(name, runId, startedAt, stepResults, signal) {
4656
+ return assembleRun({
4657
+ runId,
4658
+ name,
4659
+ status: "cancelled",
4660
+ stepResults,
4661
+ startedAt,
4662
+ error: { name: "AbortError", message: String(signal.reason ?? "Aborted") }
4663
+ });
4664
+ }
4665
+ function validateWorkflowSchema(schema, value) {
4666
+ if (schema === void 0) return void 0;
4667
+ let parsed;
4668
+ try {
4669
+ parsed = schema.safeParse(value);
4670
+ } catch {
4671
+ return "schema uses async refinements, which whole-workflow validation does not support (use a synchronous Zod schema)";
4672
+ }
4673
+ if (parsed.success) return void 0;
4674
+ return parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
4675
+ }
4676
+ function makeStateController(options, initial) {
4677
+ let current = initial;
4678
+ return {
4679
+ getState: () => current,
4680
+ setState: (next) => {
4681
+ const issues = validateWorkflowSchema(options.stateSchema, next);
4682
+ if (issues !== void 0) throw new WorkflowStateError(options.name, issues);
4683
+ current = next;
4684
+ }
4685
+ };
4686
+ }
4687
+ var init_executor_helpers = __esm({
4688
+ "src/internal/workflow/executor-helpers.ts"() {
4689
+ init_workflow();
4690
+ }
4691
+ });
4692
+
4606
4693
  // src/internal/workflow/run-id.ts
4607
4694
  function mintRunId() {
4608
4695
  return `wfr-${globalThis.crypto.randomUUID().replace(/-/g, "").slice(0, 8)}`;
@@ -5269,6 +5356,8 @@ async function handleSuspend(err, ctx) {
5269
5356
  suspendedPayload: err.payload,
5270
5357
  stepResults: ctx.stepResults,
5271
5358
  accumulatedInput: ctx.acc,
5359
+ state: ctx.stepCtx.state,
5360
+ // SE29 — capture the shared state at suspend
5272
5361
  options: ctx.options
5273
5362
  });
5274
5363
  } catch (snapErr) {
@@ -5320,7 +5409,7 @@ async function runOneStep(args) {
5320
5409
  result = await dispatchStep(args.step, args.acc, args.ctx, args.options, args.stepResults);
5321
5410
  } catch (err) {
5322
5411
  if (err instanceof WorkflowSuspendedSentinel) {
5323
- const outcome = await handleSuspend(err, { ...args, stepSpan });
5412
+ const outcome = await handleSuspend(err, { ...args, stepSpan, stepCtx: args.ctx });
5324
5413
  return { kind: "terminal", run: outcome.run };
5325
5414
  }
5326
5415
  result = {
@@ -5337,22 +5426,42 @@ async function runOneStep(args) {
5337
5426
  stepSpan.end();
5338
5427
  return { kind: "ok", result };
5339
5428
  }
5340
- function abortRun(name, runId, startedAt, stepResults, signal) {
5341
- return assembleRun({
5342
- runId,
5343
- name,
5344
- status: "cancelled",
5345
- stepResults,
5346
- startedAt,
5347
- error: { name: "AbortError", message: String(signal.reason ?? "Aborted") }
5348
- });
5429
+ function handleStepOutcome(outcome, step, loop, onStepEvent) {
5430
+ if (outcome.kind === "terminal") {
5431
+ if (outcome.run.status === "suspended") {
5432
+ onStepEvent?.({ type: "workflow_suspended", stepId: step.id });
5433
+ }
5434
+ return { terminal: outcome.run };
5435
+ }
5436
+ loop.stepResults.push(outcome.result);
5437
+ if (outcome.result.status === "failed") {
5438
+ onStepEvent?.({
5439
+ type: "step_failed",
5440
+ stepId: step.id,
5441
+ error: outcome.result.error ?? { name: "WorkflowStepError", message: "step failed" }
5442
+ });
5443
+ return {
5444
+ terminal: assembleRun({
5445
+ runId: loop.runId,
5446
+ name: loop.name,
5447
+ status: "failed",
5448
+ stepResults: loop.stepResults,
5449
+ startedAt: loop.startedAt,
5450
+ error: outcome.result.error
5451
+ })
5452
+ };
5453
+ }
5454
+ onStepEvent?.({ type: "step_completed", stepId: step.id, output: outcome.result.output });
5455
+ return { acc: outcome.result.output };
5349
5456
  }
5350
5457
  async function runStepsLoop(params) {
5351
- const { options, steps, ctx, runId, startedAt, signal } = params;
5458
+ const { options, steps, ctx, runId, startedAt, signal, onStepEvent } = params;
5352
5459
  const stepResults = [...params.initialStepResults ?? []];
5460
+ const loop = { stepResults, runId, name: options.name, startedAt };
5353
5461
  let acc = params.input;
5354
5462
  for (const step of steps) {
5355
5463
  if (signal.aborted) return abortRun(options.name, runId, startedAt, stepResults, signal);
5464
+ onStepEvent?.({ type: "step_started", stepId: step.id });
5356
5465
  const outcome = await runOneStep({
5357
5466
  step,
5358
5467
  acc,
@@ -5363,20 +5472,22 @@ async function runStepsLoop(params) {
5363
5472
  name: options.name,
5364
5473
  startedAt
5365
5474
  });
5366
- if (outcome.kind === "terminal") return outcome.run;
5367
- stepResults.push(outcome.result);
5368
- if (outcome.result.status === "failed") {
5369
- return assembleRun({
5370
- runId,
5371
- name: options.name,
5372
- status: "failed",
5373
- stepResults,
5374
- startedAt,
5375
- error: outcome.result.error
5376
- });
5377
- }
5378
- acc = outcome.result.output;
5475
+ const handled = handleStepOutcome(outcome, step, loop, onStepEvent);
5476
+ if ("terminal" in handled) return handled.terminal;
5477
+ acc = handled.acc;
5478
+ }
5479
+ const outputIssues = validateWorkflowSchema(options.outputSchema, acc);
5480
+ if (outputIssues !== void 0) {
5481
+ return assembleRun({
5482
+ runId,
5483
+ name: options.name,
5484
+ status: "failed",
5485
+ stepResults,
5486
+ startedAt,
5487
+ error: errToShape(new WorkflowOutputError(options.name, outputIssues))
5488
+ });
5379
5489
  }
5490
+ onStepEvent?.({ type: "workflow_completed" });
5380
5491
  return assembleRun({
5381
5492
  runId,
5382
5493
  name: options.name,
@@ -5399,8 +5510,33 @@ async function executeWorkflow(options, steps, input, runOpts) {
5399
5510
  runSpan.end();
5400
5511
  return abortRun(options.name, runId, startedAt, [], signal);
5401
5512
  }
5402
- const ctx = makeStepContext(runId, signal);
5513
+ const internal = runOpts;
5514
+ const seededState = internal?.restoredState !== void 0 ? internal.restoredState : options.initialState;
5515
+ const stateController = makeStateController(options, seededState);
5516
+ const ctx = makeStepContext(runId, signal, stateController);
5403
5517
  try {
5518
+ const inputIssues = validateWorkflowSchema(options.inputSchema, input);
5519
+ if (inputIssues !== void 0) {
5520
+ return assembleRun({
5521
+ runId,
5522
+ name: options.name,
5523
+ status: "failed",
5524
+ stepResults: [],
5525
+ startedAt,
5526
+ error: errToShape(new WorkflowInputError(options.name, inputIssues))
5527
+ });
5528
+ }
5529
+ const stateIssues = seededState !== void 0 ? validateWorkflowSchema(options.stateSchema, seededState) : void 0;
5530
+ if (stateIssues !== void 0) {
5531
+ return assembleRun({
5532
+ runId,
5533
+ name: options.name,
5534
+ status: "failed",
5535
+ stepResults: [],
5536
+ startedAt,
5537
+ error: errToShape(new WorkflowStateError(options.name, stateIssues))
5538
+ });
5539
+ }
5404
5540
  return await runStepsLoop({
5405
5541
  options,
5406
5542
  steps,
@@ -5409,7 +5545,8 @@ async function executeWorkflow(options, steps, input, runOpts) {
5409
5545
  runId,
5410
5546
  startedAt,
5411
5547
  signal,
5412
- ...runOpts?.initialStepResults !== void 0 ? { initialStepResults: runOpts.initialStepResults } : {}
5548
+ ...internal?.initialStepResults !== void 0 ? { initialStepResults: internal.initialStepResults } : {},
5549
+ ...internal?.onStepEvent !== void 0 ? { onStepEvent: internal.onStepEvent } : {}
5413
5550
  });
5414
5551
  } finally {
5415
5552
  runSpan.end();
@@ -5440,28 +5577,17 @@ async function dispatchStep(step, input, ctx, options, prevStepResults) {
5440
5577
  }
5441
5578
  }
5442
5579
  }
5443
- function assembleRun(params) {
5444
- const endedAt = Date.now();
5445
- return {
5446
- id: params.runId,
5447
- name: params.name,
5448
- status: params.status,
5449
- startedAt: params.startedAt,
5450
- endedAt,
5451
- stepResults: params.stepResults,
5452
- ...params.output !== void 0 ? { output: params.output } : {},
5453
- ...params.error !== void 0 ? { error: params.error } : {}
5454
- };
5455
- }
5456
5580
  async function saveSnapshot(p) {
5457
5581
  const snapshot = {
5458
- _schemaVersion: 1,
5582
+ _schemaVersion: 2,
5583
+ // SE29 — carries `state`
5459
5584
  runId: p.runId,
5460
5585
  workflowName: p.workflowName,
5461
5586
  currentStepId: p.currentStepId,
5462
5587
  suspendedPayload: p.suspendedPayload,
5463
5588
  stepResults: p.stepResults,
5464
5589
  accumulatedInput: p.accumulatedInput,
5590
+ ...p.state !== void 0 ? { state: p.state } : {},
5465
5591
  suspendedAt: Date.now()
5466
5592
  };
5467
5593
  const store = getSnapshotStoreFor(p.options);
@@ -5494,7 +5620,10 @@ async function resumeWorkflow(opts) {
5494
5620
  signal: opts.signal,
5495
5621
  runId: opts.runId,
5496
5622
  // M3 #62 — restore prior step outputs so the resumed run is not lossy (internal seam).
5497
- initialStepResults: snapshot.stepResults
5623
+ initialStepResults: snapshot.stepResults,
5624
+ // SE29 — restore shared state (v2 snapshot). A v1 snapshot has no `state` →
5625
+ // executeWorkflow falls back to `options.initialState`.
5626
+ ...snapshot.state !== void 0 ? { restoredState: snapshot.state } : {}
5498
5627
  });
5499
5628
  }
5500
5629
  var init_executor = __esm({
@@ -5502,6 +5631,7 @@ var init_executor = __esm({
5502
5631
  init_workflow();
5503
5632
  init_ctx();
5504
5633
  init_error_shape();
5634
+ init_executor_helpers();
5505
5635
  init_run_id();
5506
5636
  init_single_flight();
5507
5637
  init_snapshot_store();
@@ -21031,6 +21161,45 @@ var PersistenceSchema = zod.z.object({
21031
21161
 
21032
21162
  // src/workflow.ts
21033
21163
  init_path_guard();
21164
+
21165
+ // src/internal/workflow/event-stream.ts
21166
+ function createEventStream() {
21167
+ const buffer = [];
21168
+ const waiters = [];
21169
+ let ended = false;
21170
+ const stream = {
21171
+ push(event) {
21172
+ if (ended) return;
21173
+ const waiter = waiters.shift();
21174
+ if (waiter !== void 0) waiter({ value: event, done: false });
21175
+ else buffer.push(event);
21176
+ },
21177
+ end() {
21178
+ if (ended) return;
21179
+ ended = true;
21180
+ for (const waiter of waiters.splice(0)) waiter({ value: void 0, done: true });
21181
+ },
21182
+ next() {
21183
+ const buffered = buffer.shift();
21184
+ if (buffered !== void 0) return Promise.resolve({ value: buffered, done: false });
21185
+ if (ended) return Promise.resolve({ value: void 0, done: true });
21186
+ return new Promise((resolve3) => waiters.push(resolve3));
21187
+ },
21188
+ // `for await` calls return() on break/throw — close early so events stop
21189
+ // buffering in memory for a consumer that stopped iterating.
21190
+ return() {
21191
+ stream.end();
21192
+ buffer.length = 0;
21193
+ return Promise.resolve({ value: void 0, done: true });
21194
+ },
21195
+ [Symbol.asyncIterator]() {
21196
+ return stream;
21197
+ }
21198
+ };
21199
+ return stream;
21200
+ }
21201
+
21202
+ // src/workflow.ts
21034
21203
  init_workflow();
21035
21204
  var RetryPolicySchema = zod.z.object({
21036
21205
  // EC-3 absorbed: maxAttempts MUST be a finite int in [1, 20].
@@ -21042,7 +21211,12 @@ var RetryPolicySchema = zod.z.object({
21042
21211
  });
21043
21212
  var WorkflowOptionsSchema = zod.z.object({
21044
21213
  name: zod.z.string().min(1).max(128),
21045
- persistence: PersistenceSchema
21214
+ persistence: PersistenceSchema,
21215
+ // SE27 — declared so a future `new WorkflowBuilder(parsed)` refactor cannot
21216
+ // silently drop them (create() passes the ORIGINAL options today, but the
21217
+ // schema is also the documentation of the shape).
21218
+ inputSchema: zod.z.custom().optional(),
21219
+ outputSchema: zod.z.custom().optional()
21046
21220
  });
21047
21221
  var WorkflowBuilder = class {
21048
21222
  /** @internal */
@@ -21219,6 +21393,36 @@ var Workflow = class {
21219
21393
  }
21220
21394
  return result;
21221
21395
  }
21396
+ /**
21397
+ * SE28 — run the workflow and STREAM step-level events as they happen. Returns
21398
+ * an async iterator of {@link WorkflowEvent}s (`step_started` / `step_completed`
21399
+ * / `step_failed` / `workflow_suspended` / `workflow_completed`, top-level
21400
+ * steps) plus a `result` promise resolving to the same terminal
21401
+ * {@link WorkflowRun} `run()` returns. Iterate for progress; await `result` for
21402
+ * the outcome. The stream ends when the run terminates.
21403
+ *
21404
+ * `result` is the AUTHORITATIVE terminal status. Not every terminal state has a
21405
+ * closing event: a step failure emits `step_failed`, but an `outputSchema`
21406
+ * rejection (SE27) or an abort ends the stream WITHOUT `workflow_completed` —
21407
+ * always `await result` to read the final `status`. Consuming order is free:
21408
+ * awaiting `result` without draining, or draining without awaiting `result`,
21409
+ * both work (breaking out of `for await` stops the buffering early).
21410
+ */
21411
+ stream(input, opts) {
21412
+ const queue = createEventStream();
21413
+ const result = (async () => {
21414
+ const { executeWorkflow: executeWorkflow2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
21415
+ try {
21416
+ return await executeWorkflow2(this._options, this._steps, input, {
21417
+ ...opts,
21418
+ onStepEvent: (event) => queue.push(event)
21419
+ });
21420
+ } finally {
21421
+ queue.end();
21422
+ }
21423
+ })();
21424
+ return Object.assign(queue, { result });
21425
+ }
21222
21426
  /**
21223
21427
  * Resume a suspended workflow from its snapshot. Throws
21224
21428
  * `WorkflowSnapshotNotFoundError` if `runId` is unknown.