@voltro/workflow 0.24.0 → 0.26.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
@@ -426,42 +426,58 @@ declare type StepOptions<R, Success extends Schema.Schema.Any, Error extends Sch
426
426
  /** Structured value recorded into `_voltro_workflow_run_steps.input`.
427
427
  * Pass whatever makes the step debuggable in isolation. */
428
428
  readonly input?: unknown;
429
- /** Declarative retry-policy summary recorded into
430
- * `_voltro_workflow_run_steps.retryPolicy`. Pure metadata does
431
- * NOT change retry behavior on its own. Wire actual retries via
432
- * `Effect.retry` inside `execute:` or via `interruptRetryPolicy`
433
- * above; this field tells the dashboard what the intended policy
434
- * looks like so the per-attempt panel can show "attempt 2 of 5,
435
- * exponential 1s base" instead of just "attempt 2". */
429
+ /** Declarative retry policy ENFORCED. The framework compiles it to an
430
+ * Effect `Schedule` and retries `execute` accordingly (backoff, jitter,
431
+ * attempt cap, time budget, error classification, provider `Retry-After`).
432
+ * See {@link StepRetryPolicy}. Retries run INSIDE this one step and are
433
+ * transparent to the durable engine; the step's FINAL outcome is recorded.
434
+ * The serialisable knobs are also stored as `_voltro_workflow_run_steps.
435
+ * retryPolicy` for the dashboard. `retry: { maxAttempts: 5 }` is enough. */
436
436
  readonly retry?: StepRetryPolicy;
437
437
  };
438
438
 
439
439
  /**
440
- * Declarative retry-policy summary. Pure metadata for the dashboard;
441
- * the framework does NOT apply this. Users wire actual retry behavior
442
- * via `Effect.retry(Schedule.*)` inside `execute:` or
443
- * `Activity.make`'s `interruptRetryPolicy`.
444
- *
445
- * Shape is intentionally close to Effect's Schedule combinators so a
446
- * future framework version can auto-translate declarative policies
447
- * into a Schedule + apply it. For now: record + display.
448
- *
449
- * { strategy: 'exponential', maxAttempts: 5, baseDelay: '1 second', maxDelay: '30 seconds' }
450
- * { strategy: 'fixed', maxAttempts: 3, baseDelay: '500 millis' }
451
- * { strategy: 'linear', maxAttempts: 4, baseDelay: '1 second', step: '2 seconds' }
440
+ * A step's retry policy. Every field is optional — `retry: {}` is already a
441
+ * sensible policy (3 attempts, exponential backoff, jittered). All fields are
442
+ * JSON-serialisable EXCEPT `retryable` (a predicate), which the recorder drops
443
+ * when it stores the policy as dashboard metadata.
452
444
  */
