@voltro/workflow 0.80.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cluster.d.ts CHANGED
@@ -5,9 +5,16 @@ import { DataStore } from '@voltro/database';
5
5
  import { Duration } from 'effect';
6
6
  import { Effect } from 'effect';
7
7
  import { Layer } from 'effect';
8
+ import { MalformedMessage } from '@effect/cluster/ClusterError';
8
9
  import { MessageStorage } from '@effect/cluster';
10
+ import { MysqlCharset } from '@voltro/database/sql';
11
+ import { PersistenceError } from '@effect/cluster/ClusterError';
12
+ import { QueryDescriptor } from '@voltro/database';
13
+ import { Row } from '@voltro/database';
14
+ import { Schema } from 'effect';
9
15
  import { Sharding } from '@effect/cluster';
10
16
  import { SqlClient } from '@effect/sql';
17
+ import { SqlClient as SqlClient_2 } from '@effect/sql/SqlClient';
11
18
  import { SqlError } from '@effect/sql';
12
19
  import { SqlError as SqlError_2 } from '@effect/sql/SqlError';
13
20
  import { Workflow } from '@effect/workflow';
@@ -22,6 +29,43 @@ import { WorkflowEngine } from '@effect/workflow';
22
29
  */
23
30
  export declare const activityPrimaryKey: (activityName: string, attempt: number) => string;
24
31
 
