@voltro/workflow 0.81.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ import { DurableRateLimiter as durableRateLimiterModule } from '@effect/workflow
8
8
  import { Duration } from 'effect';
9
9
  import { Effect } from 'effect';
10
10
  import { FiberRef } from 'effect';
11
+ import { FieldBinding } from '@voltro/database';
12
+ import { FieldCipher } from '@voltro/database';
11
13
  import { FieldDefinitions } from '@voltro/database';
12
14
  import { Layer } from 'effect';
13
15
  import { ParseResult } from 'effect';
@@ -24,6 +26,8 @@ import { WorkflowEngine } from '@effect/workflow';
24
26
  import { Workflow as workflowModule } from '@effect/workflow';
25
27
  import { WorkflowParentClosePolicy } from '@voltro/protocol';
26
28
  import { WorkflowQueuedCancellationResult } from '@voltro/protocol';
29
+ import { WorkflowRestartObservation } from '@voltro/protocol';
30
+ import { WorkflowRestartTarget } from '@voltro/protocol';
27
31
 
28
32
  /** Dispatch an already committed admission using the same attempt/ACK boundary
29
33
  * as the drainer. Returning requires durable acceptance, not just a prepared ID.
@@ -586,11 +590,12 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
586
590
  readonly enteredAt: number;
587
591
  } | null;
588
592
  readonly generation: string;
593
+ readonly runId: string;
589
594
  readonly requestedAt: number;
590
595
  readonly executionId: string;
591
- readonly runId: string;
592
596
  readonly workflowName: string;
593
597
  readonly operation: "resume" | "cancel" | "terminate";
598
+ readonly dispatchAttempts: number;
594
599
  readonly owner: {
595
600
  readonly tenantId: string | null;
596
601
  } | null;
@@ -599,7 +604,6 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
599
604
  readonly replyId: string;
600
605
  readonly restartToken: string | null;
601
606
  } | null;
602
- readonly dispatchAttempts: number;
603
607
  };
604
608
  run?: never;
605
609
  } | {
@@ -626,11 +630,12 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
626
630
  readonly enteredAt: number;
627
631
  } | null;
628
632
  readonly generation: string;
633
+ readonly runId: string;
629
634
  readonly requestedAt: number;
630
635
  readonly executionId: string;
631
- readonly runId: string;
632
636
  readonly workflowName: string;
633
637
  readonly operation: "resume" | "cancel" | "terminate";
638
+ readonly dispatchAttempts: number;
634
639
  readonly owner: {
635
640
  readonly tenantId: string | null;
636
641
  } | null;
@@ -639,7 +644,6 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
639
644
  readonly replyId: string;
640
645
  readonly restartToken: string | null;
641
646
  } | null;
642
- readonly dispatchAttempts: number;
643
647
  };
644
648
  run?: never;
645
649
  } | {
@@ -666,11 +670,12 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
666
670
  readonly enteredAt: number;
667
671
  } | null;
668
672
  readonly generation: string;
673
+ readonly runId: string;
669
674
  readonly requestedAt: number;
670
675
  readonly executionId: string;
671
- readonly runId: string;
672
676
  readonly workflowName: string;
673
677
  readonly operation: "resume" | "cancel" | "terminate";
678
+ readonly dispatchAttempts: number;
674
679
  readonly owner: {
675
680
  readonly tenantId: string | null;
676
681
  } | null;
@@ -679,7 +684,6 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
679
684
  readonly replyId: string;
680
685
  readonly restartToken: string | null;
681
686
  } | null;
682
- readonly dispatchAttempts: number;
683
687
  };
684
688
  run?: never;
685
689
  } | {
@@ -706,11 +710,12 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
706
710
  readonly enteredAt: number;
707
711
  } | null;
708
712
  readonly generation: string;
713
+ readonly runId: string;
709
714
  readonly requestedAt: number;
710
715
  readonly executionId: string;
711
- readonly runId: string;
712
716
  readonly workflowName: string;
713
717
  readonly operation: "resume" | "cancel" | "terminate";
718
+ readonly dispatchAttempts: number;
714
719
  readonly owner: {
715
720
  readonly tenantId: string | null;
716
721
  } | null;
@@ -719,7 +724,6 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
719
724
  readonly replyId: string;
720
725
  readonly restartToken: string | null;
721
726
  } | null;
722
- readonly dispatchAttempts: number;
723
727
  };
724
728
  run: Readonly<Record<string, unknown>>;
725
729
  } | {
@@ -747,9 +751,9 @@ export declare const beginWorkflowControlDispatchInTransaction: (tx: Store, id:
747
751
  readonly enteredAt: number;
748
752
  } | null;
749
753
  generation: string;
754
+ runId: string;
750
755
  requestedAt: number;
751
756
  executionId: string;
752
- runId: string;
753
757
  workflowName: string;
754
758
  operation: "resume" | "cancel" | "terminate";
755
759
  owner: {
@@ -797,11 +801,12 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
797
801
  readonly enteredAt: number;
798
802
  } | null;
799
803
  readonly generation: string;
804
+ readonly runId: string;
800
805
  readonly requestedAt: number;
801
806
  readonly executionId: string;
802
- readonly runId: string;
803
807
  readonly workflowName: string;
804
808
  readonly operation: "resume" | "cancel" | "terminate";
809
+ readonly dispatchAttempts: number;
805
810
  readonly owner: {
806
811
  readonly tenantId: string | null;
807
812
  } | null;
@@ -810,7 +815,6 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
810
815
  readonly replyId: string;
811
816
  readonly restartToken: string | null;
812
817
  } | null;
813
- readonly dispatchAttempts: number;
814
818
  };
815
819
  } | {
816
820
  kind: "unknown";
@@ -836,11 +840,12 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
836
840
  readonly enteredAt: number;
837
841
  } | null;
838
842
  readonly generation: string;
843
+ readonly runId: string;
839
844
  readonly requestedAt: number;
840
845
  readonly executionId: string;
841
- readonly runId: string;
842
846
  readonly workflowName: string;
843
847
  readonly operation: "resume" | "cancel" | "terminate";
848
+ readonly dispatchAttempts: number;
844
849
  readonly owner: {
845
850
  readonly tenantId: string | null;
846
851
  } | null;
@@ -849,7 +854,6 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
849
854
  readonly replyId: string;
850
855
  readonly restartToken: string | null;
851
856
  } | null;
852
- readonly dispatchAttempts: number;
853
857
  };
854
858
  } | {
855
859
  kind: "superseded";
@@ -875,11 +879,12 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
875
879
  readonly enteredAt: number;
876
880
  } | null;
877
881
  readonly generation: string;
882
+ readonly runId: string;
878
883
  readonly requestedAt: number;
879
884
  readonly executionId: string;
880
- readonly runId: string;
881
885
  readonly workflowName: string;
882
886
  readonly operation: "resume" | "cancel" | "terminate";
887
+ readonly dispatchAttempts: number;
883
888
  readonly owner: {
884
889
  readonly tenantId: string | null;
885
890
  } | null;
@@ -888,7 +893,6 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
888
893
  readonly replyId: string;
889
894
  readonly restartToken: string | null;
890
895
  } | null;
891
- readonly dispatchAttempts: number;
892
896
  };
893
897
  } | {
894
898
  kind: "not-suspended";
@@ -914,11 +918,12 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
914
918
  readonly enteredAt: number;
915
919
  } | null;
916
920
  readonly generation: string;
921
+ readonly runId: string;
917
922
  readonly requestedAt: number;
918
923
  readonly executionId: string;
919
- readonly runId: string;
920
924
  readonly workflowName: string;
921
925
  readonly operation: "resume" | "cancel" | "terminate";
926
+ readonly dispatchAttempts: number;
922
927
  readonly owner: {
923
928
  readonly tenantId: string | null;
924
929
  } | null;
@@ -927,7 +932,6 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
927
932
  readonly replyId: string;
928
933
  readonly restartToken: string | null;
929
934
  } | null;
930
- readonly dispatchAttempts: number;
931
935
  };
932
936
  } | {
933
937
  kind: "bound";
@@ -953,11 +957,12 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
953
957
  readonly enteredAt: number;
954
958
  } | null;
955
959
  readonly generation: string;
960
+ readonly runId: string;
956
961
  readonly requestedAt: number;
957
962
  readonly executionId: string;
958
- readonly runId: string;
959
963
  readonly workflowName: string;
960
964
  readonly operation: "resume" | "cancel" | "terminate";
965
+ readonly dispatchAttempts: number;
961
966
  readonly owner: {
962
967
  readonly tenantId: string | null;
963
968
  } | null;
@@ -966,7 +971,6 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
966
971
  readonly replyId: string;
967
972
  readonly restartToken: string | null;
968
973
  } | null;
969
- readonly dispatchAttempts: number;
970
974
  };
971
975
  } | {
972
976
  kind: "waiting-journal";
@@ -992,11 +996,12 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
992
996
  readonly enteredAt: number;
993
997
  } | null;
994
998
  readonly generation: string;
999
+ readonly runId: string;
995
1000
  readonly requestedAt: number;
996
1001
  readonly executionId: string;
997
- readonly runId: string;
998
1002
  readonly workflowName: string;
999
1003
  readonly operation: "resume" | "cancel" | "terminate";
1004
+ readonly dispatchAttempts: number;
1000
1005
  readonly owner: {
1001
1006
  readonly tenantId: string | null;
1002
1007
  } | null;
@@ -1005,7 +1010,6 @@ export declare const bindWorkflowResumeJournalInTransaction: (tx: Store, id: str
1005
1010
  readonly replyId: string;
1006
1011
  readonly restartToken: string | null;
1007
1012
  } | null;
1008
- readonly dispatchAttempts: number;
1009
1013
  };
1010
1014
  }>;
1011
1015
 
@@ -1652,7 +1656,9 @@ export declare const dueIntents: (store: AdmissionDataStore, now: number, limit:
1652
1656
  export { durableClock }
1653
1657
 
1654
1658
  /** Define a durable queue for concurrency-controlled side work. */