453
445
  export declare interface StepRetryPolicy {
454
- readonly strategy: 'exponential' | 'fixed' | 'linear';
455
- readonly maxAttempts: number;
456
- /** Effect Duration string, e.g. `'1 second'`, `'500 millis'`. */
446
+ /** Total attempts INCLUDING the first. Default `3`. `1` disables retry. */
447
+ readonly maxAttempts?: number;
448
+ /** Backoff shape between attempts. Default `'exponential'`. */
449
+ readonly strategy?: 'exponential' | 'fixed' | 'linear';
450
+ /** First delay (Effect Duration string, e.g. `'500 millis'`). Default `'200 millis'`. */
457
451
  readonly baseDelay?: string;
458
- /** Cap on exponential / linear growth, e.g. `'30 seconds'`. */
459
- readonly maxDelay?: string;
460
- /** Increment for `linear` strategy. */
452
+ /** Growth factor for `'exponential'`. Default `2`. */
453
+ readonly factor?: number;
454
+ /** Increment per attempt for `'linear'`. Default = `baseDelay`. */
461
455
  readonly step?: string;
462
- /** Free-form note rendered as a chip in the dashboard. Useful when
463
- * the retry behavior is partially implemented in the user's
464
- * Effect chain and the summary is just documentation. */
456
+ /** Ceiling on any single delay, so exponential growth can't run away
457
+ * (e.g. `'30 seconds'`). Applied before jitter. */
458
+ readonly maxDelay?: string;
459
+ /** Full jitter on each delay — spreads retries so a fleet doesn't
460
+ * re-hit a recovering dependency in lockstep. Default `true`. */
461
+ readonly jitter?: boolean;
462
+ /** A total wall-clock BUDGET across all attempts: stop retrying once
463
+ * this much time has elapsed since the first attempt, even if attempts
464
+ * remain (e.g. `'5 minutes'`). A deadline, not a count. */
465
+ readonly maxElapsed?: string;
466
+ /** Retry ONLY failures whose typed-error `_tag` is in this list; every
467
+ * other error fails fast. The declarative "retry transient, fail
468
+ * permanent" — e.g. `['ProviderDown', 'RateLimited']`. */
469
+ readonly retryableErrors?: ReadonlyArray<string>;
470
+ /** Retry-predicate on the raw error (wins over `retryableErrors` when
471
+ * both are set). Not serialised into dashboard metadata. Default:
472
+ * retry every failure. */
473
+ readonly retryable?: (error: unknown) => boolean;
474
+ /** Honor a provider's own backoff: if a retryable error carries a
475
+ * `retryAfterMillis` number (or `retryAfter` in seconds), use exactly that as
476
+ * the delay before the next attempt — REPLACING the computed backoff for
477
+ * that attempt (a 429 `Retry-After`, say). Falls back to the normal backoff
478
+ * when the error carries no hint. Default `false`. */
479
+ readonly respectRetryAfter?: boolean;
480
+ /** Free-form note rendered as a chip in the dashboard. */
465
481
  readonly note?: string;
466
482
  }
467
483
 
