@voltro/workflow 0.81.0 → 0.83.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,10 +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';
9
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';
10
15
  import { Sharding } from '@effect/cluster';
11
16
  import { SqlClient } from '@effect/sql';
17
+ import { SqlClient as SqlClient_2 } from '@effect/sql/SqlClient';
12
18
  import { SqlError } from '@effect/sql';
13
19
  import { SqlError as SqlError_2 } from '@effect/sql/SqlError';
14
20
  import { Workflow } from '@effect/workflow';
@@ -23,6 +29,43 @@ import { WorkflowEngine } from '@effect/workflow';
23
29
  */
24
30
  export declare const activityPrimaryKey: (activityName: string, attempt: number) => string;
25
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
+
26
69
  /** Database-only capture while holding the resident admission/entry lock.
27
70
  * observe reads the journal; it must never wake/reset the engine. Missing
28
71
  * journal evidence waits without inventing a binding. After commit, every
@@ -56,11 +99,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
56
99
  readonly enteredAt: number;
57
100
  } | null;
58
101
  readonly generation: string;
102
+ readonly runId: string;
59
103
  readonly requestedAt: number;
60
104
  readonly executionId: string;
61
- readonly runId: string;
62
105
  readonly workflowName: string;
63
106
  readonly operation: "resume" | "cancel" | "terminate";
107
+ readonly dispatchAttempts: number;
64
108
  readonly owner: {
65
109
  readonly tenantId: string | null;
66
110
  } | null;
@@ -69,7 +113,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
69
113
  readonly replyId: string;
70
114
  readonly restartToken: string | null;
71
115
  } | null;
72
- readonly dispatchAttempts: number;
73
116
  };
74
117
  } | {
75
118
  kind: "unknown";
@@ -95,11 +138,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
95
138
  readonly enteredAt: number;
96
139
  } | null;
97
140
  readonly generation: string;
141
+ readonly runId: string;
98
142
  readonly requestedAt: number;
99
143
  readonly executionId: string;
100
- readonly runId: string;
101
144
  readonly workflowName: string;
102
145
  readonly operation: "resume" | "cancel" | "terminate";
146
+ readonly dispatchAttempts: number;
103
147
  readonly owner: {
104
148
  readonly tenantId: string | null;
105
149
  } | null;
@@ -108,7 +152,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
108
152
  readonly replyId: string;
109
153
  readonly restartToken: string | null;
110
154
  } | null;
111
- readonly dispatchAttempts: number;
112
155
  };
113
156
  } | {
114
157
  kind: "superseded";
@@ -134,11 +177,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
134
177
  readonly enteredAt: number;
135
178
  } | null;
136
179
  readonly generation: string;
180
+ readonly runId: string;
137
181
  readonly requestedAt: number;
138
182
  readonly executionId: string;
139
- readonly runId: string;
140
183
  readonly workflowName: string;
141
184
  readonly operation: "resume" | "cancel" | "terminate";
185
+ readonly dispatchAttempts: number;
142
186
  readonly owner: {
143
187
  readonly tenantId: string | null;
144
188
  } | null;
@@ -147,7 +191,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
147
191
  readonly replyId: string;
148
192
  readonly restartToken: string | null;
149
193
  } | null;
150
- readonly dispatchAttempts: number;
151
194
  };
152
195
  } | {
153
196
  kind: "not-suspended";
@@ -173,11 +216,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
173
216
  readonly enteredAt: number;
174
217
  } | null;
175
218
  readonly generation: string;
219
+ readonly runId: string;
176
220
  readonly requestedAt: number;
177
221
  readonly executionId: string;
178
- readonly runId: string;
179
222
  readonly workflowName: string;
180
223
  readonly operation: "resume" | "cancel" | "terminate";
224
+ readonly dispatchAttempts: number;
181
225
  readonly owner: {
182
226
  readonly tenantId: string | null;
183
227
  } | null;
@@ -186,7 +230,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
186
230
  readonly replyId: string;
187
231
  readonly restartToken: string | null;
188
232
  } | null;
189
- readonly dispatchAttempts: number;
190
233
  };
191
234
  } | {
192
235
  kind: "bound";
@@ -212,11 +255,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
212
255
  readonly enteredAt: number;
213
256
  } | null;
214
257
  readonly generation: string;
258
+ readonly runId: string;
215
259
  readonly requestedAt: number;
216
260
  readonly executionId: string;
217
- readonly runId: string;
218
261
  readonly workflowName: string;
219
262
  readonly operation: "resume" | "cancel" | "terminate";
263
+ readonly dispatchAttempts: number;
220
264
  readonly owner: {
221
265
  readonly tenantId: string | null;
222
266
  } | null;
@@ -225,7 +269,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
225
269
  readonly replyId: string;
226
270
  readonly restartToken: string | null;
227
271
  } | null;
228
- readonly dispatchAttempts: number;
229
272
  };
230
273
  } | {
231
274
  kind: "waiting-journal";
@@ -251,11 +294,12 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
251
294
  readonly enteredAt: number;
252
295
  } | null;
253
296
  readonly generation: string;
297
+ readonly runId: string;
254
298
  readonly requestedAt: number;
255
299
  readonly executionId: string;
256
- readonly runId: string;
257
300
  readonly workflowName: string;
258
301
  readonly operation: "resume" | "cancel" | "terminate";
302
+ readonly dispatchAttempts: number;
259
303
  readonly owner: {
260
304
  readonly tenantId: string | null;
261
305
  } | null;
@@ -264,7 +308,6 @@ declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, ob
264
308
  readonly replyId: string;
265
309
  readonly restartToken: string | null;
266
310
  } | null;
267
- readonly dispatchAttempts: number;
268
311
  };
269
312
  }>;
270
313
 
@@ -371,11 +414,29 @@ declare interface ClusterRunner {
371
414
  readonly healthy: boolean;
372
415
  }
373
416
 
417
+ declare type Command = Parameters<typeof readWorkflowRestartReservationInTransaction>[1];
418
+
374
419
  declare type ControlStore = Parameters<typeof bindWorkflowResumeJournalInTransaction>[0];
375
420
 
376
421
  /** Default port for the cluster's single-runner listener. Override per deploy. */