1655
- export declare const durableQueue: typeof durableQueueModule.make;
1659
+ export declare const durableQueue: typeof durableQueueModule.make & (<Options extends Parameters<typeof durableQueueModule.make>[0] & {
1660
+ readonly encrypted?: boolean;
1661
+ }>(options: Options) => ReturnType<typeof durableQueueModule.make>);
1656
1662
 
1657
1663
  export { durableQueueModule }
1658
1664
 
@@ -1677,6 +1683,7 @@ export declare const enterWorkflowRunGenerationInTransaction: (tx: AdmissionData
1677
1683
  readonly row: Row;
1678
1684
  readonly previous: Row | undefined;
1679
1685
  readonly terminalEvent?: Row;
1686
+ readonly restartEvent?: Row;
1680
1687
  }>;
1681
1688
 
1682
1689
  /**
@@ -1767,6 +1774,9 @@ export declare const getCurrentWorkflowRunId: () => Effect.Effect<string | undef
1767
1774
  * the path it took before this feature existed. */
1768
1775
  export declare const getWorkflowFlowControl: (value: unknown) => ResolvedFlowControl | undefined;
1769
1776
 
1777
+ /** The journal cipher in effect, if any. */
1778
+ export declare const getWorkflowJournalCipher: () => FieldCipher | undefined;
1779
+
1770
1780
  /** The resolved schedule attached to a workflow definition, or undefined. */