@@ -637,7 +653,13 @@ export declare interface WorkflowResolvedRun {
637
653
  * open-text on purpose (future phases add new types without a column
638
654
  * migration) — but at emit time we still want type-safety.
639
655
  */
640
- export declare type WorkflowRunEventType = 'run-started' | 'run-suspended' | 'run-resumed' | 'run-cancelled' | 'run-succeeded' | 'run-failed'
656
+ export declare type WorkflowRunEventType = 'run-started' | 'run-suspended' | 'run-resumed'
657
+ /** Emitted when an operator re-drives a terminally-FAILED run from its
658
+ * durable journal (`voltro workflows redrive` / `ctx.workflows.redrive`).
659
+ * `payload` carries `{ reason, activitiesReset }` — how many failed step
660
+ * attempts were reset so they re-execute (completed steps replay). Distinct
661
+ * from `run-resumed`, which continues a *suspended* run. */
662
+ | 'run-redriven' | 'run-cancelled' | 'run-succeeded' | 'run-failed'
641
663
  /** Emitted when `sleep({ name, duration })` enters the wait — `payload`
642
664
  * carries `{ name, durationMs, scheduledWakeAt }`. The dashboard
643
665
  * Gantt renders these as a hatched bar in the timeline so a
@@ -681,6 +703,18 @@ export declare type WorkflowRunEventType = 'run-started' | 'run-suspended' | 'ru
681
703
  * errorTag, errorMessage }`; the caller's update promise rejects. */
682
704
  | 'update-failed';
683
705
 
706
+ /** A workflow run's TERMINAL outcome, handed to the metrics hook. Kept local to
707
+ * this package (structurally matching `@voltro/runtime`'s `WorkflowRunRecord`) so
708
+ * the workflow package stays free of a `@voltro/runtime` dependency — the CLI
709
+ * wiring supplies a `recordRun` that forwards to `recordWorkflowRun`. */
710
+ declare interface WorkflowRunOutcome {
711
+ readonly name: string;
712
+ readonly status: 'succeeded' | 'failed';
713
+ readonly durationMs: number;
714
+ /** UNIX seconds — set only on success (drives the last-success gauge). */
715
+ readonly completedAtSec?: number;
716
+ }
717
+
684
718
  export declare class WorkflowRunRecorder extends WorkflowRunRecorder_base {
685
719
  }
686
720
 
@@ -695,6 +729,11 @@ export declare interface WorkflowRunRecorderOptions {
695
729
  /** Dormancy wakeup producer — present only in sleep mode. Absent →
696
730
  * zero overhead (always-on default). */
697
731
  readonly wakeups?: WorkflowWakeupHook;
732
+ /** Terminal-outcome metrics hook — supplied by the CLI wiring (mirrors
733
+ * `emit`/`wakeups`), so this package needs no `@voltro/runtime` dep. Called once
734
+ * per run at a TERMINAL outcome (succeeded/failed), never on suspend. Absent →
735
+ * no metrics, zero overhead. */
736
+ readonly recordRun?: (outcome: WorkflowRunOutcome) => void;
698
737
  }
699
738
 
700
739
  /** Per-step lifecycle hooks. Implementations should not throw —
@@ -826,6 +865,25 @@ export declare interface WorkflowVersionOptions {
826
865
  readonly version?: string | number;
827
866
  readonly compatibleWith?: ReadonlyArray<string | number>;
828
867
  readonly patches?: ReadonlyArray<string>;
868
+ /**
869
+ * When `true`, a workflow whose top-level body FAILS does not become a
870
+ * terminal `failed` run — it **suspends** with its durable journal intact, so
871
+ * `voltro workflows resume <id>` (or `ctx.workflows.resume`) re-drives it from
872
+ * the point of failure: every completed activity replays from the journal
873
+ * (NOT re-executed) and only the failed activity runs again. This is the
874
+ * durable-execution way to make a workflow recoverable across a transient
875
+ * downstream outage without re-doing prior work.
876
+ *
877
+ * Default `false` — a failure is terminal (the dead-letter state; see
878
+ * `voltro workflows list --dead-letter`). Choose per workflow: `true` for a
879
+ * long multi-step pipeline where re-doing step 1..N-1 is expensive or unsafe;
880
+ * `false` for a short idempotent job where a fresh `retry` is simpler. A
881
+ * suspended-on-failure run shows up under `--status suspended`, NOT in the
882
+ * dead-letter view — it is recoverable, not dead.
883
+ *
884
+ * Maps to `@effect/workflow`'s `SuspendOnFailure` annotation.
885
+ */
886
+ readonly suspendOnFailure?: boolean;
829
887
  }
830
888
 