377
422
  export declare const DEFAULT_RUNNER_LISTEN_PORT: 34000;
378
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
+
379
440
  /** Probe the live connection for an active wsrep (Galera / Percona XtraDB)
380
441
  * cluster. `@@wsrep_on = 1` iff Galera replication is active; the variable is
381
442
  * absent on stock MySQL (→ query errors → not a wsrep cluster) and `0` on a
@@ -384,6 +445,28 @@ export declare const DEFAULT_RUNNER_LISTEN_PORT: 34000;
384
445
  * somehow can't be probed can still force `VOLTRO_WORKFLOW_SHARD_LOCK=row`. */
385
446
  export declare const detectWsrepCluster: Effect.Effect<boolean, never, SqlClient.SqlClient>;
386
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
+
387
470
  /** Internal cluster dispatch, not an acceptance or completion API. The host
388
471
  * supplies the resident admission transaction and the matching journal layer.
389
472
  * Only the recorder's next entry can confirm the persisted resume receipt. */
@@ -414,11 +497,12 @@ declare const dispatchWorkflowResume: (options: {
414
497
  readonly enteredAt: number;
415
498
  } | null;
416
499
  readonly generation: string;
500
+ readonly runId: string;
417
501
  readonly requestedAt: number;
418
502
  readonly executionId: string;
419
- readonly runId: string;
420
503
  readonly workflowName: string;
421
504
  readonly operation: "resume" | "cancel" | "terminate";
505
+ readonly dispatchAttempts: number;
422
506
  readonly owner: {
423
507
  readonly tenantId: string | null;
424
508
  } | null;
@@ -427,7 +511,6 @@ declare const dispatchWorkflowResume: (options: {
427
511
  readonly replyId: string;
428
512
  readonly restartToken: string | null;
429
513
  } | null;
430
- readonly dispatchAttempts: number;
431
514
  };
432
515
  } | {
433
516
  kind: "unknown";
@@ -453,11 +536,12 @@ declare const dispatchWorkflowResume: (options: {
453
536
  readonly enteredAt: number;
454
537
  } | null;
455
538
  readonly generation: string;
539
+ readonly runId: string;
456
540
  readonly requestedAt: number;
457
541
  readonly executionId: string;
458
- readonly runId: string;
459
542
  readonly workflowName: string;
460
543
  readonly operation: "resume" | "cancel" | "terminate";
544
+ readonly dispatchAttempts: number;
461
545
  readonly owner: {
462
546
  readonly tenantId: string | null;
463
547
  } | null;
@@ -466,7 +550,6 @@ declare const dispatchWorkflowResume: (options: {
466
550
  readonly replyId: string;
467
551
  readonly restartToken: string | null;
468
552
  } | null;
469
- readonly dispatchAttempts: number;
470
553
  };
471
554
  } | {
472
555
  kind: "superseded";
@@ -492,11 +575,12 @@ declare const dispatchWorkflowResume: (options: {
492
575
  readonly enteredAt: number;
493
576
  } | null;
494
577
  readonly generation: string;
578
+ readonly runId: string;
495
579
  readonly requestedAt: number;
496
580
  readonly executionId: string;
497
- readonly runId: string;
498
581
  readonly workflowName: string;
499
582
  readonly operation: "resume" | "cancel" | "terminate";
583
+ readonly dispatchAttempts: number;
500
584
  readonly owner: {
501
585
  readonly tenantId: string | null;
502
586
  } | null;
@@ -505,7 +589,6 @@ declare const dispatchWorkflowResume: (options: {
505
589
  readonly replyId: string;
506
590
  readonly restartToken: string | null;
507
591
  } | null;
508
- readonly dispatchAttempts: number;
509
592
  };
510
593
  } | {
511
594
  kind: "not-suspended";
@@ -531,11 +614,12 @@ declare const dispatchWorkflowResume: (options: {
531
614
  readonly enteredAt: number;
532
615
  } | null;
533
616
  readonly generation: string;
617
+ readonly runId: string;
534
618
  readonly requestedAt: number;
535
619
  readonly executionId: string;
536
- readonly runId: string;
537
620
  readonly workflowName: string;
538
621
  readonly operation: "resume" | "cancel" | "terminate";
622
+ readonly dispatchAttempts: number;
539
623
  readonly owner: {
540
624
  readonly tenantId: string | null;
541
625
  } | null;
@@ -544,7 +628,6 @@ declare const dispatchWorkflowResume: (options: {
544
628
  readonly replyId: string;
545
629
  readonly restartToken: string | null;
546
630
  } | null;
547
- readonly dispatchAttempts: number;
548
631
  };
549
632
  } | {
550
633
  kind: "waiting-journal";
@@ -570,11 +653,12 @@ declare const dispatchWorkflowResume: (options: {
570
653
  readonly enteredAt: number;
571
654
  } | null;
572
655
  readonly generation: string;
656
+ readonly runId: string;
573
657
  readonly requestedAt: number;
574
658
  readonly executionId: string;
575
- readonly runId: string;
576
659
  readonly workflowName: string;
577
660
  readonly operation: "resume" | "cancel" | "terminate";
661
+ readonly dispatchAttempts: number;
578
662
  readonly owner: {
579
663
  readonly tenantId: string | null;
580
664
  } | null;
@@ -583,7 +667,6 @@ declare const dispatchWorkflowResume: (options: {
583
667
  readonly replyId: string;
584
668
  readonly restartToken: string | null;
585
669
  } | null;
586
- readonly dispatchAttempts: number;
587
670
  };
588
671
  } | {
589
672
  kind: "already-terminal";
@@ -609,11 +692,12 @@ declare const dispatchWorkflowResume: (options: {
609
692
  readonly enteredAt: number;
610
693
  } | null;
611
694
  readonly generation: string;
695
+ readonly runId: string;
612
696
  readonly requestedAt: number;
613
697
  readonly executionId: string;
614
- readonly runId: string;
615
698
  readonly workflowName: string;
616
699
  readonly operation: "resume" | "cancel" | "terminate";
700
+ readonly dispatchAttempts: number;
617
701
  readonly owner: {
618
702
  readonly tenantId: string | null;
619
703
  } | null;
@@ -622,7 +706,6 @@ declare const dispatchWorkflowResume: (options: {
622
706
  readonly replyId: string;
623
707
  readonly restartToken: string | null;
624
708
  } | null;
625
- readonly dispatchAttempts: number;
626
709
  };
627
710
  run: Readonly<Record<string, unknown>>;
628
711
  } | {
@@ -650,9 +733,9 @@ declare const dispatchWorkflowResume: (options: {
650
733
  readonly enteredAt: number;
651
734
  } | null;
652
735
  generation: string;
736
+ runId: string;
653
737
  requestedAt: number;
654
738
  executionId: string;
655
- runId: string;
656
739
  workflowName: string;
657
740
  operation: "resume" | "cancel" | "terminate";
658
741
  owner: {
@@ -679,6 +762,12 @@ declare const dispatchWorkflowResume: (options: {
679
762
  };
680
763
  }, SqlError_2, SqlClient.SqlClient | Sharding.Sharding>;
681
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
+
682
771
  /** A step attempt to reset so it re-executes on the next drive. */
683
772
  export declare interface FailedActivityRef {
684
773
  readonly name: string;
@@ -695,6 +784,43 @@ export declare const makeClusterCronLayer: (spec: ClusterCronSpec) => Layer.Laye
695
784
 
696
785
  export declare const makeClusterDiagnostics: (options: ClusterDiagnosticsOptions) => Effect.Effect<ClusterDiagnosticsOps, never, SqlClient.SqlClient>;
697
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
+
698
824
  /** Parse `VOLTRO_WORKFLOW_SHARD_LOCK` (default `'auto'`). Throws on an
699
825
  * unrecognized value so a typo fails boot loudly instead of silently keeping
700
826
  * the advisory path that breaks cross-pod handoff on Galera. */
@@ -710,6 +836,240 @@ export declare const parseShardLockMode: (raw?: string | undefined) => ShardLock
710
836
  */
711
837
  export declare const pollWorkflowStorage: Effect.Effect<void, never, Sharding.Sharding>;
712
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
+
713
1073
  /**
714
1074
  * Re-drive a terminally-`failed` workflow run from the step it died on.
715
1075
  *
@@ -767,6 +1127,130 @@ export declare interface RedriveTarget {
767
1127
  readonly failedActivities: ReadonlyArray<FailedActivityRef>;
768
1128
  }
769
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
+
770
1254
  /**
771
1255
  * Read the failover-tuning knobs from the environment, for the boot paths to
772
1256
  * spread into {@link workflowEngineLayer}. Operators set:
@@ -802,9 +1286,10 @@ export declare const resolveRunnerIdentity: (opts: {
802
1286
  /**
803
1287
  * Resolve the runner-storage backend for `SingleRunner`.
804
1288
  *
805
- * Default by dialect: sqlite / turso → `'memory'` (`@effect/cluster` has no
806
- * sqlite advisory-lock branch); every other SQL dialect → `'sql'` (durable +
807
- * multi-runner shard coordination via `cluster_runners` / `cluster_locks`).
1289
+ * The resolved connection capability selects SQL ownership for shared primaries.
1290
+ * Local/embedded SQLite-family connections retain memory ownership; direct
1291
+ * remote Turso primaries use SQL row locks. Without capability information,
1292
+ * the SQLite-family fallback remains memory.
808
1293
  *
809
1294
  * `VOLTRO_WORKFLOW_RUNNER_STORAGE` (or an explicit `override`) forces the
810
1295
  * choice. The load-bearing case is `'memory'` on a real SQL dialect: it keeps
@@ -825,6 +1310,8 @@ export declare const resolveRunnerIdentity: (opts: {
825
1310
  */
826
1311
  export declare const resolveRunnerStorage: (opts: {
827
1312
  readonly sqliteFamily: boolean;
1313
+ /** Whether the resolved connection supports shared SQL runner ownership. */
1314
+ readonly sqlRunnerSupported?: boolean | undefined;
828
1315
  readonly override?: string | undefined;
829
1316
  }) => RunnerStorage;
830
1317
 
@@ -910,6 +1397,13 @@ export declare interface RunnerIdentity {
910
1397
  */
911
1398
  export declare type RunnerStorage = 'sql' | 'memory';
912
1399
 
1400
+ declare const Selection: Schema.Union<[Schema.Struct<{
1401
+ kind: Schema.Literal<["failed-steps"]>;
1402
+ }>, Schema.Struct<{
1403
+ kind: Schema.Literal<["from-step"]>;
1404
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
1405
+ }>]>;
1406
+
913
1407
  declare interface ShardLockHolder {
914
1408
  readonly shardId: string;
915
1409
  readonly mode: 'advisory' | 'row';
@@ -965,7 +1459,7 @@ declare interface UnreadMessage extends ClusterMessageState {
965
1459
  readonly unreadForMs: number;
966
1460
  }
967
1461
 
968
- export declare const workflowEngineLayer: (options: WorkflowEngineLayerOptions) => Layer.Layer<WorkflowEngine.WorkflowEngine | Sharding.Sharding | MessageStorage.MessageStorage | ClusterDiagnostics | WorkflowResumeDispatcher>;
1462
+ export declare const workflowEngineLayer: (options: WorkflowEngineLayerOptions) => Layer.Layer<WorkflowEngine.WorkflowEngine | Sharding.Sharding | MessageStorage.MessageStorage | ClusterDiagnostics | WorkflowResumeDispatcher | WorkflowRestartDispatcher>;
969
1463
 
970
1464
  export declare interface WorkflowEngineLayerOptions {
971
1465
  /**
@@ -976,7 +1470,7 @@ export declare interface WorkflowEngineLayerOptions {
976
1470
  * the cluster's accounting clean.
977
1471
  */
978
1472
  readonly sqlClientLayer: Layer.Layer<SqlClient.SqlClient, ConfigError.ConfigError | SqlError.SqlError, never>;
979
- /** `'sql'` for postgres / mysql / mariadb / mssql, `'memory'` for sqlite. */
1473
+ /** SQL ownership for shared primaries; memory for local/embedded stores. */
980
1474
  readonly runnerStorage: RunnerStorage;
981
1475
  /**
982
1476
  * The resolved dialect id (`postgres` / `mysql` / `mariadb` / `mssql` /
@@ -1050,6 +1544,27 @@ export declare interface WorkflowEngineLayerOptions {
1050
1544
  readonly extraShardingLayers?: ReadonlyArray<Layer.Layer<never, never, Sharding.Sharding>>;
1051
1545
  }
1052
1546
 
1547
+ export declare class WorkflowRestartDispatcher extends WorkflowRestartDispatcher_base {
1548
+ }
1549
+
1550
+ declare const WorkflowRestartDispatcher_base: Context.TagClass<WorkflowRestartDispatcher, "@voltro/workflow/WorkflowRestartDispatcher", {
1551
+ readonly prepare: (options: Omit<Parameters<typeof prepareWorkflowRestartCommand>[0], "observe">) => Effect.Effect<Awaited<ReturnType<typeof prepareWorkflowRestartCommand>>, unknown>;
1552
+ readonly dispatch: (options: Parameters<typeof dispatchWorkflowRestart>[0]) => Effect.Effect<Effect.Effect.Success<ReturnType<typeof dispatchWorkflowRestart>>, Effect.Effect.Error<ReturnType<typeof dispatchWorkflowRestart>>>;
1553
+ readonly observe: (target: WorkflowRestartObservationTarget) => Effect.Effect<Effect.Effect.Success<ReturnType<typeof observeWorkflowRestartReplies>>, Effect.Effect.Error<ReturnType<typeof observeWorkflowRestartReplies>>>;
1554
+ }>;
1555
+
1556
+ declare interface WorkflowRestartObservationTarget {
1557
+ readonly workflow: Workflow.Any;
1558
+ readonly executionId: string;
1559
+ readonly delivery: {
1560
+ readonly requestId: string;
1561
+ readonly restartToken: string | null;
1562
+ };
1563
+ readonly activities: ReadonlyArray<FailedActivityRef>;
1564
+ }
1565
+
1566
+ declare type WorkflowRestartSelection = typeof Selection.Type;
1567
+
1053
1568
  /** Engine-bound dispatch capability. Accepts resident transactions, not a
1054
1569
  * caller-selected SQL connection. Results acknowledge attempts, never entry. */
1055
1570
  export declare class WorkflowResumeDispatcher extends WorkflowResumeDispatcher_base {
@@ -1059,4 +1574,19 @@ declare const WorkflowResumeDispatcher_base: Context.TagClass<WorkflowResumeDisp
1059
1574
  readonly dispatch: (options: Parameters<typeof dispatchWorkflowResume>[0]) => Effect.Effect<Effect.Effect.Success<ReturnType<typeof dispatchWorkflowResume>>, Effect.Effect.Error<ReturnType<typeof dispatchWorkflowResume>>>;
1060
1575
  }>;
1061
1576
 
1577
+ /** Raw engine evidence, before workflow-result decoding. Server-only consumers
1578
+ * must authorize access and project this reply before exposing it to a client.
1579
+ * A reply does not by itself establish recorder entry or successful execution. */
1580
+ export declare type WorkflowRunReplyObservation = {
1581
+ readonly kind: 'not-found';
1582
+ } | {
1583
+ readonly kind: 'pending';
1584
+ readonly requestId: string;
1585
+ } | {
1586
+ readonly kind: 'reply';
1587
+ readonly requestId: string;
1588
+ readonly replyId: string;
1589
+ readonly exit: unknown;
1590
+ };
1591
+
1062
1592
  export { }