1771
1781
  export declare const getWorkflowSchedule: (value: unknown) => ResolvedWorkflowSchedule | undefined;
1772
1782
 
@@ -1878,6 +1888,9 @@ export declare const isFinishExpired: (control: ResolvedFlowControl, run: {
1878
1888
  readonly terminal: boolean;
1879
1889
  }, now: number) => boolean;
1880
1890
 
1891
+ /** A value in one of the cipher's envelopes (`enc:v1:` / `enc:v2:`). */
1892
+ export declare const isSealedJournalValue: (value: unknown) => value is string;
1893
+
1881
1894
  /**
1882
1895
  * Has a pending row outlived `timeouts.start`?
1883
1896
  *
@@ -1898,6 +1911,9 @@ export declare const isStartExpired: (control: ResolvedFlowControl, pending: {
1898
1911
  */
1899
1912
  export declare const isUnacknowledgedSubmission: (cause: unknown) => boolean;
1900
1913
 
1914
+ /** Is the workflow of this name declared `encrypted`? */
1915
+ export declare const isWorkflowEncrypted: (name: string) => boolean;
1916
+
1901
1917
  export declare const isWorkflowWorkerLayer: (value: unknown) => value is WorkflowWorkerLayerBrand;
1902
1918
 
1903
1919
  /**
@@ -2007,6 +2023,36 @@ export declare const noteAdmissionSubmissionFailure: (tx: Pick<DataStore, "query
2007
2023
 
2008
2024
  export declare const noteIntentAttempt: (store: AdmissionDataStore, intent: PendingIntent, error: string, now: number) => Promise<void>;
2009
2025
 
2026
+ /** An error message as stored, opened (see {@link sealErrorText}). */
2027
+ export declare const openErrorText: (stored: string | null | undefined) => string | null;
2028
+
2029
+ /** The inverse of {@link sealEventPayload}, tolerant like every read path. */
2030
+ export declare const openEventPayload: <P>(payload: P) => P;
2031
+
2032
+ /** The inverse of {@link sealJournalValue}; a plaintext value passes through. */
2033
+ export declare const openJournalValue: (stored: unknown, what: string, binding?: FieldBinding) => unknown;
2034
+
2035
+ /**
2036
+ * A stored payload with its sealed field values opened — what an authorised
2037
+ * reader (the app's live run rows, `listRuns`, the dashboard, a failure
2038
+ * handler) receives. At-rest protection is the point; hiding the value from the
2039
+ * caller the access rules already let in is not. A plaintext payload returns
2040
+ * unchanged.
2041
+ */
2042
+ export declare const openPayloadFields: (payload: unknown, what: string) => unknown;
2043
+
2044
+ /**
2045
+ * A stored workflow row — a run, a step or a run event — with every sealed
2046
+ * value opened, for a reader the access rules already let in: the live rows
2047
+ * pushed to subscribers, the client's read built-ins, the inspect surface.
2048
+ *
2049
+ * ONE function for every reader, because a reader that opened one column and
2050
+ * not the next is how a feature ends up "encrypted" in one view and garbage in
2051
+ * another. Tolerant throughout: a value that will not open (no cipher here, a
2052
+ * rotated key) stays as stored rather than failing the whole read.
2053
+ */
2054
+ export declare const openWorkflowRow: <R>(row: R) => R;
2055
+
2010
2056
  /**
2011
2057
  * Park a `start(name, payload, { at })` as a DURABLE pending row.
2012
2058
  *
@@ -2244,7 +2290,13 @@ export declare const pooledConcurrencyKey: (pool: string | undefined, rawKey: st
2244
2290
  * This records intent, not engine acceptance, and does not release capacity. */