831
889
  export declare const WorkflowVersionTypeId: unique symbol;
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { C as e, E as t, T as n, _ as r, a as i, b as a, c as o, d as s, f as c, g as l, h as u, i as d, l as f, m as p, n as m, o as h, p as g, r as _, s as v, t as y, u as b, v as x, w as S, x as C, y as w } from "./primitives-CWy1iu5w.js";
2
- import { _ as T, a as E, b as D, c as O, d as k, f as A, g as j, h as M, i as N, l as P, m as F, n as I, o as L, p as R, r as z, s as B, t as V, u as H, v as U, x as W, y as G } from "./src-ByK8t2Nv.js";
1
+ import { C as e, E as t, T as n, _ as r, a as i, b as a, c as o, d as s, f as c, g as l, h as u, i as d, l as f, m as p, n as m, o as h, p as g, r as _, s as v, t as y, u as b, v as x, w as S, x as C, y as w } from "./primitives-Dgu3O55Q.js";
2
+ import { _ as T, a as E, b as D, c as O, d as k, f as A, g as j, h as M, i as N, l as P, m as F, n as I, o as L, p as R, r as z, s as B, t as V, u as H, v as U, x as W, y as G } from "./src-KDx7NTsa.js";
3
3
  export { a as CurrentWorkflowExecutionId, C as CurrentWorkflowRunId, y as WorkflowMessagesProperty, e as WorkflowRunRecorder, S as WorkflowStepInterceptorTag, m as WorkflowVersionTypeId, _ as WorkflowWorkerLayerTypeId, U as _voltroWorkflowRunEventsTable, G as _voltroWorkflowRunStepsTable, D as _voltroWorkflowRunsTable, T as _voltroWorkflowStartContextsTable, j as awaitSignal, P as awaitSignalSuspending, O as awaitUpdate, z as closeWorkflowChildrenForParent, H as completeSuspendingSignal, d as durableClock, i as durableQueue, h as durableQueueModule, v as durableRateLimiterModule, n as getCurrentWorkflowExecutionId, t as getCurrentWorkflowRunId, o as getWorkflowVersionMetadata, W as inMemoryWorkflowEngineLayer, I as inspectWorkflow, f as isWorkflowWorkerLayer, V as makeInMemoryRecorder, N as makeWorkflowRunRecorder, A as makeWorkflowUpdateId, b as processQueue, s as queueWorker, c as rateLimit, R as resolveWorkflowMessageRun, F as sendWorkflowSignal, M as sendWorkflowUpdate, E as serialiseWorkflowRowForWire, g as sleep, p as step, u as stepIdempotencyKey, l as stepModule, k as suspendingSignalDeferredName, L as truncateWorkflowValue, r as withCompensation, x as workflow, w as workflowModule, B as wrapWorkflowExecuteWithRunRecording };