32
+ /** The narrow slice of `DataStore` flow control needs. */
33
+ declare interface AdmissionDataStore {
34
+ /** Native arbitration, preserved by every resident transaction adapter. */
35
+ readonly insertIgnoreWithOutcome: DataStore['insertIgnoreWithOutcome'];
36
+ /** Host schema codecs: queue rows carry encoded values, controls and the
37
+ * engine consume decoded payloads. Absent for a JSON-only low-level host. */
38
+ readonly encodeWorkflowPayload?: (workflowName: string, payload: unknown) => unknown;
39
+ readonly decodeWorkflowPayload?: (workflowName: string, payload: unknown) => unknown;
40
+ query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
41
+ insert(table: string, row: Row): Promise<Row>;
42
+ update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<Row | null>;
43
+ delete(table: string, primaryKey: string): Promise<boolean>;
44
+ upsert(table: string, row: Row, options: {
45
+ conflictColumns: ReadonlyArray<string>;
46
+ update?: ReadonlyArray<string> | ((existing: Row) => Readonly<Record<string, unknown>>);
47
+ }): Promise<Row>;
48
+ }
49
+
50
+ /** The run currently holding a singleton key. */
51
+ declare interface AdmissionIncumbent {
52
+ readonly executionId: string;
53
+ readonly runId: string;
54
+ /** Epoch ms, for the "how long has it held this" the ledger reports. */
55
+ readonly admittedAt: number;
56
+ }
57
+
58
+ /** Every flow-control key resolved against ONE concrete payload. */
59
+ declare interface AdmissionKeys {
60
+ readonly debounceKey?: string;
61
+ readonly singletonKey?: string;
62
+ readonly concurrencyKey?: string;
63
+ readonly throttleKey?: string;
64
+ readonly rateLimitKey?: string;
65
+ readonly batchKey?: string;
66
+ readonly priority: number;
67
+ }
68
+
25
69
  /** Database-only capture while holding the resident admission/entry lock.
26
70
  * observe reads the journal; it must never wake/reset the engine. Missing
27
71
  * journal evidence waits without inventing a binding. After commit, every
@@ -55,11 +99,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
55
99
  readonly enteredAt: number;
56
100
  } | null;
57
101
  readonly generation: string;
102
+ readonly runId: string;
58
103
  readonly requestedAt: number;
59
104
  readonly executionId: string;
60
- readonly runId: string;
61
105
  readonly workflowName: string;
62
106
  readonly operation: "resume" | "cancel" | "terminate";
107
+ readonly dispatchAttempts: number;
63
108
  readonly owner: {
64
109
  readonly tenantId: string | null;
65
110
  } | null;
@@ -68,7 +113,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
68
113
  readonly replyId: string;
69
114
  readonly restartToken: string | null;
70
115
  } | null;
71
- readonly dispatchAttempts: number;
72
116
  };
73
117
  } | {
74
118
  kind: "unknown";
@@ -94,11 +138,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
94
138
  readonly enteredAt: number;
95
139
  } | null;
96
140
  readonly generation: string;
141
+ readonly runId: string;
97
142
  readonly requestedAt: number;
98
143
  readonly executionId: string;
99
- readonly runId: string;
100
144
  readonly workflowName: string;
101
145
  readonly operation: "resume" | "cancel" | "terminate";
146
+ readonly dispatchAttempts: number;
102
147
  readonly owner: {
103
148
  readonly tenantId: string | null;
104
149
  } | null;
@@ -107,7 +152,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
107
152
  readonly replyId: string;
108
153
  readonly restartToken: string | null;
109
154
  } | null;
110
- readonly dispatchAttempts: number;
111
155
  };
112
156
  } | {
113
157
  kind: "superseded";
@@ -133,11 +177,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
133
177
  readonly enteredAt: number;
134
178
  } | null;
135
179
  readonly generation: string;
180
+ readonly runId: string;
136
181
  readonly requestedAt: number;
137
182
  readonly executionId: string;
138
- readonly runId: string;
139
183
  readonly workflowName: string;
140
184
  readonly operation: "resume" | "cancel" | "terminate";
185
+ readonly dispatchAttempts: number;
141
186
  readonly owner: {
142
187
  readonly tenantId: string | null;
143
188
  } | null;
@@ -146,7 +191,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
146
191
  readonly replyId: string;
147
192
  readonly restartToken: string | null;
148
193
  } | null;
149
- readonly dispatchAttempts: number;
150
194
  };
151
195
  } | {
152
196
  kind: "not-suspended";
@@ -172,11 +216,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
172
216
  readonly enteredAt: number;
173
217
  } | null;
174
218
  readonly generation: string;
219
+ readonly runId: string;
175
220
  readonly requestedAt: number;
176
221
  readonly executionId: string;
177
- readonly runId: string;
178
222
  readonly workflowName: string;
179
223
  readonly operation: "resume" | "cancel" | "terminate";
224
+ readonly dispatchAttempts: number;
180
225
  readonly owner: {
181
226
  readonly tenantId: string | null;
182
227
  } | null;
@@ -185,7 +230,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
185
230
  readonly replyId: string;
186
231
  readonly restartToken: string | null;
187
232
  } | null;
188
- readonly dispatchAttempts: number;
189
233
  };
190
234
  } | {
191
235
  kind: "bound";
@@ -211,11 +255,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
211
255
  readonly enteredAt: number;
212
256
  } | null;
213
257
  readonly generation: string;
258
+ readonly runId: string;
214
259
  readonly requestedAt: number;
215
260
  readonly executionId: string;
216
- readonly runId: string;
217
261
  readonly workflowName: string;
218
262
  readonly operation: "resume" | "cancel" | "terminate";
263
+ readonly dispatchAttempts: number;
219
264
  readonly owner: {
220
265
  readonly tenantId: string | null;
221
266
  } | null;
@@ -224,7 +269,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
224
269
  readonly replyId: string;
225
270
  readonly restartToken: string | null;
226
271
  } | null;
227
- readonly dispatchAttempts: number;
228
272
  };
229
273
  } | {
230
274
  kind: "waiting-journal";
@@ -250,11 +294,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
250
294
  readonly enteredAt: number;
251
295
  } | null;
252
296
  readonly generation: string;
297
+ readonly runId: string;
253
298
  readonly requestedAt: number;
254
299
  readonly executionId: string;
255
- readonly runId: string;
256
300
  readonly workflowName: string;
257
301
  readonly operation: "resume" | "cancel" | "terminate";
302
+ readonly dispatchAttempts: number;
258
303
  readonly owner: {
259
304
  readonly tenantId: string | null;
260
305
  } | null;
@@ -263,7 +308,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
263
308
  readonly replyId: string;
264
309
  readonly restartToken: string | null;
265
310
  } | null;
266
- readonly dispatchAttempts: number;
267
311
  };
268
312
  }>;
269
313
 
@@ -349,17 +393,50 @@ declare interface ClusterMessageState {
349
393
  readonly lastRead: Date | null;
350
394
  }
351
395
 
396
+ /**
397
+ * A pass-through `SqlClient` layer that runs the cluster first-boot migration
398
+ * (serialized behind the reserved-connection migration lock) as a build
399
+ * side-effect, then re-exposes the SAME `SqlClient`. Provided in place of the raw
400
+ * dialect SqlClient layer so the cluster engine consumes THIS one — forcing the
401
+ * migration to complete before `SingleRunner` builds and hits its own (now no-op)
402
+ * DDL. Skipped entirely on the warm path, so a running cluster and single-runner
403
+ * restarts cost nothing.
404
+ */
405
+ export declare const clusterMigrationGateLayer: (opts: {
406
+ readonly runnerStorage: RunnerStorage;
407
+ /** The declared store charset (mysql family) — the tables this gate creates are created in it. */
408
+ readonly charset?: MysqlCharset | undefined;
409
+ }) => Layer.Layer<SqlClient.SqlClient, SqlError.SqlError, SqlClient.SqlClient>;
410
+
352
411
  declare interface ClusterRunner {
353
412
  readonly address: string;
354
413
  readonly lastHeartbeat: Date | null;
355
414
  readonly healthy: boolean;
356
415
  }
357
416
 
417
+ declare type Command = Parameters<typeof readWorkflowRestartReservationInTransaction>[1];
418
+
358
419
  declare type ControlStore = Parameters<typeof bindWorkflowResumeJournalInTransaction>[0];
359
420
 
360
421
  /** Default port for the cluster's single-runner listener. Override per deploy. */
361
422
  export declare const DEFAULT_RUNNER_LISTEN_PORT: 34000;
362
423
 
424
+ declare type DeferMode = DeferringControl | 'paused' | 'singleton';
425
+
426
+ /**
427
+ * Controls that can DEFER a start rather than answer it immediately.
428
+ *
429
+ * The distinction matters at exactly one place: `ctx.workflows.run(...)` and
430
+ * `start(..., { wait: true })` block for the run's RESULT. There is no coherent
431
+ * result to return for a start that was collapsed into a future run, so those
432
+ * two callers reject a deferring control loudly instead of doing something
433
+ * surprising. Admission controls that have a synchronous answer — singleton,
434
+ * rate limit — still apply on every path.
435
+ */
436
+ declare const DEFERRING_CONTROLS: readonly ["debounce", "batch", "throttle", "concurrency"];
437
+
438
+ declare type DeferringControl = (typeof DEFERRING_CONTROLS)[number];
439
+
363
440
  /** Probe the live connection for an active wsrep (Galera / Percona XtraDB)
364
441
  * cluster. `@@wsrep_on = 1` iff Galera replication is active; the variable is
365
442
  * absent on stock MySQL (→ query errors → not a wsrep cluster) and `0` on a
@@ -368,6 +445,28 @@ export declare const DEFAULT_RUNNER_LISTEN_PORT: 34000;
368
445
  * somehow can't be probed can still force `VOLTRO_WORKFLOW_SHARD_LOCK=row`. */