2245
2291
  export declare const prepareAdmissionSubmissionExecutionInTransaction: (tx: Pick<DataStore, "query" | "update">, id: string, proposedExecutionId: string) => Promise<AdmissionSubmission>;
2246
2292
 
2247
- /** Enqueue work and await the durable queue worker's result. */
2293
+ /**
2294
+ * Enqueue work and await the durable queue worker's result.
2295
+ *
2296
+ * From inside an encrypted workflow the queue must be encrypted too — its
2297
+ * journal would otherwise hold the workflow's data in plaintext one hop away,
2298
+ * the same reason an encrypted workflow's `onFailure` handler must be.
2299
+ */
2248
2300
  export declare const processQueue: typeof durableQueueModule.process;
2249
2301
 
2250
2302
  /**
@@ -2321,6 +2373,51 @@ export declare const readWorkflowControlInTransaction: (tx: Pick<Store, "query">
2321
2373
  /** Strict read: a damaged record is not an absent report. */
2322
2374
  export declare const readWorkflowFailureRecord: (tx: Pick<DataStore, "query">, id: string) => Promise<WorkflowFailureRecord | undefined>;
2323
2375
 
2376
+ /** Read an accepted command before observing the journal again. Its captured
2377
+ * replies remain authoritative after reset or body entry. This grants no new
2378
+ * dispatch permission: dispatch must still recheck the resident generation. */
2379
+ declare const readWorkflowRestartCommandInTransaction: (tx: AdmissionDataStore, input: {
2380
+ readonly id: string;
2381
+ readonly runId: string;
2382
+ readonly workflowName: string;
2383
+ readonly tenantId: string | null;
2384
+ readonly selection: WorkflowRestartSelection;
2385
+ }) => Promise<{
2386
+ readonly id: string;
2387
+ readonly tenantId: string | null;
2388
+ readonly token: string;
2389
+ readonly generation: string;
2390
+ readonly runId: string;
2391
+ readonly requestedAt: Date;
2392
+ readonly executionId: string;
2393
+ readonly workflowName: string;
2394
+ readonly selection: {
2395
+ readonly kind: "failed-steps";
2396
+ } | {
2397
+ readonly kind: "from-step";
2398
+ readonly stepName: string;
2399
+ };
2400
+ readonly dispatchAttempts: number;
2401
+ readonly enteredGeneration: string | null;
2402
+ readonly ledgerId: string;
2403
+ readonly eviction: {
2404
+ readonly runId: string;
2405
+ readonly executionId: string;
2406
+ } | null;
2407
+ readonly delivery: {
2408
+ readonly requestId: string;
2409
+ readonly restartToken: string | null;
2410
+ };
2411
+ readonly replies: readonly {
2412
+ readonly requestId: string;
2413
+ readonly replyId: string;
2414
+ }[];
2415
+ } | undefined>;
2416
+
2417
+ /** Read-only reconciliation. No observation of engine replies, admission,
2418
+ * journal reset, or dispatch is performed. Internal ownership data stays here. */
2419
+ export declare const readWorkflowRestartObservationInTransaction: (tx: AdmissionDataStore, target: WorkflowRestartTarget) => Promise<WorkflowRestartObservation>;
2420
+
2324
2421
  /** Strict lookup shared by receipt owners; a malformed receipt is not absent. */
2325
2422
  export declare const readWorkflowStartReceipt: (store: Pick<DataStore, "query">, id: string) => Promise<WorkflowStartReceipt | undefined>;
2326
2423
 
@@ -2663,7 +2760,7 @@ export declare interface ResolvedFlowControl {
2663
2760
  readonly finishMs?: number;
2664
2761
  };
2665
2762
  readonly onFailure?: string;
2666
- readonly encryptSteps?: boolean;
2763
+ readonly encrypted?: boolean;
2667
2764
  readonly cancelOn?: ReadonlyArray<ResolvedCancelOn>;
2668
2765
  }