@@ -0,0 +1,164 @@
1
+ import { Activity as e, Activity as t, DurableClock as n, DurableClock as r, DurableQueue as i, DurableQueue as a, DurableRateLimiter as o, DurableRateLimiter as s, Workflow as c, Workflow as l } from "@effect/workflow";
2
+ import { Cause as u, Context as d, Duration as f, Effect as p, Exit as m, FiberRef as h, Option as g, Schedule as _ } from "effect";
3
+ //#region src/recorder.ts
4
+ var v = class extends d.Tag("@voltro/WorkflowRunRecorder")() {}, y = h.unsafeMake(void 0), b = h.unsafeMake(void 0), x = h.unsafeMake(void 0), S = () => h.get(y), C = () => h.get(b), w = class extends d.Tag("@voltro/WorkflowStepInterceptor")() {}, T = (e) => {
5
+ if (typeof e != "object" || !e) return;
6
+ let t = e.retryAfterMillis;
7
+ if (typeof t == "number" && Number.isFinite(t) && t >= 0) return f.millis(t);
8
+ let n = e.retryAfter;
9
+ if (typeof n == "number" && Number.isFinite(n) && n >= 0) return f.seconds(n);
10
+ }, E = (e) => f.decode(e), D = (e) => typeof e == "object" && e && "_tag" in e ? String(e._tag) : void 0, O = (e) => e !== void 0 && (e.maxAttempts ?? 3) > 1, k = (e) => {
11
+ if (e.retryable) return e.retryable;
12
+ if (e.retryableErrors && e.retryableErrors.length > 0) {
13
+ let t = new Set(e.retryableErrors);
14
+ return (e) => {
15
+ let n = D(e);
16
+ return n !== void 0 && t.has(n);
17
+ };
18
+ }
19
+ return () => !0;
20
+ }, A = (e) => {
21
+ let t = e.baseDelay ?? "200 millis", n = e.strategy ?? "exponential", r = e.maxAttempts ?? 3, i = n === "linear" ? _.linear(E(e.step ?? t)) : n === "fixed" ? _.spaced(E(t)) : _.exponential(E(t), e.factor ?? 2);
22
+ if (e.maxDelay !== void 0) {
23
+ let t = E(e.maxDelay);
24
+ i = _.modifyDelay(i, (e, n) => f.min(n, t));
25
+ }
26
+ (e.jitter ?? !0) === !0 && (i = _.jittered(i));
27
+ let a = _.intersect(i, _.recurs(Math.max(0, r - 1)));
28
+ return e.maxElapsed !== void 0 && (a = _.upTo(E(e.maxElapsed))(a)), a;
29
+ }, j = (e, t) => {
30
+ let n = f.toMillis(E(e.baseDelay ?? "200 millis")), r = e.strategy ?? "exponential", i = r === "fixed" ? n : r === "linear" ? n + f.toMillis(E(e.step ?? e.baseDelay ?? "200 millis")) * (t - 1) : n * (e.factor ?? 2) ** (t - 1);
31
+ return e.maxDelay !== void 0 && (i = Math.min(i, f.toMillis(E(e.maxDelay)))), (e.jitter ?? !0) === !0 && (i *= Math.random()), i;
32
+ }, M = (e, t, n, r) => p.gen(function* () {
33
+ let i = t.maxAttempts ?? 3, a = t.maxElapsed === void 0 ? Infinity : f.toMillis(E(t.maxElapsed)), o = yield* p.sync(() => Date.now()), s = 0;
34
+ for (;;) {
35
+ let c = yield* p.exit(e);
36
+ if (m.isSuccess(c)) return c.value;
37
+ let l = c.cause, d = u.failureOption(l), h = g.isSome(d) ? d.value : u.squash(l);
38
+ s += 1;
39
+ let _ = (yield* p.sync(() => Date.now())) - o;
40
+ if (s >= i || !n(h) || _ >= a) return yield* p.failCause(l);
41
+ r?.(h);
42
+ let v = T(h), y = v === void 0 ? j(t, s) : f.toMillis(v);
43
+ yield* p.sleep(f.millis(y));
44
+ }
45
+ }), N = (e, t, n) => {
46
+ if (!O(t)) return e;
47
+ let r = k(t);
48
+ if (t.respectRetryAfter === !0) return M(e, t, r, n);
49
+ let i = A(t), a = n ? p.tapError(e, (e) => p.sync(() => {
50
+ r(e) && n(e);
51
+ })) : e;
52
+ return p.retry(a, {
53
+ schedule: i,
54
+ while: r
55
+ });
56
+ }, P = Symbol.for("voltro.workflow.workerLayer"), F = Symbol.for("voltro.workflow.version"), I = "__voltroWorkflowMessages", L = (e) => typeof e == "object" && !!e && e[P] === !0, R = (e) => typeof e == "object" && e && e[F] !== void 0 ? e[F] : {
57
+ version: "1",
58
+ compatibleWith: ["1"],
59
+ patches: []
60
+ }, z = ((e) => {
61
+ let t = c.make(e), n = e.suspendOnFailure === !0 ? t.annotate(c.SuspendOnFailure, !0) : t, r = String(e.version ?? "1"), i = {
62
+ version: r,
63
+ compatibleWith: (e.compatibleWith ?? [r]).map(String),
64
+ patches: [...e.patches ?? []]
65
+ }, a = {
66
+ signals: { ...e.messages?.signals ?? {} },
67
+ updates: { ...e.messages?.updates ?? {} },
68
+ queries: { ...e.messages?.queries ?? {} }
69
+ };
70
+ return Object.defineProperty(n, F, {
71
+ value: i,
72
+ enumerable: !1
73
+ }), Object.defineProperty(n, I, {
74
+ value: a,
75
+ enumerable: !1
76
+ }), n;
77
+ }), B = i.make, V = i.process, H = ((...e) => {
78
+ let t = i.worker(...e);
79
+ return Object.defineProperty(t, P, {
80
+ value: !0,
81
+ enumerable: !1
82
+ }), t;
83
+ }), U = o.rateLimit, W = (e) => p.gen(function* () {
84
+ let t = yield* y, r = yield* p.serviceOption(v), i = f.toMillis(f.decode(e.duration));
85
+ if (t !== void 0 && r._tag === "Some") {
86
+ let a = r.value, o = new Date(Date.now() + i).toISOString(), s = yield* x;
87
+ yield* p.promise(() => a.recordEvent({
88
+ runId: t,
89
+ eventType: "timer-set",
90
+ payload: {
91
+ name: e.name,
92
+ durationMs: i,
93
+ scheduledWakeAt: o,
94
+ ...s === void 0 ? {} : { tenantId: s }
95
+ }
96
+ }).catch(() => {}));
97
+ let c = Date.now();
98
+ yield* n.sleep(e), yield* p.promise(() => a.recordEvent({
99
+ runId: t,
100
+ eventType: "timer-fired",
101
+ payload: {
102
+ name: e.name,
103
+ actualDurationMs: Date.now() - c,
104
+ ...s === void 0 ? {} : { tenantId: s }
105
+ }
106
+ }).catch(() => {}));
107
+ return;
108
+ }
109
+ yield* n.sleep(e);
110
+ }), G = (t, n, r, i) => p.gen(function* () {
111
+ let a = yield* y, o = yield* e.CurrentAttempt, s = yield* p.serviceOption(v), c = yield* p.serviceOption(w), l = c._tag === "Some" ? c.value.interceptor(n, {
112
+ runId: a ?? "unrecorded",
113
+ stepName: t,
114
+ attempt: o
115
+ }) : n, d, f;
116
+ if (a !== void 0 && s._tag === "Some") {
117
+ f = s.value;
118
+ let e = i === void 0 ? void 0 : (({ retryable: e, ...t }) => t)(i);
119
+ try {
120
+ d = yield* p.promise(() => f.startStep({
121
+ runId: a,
122
+ stepName: t,
123
+ attempt: o,
124
+ stepInput: r,
125
+ ...e === void 0 ? {} : { retryPolicy: e }
126
+ }));
127
+ } catch {
128
+ d = void 0;
129
+ }
130
+ }
131
+ let m = Date.now();
132
+ return yield* N(l, i).pipe(p.tap((e) => d && f ? p.promise(async () => {
133
+ try {
134
+ await f.endStepSuccess({
135
+ stepRecordId: d,
136
+ output: e,
137
+ durationMs: Date.now() - m
138
+ });
139
+ } catch {}
140
+ }) : p.void), p.tapErrorCause((e) => d && f && !u.isInterruptedOnly(e) ? p.promise(async () => {
141
+ try {
142
+ let t = u.failureOption(e), n = Array.from(u.failures(e)), r = Array.from(u.defects(e)), i = t._tag === "Some" ? t.value : r[0] ?? u.squash(e), a = i?.message ?? String(i), o = i && typeof i == "object" && "_tag" in i ? String(i._tag) : null;
143
+ await f.endStepFailure({
144
+ stepRecordId: d,
145
+ errorTag: o,
146
+ errorMessage: a,
147
+ errorCause: {
148
+ pretty: u.pretty(e),
149
+ failures: n,
150
+ defects: r
151
+ },
152
+ durationMs: Date.now() - m
153
+ });
154
+ } catch {}
155
+ }) : p.void));
156
+ }), K = (t) => {
157
+ let { input: n, retry: r, ...i } = t;
158
+ return e.make({
159
+ ...i,
160
+ execute: G(i.name, i.execute, n, r)
161
+ });
162
+ }, q = c.withCompensation, J = e.idempotencyKey;
163
+ //#endregion
164
+ export { v as C, S as E, x as S, C as T, q as _, B as a, b, R as c, H as d, U as f, t as g, J as h, r as i, L as l, K as m, F as n, a as o, W as p, P as r, s, I as t, V as u, z as v, w, y as x, l as y };
@@ -107,42 +107,58 @@ declare type StepOptions<R, Success extends Schema.Schema.Any, Error extends Sch
107
107
  /** Structured value recorded into `_voltro_workflow_run_steps.input`.
108
108
  * Pass whatever makes the step debuggable in isolation. */
