@voltro/workflow 0.70.0 → 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
@@ -801,6 +1303,17 @@ export declare interface CompleteSuspendingSignalInput {
801
1303
  readonly store?: WorkflowMessageStore;
802
1304
  }
803
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
+
804
1317
  /** FiberRef carrying the active workflow's executionId — set by
805
1318
  * `wrapWithRunRecording` on every workflow body entry, read by a
806
1319
  * child workflow's recorder to populate
@@ -955,7 +1468,7 @@ export declare const DEFAULT_SUSPEND_HINT_MS: number;
955
1468
  /** 24 h — see {@link WorkflowScheduleDeclaration.maxRuntime}. */
956
1469
  export declare const DEFAULT_WORKFLOW_SCHEDULE_MAX_RUNTIME_MS: number;
957
1470
 
958
- export declare type DeferMode = DeferringControl | 'paused';
1471
+ export declare type DeferMode = DeferringControl | 'paused' | 'singleton';
959
1472
 
960
1473
  /**
961
1474
  * Controls that can DEFER a start rather than answer it immediately.
@@ -1005,11 +1518,15 @@ export declare interface DiscardDecision {
1005
1518
  * button, a `cancelOn` event) go through here so neither can be the one that
1006
1519
  * leaves no trace.
1007
1520
  *
1008
- * The row is written BEFORE the delete: the other order leaves a window where
1009
- * the start is gone and nothing says why, which is precisely the state being
1010
- * 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.
1011
1524
  */
1012
- 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>;
1013
1530
 
1014
1531
  /** A row of `_voltro_workflow_events` — a published DOMAIN event. */
