@voltro/workflow 0.69.1 → 0.71.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/index.d.ts CHANGED
@@ -20,9 +20,34 @@ import { WorkflowAccessOptions } from '@voltro/protocol';
20
20
  import { WorkflowEngine } from '@effect/workflow';
21
21
  import { Workflow as workflowModule } from '@effect/workflow';
22
22
  import { WorkflowParentClosePolicy } from '@voltro/protocol';
23
+ import { WorkflowQueuedCancellationResult } from '@voltro/protocol';
24
+
25
+ /** Dispatch an already committed admission using the same attempt/ACK boundary
26
+ * as the drainer. Returning requires durable acceptance, not just a prepared ID.
27
+ * A concurrent recovery may have supplied that acceptance. */
28
+ export declare const acceptAdmissionSubmission: (deps: AdmissionDrainDeps, submission: AdmissionSubmission, control: ResolvedFlowControl, keys: ReturnType<typeof resolveAdmissionKeys>, now: number) => Promise<string>;
29
+
30
+ /** Resident, database-only acceptance boundary. Allocate the request identity
31
+ * before entering the admission transaction. Acceptance does not change the run
32
+ * and does not prove that an engine observed the request. */
33
+ export declare const acceptWorkflowControlInTransaction: (tx: Store, input: Omit<WorkflowControlRequest, "dispatchAttempts" | "result" | "owner" | "resumeJournal">) => Promise<WorkflowControlRequest>;
34
+
35
+ /** Persist an observed engine acceptance together with the lease linkage.
36
+ * A lost acknowledgement can be retried, but cannot remap a submission to a
37
+ * different execution. No engine call belongs inside this transaction. */
38
+ export declare const acknowledgeAdmissionSubmission: (store: Pick<DataStore, "insertIgnoreWithOutcome" | "transactional">, id: string, accepted: {
39
+ readonly executionId: string;
40
+ readonly runId: string | null;
41
+ }) => Promise<AdmissionSubmission>;
23
42
 
24
43
  /** The narrow slice of `DataStore` flow control needs. */