109
109
  readonly input?: unknown;
110
- /** Declarative retry-policy summary recorded into
111
- * `_voltro_workflow_run_steps.retryPolicy`. Pure metadata does
112
- * NOT change retry behavior on its own. Wire actual retries via
113
- * `Effect.retry` inside `execute:` or via `interruptRetryPolicy`
114
- * above; this field tells the dashboard what the intended policy
115
- * looks like so the per-attempt panel can show "attempt 2 of 5,
116
- * exponential 1s base" instead of just "attempt 2". */
110
+ /** Declarative retry policy ENFORCED. The framework compiles it to an
111
+ * Effect `Schedule` and retries `execute` accordingly (backoff, jitter,
112
+ * attempt cap, time budget, error classification, provider `Retry-After`).
113
+ * See {@link StepRetryPolicy}. Retries run INSIDE this one step and are
114
+ * transparent to the durable engine; the step's FINAL outcome is recorded.
115
+ * The serialisable knobs are also stored as `_voltro_workflow_run_steps.
116
+ * retryPolicy` for the dashboard. `retry: { maxAttempts: 5 }` is enough. */
117
117
  readonly retry?: StepRetryPolicy;
118
118
  };
119
119
 
120
120
  /**
121
- * Declarative retry-policy summary. Pure metadata for the dashboard;
122
- * the framework does NOT apply this. Users wire actual retry behavior
123
- * via `Effect.retry(Schedule.*)` inside `execute:` or
124
- * `Activity.make`'s `interruptRetryPolicy`.
125
- *
126
- * Shape is intentionally close to Effect's Schedule combinators so a
127
- * future framework version can auto-translate declarative policies
128
- * into a Schedule + apply it. For now: record + display.
129
- *
130
- * { strategy: 'exponential', maxAttempts: 5, baseDelay: '1 second', maxDelay: '30 seconds' }
131
- * { strategy: 'fixed', maxAttempts: 3, baseDelay: '500 millis' }
132
- * { strategy: 'linear', maxAttempts: 4, baseDelay: '1 second', step: '2 seconds' }
121
+ * A step's retry policy. Every field is optional — `retry: {}` is already a
122
+ * sensible policy (3 attempts, exponential backoff, jittered). All fields are
123
+ * JSON-serialisable EXCEPT `retryable` (a predicate), which the recorder drops
124
+ * when it stores the policy as dashboard metadata.
133
125
  */