1015
1532
  export declare interface DomainEventRow {
@@ -1033,7 +1550,7 @@ export declare interface DrainOutcome {
1033
1550
  readonly expired: number;
1034
1551
  readonly evicted: number;
1035
1552
  readonly failed: number;
1036
- /** Pending intents moved out of the live drain set this tick. */
1553
+ /** Pending intents or rejected submissions removed from the live dispatch set. */
1037
1554
  readonly deadLettered: number;
1038
1555
  /** Runs cancelled by `timeouts.finish` on this tick. */
1039
1556
  readonly timedOut: number;
@@ -1075,6 +1592,9 @@ export declare const drainTick: (deps: AdmissionDrainDeps, options?: DrainTickOp
1075
1592
 
1076
1593
  declare interface DrainTickOptions {
1077
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;
1078
1598
  }
1079
1599
 
1080
1600
  /**
@@ -1099,6 +1619,27 @@ export { durableQueueModule }
1099
1619
 
1100
1620
  export { durableRateLimiterModule }
1101
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
+
1102
1643
  /**
1103
1644
  * Read the state and decide — the ONE composition both callers use.
1104
1645
  *
@@ -1121,6 +1662,42 @@ declare interface EvaluateInput {
1121
1662
 
1122
1663
  export declare const EVENTS_TABLE = "_voltro_workflow_events";
1123
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
+
1124
1701
  /** Raised when a user-supplied key function throws. Named separately because
1125
1702
  * the alternative — letting it escape — surfaces as an opaque failure inside
1126
1703
  * `start()` with no hint that the app's own lambda produced it. */
@@ -1154,6 +1731,12 @@ export declare const getWorkflowSchedule: (value: unknown) => ResolvedWorkflowSc
1154
1731
 
1155
1732
  export declare const getWorkflowVersionMetadata: (value: unknown) => WorkflowVersionMetadata;
1156
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
+
1157
1740
  /** `true` when this workflow declares at least one `cancelOn` entry — i.e. when
1158
1741
  * the cancellation sweep has anything to do for it. */
1159
1742
  export declare const hasCancelOn: (control: ResolvedFlowControl | undefined) => boolean;
@@ -1359,6 +1942,13 @@ declare type NormaliseWorkflowMessages<M extends WorkflowMessageSchemas | undefi
1359
1942
  * `patch()` then reports the PRE-patch branch. */
1360
1943
  export declare const normaliseWorkflowPatches: (value: unknown) => ReadonlyArray<string> | undefined;
1361
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
+
1362
1952
  export declare const noteIntentAttempt: (store: AdmissionDataStore, intent: PendingIntent, error: string, now: number) => Promise<void>;
1363
1953
 
1364
1954
  /**
@@ -1388,8 +1978,8 @@ export declare interface ParkDelayedStartInput {
1388
1978
  readonly tenantId: string | null;
1389
1979
  readonly queuedAppVersion?: string | null;
1390
1980
  readonly queuedSchemaFingerprint?: string | null;
1391
- /** Epoch ms the start becomes an ARRIVAL. Must be in the future — the facade
1392
- * 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. */
1393
1983
  readonly at: number;
1394
1984
  readonly now: number;
1395
1985
  }
@@ -1455,6 +2045,10 @@ declare type PayloadSchemaOf<P> = P extends Schema.Struct.Fields ? Schema.Struct
1455
2045
 
1456
2046
  export declare const PENDING_TABLE = "_voltro_workflow_pending";
1457
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
+
1458
2052
  /** Every hold currently parked — the operator's view, and the poller's input. */
1459
2053
  export declare const pendingBudgetHolds: (store: DataStore, options?: {
1460
2054
  readonly budget?: string;
@@ -1511,6 +2105,17 @@ export declare interface PendingIntent {
1511
2105
  */
1512
2106
  export declare const pendingSlotId: (workflowName: string, mode: string, controlKey: string) => string;
1513
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
+
1514
2119
  /**
1515
2120
  * The same read, plus the answer to "when is the next one due".
1516
2121
  *
@@ -1552,6 +2157,10 @@ declare interface PersistDeferInput {
1552
2157
  readonly existingId?: string;
1553
2158
  }
1554
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
+
1555
2164
  /**
1556
2165
  * Which runs and intents a batch of events cancels.
1557
2166
  *
@@ -1565,16 +2174,20 @@ export declare const planCancellations: (input: CancelOnPlanInput) => CancelOnPl
1565
2174
  /**
1566
2175
  * The stored spelling of a concurrency key, pool-aware.
1567
2176
  *
1568
- * `\u0000` separators, because the prefix must be unforgeable BY ACCIDENT: an
1569
- * app's key function returning a string that collides with a pooled spelling
1570
- * would silently count that run into another pool's budget. No app key
1571
- * legitimately contains NUL, and a key function constructing this exact shape
1572
- * on purpose is sabotage, not a typo. Kept human-readable (not hashed) so a
1573
- * ledger row's key still SAYS which pool it held — "why was this dropped" must
1574
- * 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.
1575
2181
  */
1576
2182
  export declare const pooledConcurrencyKey: (pool: string | undefined, rawKey: string) => string;
1577
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
+
1578
2191
  /** Enqueue work and await the durable queue worker's result. */
1579
2192
  export declare const processQueue: typeof durableQueueModule.process;
1580
2193
 
@@ -1589,6 +2202,10 @@ export declare const processQueue: typeof durableQueueModule.process;
1589
2202
  */
1590
2203
  export declare const pruneAdmissions: (store: AdmissionDataStore, olderThan: Date, limit?: number) => Promise<number>;
1591
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
+
1592
2209
  /** Start a durable queue worker layer with bounded concurrency.
1593
2210
  *
1594
2211
  * `DurableQueue.worker` is itself generic (`<Payload, Success, Error, R>`), so
@@ -1611,6 +2228,8 @@ export declare const rateLimit: typeof durableRateLimiterModule.rateLimit;
1611
2228
  */
1612
2229
  export declare const readAdmissionState: (input: ReadStateInput) => Promise<AdmissionState>;
1613
2230
 
2231
+ export declare const readAdmissionSubmission: (tx: Pick<DataStore, "query">, id: string) => Promise<AdmissionSubmission | undefined>;
2232
+
1614
2233
  /**
1615
2234
  * Every workflow name currently paused.
1616
2235
  *
@@ -1626,6 +2245,10 @@ export declare const readAdmissionState: (input: ReadStateInput) => Promise<Admi
1626
2245
  */
1627
2246
  export declare const readPausedWorkflows: (store: AdmissionDataStore) => Promise<ReadonlySet<string>>;
1628
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
+
1629
2252
  declare interface ReadStateInput {
1630
2253
  readonly store: AdmissionDataStore;
1631
2254
  readonly control: ResolvedFlowControl;
@@ -1637,6 +2260,48 @@ declare interface ReadStateInput {
1637
2260
  /** Read the stored position for a sweep consumer. */
1638
2261
  export declare const readWatermark: (store: CancelOnDataStore, name: string) => Promise<Date | undefined>;
1639
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
+
1640
2305
  /**
1641
2306
  * Append one ledger row. Also the lease, when the outcome is `admitted`.
1642
2307
  *
@@ -1702,6 +2367,27 @@ export declare interface RecordedStepShape {
1702
2367
  readonly stepName: string;
1703
2368
  }
1704
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
+
1705
2391
  /**
1706
2392
  * Resume every run parked on `budget`.
1707
2393
  *
@@ -1772,9 +2458,83 @@ export declare interface ReplayShapeTracker {
1772
2458
  */
1773
2459
  export declare const reportNondeterminism: (findings: ReadonlyArray<NondeterminismFinding>) => Effect.Effect<void>;
1774
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
+
1775
2533
  /** Test seam: forget which (workflow, signal) pairs have been hinted. */
1776
2534
  export declare const resetSuspendHintsForTest: () => void;
1777
2535
 
2536
+ export declare type ResidentQueuedCancellationTarget = typeof Target.Type;
2537
+
1778
2538
  /** Resolve every declared key against one payload. Pure; throws only
1779
2539
  * {@link FlowControlKeyError}. */
1780
2540
  export declare const resolveAdmissionKeys: (control: ResolvedFlowControl, payload: unknown) => AdmissionKeys;
@@ -1878,6 +2638,15 @@ export declare const resolveSuspendHintMs: () => number;
1878
2638
 
1879
2639
  export declare const resolveWorkflowMessageRun: (store: WorkflowMessageStore, target: WorkflowMessageTarget) => Promise<WorkflowResolvedRun>;
1880
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
+
1881
2650
  export declare const resumeWorkflow: (store: AdmissionDataStore, workflowName: string) => Promise<boolean>;
1882
2651
 
1883
2652
  /** Re-arm one dead-lettered intent after an operator has deployed a schema or
@@ -1974,11 +2743,27 @@ export declare const sleepUntil: (options: {
1974
2743
  readonly until: Date | number;
1975
2744
  }) => Effect.Effect<void, never, never>;
1976
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
+
1977
2757
  /** The narrow store slice this needs. Structural, like `CancelOnDataStore`. */
1978
2758
  export declare interface StalenessDataStore {
1979
2759
  query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
1980
2760
  }
1981
2761
 
2762
+ export declare interface StalenessRunCursor {
2763
+ readonly startedAt: Date;
2764
+ readonly runId: string;
2765
+ }
2766
+
1982
2767
  export declare interface StalenessSweepDeps {
1983
2768
  readonly store: StalenessDataStore;
1984
2769
  /**
@@ -2014,6 +2799,9 @@ export declare interface StalenessSweepOptions {
2014
2799
  readonly stallAfterMs?: number;
2015
2800
  readonly runPage?: number;
2016
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;
2017
2805
  }
2018
2806
 
2019
2807
  export declare interface StalenessSweepResult {
@@ -2027,14 +2815,16 @@ export declare interface StalenessSweepResult {
2027
2815
  readonly alreadyReported: number;
2028
2816
  /** Excluded because they are inside a durable timer that has not come due. */
2029
2817
  readonly waitingOnTimer: number;
2030
- /** The candidate page filled up: more live old runs exist and the next tick
2031
- * will reach them. Reported rather than inferred — the counts alone cannot
2032
- * 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. */
2033
2820
  readonly sawFullRunPage: boolean;
2034
2821
  readonly failures: ReadonlyArray<{
2035
2822
  readonly subject: string;
2036
2823
  readonly detail: string;
2037
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;
2038
2828
  }
2039
2829
 
2040
2830
  export declare interface StalledRun {
@@ -2175,6 +2965,34 @@ export declare interface StepRetryPolicy {
2175
2965
  readonly note?: string;
2176
2966
  }
2177
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
+
2178
2996
  /**
2179
2997
  * Park the run until the budget has headroom.
2180
2998
  *
@@ -2269,6 +3087,50 @@ export declare const sweepCancelOn: (deps: CancelOnSweepDeps, options: CancelOnS
2269
3087
  */
2270
3088
  export declare const sweepStalledRuns: (deps: StalenessSweepDeps, options?: StalenessSweepOptions) => Promise<StalenessSweepResult>;
2271
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
+
2272
3134
  export declare const truncateWorkflowValue: (value: unknown, bytes?: number) => unknown;
2273
3135
 
2274
3136
  export declare interface UnreadMessage extends ClusterMessageState {
@@ -2309,6 +3171,10 @@ export declare const validateWorkflowSchedule: (workflowName: string, declaratio
2309
3171
  */
2310
3172
  export declare const _voltroBudgetHoldsTable: TableLike;
2311
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
+
2312
3178
  /**
2313
3179
  * `_voltro_workflow_admissions` — append-only ledger, and the lease register.
2314
3180
  *
@@ -2318,6 +3184,19 @@ export declare const _voltroBudgetHoldsTable: TableLike;
2318
3184
  */
2319
3185
  export declare const _voltroWorkflowAdmissionsTable: SchemaTable;
2320
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
+
2321
3200
  /**
2322
3201
  * `_voltro_workflow_pauses` — one row per PAUSED workflow. Absent ⇒ running.
2323
3202
  *
@@ -2350,10 +3229,24 @@ export declare const _voltroWorkflowRunStepsTable: SchemaTable;
2350
3229
 
2351
3230
  export declare const _voltroWorkflowStartContextsTable: TableLike;
2352
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
+
2353
3239
  export declare const _voltroWorkflowWatermarksTable: SchemaTable;
2354
3240
 
2355
3241
  export declare const WATERMARKS_TABLE = "_voltro_workflow_watermarks";
2356
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
+
2357
3250
  /**
2358
3251
  * Add compensating (rollback) logic to a top-level effect in a workflow
2359
3252
  * body. The finalizer runs if the WHOLE workflow later fails — use it for
@@ -2564,6 +3457,8 @@ export declare interface WorkflowConcurrency<Payload> {
2564
3457
  readonly pool?: string;
2565
3458
  }
2566
3459
 
3460
+ export declare type WorkflowControlRequest = typeof RequestSchema.Type;
3461
+
2567
3462
  /**
2568
3463
  * Collapse a burst of starts into ONE run, `period` after the last one.
2569
3464
  *
@@ -2596,6 +3491,21 @@ export declare interface WorkflowDebounce<Payload> {
2596
3491
  }
2597
3492
 
2598
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
+ };
2599
3509
  readonly name: string;
2600
3510
  readonly subject?: unknown;
2601
3511
  readonly source?: string | null;
@@ -2604,6 +3514,7 @@ export declare interface WorkflowExecuteRecordingOptions extends WorkflowRunReco
2604
3514
  readonly workflowCompatibleVersions?: ReadonlyArray<string> | null;
2605
3515
  readonly workflowPatches?: ReadonlyArray<string> | null;
2606
3516
  readonly parentExecutionId?: string | null;
3517
+ readonly parentGeneration?: string | null;
2607
3518
  readonly parentClosePolicy?: WorkflowParentClosePolicy | null;
2608
3519
  readonly onParentClose?: (input: {
2609
3520
  readonly workflowName: string;
@@ -2613,30 +3524,15 @@ export declare interface WorkflowExecuteRecordingOptions extends WorkflowRunReco
2613
3524
  }) => Promise<void>;
2614
3525
  readonly recorder?: WorkflowRunRecorderService;
2615
3526
  readonly pluginWorkflowStepLayer?: Layer.Layer<any, never, never>;
2616
- /**
2617
- * A run reached a TERMINAL state — supplied by the CLI wiring so this package
2618
- * needs no knowledge of flow control.
2619
- *
2620
- * It exists to free the run's admission lease. That lease is what a
2621
- * `concurrency` limit counts, so a terminal transition that does not release
2622
- * it does not error, log, or fail a test — the limit simply admits one fewer
2623
- * run, permanently, and then one fewer again. A deployment that slowly stops
2624
- * doing work with nothing pointing at why.
2625
- *
2626
- * Called on success AND on failure, and DELIBERATELY not on suspend: a
2627
- * suspended run is still holding the resource it was admitted for.
2628
- *
2629
- * On a failure it also carries the payload and the error, so the caller
2630
- * can raise the workflow's `onFailure` — the one route into that signal
2631
- * that produces a `failed` row, and the one that was not wired: the
2632
- * flow-control paths (an expired start, a finish timeout, a dead-lettered
2633
- * intent) notified, and a body that failed did not.
2634
- */
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. */
2635
3531
  readonly onTerminal?: (input: {
2636
3532
  readonly workflowName: string;
2637
3533
  readonly executionId: string;
2638
3534
  readonly runId?: string;
2639
- readonly status: 'succeeded' | 'failed';
3535
+ readonly status: 'succeeded' | 'failed' | 'cancelled';
2640
3536
  /** The run's payload — present on `failed`, for the failure report. */
2641
3537
  readonly payload?: unknown;
2642
3538
  readonly errorTag?: string | null;
@@ -2675,6 +3571,28 @@ export declare interface WorkflowExecuteRecordingOptions extends WorkflowRunReco
2675
3571
  readonly replayShapeLimit?: number;
2676
3572
  }
2677
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
+
2678
3596
  /**
2679
3597
  * What an `onFailure` workflow receives. Declare it as that workflow's
2680
3598
  * `payload` schema — the framework validates against it like any other start,
@@ -2878,33 +3796,8 @@ export declare interface WorkflowMessageTarget {
2878
3796
 
2879
3797
  export { workflowModule }
2880
3798
 
2881
- export declare interface WorkflowParentCloseDecision {
2882
- readonly runId: string;
2883
- readonly workflowName: string;
2884
- readonly executionId: string;
2885
- readonly policy: WorkflowParentClosePolicy | null;
2886
- readonly action: 'cancelled' | 'abandoned' | 'skipped' | 'failed';
2887
- readonly reason?: string;
2888
- }
2889
-
2890
- export declare interface WorkflowParentCloseEmitter {
2891
- emit(channel: 'workflowRuns' | 'workflowSteps' | 'workflowEvents', event: {
2892
- readonly op: 'insert' | 'update';
2893
- readonly row: Record<string, unknown>;
2894
- }): void;
2895
- }
2896
-
2897
- export declare interface WorkflowParentCloseLogger {
2898
- warn(message: string, fields?: Record<string, unknown>): void;
2899
- }
2900
-
2901
3799
  export declare type WorkflowParentCloseStatus = 'succeeded' | 'failed' | 'cancelled';
2902
3800
 
2903
- export declare interface WorkflowParentCloseStore {
2904
- query(descriptor: QueryDescriptor): Promise<ReadonlyArray<unknown>>;
2905
- update(table: string, id: string, patch: unknown): Promise<unknown | null>;
2906
- }
2907
-
2908
3801
  /** Everything `Workflow.make` accepts for `payload`: a struct Schema, or bare
2909
3802
  * `Fields`. Both stay supported — a key lambda must not silently degrade to
2910
3803
  * `any` because the workflow spelled its payload the shorter way. */
@@ -2951,6 +3844,28 @@ export declare interface WorkflowResolvedRun {
2951
3844
  readonly tag?: string;
2952
3845
  }
2953
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
+
2954
3869
  /**
2955
3870
  * Closed enumeration of event types the framework knows how to emit.
2956
3871
  * Kept here (not in `runEventsTable.ts`) so callers in the recorder
@@ -2964,7 +3879,10 @@ export declare type WorkflowRunEventType = 'run-started' | 'run-suspended' | 'ru
2964
3879
  * `payload` carries `{ reason, activitiesReset }` — how many failed step
2965
3880
  * attempts were reset so they re-execute (completed steps replay). Distinct
2966
3881
  * from `run-resumed`, which continues a *suspended* run. */
2967
- | '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'
2968
3886
  /** Emitted when `sleep({ name, duration })` enters the wait — `payload`
2969
3887
  * carries `{ name, durationMs, scheduledWakeAt }`. The dashboard
2970
3888
  * Gantt renders these as a hatched bar in the timeline so a
@@ -3024,13 +3942,22 @@ export declare type WorkflowRunEventType = 'run-started' | 'run-suspended' | 'ru
3024
3942
  * lastProgressAt, reason }`. Diagnostic only — the run is untouched. */
3025
3943
  | 'run-stalled';
3026
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
+
3027
3954
  /** A workflow run's TERMINAL outcome, handed to the metrics hook. Kept local to
3028
3955
  * this package (structurally matching `@voltro/runtime`'s `WorkflowRunRecord`) so
3029
3956
  * the workflow package stays free of a `@voltro/runtime` dependency — the CLI
3030
3957
  * wiring supplies a `recordRun` that forwards to `recordWorkflowRun`. */
3031
3958
  declare interface WorkflowRunOutcome {
3032
3959
  readonly name: string;
3033
- readonly status: 'succeeded' | 'failed';
3960
+ readonly status: 'succeeded' | 'failed' | 'cancelled';
3034
3961
  readonly durationMs: number;
3035
3962
  /** UNIX seconds — set only on success (drives the last-success gauge). */
3036
3963
  readonly completedAtSec?: number;
@@ -3249,6 +4176,21 @@ export declare interface WorkflowSingleton<Payload> {
3249
4176
  readonly mode: 'skip' | 'cancel';
3250
4177
  }
3251
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
+
3252
4194
  /**
3253
4195
  * Per-step plugin interceptor. Plugins can wrap every `step()`
3254
4196
  * (== `Activity.make`) invocation with observability, suppress /
@@ -3404,7 +4346,7 @@ export declare interface WorkflowWorkerLayerBrand {
3404
4346
 
3405
4347
  export declare const WorkflowWorkerLayerTypeId: unique symbol;
3406
4348
 
3407
- 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>;
3408
4350
 
3409
4351
  /**
3410
4352
  * Advance the stored position.