25
44
  export declare interface AdmissionDataStore {
45
+ /** Native arbitration, preserved by every resident transaction adapter. */
46
+ readonly insertIgnoreWithOutcome: DataStore['insertIgnoreWithOutcome'];
47
+ /** Host schema codecs: queue rows carry encoded values, controls and the
48
+ * engine consume decoded payloads. Absent for a JSON-only low-level host. */
49
+ readonly encodeWorkflowPayload?: (workflowName: string, payload: unknown) => unknown;
50
+ readonly decodeWorkflowPayload?: (workflowName: string, payload: unknown) => unknown;
26
51
  query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
27
52
  insert(table: string, row: Row): Promise<Row>;
28
53
  update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<Row | null>;
@@ -65,7 +90,20 @@ export declare type AdmissionDecision = {
65
90
  };
66
91
 
67
92
  export declare interface AdmissionDrainDeps {
93
+ /** Cross-store capacity check after resident commit and before engine I/O.
94
+ * Replays must reuse this submission's durable permit. False is ordinary
95
+ * waiting, not a rejected or failed engine attempt. Never call inside SQL. */
96
+ readonly acquireSubmissionCapacity?: (submission: AdmissionSubmission) => Promise<boolean>;
97
+ /** Derive the declared engine key without starting work. Called outside SQL
98
+ * transactions, only when the durable submission has no prepared identity. */
99
+ readonly resolveExecutionId: (workflowName: string, payload: unknown) => Promise<string>;
68
100
  readonly store: AdmissionDataStore;
101
+ /** Host-wide pauses plus resident pauses, read outside resident transactions.
102
+ * The resident transaction may add a newer local pause, never clear this set. */
103
+ readonly readPauses?: () => Promise<ReadonlySet<string>>;
104
+ /** Own the shared admission transaction. Callback is database-only and may
105
+ * replay; it must serialize with direct arrivals in this resident store. */
106
+ readonly transaction: <A>(work: (tx: AdmissionDataStore) => Promise<A>) => Promise<A>;
69
107
  /** Resolved flow control per workflow name. A name missing from this map has
70
108
  * no controls, so any pending row under it is an orphan — see `drainTick`. */
71
109
  readonly controls: ReadonlyMap<string, ResolvedFlowControl>;
@@ -81,7 +119,7 @@ export declare interface AdmissionDrainDeps {
81
119
  * with nothing running. That shipped; the CLI's implementation passes an
82
120
  * `admitted` marker that skips the gate.
83
121
  */
84
- readonly startAdmitted: (workflowName: string, payload: unknown, callerContext: unknown) => Promise<AdmittedStart>;
122
+ readonly startAdmitted: (workflowName: string, payload: unknown, callerContext: unknown, preparedExecutionId?: string) => Promise<AdmittedStart>;
85
123
  /**
86
124
  * Cancel a run through the durable run-state boundary — engine interrupt,
87
125
  * terminal row, event, and child cleanup — used by singleton eviction and
@@ -108,6 +146,9 @@ export declare interface AdmissionDrainDeps {
108
146
  * a failure that does not clear, which is no longer transient in any sense a
109
147
  * retry can help. Signalled to `onFailure` once and retained, exactly like a
110
148
  * permanent payload failure; `voltro workflows flow retry` re-arms it.
149
+ * Unacknowledged submissions instead persist an automatic-retry stop with
150
+ * acceptance unknown: no abandonment report and no concurrency release.
151
+ * Pending-intent retry commands do not reconcile those submissions.
111
152
  */
112
153
  readonly deadLetterAfterAttempts?: number | false;
113
154
  readonly onAbandoned: (input: {
@@ -135,8 +176,8 @@ export declare interface AdmissionDrainDeps {
135
176
  * lastError climb on the row) instead of a silent abandon.
136
177
  */
137
178
  readonly registeredWorkflows?: ReadonlySet<string>;
138
- /** Classify a start failure that retrying the SAME stored payload cannot
139
- * repair. The CLI wires `WorkflowPayloadError`; unknown engine/store errors
179
+ /** Classify an input rejection guaranteed to happen BEFORE engine I/O, not
180
+ * merely an error retrying cannot repair. The CLI wires `WorkflowPayloadError`; unknown engine/store errors
140
181
  * stay on the retry path because abandoning transient work is worse than a
141
182
  * visible retry. */
142
183
  readonly isPermanentStartError?: (cause: unknown) => boolean;
@@ -185,6 +226,8 @@ export declare interface AdmissionState {
185
226
  readonly firstSeenAt: number;
186
227
  };
187
228
  readonly singletonHolder?: AdmissionIncumbent;
229
+ /** A reservation exists but its execution identity is not confirmed yet. */
230
+ readonly singletonUnacknowledged?: boolean;
188
231
  /** Runs in flight under `concurrencyKey` (leases held, not yet released). */
189
232
  readonly inFlight?: number;
190
233
  /** Admission timestamps inside the throttle / rate-limit window,
@@ -193,6 +236,11 @@ export declare interface AdmissionState {
193
236
  readonly recentAdmissions?: ReadonlyArray<number>;
194
237
  }
195
238
 
239
+ /** Immutable engine input, stored beside its resident queue. Payload is schema
240
+ * encoded. A retry must use this value, not reconstruct it from current defaults
241
+ * or a batch that has accumulated additional items. */
242
+ export declare type AdmissionSubmission = typeof SubmissionSchema.Type;
243
+
196
244
  /** What the caller learns about a start that went through flow control. */
197
245
  export declare type AdmissionVerdict = {
198
246
  /** Start it now. The ledger row is already written; call `linkExecution`
@@ -266,11 +314,9 @@ export declare interface AdmitStartInput {
266
314
  * `true` when the caller is BLOCKING for the result (`ctx.workflows.run`, or
267
315
  * `start({ wait: true })`).
268
316
  *
269
- * A deferring control has no coherent answer for such a caller — there is no
270
- * result to return for a start that was collapsed into a future run — so the
271
- * facade rejects that combination before calling in here. This flag is
272
- * carried so the ledger records WHY a run bypassed the queue, not to change
273
- * the decision.
317
+ * Observation does not change admission. A deferred caller follows its
318
+ * durable receipt until an execution is accepted or non-execution settles.
319
+ * This flag annotates immediate ledger entries; it never bypasses a control.
274
320
  */
275
321
  readonly waiting: boolean;
276
322
  }
@@ -488,6 +534,470 @@ export declare interface AwaitUpdateOptions<A, B = A> {
488
534
  readonly timeoutMs?: number;
489
535
  }
490
536
 
537
+ /** Persist BEFORE calling the engine. Failed acknowledgements and process
538
+ * death must leave evidence that an earlier call could have been accepted.
539
+ * Return null when this source is no longer dispatchable. Database-only;
540
+ * the returned attempt number fences a later permanent-rejection decision. */
541
+ export declare const beginAdmissionSubmissionDispatchInTransaction: (tx: Pick<DataStore, "query" | "update">, id: string, now: number) => Promise<AdmissionSubmission | null>;
542
+
543
+ /** Persist the attempt before engine I/O. A worker dying after this commit
544
+ * leaves ambiguity, not evidence of rejection. Re-read the resident generation
545
+ * on every attempt; an old control request cannot target a replacement body.
546
+ * The caller must still use an engine operation fenced to this generation:
547
+ * this transaction cannot fence an unqualified interrupt after it commits. */
548
+ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id: string) => Promise<{
549
+ kind: "settled";
550
+ request: {
551
+ readonly id: string;
552
+ readonly cause?: {
553
+ readonly detail?: {
554
+ readonly [x: string]: unknown;
555
+ } | undefined;
556
+ readonly reason: string;
557
+ } | null | undefined;
558
+ readonly result: {
559
+ readonly status: "failed" | "cancelled" | "succeeded";
560
+ readonly kind: "confirmed" | "already-terminal";
561
+ readonly errorMessage: string | null;
562
+ readonly output: unknown;
563
+ readonly errorTag: string | null;
564
+ readonly completedAt: number;
565
+ } | {
566
+ readonly status: "running";
567
+ readonly kind: "confirmed";
568
+ readonly generation: string;
569
+ readonly enteredAt: number;
570
+ } | null;
571
+ readonly requestedAt: number;
572
+ readonly executionId: string;
573
+ readonly runId: string;
574
+ readonly workflowName: string;
575
+ readonly operation: "resume" | "cancel" | "terminate";
576
+ readonly generation: string;
577
+ readonly owner: {
578
+ readonly tenantId: string | null;
579
+ } | null;
580
+ readonly resumeJournal: {
581
+ readonly requestId: string;
582
+ readonly replyId: string;
583
+ readonly restartToken: string | null;
584
+ } | null;
585
+ readonly dispatchAttempts: number;
586
+ };
587
+ run?: never;
588
+ } | {
589
+ kind: "unknown";
590
+ request: {
591
+ readonly id: string;
592
+ readonly cause?: {
593
+ readonly detail?: {
594
+ readonly [x: string]: unknown;
595
+ } | undefined;
596
+ readonly reason: string;
597
+ } | null | undefined;
598
+ readonly result: {
599
+ readonly status: "failed" | "cancelled" | "succeeded";
600
+ readonly kind: "confirmed" | "already-terminal";
601
+ readonly errorMessage: string | null;
602
+ readonly output: unknown;
603
+ readonly errorTag: string | null;
604
+ readonly completedAt: number;
605
+ } | {
606
+ readonly status: "running";
607
+ readonly kind: "confirmed";
608
+ readonly generation: string;
609
+ readonly enteredAt: number;
610
+ } | null;
611
+ readonly requestedAt: number;
612
+ readonly executionId: string;
613
+ readonly runId: string;
614
+ readonly workflowName: string;
615
+ readonly operation: "resume" | "cancel" | "terminate";
616
+ readonly generation: string;
617
+ readonly owner: {
618
+ readonly tenantId: string | null;
619
+ } | null;
620
+ readonly resumeJournal: {
621
+ readonly requestId: string;
622
+ readonly replyId: string;
623
+ readonly restartToken: string | null;
624
+ } | null;
625
+ readonly dispatchAttempts: number;
626
+ };
627
+ run?: never;
628
+ } | {
629
+ kind: "superseded";
630
+ request: {
631
+ readonly id: string;
632
+ readonly cause?: {
633
+ readonly detail?: {
634
+ readonly [x: string]: unknown;
635
+ } | undefined;
636
+ readonly reason: string;
637
+ } | null | undefined;
638
+ readonly result: {
639
+ readonly status: "failed" | "cancelled" | "succeeded";
640
+ readonly kind: "confirmed" | "already-terminal";
641
+ readonly errorMessage: string | null;
642
+ readonly output: unknown;
643
+ readonly errorTag: string | null;
644
+ readonly completedAt: number;
645
+ } | {
646
+ readonly status: "running";
647
+ readonly kind: "confirmed";
648
+ readonly generation: string;
649
+ readonly enteredAt: number;
650
+ } | null;
651
+ readonly requestedAt: number;
652
+ readonly executionId: string;
653
+ readonly runId: string;
654
+ readonly workflowName: string;
655
+ readonly operation: "resume" | "cancel" | "terminate";
656
+ readonly generation: string;
657
+ readonly owner: {
658
+ readonly tenantId: string | null;
659
+ } | null;
660
+ readonly resumeJournal: {
661
+ readonly requestId: string;
662
+ readonly replyId: string;
663
+ readonly restartToken: string | null;
664
+ } | null;
665
+ readonly dispatchAttempts: number;
666
+ };
667
+ run?: never;
668
+ } | {
669
+ kind: "already-terminal";
670
+ request: {
671
+ readonly id: string;
672
+ readonly cause?: {
673
+ readonly detail?: {
674
+ readonly [x: string]: unknown;
675
+ } | undefined;
676
+ readonly reason: string;
677
+ } | null | undefined;
678
+ readonly result: {
679
+ readonly status: "failed" | "cancelled" | "succeeded";
680
+ readonly kind: "confirmed" | "already-terminal";
681
+ readonly errorMessage: string | null;
682
+ readonly output: unknown;
683
+ readonly errorTag: string | null;
684
+ readonly completedAt: number;
685
+ } | {
686
+ readonly status: "running";
687
+ readonly kind: "confirmed";
688
+ readonly generation: string;
689
+ readonly enteredAt: number;
690
+ } | null;
691
+ readonly requestedAt: number;
692
+ readonly executionId: string;
693
+ readonly runId: string;
694
+ readonly workflowName: string;
695
+ readonly operation: "resume" | "cancel" | "terminate";
696
+ readonly generation: string;
697
+ readonly owner: {
698
+ readonly tenantId: string | null;
699
+ } | null;
700
+ readonly resumeJournal: {
701
+ readonly requestId: string;
702
+ readonly replyId: string;
703
+ readonly restartToken: string | null;
704
+ } | null;
705
+ readonly dispatchAttempts: number;
706
+ };
707
+ run: Readonly<Record<string, unknown>>;
708
+ } | {
709
+ kind: "dispatch";
710
+ request: {
711
+ dispatchAttempts: number;
712
+ id: string;
713
+ cause?: {
714
+ readonly detail?: {
715
+ readonly [x: string]: unknown;
716
+ } | undefined;
717
+ readonly reason: string;
718
+ } | null | undefined;
719
+ result: {
720
+ readonly status: "failed" | "cancelled" | "succeeded";
721
+ readonly kind: "confirmed" | "already-terminal";
722
+ readonly errorMessage: string | null;
723
+ readonly output: unknown;
724
+ readonly errorTag: string | null;
725
+ readonly completedAt: number;
726
+ } | {
727
+ readonly status: "running";
728
+ readonly kind: "confirmed";
729
+ readonly generation: string;
730
+ readonly enteredAt: number;
731
+ } | null;
732
+ requestedAt: number;
733
+ executionId: string;
734
+ runId: string;
735
+ workflowName: string;
736
+ operation: "resume" | "cancel" | "terminate";
737
+ generation: string;
738
+ owner: {
739
+ readonly tenantId: string | null;
740
+ } | null;
741
+ resumeJournal: {
742
+ readonly requestId: string;
743
+ readonly replyId: string;
744
+ readonly restartToken: string | null;
745
+ } | null;
746
+ };
747
+ run?: never;
748
+ }>;
749
+
750
+ /** Database-only capture while holding the resident admission/entry lock.
751
+ * observe reads the journal; it must never wake/reset the engine. Missing
752
+ * journal evidence waits without inventing a binding. After commit, every
753
+ * dispatch uses this immutable observation, including after a lost ACK. */
754
+ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: string, observe: (target: {
755
+ readonly workflowName: string;
756
+ readonly executionId: string;
757
+ readonly requestId: string;
758
+ readonly restartToken: string | null;
759
+ }) => Promise<unknown>) => Promise<{
760
+ kind: "settled";
761
+ request: {
762
+ readonly id: string;
763
+ readonly cause?: {
764
+ readonly detail?: {
765
+ readonly [x: string]: unknown;
766
+ } | undefined;
767
+ readonly reason: string;
768
+ } | null | undefined;
769
+ readonly result: {
770
+ readonly status: "failed" | "cancelled" | "succeeded";
771
+ readonly kind: "confirmed" | "already-terminal";
772
+ readonly errorMessage: string | null;
773
+ readonly output: unknown;
774
+ readonly errorTag: string | null;
775
+ readonly completedAt: number;
776
+ } | {
777
+ readonly status: "running";
778
+ readonly kind: "confirmed";
779
+ readonly generation: string;
780
+ readonly enteredAt: number;
781
+ } | null;
782
+ readonly requestedAt: number;
783
+ readonly executionId: string;
784
+ readonly runId: string;
785
+ readonly workflowName: string;
786
+ readonly operation: "resume" | "cancel" | "terminate";
787
+ readonly generation: string;
788
+ readonly owner: {
789
+ readonly tenantId: string | null;
790
+ } | null;
791
+ readonly resumeJournal: {
792
+ readonly requestId: string;
793
+ readonly replyId: string;
794
+ readonly restartToken: string | null;
795
+ } | null;
796
+ readonly dispatchAttempts: number;
797
+ };
798
+ } | {
799
+ kind: "unknown";
800
+ request: {
801
+ readonly id: string;
802
+ readonly cause?: {
803
+ readonly detail?: {
804
+ readonly [x: string]: unknown;
805
+ } | undefined;
806
+ readonly reason: string;
807
+ } | null | undefined;
808
+ readonly result: {
809
+ readonly status: "failed" | "cancelled" | "succeeded";
810
+ readonly kind: "confirmed" | "already-terminal";
811
+ readonly errorMessage: string | null;
812
+ readonly output: unknown;
813
+ readonly errorTag: string | null;
814
+ readonly completedAt: number;
815
+ } | {
816
+ readonly status: "running";
817
+ readonly kind: "confirmed";
818
+ readonly generation: string;
819
+ readonly enteredAt: number;
820
+ } | null;
821
+ readonly requestedAt: number;
822
+ readonly executionId: string;
823
+ readonly runId: string;
824
+ readonly workflowName: string;
825
+ readonly operation: "resume" | "cancel" | "terminate";
826
+ readonly generation: string;
827
+ readonly owner: {
828
+ readonly tenantId: string | null;
829
+ } | null;
830
+ readonly resumeJournal: {
831
+ readonly requestId: string;
832
+ readonly replyId: string;
833
+ readonly restartToken: string | null;
834
+ } | null;
835
+ readonly dispatchAttempts: number;
836
+ };
837
+ } | {
838
+ kind: "superseded";
839
+ request: {
840
+ readonly id: string;
841
+ readonly cause?: {
842
+ readonly detail?: {
843
+ readonly [x: string]: unknown;
844
+ } | undefined;
845
+ readonly reason: string;
846
+ } | null | undefined;
847
+ readonly result: {
848
+ readonly status: "failed" | "cancelled" | "succeeded";
849
+ readonly kind: "confirmed" | "already-terminal";
850
+ readonly errorMessage: string | null;
851
+ readonly output: unknown;
852
+ readonly errorTag: string | null;
853
+ readonly completedAt: number;
854
+ } | {
855
+ readonly status: "running";
856
+ readonly kind: "confirmed";
857
+ readonly generation: string;
858
+ readonly enteredAt: number;
859
+ } | null;
860
+ readonly requestedAt: number;
861
+ readonly executionId: string;
862
+ readonly runId: string;
863
+ readonly workflowName: string;
864
+ readonly operation: "resume" | "cancel" | "terminate";
865
+ readonly generation: string;
866
+ readonly owner: {
867
+ readonly tenantId: string | null;
868
+ } | null;
869
+ readonly resumeJournal: {
870
+ readonly requestId: string;
871
+ readonly replyId: string;
872
+ readonly restartToken: string | null;
873
+ } | null;
874
+ readonly dispatchAttempts: number;
875
+ };
876
+ } | {
877
+ kind: "not-suspended";
878
+ request: {
879
+ readonly id: string;
880
+ readonly cause?: {
881
+ readonly detail?: {
882
+ readonly [x: string]: unknown;
883
+ } | undefined;
884
+ readonly reason: string;
885
+ } | null | undefined;
886
+ readonly result: {
887
+ readonly status: "failed" | "cancelled" | "succeeded";
888
+ readonly kind: "confirmed" | "already-terminal";
889
+ readonly errorMessage: string | null;
890
+ readonly output: unknown;
891
+ readonly errorTag: string | null;
892
+ readonly completedAt: number;
893
+ } | {
894
+ readonly status: "running";
895
+ readonly kind: "confirmed";
896
+ readonly generation: string;
897
+ readonly enteredAt: number;
898
+ } | null;
899
+ readonly requestedAt: number;
900
+ readonly executionId: string;
901
+ readonly runId: string;
902
+ readonly workflowName: string;
903
+ readonly operation: "resume" | "cancel" | "terminate";
904
+ readonly generation: string;
905
+ readonly owner: {
906
+ readonly tenantId: string | null;
907
+ } | null;
908
+ readonly resumeJournal: {
909
+ readonly requestId: string;
910
+ readonly replyId: string;
911
+ readonly restartToken: string | null;
912
+ } | null;
913
+ readonly dispatchAttempts: number;
914
+ };
915
+ } | {
916
+ kind: "bound";
917
+ request: {
918
+ readonly id: string;
919
+ readonly cause?: {
920
+ readonly detail?: {
921
+ readonly [x: string]: unknown;
922
+ } | undefined;
923
+ readonly reason: string;
924
+ } | null | undefined;
925
+ readonly result: {
926
+ readonly status: "failed" | "cancelled" | "succeeded";
927
+ readonly kind: "confirmed" | "already-terminal";
928
+ readonly errorMessage: string | null;
929
+ readonly output: unknown;
930
+ readonly errorTag: string | null;
931
+ readonly completedAt: number;
932
+ } | {
933
+ readonly status: "running";
934
+ readonly kind: "confirmed";
935
+ readonly generation: string;
936
+ readonly enteredAt: number;
937
+ } | null;
938
+ readonly requestedAt: number;
939
+ readonly executionId: string;
940
+ readonly runId: string;
941
+ readonly workflowName: string;
942
+ readonly operation: "resume" | "cancel" | "terminate";
943
+ readonly generation: string;
944
+ readonly owner: {
945
+ readonly tenantId: string | null;
946
+ } | null;
947
+ readonly resumeJournal: {
948
+ readonly requestId: string;
949
+ readonly replyId: string;
950
+ readonly restartToken: string | null;
951
+ } | null;
952
+ readonly dispatchAttempts: number;
953
+ };
954
+ } | {
955
+ kind: "waiting-journal";
956
+ request: {
957
+ readonly id: string;
958
+ readonly cause?: {
959
+ readonly detail?: {
960
+ readonly [x: string]: unknown;
961
+ } | undefined;
962
+ readonly reason: string;
963
+ } | null | undefined;
964
+ readonly result: {
965
+ readonly status: "failed" | "cancelled" | "succeeded";
966
+ readonly kind: "confirmed" | "already-terminal";
967
+ readonly errorMessage: string | null;
968
+ readonly output: unknown;
969
+ readonly errorTag: string | null;
970
+ readonly completedAt: number;
971
+ } | {
972
+ readonly status: "running";
973
+ readonly kind: "confirmed";
974
+ readonly generation: string;
975
+ readonly enteredAt: number;
976
+ } | null;
977
+ readonly requestedAt: number;
978
+ readonly executionId: string;
979
+ readonly runId: string;
980
+ readonly workflowName: string;
981
+ readonly operation: "resume" | "cancel" | "terminate";
982
+ readonly generation: string;
983
+ readonly owner: {
984
+ readonly tenantId: string | null;
985
+ } | null;
986
+ readonly resumeJournal: {
987
+ readonly requestId: string;
988
+ readonly replyId: string;
989
+ readonly restartToken: string | null;
990
+ } | null;
991
+ readonly dispatchAttempts: number;
992
+ };
993
+ }>;
994
+
995
+ /** Bind only still-pending receipts in the SAME transaction that takes the
996
+ * queue rows. Previously bound receipts may name a reused debounce slot and
997
+ * must never be reassigned. Page by state transition, not OFFSET: every updated
998
+ * row leaves the next read, so a large collapsed burst cannot lose a page. */
999
+ export declare const bindWorkflowStartReceipts: (tx: Pick<DataStore, "query" | "update">, submissionId: string) => Promise<number>;
1000
+
491
1001
  export declare const BUDGET_HOLDS_TABLE = "_voltro_budget_holds";
492
1002
 
493
1003
  /** Raised into the workflow when a hold outlived its ceiling. */
@@ -594,19 +1104,12 @@ export declare interface CancelOnSweepDeps {
594
1104
  readonly store: CancelOnDataStore;
595
1105
  /** Cancel a live run. MUST be idempotent — the boundary instant is re-read
596
1106
  * every tick, so a cancel can legitimately be issued twice. */
597
- readonly cancelRun: (input: {
1107
+ readonly cancelRun: (input: CancelDecision & {
598
1108
  readonly workflowName: string;
599
- readonly runId: string;
600
- readonly executionId: string;
601
- readonly reason: string;
602
- readonly event: string;
603
1109
  }) => Promise<void>;
604
1110
  /** Discard a queued admission intent. Also idempotent. */
605
- readonly discardIntent: (input: {
1111
+ readonly discardIntent: (input: DiscardDecision & {
606
1112
  readonly workflowName: string;
607
- readonly intentId: string;
608
- readonly reason: string;
609
- readonly event: string;
610
1113
  }) => Promise<void>;
611
1114
  readonly now?: () => number;
612
1115
  readonly log?: {
@@ -674,18 +1177,17 @@ export declare interface CancelProblem {
674
1177
  readonly detail: string;
675
1178
  }
676
1179
 
677
- export declare const closeWorkflowChildrenForParent: (options: CloseWorkflowChildrenForParentOptions) => Promise<ReadonlyArray<WorkflowParentCloseDecision>>;
1180
+ /** Database-only acceptance and observation under the caller's resident
1181
+ * admission transaction. The caller supplies authorized tenant identity. */
1182
+ export declare const cancelQueuedWorkflowStartInTransaction: (tx: AdmissionDataStore, input: ResidentQueuedCancellationTarget & {
1183
+ readonly now: number;
1184
+ readonly cause?: NonNullable<WorkflowControlRequest["cause"]>;
1185
+ }) => Promise<WorkflowQueuedCancellationResult>;
678
1186
 
679
- export declare interface CloseWorkflowChildrenForParentOptions {
680
- readonly store: WorkflowParentCloseStore;
681
- readonly parentExecutionId: string;
682
- readonly parentStatus: WorkflowParentCloseStatus;
683
- readonly recorder?: WorkflowRunRecorderService;
684
- readonly emit?: WorkflowParentCloseEmitter['emit'];
685
- readonly log?: WorkflowParentCloseLogger;
686
- readonly limit?: number;
687
- readonly interruptChild: (workflowName: string, executionId: string, policy: Exclude<WorkflowParentClosePolicy, 'abandon'>) => Promise<void>;
688
- }
1187
+ /** A clean suspension already ended the application body. Settle only its
1188
+ * generation-bound cancellation, under the same lock that owns reentry.
1189
+ * A late engine wake will observe cancelled and cannot construct a new body. */
1190
+ export declare const cancelSuspendedWorkflowRunInTransaction: (tx: AdmissionDataStore, owner: WorkflowRunGeneration, now: number) => Promise<boolean>;
689
1191
 
690
1192
  /** The engine's own re-read rule: an unprocessed message whose `last_read` is
691
1193
  * older than this is re-delivered by the shard's owner on its next poll. A
@@ -711,6 +1213,25 @@ export declare interface ClusterDiagnosticsOps {
711
1213
  readonly shardLockHolders: (shardIds: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<ShardLockHolder>>;
712
1214
  /** The runner table — every address that registered, with its heartbeat. */
713
1215
  readonly runners: () => Effect.Effect<ReadonlyArray<ClusterRunner>>;
1216
+ /**
1217
+ * Delete the unprocessed messages addressed to one entity — the ONE write on
1218
+ * this service. A run that ended outside the engine (cancelled by a sweep or
1219
+ * an operator, parked as failed) leaves its unanswered requests behind: the
1220
+ * `activity` a killed executor never replied to, the `run` itself if it was
1221
+ * never processed. Every runner that acquires the shard re-reads them for the
1222
+ * re-read window, forever, and an `activity` request for a run that does not
1223
+ * execute waits on a latch that never opens. Nothing consumes them; deleting
1224
+ * is what the engine's own reset does for one request. Returns how many.
1225
+ *
1226
+ * `unreadForMs`: only messages a runner last read at least this long ago —
1227
+ * the guard for the sweep, which must not take a request an operator's
1228
+ * redrive cleared a moment ago and a runner has just re-read.
1229
+ */
1230
+ readonly discardMessages: (input: {
1231
+ readonly entityType: string;
1232
+ readonly entityId: string;
1233
+ readonly unreadForMs?: number;
1234
+ }) => Effect.Effect<number>;
714
1235
  }
715
1236
 
716
1237
  export declare interface ClusterMessageState {
@@ -782,6 +1303,17 @@ export declare interface CompleteSuspendingSignalInput {
782
1303
  readonly store?: WorkflowMessageStore;
783
1304
  }
784
1305
 
1306
+ /** Complete an executing body only after its actual exit. Suspended cancellation
1307
+ * is owned by the entry transaction, not by this caller-facing helper. */
1308
+ export declare const completeWorkflowRunGenerationInTransaction: (tx: AdmissionDataStore, input: WorkflowRunCompletion) => Promise<{
1309
+ readonly kind: "superseded" | "not-running";
1310
+ } | {
1311
+ readonly kind: "committed";
1312
+ readonly row: Row;
1313
+ readonly failureId: string | null;
1314
+ readonly terminalEvent: Row;
1315
+ }>;
1316
+
785
1317
  /** FiberRef carrying the active workflow's executionId — set by
786
1318
  * `wrapWithRunRecording` on every workflow body entry, read by a
787
1319
  * child workflow's recorder to populate
@@ -936,7 +1468,7 @@ export declare const DEFAULT_SUSPEND_HINT_MS: number;
936
1468
  /** 24 h — see {@link WorkflowScheduleDeclaration.maxRuntime}. */
937
1469
  export declare const DEFAULT_WORKFLOW_SCHEDULE_MAX_RUNTIME_MS: number;
938
1470
 
939
- export declare type DeferMode = DeferringControl | 'paused';
1471
+ export declare type DeferMode = DeferringControl | 'paused' | 'singleton';
940
1472
 
941
1473
  /**
942
1474
  * Controls that can DEFER a start rather than answer it immediately.
@@ -986,11 +1518,15 @@ export declare interface DiscardDecision {
986
1518
  * button, a `cancelOn` event) go through here so neither can be the one that
987
1519
  * leaves no trace.
988
1520
  *
989
- * The row is written BEFORE the delete: the other order leaves a window where
990
- * the start is gone and nothing says why, which is precisely the state being
991
- * ruled out. A duplicate ledger row from a retry is a far cheaper failure.
1521
+ * Audit, queued caller settlement and deletion share the resident admission
1522
+ * transaction. A failed write retains the source; competing discard/drain
1523
+ * operations cannot both consume the same queued input.
992
1524
  */
993
- export declare const discardIntent: (store: AdmissionDataStore, intentId: string, reason: string, now: number) => Promise<boolean>;
1525
+ export declare const discardIntent: (store: Pick<DataStore, "insertIgnoreWithOutcome" | "transactional">, intentId: string, reason: string, now: number) => Promise<boolean>;
1526
+
1527
+ /** Database-only discard under the resident admission lock. Audit, unbound
1528
+ * caller settlement and source deletion must commit or roll back together. */
1529
+ export declare const discardIntentInTransaction: (store: AdmissionDataStore, intentId: string, reason: string, now: number) => Promise<boolean>;
994
1530
 
995
1531
  /** A row of `_voltro_workflow_events` — a published DOMAIN event. */
996
1532
  export declare interface DomainEventRow {
@@ -1014,7 +1550,7 @@ export declare interface DrainOutcome {
1014
1550
  readonly expired: number;
1015
1551
  readonly evicted: number;
1016
1552
  readonly failed: number;
1017
- /** Pending intents moved out of the live drain set this tick. */
1553
+ /** Pending intents or rejected submissions removed from the live dispatch set. */
1018
1554
  readonly deadLettered: number;
1019
1555
  /** Runs cancelled by `timeouts.finish` on this tick. */
1020
1556
  readonly timedOut: number;
@@ -1056,6 +1592,9 @@ export declare const drainTick: (deps: AdmissionDrainDeps, options?: DrainTickOp
1056
1592
 
1057
1593
  declare interface DrainTickOptions {
1058
1594
  readonly batchSize?: number;
1595
+ /** Host has a change-channel-backed belief that the pending queue is empty.
1596
+ * Submission recovery still runs; pending rows and submissions are not one queue. */
1597
+ readonly skipPending?: boolean;
1059
1598
  }
1060
1599
 
1061
1600
  /**
@@ -1080,6 +1619,27 @@ export { durableQueueModule }
1080
1619
 
1081
1620
  export { durableRateLimiterModule }
1082
1621
 
1622
+ /** Establish the body-entry owner before running application code. Existing
1623
+ * execution identity retains its run ID and original input; return the previous
1624
+ * snapshot so crash/version guards decide from the state they actually replaced.
1625
+ * Both row creation and re-entry execute under the resident admission lock. */
1626
+ export declare const enterWorkflowRunGenerationInTransaction: (tx: AdmissionDataStore, input: {
1627
+ readonly workflowName: string;
1628
+ readonly executionId: string;
1629
+ readonly generation: string;
1630
+ readonly initialRow: Row;
1631
+ readonly now: number;
1632
+ readonly delivery?: {
1633
+ readonly requestId: string;
1634
+ readonly restartToken: string | null;
1635
+ } | null;
1636
+ }) => Promise<{
1637
+ readonly kind: "created" | "entered" | "replayed" | "cancelled" | "already-cancelled";
1638
+ readonly row: Row;
1639
+ readonly previous: Row | undefined;
1640
+ readonly terminalEvent?: Row;
1641
+ }>;
1642
+
1083
1643
  /**
1084
1644
  * Read the state and decide — the ONE composition both callers use.
1085
1645
  *
@@ -1102,6 +1662,42 @@ declare interface EvaluateInput {
1102
1662
 
1103
1663
  export declare const EVENTS_TABLE = "_voltro_workflow_events";
1104
1664
 
1665
+ /** Submit under an identity the durable owner already chose and persisted.
1666
+ * Does not derive the key again, reserve capacity, or persist caller context.
1667
+ * Call only AFTER those responsibilities have committed, outside their
1668
+ * transaction. Reusing an ID requires the same immutable encoded input.
1669
+ * By default returns the accepted execution ID. With discard:false, waits for
1670
+ * the typed body result under that same ID and preserves its failure channel.
1671
+ * Record acceptance before joining; body completion is a separate event.
1672
+ * Ordinary starts do not opt into this capability automatically. */
1673
+ export declare const executePreparedWorkflow: <Payload extends Schema.Schema.Any, Success extends Schema.Schema.Any, Error extends Schema.Schema.All, Discard extends boolean = true>(definition: {
1674
+ readonly payloadSchema: Payload;
1675
+ readonly successSchema: Success;
1676
+ readonly errorSchema: Error;
1677
+ }, payload: Payload["Type"], executionId: string, options?: {
1678
+ readonly discard: Discard;
1679
+ }) => Effect.Effect<Discard extends true ? string : Success["Type"], Error["Type"], WorkflowEngine.WorkflowEngine | Payload["Context"] | Success["Context"] | Error["Context"]>;
1680
+
1681
+ declare const FailureRecordSchema: Schema.Struct<{
1682
+ id: typeof Schema.String;
1683
+ workflowName: typeof Schema.String;
1684
+ tenantId: Schema.NullOr<typeof Schema.String>;
1685
+ sourceId: typeof Schema.String;
1686
+ sourceKind: Schema.Literal<["submission", "intent", "run"]>;
1687
+ handler: Schema.NullOr<typeof Schema.String>;
1688
+ callerContext: typeof Schema.Unknown;
1689
+ report: Schema.Struct<{
1690
+ workflow: typeof Schema.String;
1691
+ payload: typeof Schema.Unknown;
1692
+ errorTag: Schema.NullOr<typeof Schema.String>;
1693
+ errorMessage: Schema.NullOr<typeof Schema.String>;
1694
+ runId: Schema.NullOr<typeof Schema.String>;
1695
+ executionId: Schema.NullOr<typeof Schema.String>;
1696
+ reason: typeof Schema.String;
1697
+ failedAt: typeof Schema.Number;
1698
+ }>;
1699
+ }>;
1700
+
1105
1701
  /** Raised when a user-supplied key function throws. Named separately because
1106
1702
  * the alternative — letting it escape — surfaces as an opaque failure inside
1107
1703
  * `start()` with no hint that the app's own lambda produced it. */
@@ -1135,6 +1731,12 @@ export declare const getWorkflowSchedule: (value: unknown) => ResolvedWorkflowSc
1135
1731
 
1136
1732
  export declare const getWorkflowVersionMetadata: (value: unknown) => WorkflowVersionMetadata;
1137
1733
 
1734
+ /** Transfer a stored incident to the existing durable arrival queue. Must share
1735
+ * the resident admission transaction with its source transition when creating
1736
+ * a report. No engine I/O; a queued receipt is not handler acceptance.
1737
+ * The handler's declared controls apply when the arrival is drained. */
1738
+ export declare const handoffWorkflowFailureInTransaction: (tx: AdmissionDataStore, reportId: string, now: number) => Promise<WorkflowFailureHandoff>;
1739
+
1138
1740
  /** `true` when this workflow declares at least one `cancelOn` entry — i.e. when
1139
1741
  * the cancellation sweep has anything to do for it. */
1140
1742
  export declare const hasCancelOn: (control: ResolvedFlowControl | undefined) => boolean;
@@ -1340,6 +1942,13 @@ declare type NormaliseWorkflowMessages<M extends WorkflowMessageSchemas | undefi
1340
1942
  * `patch()` then reports the PRE-patch branch. */
1341
1943
  export declare const normaliseWorkflowPatches: (value: unknown) => ReadonlyArray<string> | undefined;
1342
1944
 
1945
+ /** Record a failed submit/ack attempt on its durable owner, not a consumed
1946
+ * pending row. Missing acknowledgement does NOT prove that the engine rejected
1947
+ * the call; this record deliberately makes no terminal-execution claim.
1948
+ * Call inside the owning admission transaction. A reached attempt limit stops
1949
+ * automatic retry but retains the lease and receipt for reconciliation. */
1950
+ export declare const noteAdmissionSubmissionFailure: (tx: Pick<DataStore, "query" | "update">, id: string, error: string, now: number, maxAttempts?: number | false) => Promise<AdmissionSubmission>;
1951
+
1343
1952
  export declare const noteIntentAttempt: (store: AdmissionDataStore, intent: PendingIntent, error: string, now: number) => Promise<void>;
1344
1953
 
1345
1954
  /**
@@ -1369,8 +1978,8 @@ export declare interface ParkDelayedStartInput {
1369
1978
  readonly tenantId: string | null;
1370
1979
  readonly queuedAppVersion?: string | null;
1371
1980
  readonly queuedSchemaFingerprint?: string | null;
1372
- /** Epoch ms the start becomes an ARRIVAL. Must be in the future — the facade
1373
- * starts a past-`at` immediately and never calls in here. */
1981
+ /** Epoch ms the start becomes an ARRIVAL. A transaction may enqueue for now;
1982
+ * the row is invisible to other connections until the business commit. */
1374
1983
  readonly at: number;
1375
1984
  readonly now: number;
1376
1985
  }
@@ -1436,6 +2045,10 @@ declare type PayloadSchemaOf<P> = P extends Schema.Struct.Fields ? Schema.Struct
1436
2045
 
1437
2046
  export declare const PENDING_TABLE = "_voltro_workflow_pending";
1438
2047
 
2048
+ /** Bounded recovery window. Only completed queue transfers may be dispatched;
2049
+ * a bare reservation does not prove that its selected rows were consumed. */
2050
+ export declare const pendingAdmissionSubmissions: (store: Pick<DataStore, "query">, limit: number, afterId?: string) => Promise<ReadonlyArray<AdmissionSubmission>>;
2051
+
1439
2052
  /** Every hold currently parked — the operator's view, and the poller's input. */
1440
2053
  export declare const pendingBudgetHolds: (store: DataStore, options?: {
1441
2054
  readonly budget?: string;
@@ -1492,6 +2105,17 @@ export declare interface PendingIntent {
1492
2105
  */
1493
2106
  export declare const pendingSlotId: (workflowName: string, mode: string, controlKey: string) => string;
1494
2107
 
2108
+ export declare interface PendingSubmissionInput {
2109
+ readonly id: string;
2110
+ readonly workflowName: string;
2111
+ readonly tenantId: string | null;
2112
+ readonly intentIds: ReadonlyArray<string>;
2113
+ readonly now: number;
2114
+ /** Pure schema/control preparation from the rows read INSIDE the transaction.
2115
+ * May run again after a database conflict. No engine or provider I/O. */
2116
+ readonly prepare: (intents: ReadonlyArray<PendingIntent>) => Omit<ReserveAdmissionSubmissionInput, 'id' | 'tenantId' | 'intentIds' | 'now'>;
2117
+ }
2118
+
1495
2119
  /**
1496
2120
  * The same read, plus the answer to "when is the next one due".
1497
2121
  *
@@ -1533,6 +2157,10 @@ declare interface PersistDeferInput {
1533
2157
  readonly existingId?: string;
1534
2158
  }
1535
2159
 
2160
+ /** Internal database-only constructor for an admission already recorded in the
2161
+ * same transaction. Never re-evaluate capacity against that admission's lease. */
2162
+ declare const persistReservedSubmission: (tx: Pick<DataStore, "insert">, input: Pick<AdmissionSubmission, "id" | "workflowName" | "tenantId" | "ledgerId" | "payload" | "callerContext" | "intentIds" | "eviction">) => Promise<AdmissionSubmission>;
2163
+
1536
2164
  /**
1537
2165
  * Which runs and intents a batch of events cancels.
1538
2166
  *
@@ -1546,16 +2174,20 @@ export declare const planCancellations: (input: CancelOnPlanInput) => CancelOnPl
1546
2174
  /**
1547
2175
  * The stored spelling of a concurrency key, pool-aware.
1548
2176
  *
1549
- * `\u0000` separators, because the prefix must be unforgeable BY ACCIDENT: an
1550
- * app's key function returning a string that collides with a pooled spelling
1551
- * would silently count that run into another pool's budget. No app key
1552
- * legitimately contains NUL, and a key function constructing this exact shape
1553
- * on purpose is sabotage, not a typo. Kept human-readable (not hashed) so a
1554
- * ledger row's key still SAYS which pool it held — "why was this dropped" must
1555
- * stay answerable by looking.
2177
+ * JSON tuples escape control characters (PostgreSQL text rejects NUL) and
2178
+ * preserve component boundaries. Encode unpooled keys too: a raw application
2179
+ * key must never impersonate a pooled tuple. Null identifies an unpooled key,
2180
+ * independently of every possible string-valued pool name.
1556
2181
  */
1557
2182
  export declare const pooledConcurrencyKey: (pool: string | undefined, rawKey: string) => string;
1558
2183
 
2184
+ /** Bind the proposed application-derived execution identity before any engine
2185
+ * call. Compute the proposal outside this database-only transaction; after a
2186
+ * conflict, the first committed identity wins, never the retry's new proposal.
2187
+ * A previous unbound dispatch is uncertain and cannot safely acquire a new ID.
2188
+ * This records intent, not engine acceptance, and does not release capacity. */
2189
+ export declare const prepareAdmissionSubmissionExecutionInTransaction: (tx: Pick<DataStore, "query" | "update">, id: string, proposedExecutionId: string) => Promise<AdmissionSubmission>;
2190
+
1559
2191
  /** Enqueue work and await the durable queue worker's result. */
1560
2192
  export declare const processQueue: typeof durableQueueModule.process;
1561
2193
 
@@ -1570,6 +2202,10 @@ export declare const processQueue: typeof durableQueueModule.process;
1570
2202
  */
1571
2203
  export declare const pruneAdmissions: (store: AdmissionDataStore, olderThan: Date, limit?: number) => Promise<number>;
1572
2204
 
2205
+ /** Resident entry consumes these requests. Public acceptance and recovery are
2206
+ * not wired yet. A persisted submission target is intent, not cancellation. */
2207
+ export declare const queuedCancellationRequestsTable: SchemaTable;
2208
+
1573
2209
  /** Start a durable queue worker layer with bounded concurrency.
1574
2210
  *
1575
2211
  * `DurableQueue.worker` is itself generic (`<Payload, Success, Error, R>`), so
@@ -1592,6 +2228,8 @@ export declare const rateLimit: typeof durableRateLimiterModule.rateLimit;
1592
2228
  */
1593
2229
  export declare const readAdmissionState: (input: ReadStateInput) => Promise<AdmissionState>;
1594
2230
 
2231
+ export declare const readAdmissionSubmission: (tx: Pick<DataStore, "query">, id: string) => Promise<AdmissionSubmission | undefined>;
2232
+
1595
2233
  /**
1596
2234
  * Every workflow name currently paused.
1597
2235
  *
@@ -1607,6 +2245,10 @@ export declare const readAdmissionState: (input: ReadStateInput) => Promise<Admi
1607
2245
  */
1608
2246
  export declare const readPausedWorkflows: (store: AdmissionDataStore) => Promise<ReadonlySet<string>>;
1609
2247
 
2248
+ /** Observe the original request without accepting, dispatching or repairing it.
2249
+ * It remains readable after the source receipt has been removed. */
2250
+ export declare const readQueuedWorkflowCancellationInTransaction: (tx: Pick<AdmissionDataStore, "query">, input: ResidentQueuedCancellationTarget) => Promise<WorkflowQueuedCancellationResult>;
2251
+
1610
2252
  declare interface ReadStateInput {
1611
2253
  readonly store: AdmissionDataStore;
1612
2254
  readonly control: ResolvedFlowControl;
@@ -1618,6 +2260,48 @@ declare interface ReadStateInput {
1618
2260
  /** Read the stored position for a sweep consumer. */
1619
2261
  export declare const readWatermark: (store: CancelOnDataStore, name: string) => Promise<Date | undefined>;
1620
2262
 
2263
+ export declare const readWorkflowControlInTransaction: (tx: Pick<Store, "query">, id: string) => Promise<WorkflowControlRequest | undefined>;
2264
+
2265
+ /** Strict read: a damaged record is not an absent report. */
2266
+ export declare const readWorkflowFailureRecord: (tx: Pick<DataStore, "query">, id: string) => Promise<WorkflowFailureRecord | undefined>;
2267
+
2268
+ /** Strict lookup shared by receipt owners; a malformed receipt is not absent. */
2269
+ export declare const readWorkflowStartReceipt: (store: Pick<DataStore, "query">, id: string) => Promise<WorkflowStartReceipt | undefined>;
2270
+
2271
+ declare const ReceiptSchema: Schema.filter<Schema.Struct<{
2272
+ id: typeof Schema.String;
2273
+ workflowName: typeof Schema.String;
2274
+ tenantId: Schema.NullOr<typeof Schema.String>;
2275
+ pendingId: typeof Schema.String;
2276
+ submissionId: Schema.NullOr<typeof Schema.String>;
2277
+ terminal: Schema.NullOr<Schema.Union<[Schema.Struct<{
2278
+ kind: Schema.Literal<["dropped"]>;
2279
+ at: typeof Schema.Number;
2280
+ retryAfterMs: typeof Schema.Number;
2281
+ }>, Schema.Struct<{
2282
+ kind: Schema.Literal<["skipped"]>;
2283
+ at: typeof Schema.Number;
2284
+ executionId: typeof Schema.String;
2285
+ runId: typeof Schema.String;
2286
+ }>, Schema.Struct<{
2287
+ kind: Schema.Literal<["superseded"]>;
2288
+ at: typeof Schema.Number;
2289
+ }>, Schema.Struct<{
2290
+ kind: Schema.Literal<["cancelled"]>;
2291
+ at: typeof Schema.Number;
2292
+ }>, Schema.Struct<{
2293
+ kind: Schema.Literal<["abandoned"]>;
2294
+ at: typeof Schema.Number;
2295
+ failureId: typeof Schema.NonEmptyString;
2296
+ }>]>>;
2297
+ }>>;
2298
+
2299
+ /** Reconcile under the resident admission lock. Only a terminal pre-admission
2300
+ * drop/skip/supersession or proven pre-engine rejection permits a new arrival. Preserve every previous receipt and
2301
+ * the immutable report. Unknown or stopped engine acceptance never creates a
2302
+ * second submission. This is acceptance, not completion or provider delivery. */
2303
+ export declare const reconcileWorkflowFailureInTransaction: (tx: AdmissionDataStore, reportId: string, now: number) => Promise<WorkflowFailureDelivery>;
2304
+
1621
2305
  /**
1622
2306
  * Append one ledger row. Also the lease, when the outcome is `admitted`.
1623
2307
  *
@@ -1683,6 +2367,27 @@ export declare interface RecordedStepShape {
1683
2367
  readonly stepName: string;
1684
2368
  }
1685
2369
 
2370
+ /** Database-only: call inside the resident admission transaction, together
2371
+ * with the source's terminal transition. Never notify the handler from here.
2372
+ * Replaying an identity keeps the first report AND recipient, even after a
2373
+ * deployment changes the handler or a new attempt supplies a later timestamp. */
2374
+ export declare const recordWorkflowFailureInTransaction: (tx: Pick<DataStore, "query" | "insert">, input: WorkflowFailureRecord) => Promise<WorkflowFailureRecord>;
2375
+
2376
+ /** Call inside the transaction that persists the pending start. The caller
2377
+ * allocates id once, before any retryable callback. pendingId is a reusable
2378
+ * queue slot; id is this caller's durable receipt and must not be that slot. */
2379
+ export declare const recordWorkflowStartReceipt: (tx: Pick<DataStore, "insertIgnoreWithOutcome">, input: Omit<WorkflowStartReceipt, "submissionId" | "terminal">) => Promise<WorkflowStartReceipt>;
2380
+
2381
+ /** Record proven pre-engine rejection together with its source and lease.
2382
+ * Attempt 0 means local validation before any engine call. Attempt 1 means the
2383
+ * first call returned a failure the host guarantees occurs before engine I/O.
2384
+ * A prior or competing attempt makes acceptance unknown, not rejected. */
2385
+ export declare const rejectAdmissionSubmissionInTransaction: (tx: AdmissionDataStore, id: string, attempt: number, failure: {
2386
+ readonly handler: string | null;
2387
+ readonly errorMessage: string;
2388
+ readonly now: number;
2389
+ }) => Promise<WorkflowFailureRecord | undefined>;
2390
+
1686
2391
  /**
1687
2392
  * Resume every run parked on `budget`.
1688
2393
  *
@@ -1753,9 +2458,83 @@ export declare interface ReplayShapeTracker {
1753
2458
  */
1754
2459
  export declare const reportNondeterminism: (findings: ReadonlyArray<NondeterminismFinding>) => Effect.Effect<void>;
1755
2460
 
2461
+ declare const RequestSchema: Schema.Struct<{
2462
+ id: Schema.filter<typeof Schema.String>;
2463
+ runId: Schema.filter<typeof Schema.String>;
2464
+ workflowName: Schema.filter<typeof Schema.String>;
2465
+ executionId: Schema.filter<typeof Schema.String>;
2466
+ generation: Schema.filter<typeof Schema.String>;
2467
+ operation: Schema.Literal<["cancel", "terminate", "resume"]>;
2468
+ requestedAt: Schema.filter<typeof Schema.Number>;
2469
+ /** Null means no proven owner, not a null-tenant owner. */
2470
+ owner: Schema.NullOr<Schema.Struct<{
2471
+ tenantId: Schema.NullOr<typeof Schema.String>;
2472
+ }>>;
2473
+ cause: Schema.optional<Schema.NullOr<Schema.Struct<{
2474
+ reason: typeof Schema.NonEmptyString;
2475
+ detail: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
2476
+ }>>>;
2477
+ dispatchAttempts: Schema.filter<Schema.filter<typeof Schema.Number>>;
2478
+ resumeJournal: Schema.NullOr<Schema.Struct<{
2479
+ requestId: Schema.filter<typeof Schema.String>;
2480
+ replyId: Schema.filter<typeof Schema.String>;
2481
+ restartToken: Schema.NullOr<typeof Schema.NonEmptyString>;
2482
+ }>>;
2483
+ result: Schema.NullOr<Schema.Union<[Schema.Struct<{
2484
+ kind: Schema.Literal<["confirmed", "already-terminal"]>;
2485
+ status: Schema.Literal<["succeeded", "failed", "cancelled"]>;
2486
+ completedAt: Schema.filter<typeof Schema.Number>;
2487
+ output: typeof Schema.Unknown;
2488
+ errorTag: Schema.NullOr<typeof Schema.String>;
2489
+ errorMessage: Schema.NullOr<typeof Schema.String>;
2490
+ }>, Schema.Struct<{
2491
+ kind: Schema.Literal<["confirmed"]>;
2492
+ status: Schema.Literal<["running"]>;
2493
+ enteredAt: Schema.filter<typeof Schema.Number>;
2494
+ generation: Schema.filter<typeof Schema.String>;
2495
+ }>]>>;
2496
+ }>;
2497
+
2498
+ /** Reserve once, before engine I/O. The identity lookup precedes admission:
2499
+ * otherwise a retry would wait forever for the slot its own submission holds.
2500
+ * This primitive only persists a reservation; queue consumption and durable
2501
+ * engine acknowledgement belong to the submission dispatcher. */
2502
+ export declare const reserveAdmissionSubmission: (store: Pick<DataStore, "insertIgnoreWithOutcome" | "transactional">, input: ReserveAdmissionSubmissionInput) => Promise<ReserveAdmissionSubmissionResult>;
2503
+
2504
+ export declare interface ReserveAdmissionSubmissionInput {
2505
+ /** Stable identity of this submission, independent of the process attempting it. */
2506
+ readonly id: string;
2507
+ readonly control: ResolvedFlowControl;
2508
+ /** Decoded input used only to resolve the flow-control keys. */
2509
+ readonly keyPayload: unknown;
2510
+ /** Schema-encoded complete engine input, including the selected batch items. */
2511
+ readonly encodedPayload: unknown;
2512
+ readonly callerContext: unknown;
2513
+ readonly intentIds: ReadonlyArray<string>;
2514
+ readonly tenantId: string | null;
2515
+ readonly paused: boolean;
2516
+ readonly now: number;
2517
+ readonly firstSeenAt: number;
2518
+ readonly collapsed: number;
2519
+ readonly mode: string | null;
2520
+ }
2521
+
2522
+ export declare type ReserveAdmissionSubmissionResult = {
2523
+ readonly kind: 'reserved';
2524
+ readonly submission: AdmissionSubmission;
2525
+ readonly reused: boolean;
2526
+ } | {
2527
+ readonly kind: 'not-admitted';
2528
+ readonly decision: Exclude<AdmissionDecision, {
2529
+ kind: 'admit';
2530
+ }>;
2531
+ };
2532
+
1756
2533
  /** Test seam: forget which (workflow, signal) pairs have been hinted. */
1757
2534
  export declare const resetSuspendHintsForTest: () => void;
1758
2535
 
2536
+ export declare type ResidentQueuedCancellationTarget = typeof Target.Type;
2537
+
1759
2538
  /** Resolve every declared key against one payload. Pure; throws only
1760
2539
  * {@link FlowControlKeyError}. */
1761
2540
  export declare const resolveAdmissionKeys: (control: ResolvedFlowControl, payload: unknown) => AdmissionKeys;
@@ -1859,6 +2638,15 @@ export declare const resolveSuspendHintMs: () => number;
1859
2638
 
1860
2639
  export declare const resolveWorkflowMessageRun: (store: WorkflowMessageStore, target: WorkflowMessageTarget) => Promise<WorkflowResolvedRun>;
1861
2640
 
2641
+ /** Read inside the resident admission transaction. A prepared identity is not
2642
+ * an acceptance acknowledgement; an absent or contradictory owner is not a
2643
+ * pending start. Tenant authority is supplied by the caller, never the receipt. */
2644
+ export declare const resolveWorkflowStartReceipt: (tx: Pick<DataStore, "query">, input: {
2645
+ readonly receiptId: string;
2646
+ readonly workflowName: string;
2647
+ readonly tenantId: string | null;
2648
+ }) => Promise<WorkflowStartResolution>;
2649
+
1862
2650
  export declare const resumeWorkflow: (store: AdmissionDataStore, workflowName: string) => Promise<boolean>;
1863
2651
 
1864
2652
  /** Re-arm one dead-lettered intent after an operator has deployed a schema or
@@ -1902,7 +2690,14 @@ export declare interface ShardLockHolder {
1902
2690
  /** Who holds it: `pid <n> from <client>` for a postgres advisory lock, the
1903
2691
  * runner address for a row lease. */
1904
2692
  readonly holder: string;
1905
- /** `true` when the holder is THIS runner's own lock session/address. */
2693
+ /**
2694
+ * `true` when THIS runner holds the shard — the engine's own answer
2695
+ * (`Sharding.hasShardId`), never a guess from a connection. The first
2696
+ * version compared the holder's pid with `pg_backend_pid()` of the query
2697
+ * that asked, and the pool hands every query a different backend than the
2698
+ * lock session, so a runner's own 300 locks read as a stranger's — and the
2699
+ * report's lever, ending the holder's session, would have ended the runner.
2700
+ */
1906
2701
  readonly own: boolean;
1907
2702
  readonly since: Date | null;
1908
2703
  readonly detail?: Record<string, unknown>;
@@ -1948,11 +2743,27 @@ export declare const sleepUntil: (options: {
1948
2743
  readonly until: Date | number;
1949
2744
  }) => Effect.Effect<void, never, never>;
1950
2745
 
2746
+ /** Database-only recovery step. The saved cursor and child control requests
2747
+ * commit together. A receipt belongs to the first observed child generation;
2748
+ * replay must never retarget it from a replacement row. Only a complete scan
2749
+ * observing no open bound children completes the closure. Entry checks remain
2750
+ * active after completion, so late children cannot escape this scan. */
2751
+ export declare const stageWorkflowParentClosureInTransaction: (tx: AdmissionDataStore, closureId: string, now?: number) => Promise<{
2752
+ readonly scanned: number;
2753
+ readonly controls: number;
2754
+ readonly wrapped: boolean;
2755
+ }>;
2756
+
1951
2757
  /** The narrow store slice this needs. Structural, like `CancelOnDataStore`. */
1952
2758
  export declare interface StalenessDataStore {
1953
2759
  query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
1954
2760
  }
1955
2761
 
2762
+ export declare interface StalenessRunCursor {
2763
+ readonly startedAt: Date;
2764
+ readonly runId: string;
2765
+ }
2766
+
1956
2767
  export declare interface StalenessSweepDeps {
1957
2768
  readonly store: StalenessDataStore;
1958
2769
  /**
@@ -1988,6 +2799,9 @@ export declare interface StalenessSweepOptions {
1988
2799
  readonly stallAfterMs?: number;
1989
2800
  readonly runPage?: number;
1990
2801
  readonly eventLookback?: number;
2802
+ /** Continue one physical store's oldest-first census. Omit after exhaustion
2803
+ * to revisit runs which have since made progress or become stale again. */
2804
+ readonly afterRun?: StalenessRunCursor;
1991
2805
  }
1992
2806
 
1993
2807
  export declare interface StalenessSweepResult {
@@ -2001,14 +2815,16 @@ export declare interface StalenessSweepResult {
2001
2815
  readonly alreadyReported: number;
2002
2816
  /** Excluded because they are inside a durable timer that has not come due. */
2003
2817
  readonly waitingOnTimer: number;
2004
- /** The candidate page filled up: more live old runs exist and the next tick
2005
- * will reach them. Reported rather than inferred — the counts alone cannot
2006
- * distinguish it from "nothing else was old". */
2818
+ /** The candidate page filled up: another page must be checked before the
2819
+ * census can claim exhaustion. That next page may be empty. */
2007
2820
  readonly sawFullRunPage: boolean;
2008
2821
  readonly failures: ReadonlyArray<{
2009
2822
  readonly subject: string;
2010
2823
  readonly detail: string;
2011
2824
  }>;
2825
+ /** Present when the candidate page filled. Keep per physical store, not per
2826
+ * replica or workflow. A failed candidate read does not advance the cursor. */
2827
+ readonly nextRun?: StalenessRunCursor;
2012
2828
  }
2013
2829
 
2014
2830
  export declare interface StalledRun {
@@ -2149,6 +2965,34 @@ export declare interface StepRetryPolicy {
2149
2965
  readonly note?: string;
2150
2966
  }
2151
2967
 
2968
+ declare type Store = Pick<DataStore, 'query' | 'insert' | 'update'>;
2969
+
2970
+ declare const SubmissionSchema: Schema.Struct<{
2971
+ id: typeof Schema.String;
2972
+ workflowName: typeof Schema.String;
2973
+ tenantId: Schema.NullOr<typeof Schema.String>;
2974
+ ledgerId: typeof Schema.String;
2975
+ preparedExecutionId: Schema.NullOr<typeof Schema.String>;
2976
+ executionId: Schema.NullOr<typeof Schema.String>;
2977
+ runId: Schema.NullOr<typeof Schema.String>;
2978
+ receiptsBound: typeof Schema.Boolean;
2979
+ dispatchAttempts: Schema.refine<number, typeof Schema.NonNegative>;
2980
+ failureId: Schema.NullOr<typeof Schema.String>;
2981
+ retry: Schema.Struct<{
2982
+ attempts: typeof Schema.Int;
2983
+ after: typeof Schema.Number;
2984
+ lastError: Schema.NullOr<typeof Schema.String>;
2985
+ stoppedAt: Schema.NullOr<typeof Schema.Number>;
2986
+ }>;
2987
+ payload: typeof Schema.Unknown;
2988
+ callerContext: typeof Schema.Unknown;
2989
+ intentIds: Schema.Array$<typeof Schema.String>;
2990
+ eviction: Schema.NullOr<Schema.Struct<{
2991
+ executionId: typeof Schema.String;
2992
+ runId: typeof Schema.String;
2993
+ }>>;
2994
+ }>;
2995
+
2152
2996
  /**
2153
2997
  * Park the run until the budget has headroom.
2154
2998
  *
@@ -2243,6 +3087,50 @@ export declare const sweepCancelOn: (deps: CancelOnSweepDeps, options: CancelOnS
2243
3087
  */
2244
3088
  export declare const sweepStalledRuns: (deps: StalenessSweepDeps, options?: StalenessSweepOptions) => Promise<StalenessSweepResult>;
2245
3089
 
3090
+ /** Transfer an arrival already admitted under THIS transaction. Unlike a due
3091
+ * queue drain, a size-closing batch may consume inputs before their timeout.
3092
+ * The caller supplies the encoded payload derived under this same lock. */
3093
+ export declare const takeAdmittedSubmissionInTransaction: (tx: AdmissionDataStore, input: Parameters<typeof persistReservedSubmission>[1]) => Promise<{
3094
+ readonly id: string;
3095
+ readonly tenantId: string | null;
3096
+ readonly retry: {
3097
+ readonly lastError: string | null;
3098
+ readonly attempts: number;
3099
+ readonly after: number;
3100
+ readonly stoppedAt: number | null;
3101
+ };
3102
+ readonly payload: unknown;
3103
+ readonly executionId: string | null;
3104
+ readonly runId: string | null;
3105
+ readonly failureId: string | null;
3106
+ readonly workflowName: string;
3107
+ readonly dispatchAttempts: number;
3108
+ readonly callerContext: unknown;
3109
+ readonly intentIds: readonly string[];
3110
+ readonly ledgerId: string;
3111
+ readonly receiptsBound: boolean;
3112
+ readonly preparedExecutionId: string | null;
3113
+ readonly eviction: {
3114
+ readonly executionId: string;
3115
+ readonly runId: string;
3116
+ } | null;
3117
+ }>;
3118
+
3119
+ /** Resident queue takeover. A competing transfer that already consumed any
3120
+ * selected row invalidates the selection, rather than silently starting a
3121
+ * smaller batch. Existing submission identity wins before looking at reusable
3122
+ * queue slots; a retry cannot consume a later debounce burst. */
3123
+ export declare const takePendingAdmissionSubmission: (store: Pick<DataStore, "insertIgnoreWithOutcome" | "transactional">, input: PendingSubmissionInput) => Promise<ReserveAdmissionSubmissionResult | {
3124
+ readonly kind: "selection-unavailable";
3125
+ }>;
3126
+
3127
+ declare const Target: Schema.Struct<{
3128
+ requestId: typeof Schema.NonEmptyString;
3129
+ receiptId: typeof Schema.NonEmptyString;
3130
+ workflowName: typeof Schema.NonEmptyString;
3131
+ tenantId: Schema.NullOr<typeof Schema.String>;
3132
+ }>;
3133
+
2246
3134
  export declare const truncateWorkflowValue: (value: unknown, bytes?: number) => unknown;
2247
3135
 
2248
3136
  export declare interface UnreadMessage extends ClusterMessageState {
@@ -2283,6 +3171,10 @@ export declare const validateWorkflowSchedule: (workflowName: string, declaratio
2283
3171
  */
2284
3172
  export declare const _voltroBudgetHoldsTable: TableLike;
2285
3173
 
3174
+ /** One coordination row per physical admission ledger, not per tenant/pool.
3175
+ * Contains no payload or caller identity. Held only during database decisions. */
3176
+ export declare const _voltroWorkflowAdmissionLocksTable: SchemaTable;
3177
+
2286
3178
  /**
2287
3179
  * `_voltro_workflow_admissions` — append-only ledger, and the lease register.
2288
3180
  *
@@ -2292,6 +3184,19 @@ export declare const _voltroBudgetHoldsTable: TableLike;
2292
3184
  */
2293
3185
  export declare const _voltroWorkflowAdmissionsTable: SchemaTable;
2294
3186
 
3187
+ /** Resident control identity and attempted delivery, distinct from the run's
3188
+ * outcome. An attempted interrupt is not a confirmed cancellation. */
3189
+ export declare const _voltroWorkflowControlRequestsTable: SchemaTable;
3190
+
3191
+ /** Resident, immutable incidents. Persistence is not handler delivery; the
3192
+ * delivery lifecycle must not overwrite the report or its original recipient. */
3193
+ export declare const _voltroWorkflowFailureReportsTable: SchemaTable;
3194
+
3195
+ /** Durable child-close work, committed with its parent generation's terminal
3196
+ * outcome. Also prevents body re-entry after run-history retention. No payload
3197
+ * or caller credentials; never cascade from runs or expire completed scans. */
3198
+ export declare const _voltroWorkflowParentClosuresTable: SchemaTable;
3199
+
2295
3200
  /**
2296
3201
  * `_voltro_workflow_pauses` — one row per PAUSED workflow. Absent ⇒ running.
2297
3202
  *
@@ -2324,10 +3229,24 @@ export declare const _voltroWorkflowRunStepsTable: SchemaTable;
2324
3229
 
2325
3230
  export declare const _voltroWorkflowStartContextsTable: TableLike;
2326
3231
 
3232
+ /** A caller's identity survives removal and later reuse of a debounce slot. */
3233
+ export declare const _voltroWorkflowStartReceiptsTable: SchemaTable;
3234
+
3235
+ /** Immutable engine submissions live in the same physical store as their queue
3236
+ * payloads. A reservation retry reads this row instead of taking another lease. */
3237
+ export declare const _voltroWorkflowSubmissionsTable: SchemaTable;
3238
+
2327
3239
  export declare const _voltroWorkflowWatermarksTable: SchemaTable;
2328
3240
 
2329
3241
  export declare const WATERMARKS_TABLE = "_voltro_workflow_watermarks";
2330
3242
 
3243
+ /** Serialize database-only admission decisions. Never call the engine or a
3244
+ * provider from work: the store can replay it after a transaction conflict.
3245
+ * Bootstrap is outside the transaction so a MySQL consistent read performed
3246
+ * by insertIgnore cannot establish a stale snapshot before the locking read.
3247
+ * The nonce write also makes optimistic memory transactions conflict. */
3248
+ export declare const withAdmissionTransaction: <A>(store: Pick<DataStore, "insertIgnoreWithOutcome" | "transactional">, work: (tx: DataStore) => Promise<A>) => Promise<A>;
3249
+
2331
3250
  /**
2332
3251
  * Add compensating (rollback) logic to a top-level effect in a workflow
2333
3252
  * body. The finalizer runs if the WHOLE workflow later fails — use it for
@@ -2538,6 +3457,8 @@ export declare interface WorkflowConcurrency<Payload> {
2538
3457
  readonly pool?: string;
2539
3458
  }
2540
3459
 
3460
+ export declare type WorkflowControlRequest = typeof RequestSchema.Type;
3461
+
2541
3462
  /**
2542
3463
  * Collapse a burst of starts into ONE run, `period` after the last one.
2543
3464
  *
@@ -2570,6 +3491,21 @@ export declare interface WorkflowDebounce<Payload> {
2570
3491
  }
2571
3492
 
2572
3493
  export declare interface WorkflowExecuteRecordingOptions extends WorkflowRunRecorderOptions {
3494
+ readonly store: WorkflowRecordingStore & Required<Pick<WorkflowRecordingStore, 'query'>>;
3495
+ /** Encode the full decoded workflow input before storing its durable run
3496
+ * payload. Hosts omitting this callback accept JSON-only inputs. Never use
3497
+ * the batch-item schema here: the body receives the complete batch. */
3498
+ readonly encodePayload?: (payload: unknown) => unknown;
3499
+ /** Same resident store as `store`, under the shared admission lock. The
3500
+ * database-only callback may replay; never run application code inside it. */
3501
+ readonly transaction: <T>(work: (tx: AdmissionDataStore) => Promise<T>) => Promise<T>;
3502
+ /** Bound identity for durable failure delivery. Omitted by standalone hosts
3503
+ * without a failure handler; framework boot paths provide the resolved caller. */
3504
+ readonly failureContext?: {
3505
+ readonly handler: string | null;
3506
+ readonly callerContext: unknown;
3507
+ readonly tenantId: string | null;
3508
+ };
2573
3509
  readonly name: string;
2574
3510
  readonly subject?: unknown;
2575
3511
  readonly source?: string | null;
@@ -2578,6 +3514,7 @@ export declare interface WorkflowExecuteRecordingOptions extends WorkflowRunReco
2578
3514
  readonly workflowCompatibleVersions?: ReadonlyArray<string> | null;
2579
3515
  readonly workflowPatches?: ReadonlyArray<string> | null;
2580
3516
  readonly parentExecutionId?: string | null;
3517
+ readonly parentGeneration?: string | null;
2581
3518
  readonly parentClosePolicy?: WorkflowParentClosePolicy | null;
2582
3519
  readonly onParentClose?: (input: {
2583
3520
  readonly workflowName: string;
@@ -2587,30 +3524,15 @@ export declare interface WorkflowExecuteRecordingOptions extends WorkflowRunReco
2587
3524
  }) => Promise<void>;
2588
3525
  readonly recorder?: WorkflowRunRecorderService;
2589
3526
  readonly pluginWorkflowStepLayer?: Layer.Layer<any, never, never>;
2590
- /**
2591
- * A run reached a TERMINAL state — supplied by the CLI wiring so this package
2592
- * needs no knowledge of flow control.
2593
- *
2594
- * It exists to free the run's admission lease. That lease is what a
2595
- * `concurrency` limit counts, so a terminal transition that does not release
2596
- * it does not error, log, or fail a test — the limit simply admits one fewer
2597
- * run, permanently, and then one fewer again. A deployment that slowly stops
2598
- * doing work with nothing pointing at why.
2599
- *
2600
- * Called on success AND on failure, and DELIBERATELY not on suspend: a
2601
- * suspended run is still holding the resource it was admitted for.
2602
- *
2603
- * On a failure it also carries the payload and the error, so the caller
2604
- * can raise the workflow's `onFailure` — the one route into that signal
2605
- * that produces a `failed` row, and the one that was not wired: the
2606
- * flow-control paths (an expired start, a finish timeout, a dead-lettered
2607
- * intent) notified, and a body that failed did not.
2608
- */
3527
+ /** Observation after a committed terminal transition, never an admission or
3528
+ * failure-delivery writer. The resident transaction already records the
3529
+ * failure arrival and releases capacity. Not called for superseded results
3530
+ * or failed commits; callback failure cannot undo a committed outcome. */
2609
3531
  readonly onTerminal?: (input: {
2610
3532
  readonly workflowName: string;
2611
3533
  readonly executionId: string;
2612
3534
  readonly runId?: string;
2613
- readonly status: 'succeeded' | 'failed';
3535
+ readonly status: 'succeeded' | 'failed' | 'cancelled';
2614
3536
  /** The run's payload — present on `failed`, for the failure report. */
2615
3537
  readonly payload?: unknown;
2616
3538
  readonly errorTag?: string | null;
@@ -2649,6 +3571,28 @@ export declare interface WorkflowExecuteRecordingOptions extends WorkflowRunReco
2649
3571
  readonly replayShapeLimit?: number;
2650
3572
  }
2651
3573
 
3574
+ export declare type WorkflowFailureDelivery = WorkflowFailureHandoff | {
3575
+ readonly kind: 'accepted';
3576
+ readonly executionId: string;
3577
+ } | {
3578
+ readonly kind: 'cancelled';
3579
+ readonly receiptId: string;
3580
+ };
3581
+
3582
+ export declare type WorkflowFailureHandoff = {
3583
+ readonly kind: 'unconfigured';
3584
+ } | {
3585
+ readonly kind: 'queued';
3586
+ readonly receiptId: string;
3587
+ readonly intentId: string;
3588
+ };
3589
+
3590
+ /** Low-level persistence foundation. A record proves the failure was recorded,
3591
+ * not that its handler accepted or completed it. Payload must be schema-encoded.
3592
+ * The source's owner allocates one identity before entering a retryable
3593
+ * transaction; reusable debounce slots are not permanent incident identities. */
3594
+ export declare type WorkflowFailureRecord = typeof FailureRecordSchema.Type;
3595
+
2652
3596
  /**
2653
3597
  * What an `onFailure` workflow receives. Declare it as that workflow's
2654
3598
  * `payload` schema — the framework validates against it like any other start,
@@ -2852,33 +3796,8 @@ export declare interface WorkflowMessageTarget {
2852
3796
 
2853
3797
  export { workflowModule }
2854
3798
 
2855
- export declare interface WorkflowParentCloseDecision {
2856
- readonly runId: string;
2857
- readonly workflowName: string;
2858
- readonly executionId: string;
2859
- readonly policy: WorkflowParentClosePolicy | null;
2860
- readonly action: 'cancelled' | 'abandoned' | 'skipped' | 'failed';
2861
- readonly reason?: string;
2862
- }
2863
-
2864
- export declare interface WorkflowParentCloseEmitter {
2865
- emit(channel: 'workflowRuns' | 'workflowSteps' | 'workflowEvents', event: {
2866
- readonly op: 'insert' | 'update';
2867
- readonly row: Record<string, unknown>;
2868
- }): void;
2869
- }
2870
-
2871
- export declare interface WorkflowParentCloseLogger {
2872
- warn(message: string, fields?: Record<string, unknown>): void;
2873
- }
2874
-
2875
3799
  export declare type WorkflowParentCloseStatus = 'succeeded' | 'failed' | 'cancelled';
2876
3800
 
2877
- export declare interface WorkflowParentCloseStore {
2878
- query(descriptor: QueryDescriptor): Promise<ReadonlyArray<unknown>>;
2879
- update(table: string, id: string, patch: unknown): Promise<unknown | null>;
2880
- }
2881
-
2882
3801
  /** Everything `Workflow.make` accepts for `payload`: a struct Schema, or bare
2883
3802
  * `Fields`. Both stay supported — a key lambda must not silently degrade to
2884
3803
  * `any` because the workflow spelled its payload the shorter way. */
@@ -2925,6 +3844,28 @@ export declare interface WorkflowResolvedRun {
2925
3844
  readonly tag?: string;
2926
3845
  }
2927
3846
 
3847
+ export declare interface WorkflowRunCompletion {
3848
+ readonly owner: WorkflowRunGeneration;
3849
+ readonly now: number;
3850
+ readonly durationMs: number;
3851
+ readonly outcome: {
3852
+ readonly status: 'succeeded';
3853
+ readonly output: unknown;
3854
+ }
3855
+ /** Only a confirmed exit of this body generation. A void interrupt reply
3856
+ * is not confirmation and must never be passed as this outcome. */
3857
+ | {
3858
+ readonly status: 'cancelled';
3859
+ } | {
3860
+ readonly status: 'failed';
3861
+ readonly errorTag: string | null;
3862
+ readonly errorMessage: string | null;
3863
+ readonly handler: string | null;
3864
+ readonly callerContext: unknown;
3865
+ readonly tenantId: string | null;
3866
+ };
3867
+ }
3868
+
2928
3869
  /**
2929
3870
  * Closed enumeration of event types the framework knows how to emit.
2930
3871
  * Kept here (not in `runEventsTable.ts`) so callers in the recorder
@@ -2938,7 +3879,10 @@ export declare type WorkflowRunEventType = 'run-started' | 'run-suspended' | 'ru
2938
3879
  * `payload` carries `{ reason, activitiesReset }` — how many failed step
2939
3880
  * attempts were reset so they re-execute (completed steps replay). Distinct
2940
3881
  * from `run-resumed`, which continues a *suspended* run. */
2941
- | 'run-redriven' | 'run-cancelled' | 'run-succeeded' | 'run-failed'
3882
+ | 'run-redriven' | 'run-cancelled'
3883
+ /** Durable receipt settlement, including the original control cause. This
3884
+ * is not a second run transition or proof that a late request caused it. */
3885
+ | 'control-settled' | 'run-succeeded' | 'run-failed'
2942
3886
  /** Emitted when `sleep({ name, duration })` enters the wait — `payload`
2943
3887
  * carries `{ name, durationMs, scheduledWakeAt }`. The dashboard
2944
3888
  * Gantt renders these as a hatched bar in the timeline so a
@@ -2998,13 +3942,22 @@ export declare type WorkflowRunEventType = 'run-started' | 'run-suspended' | 'ru
2998
3942
  * lastProgressAt, reason }`. Diagnostic only — the run is untouched. */
2999
3943
  | 'run-stalled';
3000
3944
 
3945
+ export declare interface WorkflowRunGeneration {
3946
+ readonly runId: string;
3947
+ readonly workflowName: string;
3948
+ readonly executionId: string;
3949
+ /** Allocate once before entering the replayable transaction. Not a timestamp
3950
+ * or a reclaim counter: both may repeat across distinct body entries. */
3951
+ readonly generation: string;
3952
+ }
3953
+
3001
3954
  /** A workflow run's TERMINAL outcome, handed to the metrics hook. Kept local to
3002
3955
  * this package (structurally matching `@voltro/runtime`'s `WorkflowRunRecord`) so
3003
3956
  * the workflow package stays free of a `@voltro/runtime` dependency — the CLI
3004
3957
  * wiring supplies a `recordRun` that forwards to `recordWorkflowRun`. */
3005
3958
  declare interface WorkflowRunOutcome {
3006
3959
  readonly name: string;
3007
- readonly status: 'succeeded' | 'failed';
3960
+ readonly status: 'succeeded' | 'failed' | 'cancelled';
3008
3961
  readonly durationMs: number;
3009
3962
  /** UNIX seconds — set only on success (drives the last-success gauge). */
3010
3963
  readonly completedAtSec?: number;
@@ -3223,6 +4176,21 @@ export declare interface WorkflowSingleton<Payload> {
3223
4176
  readonly mode: 'skip' | 'cancel';
3224
4177
  }
3225
4178
 
4179
+ export declare type WorkflowStartReceipt = typeof ReceiptSchema.Type;
4180
+
4181
+ export declare type WorkflowStartResolution = {
4182
+ readonly kind: 'pending';
4183
+ } | {
4184
+ readonly kind: 'accepted';
4185
+ readonly executionId: string;
4186
+ } | {
4187
+ readonly kind: 'terminal';
4188
+ readonly outcome: NonNullable<WorkflowStartReceipt['terminal']>;
4189
+ } | {
4190
+ readonly kind: 'rejected';
4191
+ readonly failureId: string;
4192
+ };
4193
+
3226
4194
  /**
3227
4195
  * Per-step plugin interceptor. Plugins can wrap every `step()`
3228
4196
  * (== `Activity.make`) invocation with observability, suppress /
@@ -3378,7 +4346,7 @@ export declare interface WorkflowWorkerLayerBrand {
3378
4346
 
3379
4347
  export declare const WorkflowWorkerLayerTypeId: unique symbol;
3380
4348
 
3381
- export declare const wrapWorkflowExecuteWithRunRecording: (options: WorkflowExecuteRecordingOptions, userExecute: (payload: unknown, executionId: string) => Effect.Effect<unknown, unknown, never>) => (payload: unknown, executionId: string) => Effect.Effect<unknown, unknown, never>;
4349
+ export declare const wrapWorkflowExecuteWithRunRecording: (options: WorkflowExecuteRecordingOptions, userExecute: (payload: unknown, executionId: string, generation: string) => Effect.Effect<unknown, unknown, never>) => (payload: unknown, executionId: string) => Effect.Effect<unknown, unknown, never>;
3382
4350
 
3383
4351
  /**
3384
4352
  * Advance the stored position.