134
126
  declare interface StepRetryPolicy {
135
- readonly strategy: 'exponential' | 'fixed' | 'linear';
136
- readonly maxAttempts: number;
137
- /** Effect Duration string, e.g. `'1 second'`, `'500 millis'`. */
127
+ /** Total attempts INCLUDING the first. Default `3`. `1` disables retry. */
128
+ readonly maxAttempts?: number;
129
+ /** Backoff shape between attempts. Default `'exponential'`. */
130
+ readonly strategy?: 'exponential' | 'fixed' | 'linear';
131
+ /** First delay (Effect Duration string, e.g. `'500 millis'`). Default `'200 millis'`. */
138
132
  readonly baseDelay?: string;
139
- /** Cap on exponential / linear growth, e.g. `'30 seconds'`. */
140
- readonly maxDelay?: string;
141
- /** Increment for `linear` strategy. */
133
+ /** Growth factor for `'exponential'`. Default `2`. */
134
+ readonly factor?: number;
135
+ /** Increment per attempt for `'linear'`. Default = `baseDelay`. */
142
136
  readonly step?: string;
143
- /** Free-form note rendered as a chip in the dashboard. Useful when
144
- * the retry behavior is partially implemented in the user's
145
- * Effect chain and the summary is just documentation. */
137
+ /** Ceiling on any single delay, so exponential growth can't run away
138
+ * (e.g. `'30 seconds'`). Applied before jitter. */
139
+ readonly maxDelay?: string;
140
+ /** Full jitter on each delay — spreads retries so a fleet doesn't
141
+ * re-hit a recovering dependency in lockstep. Default `true`. */
142
+ readonly jitter?: boolean;
143
+ /** A total wall-clock BUDGET across all attempts: stop retrying once
144
+ * this much time has elapsed since the first attempt, even if attempts
145
+ * remain (e.g. `'5 minutes'`). A deadline, not a count. */
146
+ readonly maxElapsed?: string;
147
+ /** Retry ONLY failures whose typed-error `_tag` is in this list; every
148
+ * other error fails fast. The declarative "retry transient, fail
149
+ * permanent" — e.g. `['ProviderDown', 'RateLimited']`. */
150
+ readonly retryableErrors?: ReadonlyArray<string>;
151
+ /** Retry-predicate on the raw error (wins over `retryableErrors` when
152
+ * both are set). Not serialised into dashboard metadata. Default:
153
+ * retry every failure. */
154
+ readonly retryable?: (error: unknown) => boolean;
155
+ /** Honor a provider's own backoff: if a retryable error carries a
156
+ * `retryAfterMillis` number (or `retryAfter` in seconds), use exactly that as
157
+ * the delay before the next attempt — REPLACING the computed backoff for
158
+ * that attempt (a 429 `Retry-After`, say). Falls back to the normal backoff
159
+ * when the error carries no hint. Default `false`. */
160
+ readonly respectRetryAfter?: boolean;
161
+ /** Free-form note rendered as a chip in the dashboard. */
146
162
  readonly note?: string;
147
163
  }