369
446
  export declare const detectWsrepCluster: Effect.Effect<boolean, never, SqlClient.SqlClient>;
370
447
 
448
+ /** Internal dispatch only. A reset or poll is not evidence of body entry.
449
+ * The caller must reserve the resident generation and provide its capacity
450
+ * coordinator before dispatching. Inspect does not use this capability yet. */
451
+ declare const dispatchWorkflowRestart: (options: Parameters<typeof resetReservedWorkflowJournal>[0]) => Effect.Effect<{
452
+ kind: "waiting-capacity";
453
+ } | {
454
+ kind: "attempted";
455
+ journal: {
456
+ kind: "changed";
457
+ requestId: string;
458
+ count?: never;
459
+ } | {
460
+ kind: "already-reset";
461
+ count: number;
462
+ requestId?: never;
463
+ } | {
464
+ kind: "reset";
465
+ count: number;
466
+ requestId?: never;
467
+ };
468
+ }, SqlError_2, SqlClient.SqlClient | Sharding.Sharding>;
469
+
371
470
  /** Internal cluster dispatch, not an acceptance or completion API. The host
372
471
  * supplies the resident admission transaction and the matching journal layer.
373
472
  * Only the recorder's next entry can confirm the persisted resume receipt. */
@@ -398,11 +497,12 @@ declare const dispatchWorkflowResume: (options: {
398
497
  readonly enteredAt: number;
399
498
  } | null;
400
499
  readonly generation: string;
500
+ readonly runId: string;
401
501
  readonly requestedAt: number;
402
502
  readonly executionId: string;
403
- readonly runId: string;
404
503
  readonly workflowName: string;
405
504
  readonly operation: "resume" | "cancel" | "terminate";
505
+ readonly dispatchAttempts: number;
406
506
  readonly owner: {
407
507
  readonly tenantId: string | null;
408
508
  } | null;
@@ -411,7 +511,6 @@ declare const dispatchWorkflowResume: (options: {
411
511
  readonly replyId: string;
412
512
  readonly restartToken: string | null;
413
513
  } | null;
414
- readonly dispatchAttempts: number;
415
514
  };
416
515
  } | {
417
516
  kind: "unknown";
@@ -437,11 +536,12 @@ declare const dispatchWorkflowResume: (options: {
437
536
  readonly enteredAt: number;
438
537
  } | null;
439
538
  readonly generation: string;
539
+ readonly runId: string;
440
540
  readonly requestedAt: number;
441
541
  readonly executionId: string;
442
- readonly runId: string;
443
542
  readonly workflowName: string;
444
543
  readonly operation: "resume" | "cancel" | "terminate";
544
+ readonly dispatchAttempts: number;
445
545
  readonly owner: {
446
546
  readonly tenantId: string | null;
447
547
  } | null;
@@ -450,7 +550,6 @@ declare const dispatchWorkflowResume: (options: {
450
550
  readonly replyId: string;
451
551
  readonly restartToken: string | null;
452
552
  } | null;
453
- readonly dispatchAttempts: number;
454
553
  };
455
554
  } | {
456
555
  kind: "superseded";
@@ -476,11 +575,12 @@ declare const dispatchWorkflowResume: (options: {
476
575
  readonly enteredAt: number;
477
576
  } | null;
478
577
  readonly generation: string;
578
+ readonly runId: string;
479
579
  readonly requestedAt: number;
480
580
  readonly executionId: string;
481
- readonly runId: string;
482
581
  readonly workflowName: string;
483
582
  readonly operation: "resume" | "cancel" | "terminate";
583
+ readonly dispatchAttempts: number;
484
584
  readonly owner: {
485
585
  readonly tenantId: string | null;
486
586
  } | null;
@@ -489,7 +589,6 @@ declare const dispatchWorkflowResume: (options: {
489
589
  readonly replyId: string;
490
590
  readonly restartToken: string | null;
491
591
  } | null;
492
- readonly dispatchAttempts: number;
493
592
  };
494
593
  } | {
495
594
  kind: "not-suspended";
@@ -515,11 +614,12 @@ declare const dispatchWorkflowResume: (options: {
515
614
  readonly enteredAt: number;
516
615
  } | null;
517
616
  readonly generation: string;
617
+ readonly runId: string;
518
618
  readonly requestedAt: number;
519
619
  readonly executionId: string;
520
- readonly runId: string;
521
620
  readonly workflowName: string;
522
621
  readonly operation: "resume" | "cancel" | "terminate";
622
+ readonly dispatchAttempts: number;
523
623
  readonly owner: {
524
624
  readonly tenantId: string | null;
525
625
  } | null;
@@ -528,7 +628,6 @@ declare const dispatchWorkflowResume: (options: {
528
628
  readonly replyId: string;
529
629
  readonly restartToken: string | null;
530
630
  } | null;
531
- readonly dispatchAttempts: number;
532
631
  };
533
632
  } | {
534
633
  kind: "waiting-journal";
@@ -554,11 +653,12 @@ declare const dispatchWorkflowResume: (options: {
554
653
  readonly enteredAt: number;
555
654
  } | null;
556
655
  readonly generation: string;
656
+ readonly runId: string;
557
657
  readonly requestedAt: number;
558
658
  readonly executionId: string;
559
- readonly runId: string;
560
659
  readonly workflowName: string;
561
660
  readonly operation: "resume" | "cancel" | "terminate";
661
+ readonly dispatchAttempts: number;
562
662
  readonly owner: {
563
663
  readonly tenantId: string | null;
564
664
  } | null;
@@ -567,7 +667,6 @@ declare const dispatchWorkflowResume: (options: {
567
667
  readonly replyId: string;
568
668
  readonly restartToken: string | null;
569
669
  } | null;
570
- readonly dispatchAttempts: number;
571
670
  };
572
671
  } | {
573
672
  kind: "already-terminal";
@@ -593,11 +692,12 @@ declare const dispatchWorkflowResume: (options: {
593
692
  readonly enteredAt: number;
594
693
  } | null;
595
694
  readonly generation: string;
695
+ readonly runId: string;
596
696
  readonly requestedAt: number;
597
697
  readonly executionId: string;
598
- readonly runId: string;
599
698
  readonly workflowName: string;
600
699
  readonly operation: "resume" | "cancel" | "terminate";
700
+ readonly dispatchAttempts: number;
601
701
  readonly owner: {
602
702
  readonly tenantId: string | null;
603
703
  } | null;
@@ -606,7 +706,6 @@ declare const dispatchWorkflowResume: (options: {
606
706
  readonly replyId: string;
607
707
  readonly restartToken: string | null;
608
708
  } | null;
609
- readonly dispatchAttempts: number;
610
709
  };
611
710
  run: Readonly<Record<string, unknown>>;
612
711
  } | {
@@ -634,9 +733,9 @@ declare const dispatchWorkflowResume: (options: {
634
733
  readonly enteredAt: number;
635
734
  } | null;
636
735
  generation: string;
736
+ runId: string;
637
737
  requestedAt: number;
638
738
  executionId: string;
639
- runId: string;
640
739
  workflowName: string;
641
740
  operation: "resume" | "cancel" | "terminate";
642
741
  owner: {
@@ -663,6 +762,12 @@ declare const dispatchWorkflowResume: (options: {
663
762
  };
664
763
  }, SqlError_2, SqlClient.SqlClient | Sharding.Sharding>;
665
764
 
765
+ /** An immutable observation, not merely an execution/request identity. */
766
+ declare interface ExpectedWorkflowReply {
767
+ readonly requestId: string;
768
+ readonly replyId: string;
769
+ }
770
+
666
771
  /** A step attempt to reset so it re-executes on the next drive. */
667
772
  export declare interface FailedActivityRef {
668
773
  readonly name: string;
@@ -679,6 +784,43 @@ export declare const makeClusterCronLayer: (spec: ClusterCronSpec) => Layer.Laye
679
784
 
680
785
  export declare const makeClusterDiagnostics: (options: ClusterDiagnosticsOptions) => Effect.Effect<ClusterDiagnosticsOps, never, SqlClient.SqlClient>;
681
786
 
787
+ declare type Observation = {
788
+ readonly kind: 'observed';
789
+ readonly replies: ReadonlyArray<ExpectedWorkflowReply>;
790
+ } | {
791
+ readonly kind: 'unavailable';
792
+ readonly reason: string;
793
+ };
794
+
795
+ /** Read-only capture of exact replies in the engine journal. The resident
796
+ * reservation must bind these observations to its failed generation; the
797
+ * subsequent reset compares every reply again before changing anything. */
798
+ declare const observeWorkflowRestartReplies: (target: WorkflowRestartObservationTarget) => Effect.Effect<{
799
+ kind: "unavailable";
800
+ reason: "no-journal";
801
+ replies?: never;
802
+ } | {
803
+ kind: "unavailable";
804
+ reason: "delivery-changed";
805
+ replies?: never;
806
+ } | {
807
+ kind: "unavailable";
808
+ reason: "no-reply";
809
+ replies?: never;
810
+ } | {
811
+ kind: "unavailable";
812
+ reason: "succeeded" | "suspended" | "unrecognized-reply";
813
+ replies?: never;
814
+ } | {
815
+ kind: "unavailable";
816
+ reason: "activity-not-found";
817
+ replies?: never;
818
+ } | {
819
+ kind: "observed";
820
+ replies: readonly ExpectedWorkflowReply[];
821
+ reason?: never;
822
+ }, SqlError_2 | PersistenceError, SqlClient.SqlClient | Sharding.Sharding | MessageStorage.MessageStorage>;
823
+
682
824
  /** Parse `VOLTRO_WORKFLOW_SHARD_LOCK` (default `'auto'`). Throws on an
683
825
  * unrecognized value so a typo fails boot loudly instead of silently keeping
684
826
  * the advisory path that breaks cross-pod handoff on Galera. */
@@ -694,6 +836,240 @@ export declare const parseShardLockMode: (raw?: string | undefined) => ShardLock
694
836
  */
695
837
  export declare const pollWorkflowStorage: Effect.Effect<void, never, Sharding.Sharding>;
696
838
 
839
+ /** Prepare against the resident owner, observe through the engine, then reserve
840
+ * against the same owner. No resident transaction spans journal I/O. Recovery
841
+ * reads the accepted command before looking at journal replies that a previous
842
+ * dispatch may already have reset. This does not dispatch or authorize entry. */
843
+ declare const prepareWorkflowRestart: (options: {
844
+ readonly transaction: <T>(body: (tx: AdmissionDataStore) => Promise<T>) => Promise<T>;
845
+ readonly command: Command;
846
+ readonly workflow: Workflow.Any;
847
+ readonly readActivities: (tx: AdmissionDataStore) => Promise<ReadonlyArray<FailedActivityRef>>;
848
+ readonly control: ResolvedFlowControl;
849
+ readonly readPaused: (tx: AdmissionDataStore) => Promise<boolean>;
850
+ readonly now: number;
851
+ readonly createToken: () => string;
852
+ readonly observe: (target: WorkflowRestartObservationTarget) => Promise<Observation>;
853
+ }) => Promise<{
854
+ kind: "reserved";
855
+ request: {
856
+ readonly id: string;
857
+ readonly tenantId: string | null;
858
+ readonly token: string;
859
+ readonly generation: string;
860
+ readonly runId: string;
861
+ readonly requestedAt: Date;
862
+ readonly executionId: string;
863
+ readonly workflowName: string;
864
+ readonly selection: {
865
+ readonly kind: "failed-steps";
866
+ } | {
867
+ readonly kind: "from-step";
868
+ readonly stepName: string;
869
+ };
870
+ readonly dispatchAttempts: number;
871
+ readonly enteredGeneration: string | null;
872
+ readonly ledgerId: string;
873
+ readonly eviction: {
874
+ readonly runId: string;
875
+ readonly executionId: string;
876
+ } | null;
877
+ readonly delivery: {
878
+ readonly requestId: string;
879
+ readonly restartToken: string | null;
880
+ };
881
+ readonly replies: readonly {
882
+ readonly requestId: string;
883
+ readonly replyId: string;
884
+ }[];
885
+ };
886
+ reused: boolean;
887
+ decision?: never;
888
+ } | {
889
+ kind: "already-requested";
890
+ request?: never;
891
+ reused?: never;
892
+ decision?: never;
893
+ } | {
894
+ kind: "not-admitted";
895
+ decision: {
896
+ readonly kind: "defer";
897
+ readonly mode: DeferMode;
898
+ readonly dueAt: number;
899
+ readonly deadlineAt?: number;
900
+ readonly collapse: "replace" | "append" | "hold";
901
+ readonly keys: AdmissionKeys;
902
+ } | {
903
+ readonly kind: "drop";
904
+ readonly reason: "rate-limit";
905
+ readonly retryAfterMs: number;
906
+ readonly keys: AdmissionKeys;
907
+ } | {
908
+ readonly kind: "skip";
909
+ readonly incumbent: AdmissionIncumbent;
910
+ readonly keys: AdmissionKeys;
911
+ };
912
+ request?: never;
913
+ reused?: never;
914
+ } | {
915
+ readonly kind: "unavailable";
916
+ readonly reason: string;
917
+ }>;
918
+
919
+ /** Host command boundary: recover an immutable receipt before reading the run's
920
+ * current generation. A lost response must not turn a retry into a new restart
921
+ * after the first restart has entered, completed, or failed again. The caller
922
+ * has already resolved the physical resident and trusted workflow/tenant. */
923
+ declare const prepareWorkflowRestartCommand: (options: Omit<Parameters<typeof prepareWorkflowRestart>[0], "command"> & {
924
+ readonly command: Parameters<typeof readWorkflowRestartCommandInTransaction>[1];
925
+ }) => Promise<{
926
+ kind: "reserved";
927
+ request: {
928
+ readonly id: string;
929
+ readonly tenantId: string | null;
930
+ readonly token: string;
931
+ readonly generation: string;
932
+ readonly runId: string;
933
+ readonly requestedAt: Date;
934
+ readonly executionId: string;
935
+ readonly workflowName: string;
936
+ readonly selection: {
937
+ readonly kind: "failed-steps";
938
+ } | {
939
+ readonly kind: "from-step";
940
+ readonly stepName: string;
941
+ };
942
+ readonly dispatchAttempts: number;
943
+ readonly enteredGeneration: string | null;
944
+ readonly ledgerId: string;
945
+ readonly eviction: {
946
+ readonly runId: string;
947
+ readonly executionId: string;
948
+ } | null;
949
+ readonly delivery: {
950
+ readonly requestId: string;
951
+ readonly restartToken: string | null;
952
+ };
953
+ readonly replies: readonly {
954
+ readonly requestId: string;
955
+ readonly replyId: string;
956
+ }[];
957
+ };
958
+ reused: boolean;
959
+ decision?: never;
960
+ } | {
961
+ kind: "already-requested";
962
+ request?: never;
963
+ reused?: never;
964
+ decision?: never;
965
+ } | {
966
+ kind: "not-admitted";
967
+ decision: {
968
+ readonly kind: "defer";
969
+ readonly mode: DeferMode;
970
+ readonly dueAt: number;
971
+ readonly deadlineAt?: number;
972
+ readonly collapse: "replace" | "append" | "hold";
973
+ readonly keys: AdmissionKeys;
974
+ } | {
975
+ readonly kind: "drop";
976
+ readonly reason: "rate-limit";
977
+ readonly retryAfterMs: number;
978
+ readonly keys: AdmissionKeys;
979
+ } | {
980
+ readonly kind: "skip";
981
+ readonly incumbent: AdmissionIncumbent;
982
+ readonly keys: AdmissionKeys;
983
+ };
984
+ request?: never;
985
+ reused?: never;
986
+ } | {
987
+ readonly kind: "unavailable";
988
+ readonly reason: string;
989
+ }>;
990
+
991
+ /** Read an accepted command before observing the journal again. Its captured
992
+ * replies remain authoritative after reset or body entry. This grants no new
993
+ * dispatch permission: dispatch must still recheck the resident generation. */
994
+ declare const readWorkflowRestartCommandInTransaction: (tx: AdmissionDataStore, input: {
995
+ readonly id: string;
996
+ readonly runId: string;
997
+ readonly workflowName: string;
998
+ readonly tenantId: string | null;
999
+ readonly selection: WorkflowRestartSelection;
1000
+ }) => Promise<{
1001
+ readonly id: string;
1002
+ readonly tenantId: string | null;
1003
+ readonly token: string;
1004
+ readonly generation: string;
1005
+ readonly runId: string;
1006
+ readonly requestedAt: Date;
1007
+ readonly executionId: string;
1008
+ readonly workflowName: string;
1009
+ readonly selection: {
1010
+ readonly kind: "failed-steps";
1011
+ } | {
1012
+ readonly kind: "from-step";
1013
+ readonly stepName: string;
1014
+ };
1015
+ readonly dispatchAttempts: number;
1016
+ readonly enteredGeneration: string | null;
1017
+ readonly ledgerId: string;
1018
+ readonly eviction: {
1019
+ readonly runId: string;
1020
+ readonly executionId: string;
1021
+ } | null;
1022
+ readonly delivery: {
1023
+ readonly requestId: string;
1024
+ readonly restartToken: string | null;
1025
+ };
1026
+ readonly replies: readonly {
1027
+ readonly requestId: string;
1028
+ readonly replyId: string;
1029
+ }[];
1030
+ } | undefined>;
1031
+
1032
+ /** Internal owner check after a generation has been captured. Command recovery
1033
+ * deliberately does not derive that generation from the run's current state. */
1034
+ declare const readWorkflowRestartReservationInTransaction: (tx: AdmissionDataStore, input: Parameters<typeof readWorkflowRestartCommandInTransaction>[1] & {
1035
+ readonly generation: string;
1036
+ }) => Promise<{
1037
+ readonly id: string;
1038
+ readonly tenantId: string | null;
1039
+ readonly token: string;
1040
+ readonly generation: string;
1041
+ readonly runId: string;
1042
+ readonly requestedAt: Date;
1043
+ readonly executionId: string;
1044
+ readonly workflowName: string;
1045
+ readonly selection: {
1046
+ readonly kind: "failed-steps";
1047
+ } | {
1048
+ readonly kind: "from-step";
1049
+ readonly stepName: string;
1050
+ };
1051
+ readonly dispatchAttempts: number;
1052
+ readonly enteredGeneration: string | null;
1053
+ readonly ledgerId: string;
1054
+ readonly eviction: {
1055
+ readonly runId: string;
1056
+ readonly executionId: string;
1057
+ } | null;
1058
+ readonly delivery: {
1059
+ readonly requestId: string;
1060
+ readonly restartToken: string | null;
1061
+ };
1062
+ readonly replies: readonly {
1063
+ readonly requestId: string;
1064
+ readonly replyId: string;
1065
+ }[];
1066
+ } | undefined>;
1067
+
1068
+ /** Observe the persisted run delivery without decoding it against a possibly
1069
+ * changed workflow schema. Read failures remain errors, never `not-found`.
1070
+ * This does not acknowledge replies, reset storage or dispatch an execution. */
1071
+ export declare const readWorkflowRunReply: (workflow: Workflow.Any, executionId: string) => Effect.Effect<WorkflowRunReplyObservation, PersistenceError | MalformedMessage, Sharding.Sharding | MessageStorage.MessageStorage>;
1072
+
697
1073
  /**
698
1074
  * Re-drive a terminally-`failed` workflow run from the step it died on.
699
1075
  *
@@ -751,6 +1127,130 @@ export declare interface RedriveTarget {
751
1127
  readonly failedActivities: ReadonlyArray<FailedActivityRef>;
752
1128
  }
753
1129
 
1130
+ declare const Request_2: Schema.Struct<{
1131
+ id: typeof Schema.NonEmptyString;
1132
+ runId: typeof Schema.NonEmptyString;
1133
+ workflowName: typeof Schema.NonEmptyString;
1134
+ executionId: typeof Schema.NonEmptyString;
1135
+ generation: typeof Schema.NonEmptyString;
1136
+ token: typeof Schema.NonEmptyString;
1137
+ ledgerId: typeof Schema.NonEmptyString;
1138
+ tenantId: Schema.NullOr<typeof Schema.NonEmptyString>;
1139
+ delivery: Schema.Struct<{
1140
+ requestId: typeof Schema.NonEmptyString;
1141
+ restartToken: Schema.NullOr<typeof Schema.NonEmptyString>;
1142
+ }>;
1143
+ replies: Schema.Array$<Schema.Struct<{
1144
+ requestId: typeof Schema.NonEmptyString;
1145
+ replyId: typeof Schema.NonEmptyString;
1146
+ }>>;
1147
+ selection: Schema.Union<[Schema.Struct<{
1148
+ kind: Schema.Literal<["failed-steps"]>;
1149
+ }>, Schema.Struct<{
1150
+ kind: Schema.Literal<["from-step"]>;
1151
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
1152
+ }>]>;
1153
+ eviction: Schema.NullOr<Schema.Struct<{
1154
+ executionId: typeof Schema.NonEmptyString;
1155
+ runId: typeof Schema.String;
1156
+ }>>;
1157
+ requestedAt: typeof Schema.DateFromSelf;
1158
+ dispatchAttempts: Schema.refine<number, typeof Schema.NonNegative>;
1159
+ enteredGeneration: Schema.NullOr<typeof Schema.NonEmptyString>;
1160
+ }>;
1161
+
1162
+ /** Private dispatch composition. Capacity is mandatory, outside the resident
1163
+ * transaction; then revalidate the owner and commit its attempt before SQL I/O.
1164
+ * Returns only journal evidence, never body acceptance. The host still owns
1165
+ * engine polling and reconciliation after an ambiguous journal acknowledgement. */
1166
+ declare const resetReservedWorkflowJournal: (input: {
1167
+ readonly requestId: string;
1168
+ readonly transaction: <A>(work: (tx: AdmissionDataStore) => Promise<A>) => Promise<A>;
1169
+ readonly acquireCapacity: (request: typeof Request_2.Type) => Promise<boolean>;
1170
+ }) => Effect.Effect<{
1171
+ kind: "changed";
1172
+ requestId: string;
1173
+ count?: never;
1174
+ } | {
1175
+ kind: "already-reset";
1176
+ count: number;
1177
+ requestId?: never;
1178
+ } | {
1179
+ kind: "reset";
1180
+ count: number;
1181
+ requestId?: never;
1182
+ } | {
1183
+ kind: "waiting-capacity";
1184
+ }, SqlError_2, SqlClient_2>;
1185
+
1186
+ /** One resolved `cancelOn` entry. The schema is kept (not pre-compiled into a
1187
+ * decoder) so the sweeper can report a decode failure naming the event, which
1188
+ * is the difference between "the predicate said no" and "we could not read the
1189
+ * event at all". */
1190
+ declare interface ResolvedCancelOn {
1191
+ readonly event: string;
1192
+ readonly schema: Schema.Schema.Any;
1193
+ readonly match: (event: unknown, payload: unknown) => boolean;
1194
+ readonly withinMs?: number;
1195
+ readonly reason: string;
1196
+ }
1197
+
1198
+ /** The declaration with its durations resolved to milliseconds and its
1199
+ * defaults filled in — what the decision engine actually reads. Key functions
1200
+ * stay as functions; they are applied against a concrete payload at start. */
1201
+ declare interface ResolvedFlowControl {
1202
+ readonly workflowName: string;
1203
+ readonly debounce?: {
1204
+ readonly key: (payload: unknown) => string;
1205
+ readonly periodMs: number;
1206
+ readonly timeoutMs?: number;
1207
+ };
1208
+ readonly singleton?: {
1209
+ readonly key: (payload: unknown) => string;
1210
+ readonly mode: 'skip' | 'cancel';
1211
+ };
1212
+ readonly concurrency?: {
1213
+ readonly limit: number;
1214
+ readonly key?: (payload: unknown) => string;
1215
+ readonly pool?: string;
1216
+ };
1217
+ readonly throttle?: {
1218
+ readonly limit: number;
1219
+ readonly periodMs: number;
1220
+ readonly key?: (payload: unknown) => string;
1221
+ };
1222
+ readonly rateLimit?: {
1223
+ readonly limit: number;
1224
+ readonly periodMs: number;
1225
+ readonly key?: (payload: unknown) => string;
1226
+ };
1227
+ readonly batch?: {
1228
+ readonly key?: (payload: unknown) => string;
1229
+ readonly maxSize: number;
1230
+ readonly timeoutMs: number;
1231
+ /**
1232
+ * Schema of ONE item — what a CALLER passes to `start()`, as opposed to the
1233
+ * workflow's own `{ items: [...] }` payload.
1234
+ *
1235
+ * Carried through resolution because the facade validates an arriving start
1236
+ * against it. It used to be declared, asserted at declaration time, and then
1237
+ * dropped here — so every `start()` on a batching workflow was validated
1238
+ * against the batch shape it is not, and threw `WorkflowPayloadError` before
1239
+ * the gate ever saw it. A required option nothing reads is worse than an
1240
+ * absent one: it reads as wired.
1241
+ */
1242
+ readonly item?: unknown;
1243
+ };
1244
+ readonly priority?: (payload: unknown) => number;
1245
+ readonly timeouts?: {
1246
+ readonly startMs?: number;
1247
+ readonly finishMs?: number;
1248
+ };
1249
+ readonly onFailure?: string;
1250
+ readonly encrypted?: boolean;
1251
+ readonly cancelOn?: ReadonlyArray<ResolvedCancelOn>;
1252
+ }
1253
+
754
1254
  /**
755
1255
  * Read the failover-tuning knobs from the environment, for the boot paths to
756
1256
  * spread into {@link workflowEngineLayer}. Operators set:
@@ -894,6 +1394,13 @@ export declare interface RunnerIdentity {
894
1394
  */
895
1395
  export declare type RunnerStorage = 'sql' | 'memory';
896
1396
 
1397
+ declare const Selection: Schema.Union<[Schema.Struct<{
1398
+ kind: Schema.Literal<["failed-steps"]>;
1399
+ }>, Schema.Struct<{
1400
+ kind: Schema.Literal<["from-step"]>;
1401
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
1402
+ }>]>;
1403
+
897
1404
  declare interface ShardLockHolder {
898
1405
  readonly shardId: string;
899
1406
  readonly mode: 'advisory' | 'row';
@@ -949,7 +1456,7 @@ declare interface UnreadMessage extends ClusterMessageState {
949
1456
  readonly unreadForMs: number;
950
1457
  }
951
1458
 
952
- export declare const workflowEngineLayer: (options: WorkflowEngineLayerOptions) => Layer.Layer<WorkflowEngine.WorkflowEngine | Sharding.Sharding | MessageStorage.MessageStorage | ClusterDiagnostics | WorkflowResumeDispatcher>;
1459
+ export declare const workflowEngineLayer: (options: WorkflowEngineLayerOptions) => Layer.Layer<WorkflowEngine.WorkflowEngine | Sharding.Sharding | MessageStorage.MessageStorage | ClusterDiagnostics | WorkflowResumeDispatcher | WorkflowRestartDispatcher>;
953
1460
 
954
1461
  export declare interface WorkflowEngineLayerOptions {
955
1462
  /**
@@ -978,6 +1485,13 @@ export declare interface WorkflowEngineLayerOptions {
978
1485
  readonly shardLockMode?: ShardLockMode;
979
1486
  readonly runnerListenHost?: string;
980
1487
  readonly runnerListenPort?: number;
1488
+ /**
1489
+ * The declared store charset (`store: { dialect, charset }`, mysql family).
1490
+ * The engine's tables are created by the library with no character set named;
1491
+ * the migration gate converts the ones it creates to this, so a workflow input
1492
+ * with a 4-byte character is not refused on a `utf8mb3` server.
1493
+ */
1494
+ readonly charset?: MysqlCharset | undefined;
981
1495
  /**
982
1496
  * FAILOVER TUNING — the two knobs that govern how fast a SURVIVING replica
983
1497
  * takes over a CRASHED replica's in-flight workflows. Reclaim is a
@@ -1027,6 +1541,27 @@ export declare interface WorkflowEngineLayerOptions {
1027
1541
  readonly extraShardingLayers?: ReadonlyArray<Layer.Layer<never, never, Sharding.Sharding>>;
1028
1542
  }
1029
1543
 
1544
+ export declare class WorkflowRestartDispatcher extends WorkflowRestartDispatcher_base {
1545
+ }
1546
+
1547
+ declare const WorkflowRestartDispatcher_base: Context.TagClass<WorkflowRestartDispatcher, "@voltro/workflow/WorkflowRestartDispatcher", {
1548
+ readonly prepare: (options: Omit<Parameters<typeof prepareWorkflowRestartCommand>[0], "observe">) => Effect.Effect<Awaited<ReturnType<typeof prepareWorkflowRestartCommand>>, unknown>;
1549
+ readonly dispatch: (options: Parameters<typeof dispatchWorkflowRestart>[0]) => Effect.Effect<Effect.Effect.Success<ReturnType<typeof dispatchWorkflowRestart>>, Effect.Effect.Error<ReturnType<typeof dispatchWorkflowRestart>>>;
1550
+ readonly observe: (target: WorkflowRestartObservationTarget) => Effect.Effect<Effect.Effect.Success<ReturnType<typeof observeWorkflowRestartReplies>>, Effect.Effect.Error<ReturnType<typeof observeWorkflowRestartReplies>>>;
1551
+ }>;
1552
+
1553
+ declare interface WorkflowRestartObservationTarget {
1554
+ readonly workflow: Workflow.Any;
1555
+ readonly executionId: string;
1556
+ readonly delivery: {
1557
+ readonly requestId: string;
1558
+ readonly restartToken: string | null;
1559
+ };
1560
+ readonly activities: ReadonlyArray<FailedActivityRef>;
1561
+ }
1562
+
1563
+ declare type WorkflowRestartSelection = typeof Selection.Type;
1564
+
1030
1565
  /** Engine-bound dispatch capability. Accepts resident transactions, not a
1031
1566
  * caller-selected SQL connection. Results acknowledge attempts, never entry. */
1032
1567
  export declare class WorkflowResumeDispatcher extends WorkflowResumeDispatcher_base {
@@ -1036,4 +1571,19 @@ declare const WorkflowResumeDispatcher_base: Context.TagClass<WorkflowResumeDisp
1036
1571
  readonly dispatch: (options: Parameters<typeof dispatchWorkflowResume>[0]) => Effect.Effect<Effect.Effect.Success<ReturnType<typeof dispatchWorkflowResume>>, Effect.Effect.Error<ReturnType<typeof dispatchWorkflowResume>>>;
1037
1572
  }>;
1038
1573
 
1574
+ /** Raw engine evidence, before workflow-result decoding. Server-only consumers
1575
+ * must authorize access and project this reply before exposing it to a client.
1576
+ * A reply does not by itself establish recorder entry or successful execution. */
1577
+ export declare type WorkflowRunReplyObservation = {
1578
+ readonly kind: 'not-found';
1579
+ } | {
1580
+ readonly kind: 'pending';
1581
+ readonly requestId: string;
1582
+ } | {
1583
+ readonly kind: 'reply';
1584
+ readonly requestId: string;
1585
+ readonly replyId: string;
1586
+ readonly exit: unknown;
1587
+ };
1588
+
1039
1589
  export { }