2669
2766
 
@@ -2714,6 +2811,20 @@ export declare const resumeWorkflow: (store: AdmissionDataStore, workflowName: s
2714
2811
  * the terminal classification and retry history are reset. */
2715
2812
  export declare const retryDeadLetterIntent: (store: AdmissionDataStore, intentId: string, now: number) => Promise<boolean>;
2716
2813
 
2814
+ /**
2815
+ * Seal a JSON-safe value for a framework table. `undefined` / `null` stay as
2816
+ * they are — there is nothing in them to hide, and a nullable column must keep
2817
+ * reading as empty.
2818
+ */
2819
+ export declare const sealJournalValue: (value: unknown, what: string, binding?: FieldBinding) => unknown;
2820
+
2821
+ declare const Selection: Schema.Union<[Schema.Struct<{
2822
+ kind: Schema.Literal<["failed-steps"]>;
2823
+ }>, Schema.Struct<{
2824
+ kind: Schema.Literal<["from-step"]>;
2825
+ stepName: Schema.filter<typeof Schema.NonEmptyString>;
2826
+ }>]>;
2827
+
2717
2828
  export declare const sendWorkflowSignal: (input: {
2718
2829
  readonly store: WorkflowMessageStore;
2719
2830
  readonly recorder: WorkflowRunRecorderService;
@@ -2744,6 +2855,14 @@ export declare const serialiseWorkflowRowForWire: (row: Record<string, unknown>)
2744
2855
  */
2745
2856
  export declare const setSuspendSignalHintMs: (ms: number | undefined) => void;
2746
2857
 
2858
+ /**
2859
+ * Register the cipher encrypted workflows use — a cipher, or a function that
2860
+ * returns the current one. The boot paths pass the field-cipher registry's
2861
+ * getter, so the key `governancePlugin({ fieldEncryption })` registers is the
2862
+ * one used, including after it is set. `undefined` clears it.
2863
+ */
2864
+ export declare const setWorkflowJournalCipher: (cipher: FieldCipher | (() => FieldCipher | undefined) | undefined) => void;
2865
+
2747
2866
  export declare interface ShardLockHolder {
2748
2867
  readonly shardId: string;
2749
2868
  readonly mode: 'advisory' | 'row';
@@ -2925,6 +3044,10 @@ export declare type StallReason =
2925
3044
 
2926
3045
  export declare const step: <R, Success extends Schema.Schema.Any = typeof Schema.Void, Error extends Schema.Schema.All = typeof Schema.Never>(options: StepOptions<R, Success, Error>) => ReturnType<typeof stepModule.make<R, Success, Error>>;
2927
3046
 
3047
+ /** The address the step journal's values are sealed to (`encrypted` via the
3048
+ * recorder's `encryptStepPayload`). ONE definition, shared with the CLI. */
3049
+ export declare const STEP_JOURNAL_BINDING: FieldBinding;
3050
+
2928
3051
  /**
2929
3052
  * Derive a deterministic idempotency key for the current workflow run,
2930
3053
  * scoped to `name`. Call it inside a `step()` so a retried external
@@ -3160,8 +3283,8 @@ export declare const takeAdmittedSubmissionInTransaction: (tx: AdmissionDataStor
3160
3283
  readonly stoppedAt: number | null;
3161
3284
  };
3162
3285
  readonly payload: unknown;
3163
- readonly executionId: string | null;
3164
3286
  readonly runId: string | null;
3287
+ readonly executionId: string | null;
3165
3288
  readonly failureId: string | null;
3166
3289
  readonly workflowName: string;
3167
3290
  readonly dispatchAttempts: number;
@@ -3171,8 +3294,8 @@ export declare const takeAdmittedSubmissionInTransaction: (tx: AdmissionDataStor
3171
3294
  readonly receiptsBound: boolean;
3172
3295
  readonly preparedExecutionId: string | null;
3173
3296
  readonly eviction: {
3174
- readonly executionId: string;
3175
3297
  readonly runId: string;
3298
+ readonly executionId: string;
3176
3299
  } | null;
3177
3300
  }>;
3178
3301
 
@@ -3193,6 +3316,13 @@ declare const Target: Schema.Struct<{
3193
3316
 
3194
3317
  export declare const truncateWorkflowValue: (value: unknown, bytes?: number) => unknown;
3195
3318
 
3319
+ /**
3320
+ * A stored value opened for an authorised reader, or left as stored when it
3321
+ * cannot be (no cipher, or sealed for another place). For read paths that must
3322
+ * not turn an unreadable value into a failed read of everything around it.
3323
+ */
3324
+ export declare const tryOpenJournalValue: (stored: unknown, what: string) => unknown;
3325
+
3196
3326
  export declare interface UnreadMessage extends ClusterMessageState {
3197
3327
  /** How long ago the last runner read it. */
3198
3328
  readonly unreadForMs: number;
@@ -3326,59 +3456,76 @@ export declare const withAdmissionTransaction: <A>(store: Pick<DataStore, "inser
3326
3456
  */
3327
3457
  export declare const withCompensation: typeof workflowModule.withCompensation;
3328
3458
 
3459
+ /** Resident withdrawal boundary used by the authorized Inspect host. A reservation wins even
3460
+ * before its first dispatch attempt; this never cancels accepted engine work.
3461
+ * The caller must hold the physical resident admission transaction throughout. */
3462
+ export declare const withdrawWorkflowRestartInTransaction: (tx: AdmissionDataStore, input: Parameters<typeof readWorkflowRestartCommandInTransaction>[1] & {
3463
+ readonly now: number;
3464
+ }) => Promise<{
3465
+ kind: "withdrawn";
3466
+ withdrawal: {
3467
+ readonly id: string;
3468
+ readonly tenantId: string | null;
3469
+ readonly runId: string;
3470
+ readonly workflowName: string;
3471
+ readonly selection: {
3472
+ readonly kind: "failed-steps";
3473
+ } | {
3474
+ readonly kind: "from-step";
3475
+ readonly stepName: string;
3476
+ };
3477
+ readonly withdrawnAt: Date;
3478
+ };
3479
+ reused: boolean;
3480
+ request?: never;
3481
+ } | {
3482
+ kind: "reserved";
3483
+ request: {
3484
+ readonly id: string;
3485
+ readonly tenantId: string | null;
3486
+ readonly token: string;
3487
+ readonly generation: string;
3488
+ readonly runId: string;
3489
+ readonly requestedAt: Date;
3490
+ readonly executionId: string;
3491
+ readonly workflowName: string;
3492
+ readonly selection: {
3493
+ readonly kind: "failed-steps";
3494
+ } | {
3495
+ readonly kind: "from-step";
3496
+ readonly stepName: string;
3497
+ };
3498
+ readonly dispatchAttempts: number;
3499
+ readonly enteredGeneration: string | null;
3500
+ readonly ledgerId: string;
3501
+ readonly eviction: {
3502
+ readonly runId: string;
3503
+ readonly executionId: string;
3504
+ } | null;
3505
+ readonly delivery: {
3506
+ readonly requestId: string;
3507
+ readonly restartToken: string | null;
3508
+ };
3509
+ readonly replies: readonly {
3510
+ readonly requestId: string;
3511
+ readonly replyId: string;
3512
+ }[];
3513
+ };
3514
+ withdrawal?: never;
3515
+ reused?: never;
3516
+ }>;
3517
+
3518
+ export declare const workflow: WorkflowFn;
3519
+
3329
3520
  /**
3330
- * Define a durable workflow.
3331
- *
3332
- * ── `idempotencyKey` is the execution's IDENTITY, permanently ──────────────
3333
- *
3334
- * Read this before choosing one. It is the single most expensive
3335
- * misunderstanding this API has produced — a downstream project lost a day and
3336
- * shipped a design around the wrong model, and the wording they read
3337
- * ("deduplicates concurrent invocations with the same input") is what led them
3338
- * there. It sounds like a WINDOW: dedupe while a run is in flight. It is not.
3339
- *
3340
- * The key IS the execution. A second start with the same key REPLAYS the first
3341
- * run's journaled result — forever, not just while it is running. Once the run
3342
- * completes the key is SPENT: a later, genuinely new invocation under that key
3343
- * is a silent no-op that returns the old output. Nothing errors and nothing
3344
- * logs, because from the engine's point of view you asked for a run it already
3345
- * has.
3346
- *
3347
- * ```ts
3348
- * const a = yield* wf.execute({ id: 'same' })
3349
- * const b = yield* wf.execute({ id: 'same' }) // ← does NOT run; replays `a`
3350
- * ```
3351
- *
3352
- * So a key must be **unique per unit of work you want to happen**, not per
3353
- * logical subject. `tour:${rowId}` is wrong if the tour can be re-narrated;
3354
- * `tour:${rowId}:${updatedAt}` is right, because every edit is a new unit of
3355
- * work with its own identity.
3356
- *
3357
- * ── And a flow-control key is a DIFFERENT thing ────────────────────────────
3358
- *
3359
- * The two get conflated, and conflating them is what makes "I need to re-arm a
3360
- * key" feel like a missing feature. It is not missing; it is two fields:
3361
- *
3362
- * • `idempotencyKey` — the execution's identity. Vary it per unit of work.
3363
- * • `debounce.key` / `singleton.key` / `concurrency.key` — the RESOURCE runs
3364
- * compete for. Keep it stable.
3365
- *
3366
- * "One job, fifteen minutes after the last edit, latest state wins" is then:
3367
- *
3368
- * ```ts
3369
- * workflow({
3370
- * name: 'tourNarration',
3371
- * payload: TourPayload,
3372
- * idempotencyKey: ({ rowId, editedAt }) => `tour:${rowId}:${editedAt}`, // varies
3373
- * debounce: { key: (p) => `tour:${p.rowId}`, period: '15 minutes' }, // stable
3374
- * })
3375
- * ```
3376
- *
3377
- * Twenty edits mint twenty identities; exactly one is ever admitted. There is
3378
- * no `restart: true` and no generation counter in this API because separating
3379
- * the two keys is the mechanism those would have been.
3521
+ * The address every sealed workflow value is bound to. ONE address for the
3522
+ * engine journal and the framework's own tables: the same value travels
3523
+ * between them (a run row's payload is the journal's encoding of it, a failure
3524
+ * report copies it), and a per-table binding would refuse the copy. What the
3525
+ * GCM tag still refuses is a value moved into an `.encrypted()` column or the
3526
+ * step journal, whose addresses differ.
3380
3527
  */
3381
- export declare const workflow: WorkflowFn;
3528
+ export declare const WORKFLOW_JOURNAL_BINDING: FieldBinding;
3382
3529
 
3383
3530
  declare type WorkflowBaseOptions = Parameters<WorkflowMake>[0];
3384
3531
 
@@ -3715,27 +3862,27 @@ export declare interface WorkflowFlowControl<Payload> {
3715
3862
  */
3716
3863
  readonly onFailure?: string;
3717
3864
  /**
3718
- * Encrypt this workflow's journaled step `input` / `output` / `errorCause`.
3865
+ * Encrypt every durable copy of this workflow's data.
3719
3866
  *
3720
- * `step({ input })` is written to `_voltro_workflow_run_steps` and rendered in
3721
- * the dashboard — a feature, and the reason people pass rich input. For a step
3722
- * carrying personal data it is also a SECOND COPY, outside the `.encrypted()`
3723
- * boundary the governance plugin establishes for tables. An app that carefully
3724
- * encrypted a column and then handed the same value to a step has it in
3725
- * plaintext one table over.
3867
+ * Covered: the payload, the result and a typed failure in the engine journal
3868
+ * (`cluster_messages`, `cluster_replies`), every step's result and typed
3869
+ * failure there, the run row (`_voltro_workflow_runs.payload` / `.output`),
3870
+ * the step journal (`_voltro_workflow_run_steps`), a queued start's payload
3871
+ * and a failure report's copy of the payload. The body sees the values it was
3872
+ * given, and a run replays unchanged.
3726
3873
  *
3727
- * Reuses the cipher `governancePlugin({ fieldEncryption })` already registers,
3728
- * so there is ONE key and one rotation story rather than a second scheme that
3729
- * only workflows know about. It is therefore a BOOT ERROR to declare this
3730
- * without that plugin configured: silently falling back to plaintext would be
3731
- * the worst outcome available — the declaration reads as protection and the
3732
- * rows are readable.
3874
+ * Not covered, and said here so it is not assumed: the `idempotencyKey`
3875
+ * (hashed into the execution id, but a primary key built from free text is
3876
+ * still that text in the engine's key column — derive it from ids), signal
3877
+ * and update payloads, and anything a step writes elsewhere itself.
3733
3878
  *
3734
- * Only the journal is affected. The values the workflow BODY sees are
3735
- * unchanged, and the durable execution journal `@effect/workflow` keeps is
3736
- * untouched (encrypting that would break replay).
3879
+ * Reuses the cipher `governancePlugin({ fieldEncryption })` registers — one
3880
+ * key, one rotation story. Declaring it without that plugin configured is a
3881
+ * BOOT ERROR: a plaintext fallback would leave the declaration reading as
3882
+ * protection while the rows are readable. A plaintext value written before
3883
+ * the declaration (a run already in flight) is still read.
3737
3884
  */
3738
- readonly encryptSteps?: boolean;
3885
+ readonly encrypted?: boolean;
3739
3886
  }
3740
3887
 
3741
3888
  /** Where the RESOLVED flow-control declaration hangs off a definition. A
@@ -3917,12 +4064,19 @@ export declare const workflowRestartRequestsTable: Table<"_voltro_workflow_resta
3917
4064
  readonly tenantId: ColumnBuilder<string | null, "text", boolean>;
3918
4065
  readonly delivery: ColumnBuilder<unknown, "json", boolean>;
3919
4066
  readonly replies: ColumnBuilder<unknown, "json", boolean>;
4067
+ readonly selection: ColumnBuilder<unknown, "json", boolean>;
3920
4068
  readonly eviction: ColumnBuilder<unknown, "json", boolean>;
3921
4069
  readonly requestedAt: ColumnBuilder<Date, "timestamp", boolean>;
3922
4070
  readonly dispatchAttempts: ColumnBuilder<number, "integer", true>;
3923
4071
  readonly enteredGeneration: ColumnBuilder<string | null, "text", boolean>;
3924
4072
  }>, false, never>;
3925
4073
 
4074
+ declare type WorkflowRestartSelection = typeof Selection.Type;
4075
+
4076
+ /** Durable negative authority. Never expire while delayed commands can reuse an
4077
+ * identity. Acceptance and reservation require the same resident admission lock. */
4078
+ export declare const workflowRestartWithdrawalsTable: SchemaTable;
4079
+
3926
4080
  export declare interface WorkflowRunCompletion {
3927
4081
  readonly owner: WorkflowRunGeneration;
3928
4082
  readonly now: number;
@@ -3954,7 +4108,7 @@ export declare interface WorkflowRunCompletion {
3954
4108
  */
3955
4109
  export declare type WorkflowRunEventType = 'run-started' | 'run-suspended' | 'run-resumed'
3956
4110
  /** Emitted when an operator re-drives a terminally-FAILED run from its
3957
- * durable journal (`voltro workflows redrive` / `ctx.workflows.redrive`).
4111
+ * durable journal (`voltro workflows restart` / `ctx.workflows.restart`).
3958
4112
  * `payload` carries `{ reason, activitiesReset }` — how many failed step
3959
4113
  * attempts were reset so they re-execute (completed steps replay). Distinct
3960
4114
  * from `run-resumed`, which continues a *suspended* run. */
@@ -4061,26 +4215,6 @@ export declare interface WorkflowRunRecorderOptions {
4061
4215
  * per run at a TERMINAL outcome (succeeded/failed), never on suspend. Absent →
4062
4216
  * no metrics, zero overhead. */
4063
4217
  readonly recordRun?: (outcome: WorkflowRunOutcome) => void;
4064
- /**
4065
- * Encrypt the `input` / `output` / `errorCause` a step journals.
4066
- *
4067
- * `step({ input })` is written to `_voltro_workflow_run_steps` and shown in
4068
- * the dashboard, which is a feature and the reason people pass rich input. For
4069
- * a step carrying personal data it is also a SECOND COPY, outside the
4070
- * `.encrypted()` boundary the governance plugin establishes for tables — so an
4071
- * app that carefully encrypted a column then handed the same value to a step
4072
- * has it in plaintext one table over.
4073
- *
4074
- * Supplied by the CLI from the cipher `governancePlugin({ fieldEncryption })`
4075
- * already registers, so there is ONE key and one rotation story rather than a
4076
- * second scheme for workflows. Absent → plaintext, exactly as before.
4077
- *
4078
- * Encryption happens on the WRITE and decryption on the inspect read, so the
4079
- * dashboard is unchanged for a viewer who is allowed to see it and the value
4080
- * at rest is not. A ciphertext that cannot be decrypted renders as a marker
4081
- * rather than as garbage — see `inspectWorkflow`.
4082
- */
4083
- readonly encryptStepPayload?: (value: unknown) => unknown;
4084
4218
  /**
4085
4219
  * How much this recorder writes per STEP — `'full'` (default) or `'coarse'`.
4086
4220
  *
@@ -4117,6 +4251,10 @@ export declare interface WorkflowRunRecorderService {
4117
4251
  readonly runId: string;
4118
4252
  readonly stepName: string;
4119
4253
  readonly attempt: number;
4254
+ /** The step runs inside an `encrypted: true` workflow: a recorder that
4255
+ * persists the step's values seals them (`sealJournalValue`, bound to
4256
+ * `STEP_JOURNAL_BINDING`). Absent or false: store them as they are. */
4257
+ readonly sealed?: boolean;
4120
4258
  /** User-declared structured input recorded into
4121
4259
  * `_voltro_workflow_run_steps.input`. The recorder truncates large
4122
4260
  * payloads — pass whatever the step needs to make sense in
@@ -4133,11 +4271,15 @@ export declare interface WorkflowRunRecorderService {
4133
4271
  }) => Promise<string | undefined>;
4134
4272
  readonly endStepSuccess: (input: {
4135
4273
  readonly stepRecordId: string;
4274
+ /** As on `startStep`. */
4275
+ readonly sealed?: boolean;
4136
4276
  readonly output: unknown;
4137
4277
  readonly durationMs: number;
4138
4278
  }) => Promise<void>;
4139
4279
  readonly endStepFailure: (input: {
4140
4280
  readonly stepRecordId: string;
4281
+ /** As on `startStep`. */
4282
+ readonly sealed?: boolean;
4141
4283
  readonly errorTag: string | null;
4142
4284
  readonly errorMessage: string;
4143
4285
  /** Structured Cause record — pretty form +