148
164
 
@@ -216,6 +232,25 @@ export declare interface WorkflowVersionOptions {
216
232
  readonly version?: string | number;
217
233
  readonly compatibleWith?: ReadonlyArray<string | number>;
218
234
  readonly patches?: ReadonlyArray<string>;
235
+ /**
236
+ * When `true`, a workflow whose top-level body FAILS does not become a
237
+ * terminal `failed` run — it **suspends** with its durable journal intact, so
238
+ * `voltro workflows resume <id>` (or `ctx.workflows.resume`) re-drives it from
239
+ * the point of failure: every completed activity replays from the journal
240
+ * (NOT re-executed) and only the failed activity runs again. This is the
241
+ * durable-execution way to make a workflow recoverable across a transient
242
+ * downstream outage without re-doing prior work.
243
+ *
244
+ * Default `false` — a failure is terminal (the dead-letter state; see
245
+ * `voltro workflows list --dead-letter`). Choose per workflow: `true` for a
246
+ * long multi-step pipeline where re-doing step 1..N-1 is expensive or unsafe;
247
+ * `false` for a short idempotent job where a fresh `retry` is simpler. A
248
+ * suspended-on-failure run shows up under `--status suspended`, NOT in the
249
+ * dead-letter view — it is recoverable, not dead.
250
+ *
251
+ * Maps to `@effect/workflow`'s `SuspendOnFailure` annotation.
252
+ */
253
+ readonly suspendOnFailure?: boolean;
219
254
  }
220
255
 
221
256
  export declare const WorkflowVersionTypeId: unique symbol;
@@ -1,2 +1,2 @@
1
- import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g, v as _, y as v } from "./primitives-CWy1iu5w.js";
1
+ import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g, v as _, y as v } from "./primitives-Dgu3O55Q.js";
2
2
  export { h as WorkflowMessagesProperty, u as WorkflowVersionTypeId, p as WorkflowWorkerLayerTypeId, s as durableClock, t as durableQueue, d as durableQueueModule, m as durableRateLimiterModule, n as getWorkflowVersionMetadata, c as isWorkflowWorkerLayer, g as processQueue, r as queueWorker, i as rateLimit, f as sleep, l as step, o as stepIdempotencyKey, a as stepModule, e as withCompensation, _ as workflow, v as workflowModule };