@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.
@@ -0,0 +1,483 @@
1
+ import { T as e, d as t, f as n, p as r, s as i, t as a, w as o, y as s } from "./journalEncryption--n5VDdYO.js";
2
+ import { Activity as c, Activity as l, DurableClock as u, DurableClock as d, DurableQueue as f, DurableQueue as p, DurableRateLimiter as m, DurableRateLimiter as ee, Workflow as h, Workflow as te, WorkflowEngine as ne } from "@effect/workflow";
3
+ import { Cause as g, Context as _, Cron as re, Duration as v, Effect as y, Either as ie, Exit as b, FiberRef as x, Option as ae, Schedule as S, Schema as C } from "effect";
4
+ import { attachWorkflowAccess as w, resolveWorkflowAccess as oe } from "@voltro/protocol";
5
+ //#region src/preparedExecution.ts
6
+ var T = Symbol.for("@voltro/workflow/PreparedExecution"), E = (e, t) => {
7
+ Object.defineProperty(e, T, {
8
+ value: t,
9
+ enumerable: !0
10
+ });
11
+ }, se = (e) => typeof e == "object" && !!e && typeof e[T] == "function", ce = (e, t, n, r) => y.suspend(() => {
12
+ if (n.length === 0) return y.die(/* @__PURE__ */ Error("Prepared workflow execution requires a non-empty identity"));
13
+ let i = e[T];
14
+ return typeof i == "function" ? i(t, n, r?.discard ?? !0) : y.die(/* @__PURE__ */ Error("Workflow definition has no prepared-execution capability"));
15
+ }), le = (e) => e !== void 0 && (e.debounce !== void 0 || e.singleton !== void 0 || e.concurrency !== void 0 || e.throttle !== void 0 || e.rateLimit !== void 0 || e.batch !== void 0 || e.timeouts?.startMs !== void 0 || e.timeouts?.finishMs !== void 0), ue = (e) => e?.cancelOn !== void 0 && e.cancelOn.length > 0, de = [
16
+ "debounce",
17
+ "batch",
18
+ "throttle",
19
+ "concurrency"
20
+ ], D = (e) => {
21
+ if (e === void 0) return [];
22
+ let t = [];
23
+ return e.debounce !== void 0 && t.push("debounce"), e.batch !== void 0 && t.push("batch"), e.throttle !== void 0 && t.push("throttle"), e.concurrency !== void 0 && t.push("concurrency"), t;
24
+ }, O = (e, t, n) => {
25
+ let r;
26
+ try {
27
+ r = v.toMillis(v.decode(e));
28
+ } catch {
29
+ throw Error(`workflow '${n}': ${t} is not a duration Effect can read (got ${JSON.stringify(e)}). Use a spelled-out unit ('15 minutes', '1 hour', '30 seconds'), a number of milliseconds, or a Duration — the abbreviated forms ('15m', '1h') are NOT accepted.`);
30
+ }
31
+ if (!Number.isFinite(r) || r <= 0) throw Error(`workflow '${n}': ${t} must be a positive finite duration, got ${String(e)}`);
32
+ return r;
33
+ }, k = (e, t, n) => {
34
+ if (!Number.isInteger(e) || e <= 0) throw Error(`workflow '${n}': ${t} must be a positive integer, got ${String(e)}`);
35
+ return e;
36
+ }, A = (e, t, n) => {
37
+ if (typeof e != "function") throw Error(`workflow '${n}': ${t} must be a function of the payload`);
38
+ return e;
39
+ }, fe = (e) => {
40
+ let t = e?.ast?.propertySignatures;
41
+ if (!Array.isArray(t)) return !1;
42
+ let n = t.find((e) => e.name === "items");
43
+ return n !== void 0 && n.type?._tag === "TupleType";
44
+ }, j = (t) => {
45
+ let { workflowName: n, control: r } = t;
46
+ if (r === void 0) return;
47
+ let i = { workflowName: n };
48
+ if (r.debounce !== void 0) {
49
+ let e = r.debounce, t = O(e.period, "debounce.period", n), a = e.timeout === void 0 ? void 0 : O(e.timeout, "debounce.timeout", n);
50
+ if (a !== void 0 && a < t) throw Error(`workflow '${n}': debounce.timeout (${a}ms) is shorter than debounce.period (${t}ms), so the burst can never be collapsed — the run would be admitted before the quiet period could ever elapse. Raise the timeout or lower the period.`);
51
+ i.debounce = {
52
+ key: A(e.key, "debounce.key", n),
53
+ periodMs: t,
54
+ ...a === void 0 ? {} : { timeoutMs: a }
55
+ };
56
+ }
57
+ if (r.singleton !== void 0) {
58
+ let e = r.singleton;
59
+ if (e.mode !== "skip" && e.mode !== "cancel") throw Error(`workflow '${n}': singleton.mode must be 'skip' or 'cancel'. There is no default — 'skip' discards the incoming request and 'cancel' discards the running one, and choosing for you would silently throw away work either way.`);
60
+ i.singleton = {
61
+ key: A(e.key, "singleton.key", n),
62
+ mode: e.mode
63
+ };
64
+ }
65
+ if (r.concurrency !== void 0) {
66
+ let e = r.concurrency;
67
+ if (e.pool !== void 0 && (typeof e.pool != "string" || e.pool.trim() === "")) throw Error(`workflow '${n}': concurrency.pool must be a non-empty name — it is the identity other workflows declare to share this budget, and an empty one would silently merge every empty-named pool in the deployment into one.`);
68
+ i.concurrency = {
69
+ limit: k(e.limit, "concurrency.limit", n),
70
+ ...e.key === void 0 ? {} : { key: A(e.key, "concurrency.key", n) },
71
+ ...e.pool === void 0 ? {} : { pool: e.pool.trim() }
72
+ };
73
+ }
74
+ if (r.throttle !== void 0) {
75
+ let e = r.throttle;
76
+ i.throttle = {
77
+ limit: k(e.limit, "throttle.limit", n),
78
+ periodMs: O(e.period, "throttle.period", n),
79
+ ...e.key === void 0 ? {} : { key: A(e.key, "throttle.key", n) }
80
+ };
81
+ }
82
+ if (r.rateLimit !== void 0) {
83
+ let e = r.rateLimit;
84
+ i.rateLimit = {
85
+ limit: k(e.limit, "rateLimit.limit", n),
86
+ periodMs: O(e.period, "rateLimit.period", n),
87
+ ...e.key === void 0 ? {} : { key: A(e.key, "rateLimit.key", n) }
88
+ };
89
+ }
90
+ if (r.batch !== void 0) {
91
+ let a = r.batch;
92
+ if (t.payloadSchema !== void 0 && !fe(t.payloadSchema)) throw Error(`workflow '${n}': batch is declared, so the workflow's own \`payload\` must be the BATCH shape — \`Schema.Struct({ items: Schema.Array(Item) })\` — while callers start it with a SINGLE item. Declare the item's schema as \`batch.item\`. Checked here rather than at admission because a decode failure on the batching replica is a failure nobody is watching.`);
93
+ i.batch = {
94
+ ...a.key === void 0 ? {} : { key: A(a.key, "batch.key", n) },
95
+ maxSize: k(a.maxSize, "batch.maxSize", n),
96
+ timeoutMs: O(a.timeout, "batch.timeout", n),
97
+ ...a.item === void 0 ? {} : { item: r.encrypted === !0 && C.isSchema(a.item) ? e(a.item, `workflow '${n}' batch item`) : a.item }
98
+ };
99
+ }
100
+ if (r.priority !== void 0) {
101
+ if (typeof r.priority != "function") throw Error(`workflow '${n}': priority must be a function of the payload`);
102
+ i.priority = r.priority;
103
+ }
104
+ if (r.timeouts !== void 0) {
105
+ let e = r.timeouts, t = e.start === void 0 ? void 0 : O(e.start, "timeouts.start", n), a = e.finish === void 0 ? void 0 : O(e.finish, "timeouts.finish", n);
106
+ (t !== void 0 || a !== void 0) && (i.timeouts = {
107
+ ...t === void 0 ? {} : { startMs: t },
108
+ ...a === void 0 ? {} : { finishMs: a }
109
+ });
110
+ }
111
+ if (i.debounce !== void 0 && i.batch !== void 0) throw Error(`workflow '${n}': debounce and batch cannot both be declared. Both collect starts and admit one run, but debounce's timer RESETS on every arrival (collapse a burst, latest payload wins) while batch's does not (accumulate items until maxSize or timeout). Pick the one you mean.`);
112
+ if (i.throttle !== void 0 && i.rateLimit !== void 0) throw Error(`workflow '${n}': throttle and rateLimit cannot both be declared. throttle QUEUES the excess and rateLimit DROPS it; declaring both means the drop fires first and the queue is never reached. Pick whether surplus work is late or gone.`);
113
+ if (r.onFailure !== void 0) {
114
+ if (typeof r.onFailure != "string" || r.onFailure.length === 0) throw Error(`workflow '${n}': onFailure must be the NAME of another workflow. A function cannot be used — the failure may be noticed by a different replica, minutes later, after the process that held the closure is gone.`);
115
+ if (r.onFailure === n) throw Error(`workflow '${n}': onFailure points at itself. A workflow that fails would then start another copy of itself, which would fail, forever — one run per failure until something else breaks. Point it at a separate notifier workflow.`);
116
+ i.onFailure = r.onFailure;
117
+ }
118
+ if (r.encrypted === !0 && (i.encrypted = !0), r.cancelOn !== void 0) {
119
+ if (!Array.isArray(r.cancelOn)) throw Error(`workflow '${n}': cancelOn must be an array of { event, schema, match }`);
120
+ let e = r.cancelOn.map((e, t) => {
121
+ let r = `cancelOn[${t}]`;
122
+ if (typeof e?.event != "string" || e.event.length === 0) throw Error(`workflow '${n}': ${r}.event must be a non-empty event name, exactly as ctx.events.publish writes it.`);
123
+ if (e.schema === void 0 || typeof e.schema.ast != "object") throw Error(`workflow '${n}': ${r}.schema must be a Schema for the event payload. It is decoded before \`match\` runs, so the predicate reads the domain type rather than raw JSON.`);
124
+ if (typeof e.match != "function") throw Error(`workflow '${n}': ${r}.match is required. Omitting it would mean "cancel every live run of this workflow whenever '${e.event}' is published" — write \`match: () => true\` if that is what you mean, so it is visible in review.`);
125
+ let i = e.within === void 0 ? void 0 : O(e.within, `${r}.within`, n);
126
+ return {
127
+ event: e.event,
128
+ schema: e.schema,
129
+ match: e.match,
130
+ ...i === void 0 ? {} : { withinMs: i },
131
+ reason: e.reason ?? `cancelOn:${e.event}`
132
+ };
133
+ });
134
+ e.length > 0 && (i.cancelOn = e);
135
+ }
136
+ if (i.timeouts?.startMs !== void 0 && D(i).length === 0) throw Error(`workflow '${n}': timeouts.start bounds how long a run may sit in the ADMISSION queue, but this workflow declares no control that can defer one (debounce, batch, throttle, concurrency). It would never fire. Use timeouts.finish to bound the run itself.`);
137
+ return i;
138
+ }, pe = (e) => {
139
+ let t = /* @__PURE__ */ new Map();
140
+ for (let n of e) {
141
+ let e = n?.concurrency;
142
+ if (e?.pool === void 0) continue;
143
+ let r = t.get(e.pool);
144
+ if (r === void 0) t.set(e.pool, {
145
+ limit: e.limit,
146
+ workflows: [n.workflowName]
147
+ });
148
+ else if (r.limit !== e.limit) throw Error(`concurrency pool '${e.pool}': '${n.workflowName}' declares limit ${e.limit}, but ${r.workflows.map((e) => `'${e}'`).join(", ")} declare${r.workflows.length === 1 ? "s" : ""} limit ${r.limit}. A pool is ONE budget — every member must declare the same number, or the enforced limit would depend on which workflow's start arrived first. Align the limits, or give the workflows different pool names if they were never meant to share.`);
149
+ else r.workflows.push(n.workflowName);
150
+ }
151
+ }, M = class extends _.Tag("@voltro/WorkflowRunRecorder")() {}, N = x.unsafeMake(void 0), P = x.unsafeMake(void 0), F = x.unsafeMake(void 0), me = y.gen(function* () {
152
+ let e = yield* x.get(P), t = yield* x.get(F);
153
+ return e === void 0 || t === void 0 ? void 0 : {
154
+ executionId: e,
155
+ step: t
156
+ };
157
+ }), he = x.unsafeMake(void 0), I = x.unsafeMake(void 0), L = x.unsafeMake([]), R = () => x.get(N), ge = () => x.get(P), z = class extends _.Tag("@voltro/WorkflowStepInterceptor")() {}, B = 864e5, _e = /* @__PURE__ */ new Set([
158
+ "skip",
159
+ "buffer",
160
+ "cancelOther"
161
+ ]), ve = /* @__PURE__ */ new Set([
162
+ "skip",
163
+ "latest",
164
+ "all"
165
+ ]), V = (e, t) => {
166
+ if (t === void 0) return;
167
+ if (typeof t.timezone != "string" || t.timezone.trim() === "") throw Error(`workflow '${e}': schedule.timezone is required (e.g. "UTC", "Europe/Berlin") — never server-local, which differs between your machine and the container.`);
168
+ let n = re.parse(t.cron, t.timezone);
169
+ if (ie.isLeft(n)) throw Error(`workflow '${e}': schedule.cron "${t.cron}" (tz="${t.timezone}") is not a valid cron expression — ${String(n.left)}`);
170
+ let r = t.onOverlap ?? "skip";
171
+ if (!_e.has(r)) throw Error(`workflow '${e}': schedule.onOverlap must be 'skip' | 'buffer' | 'cancelOther', got ${JSON.stringify(r)}`);
172
+ let i = t.backfill ?? "skip";
173
+ if (!ve.has(i)) throw Error(`workflow '${e}': schedule.backfill must be 'skip' | 'latest' | 'all', got ${JSON.stringify(i)}`);
174
+ let a = B;
175
+ return t.maxRuntime !== void 0 && (a = ye(t.maxRuntime, e)), {
176
+ cron: t.cron,
177
+ timezone: t.timezone,
178
+ ...t.payload === void 0 ? {} : { payload: t.payload },
179
+ onOverlap: r,
180
+ backfill: i,
181
+ maxRuntimeMs: a
182
+ };
183
+ }, ye = (e, t) => {
184
+ let n;
185
+ try {
186
+ n = v.toMillis(v.decode(e));
187
+ } catch {
188
+ throw Error(`workflow '${t}': schedule.maxRuntime is not a duration Effect can read (got ${JSON.stringify(e)}). Use '2 hours', a number of milliseconds, or a Duration.`);
189
+ }
190
+ if (!Number.isFinite(n) || n <= 0) throw Error(`workflow '${t}': schedule.maxRuntime must be a positive finite duration`);
191
+ return n;
192
+ }, H = "__voltroWorkflowSchedule", be = (e) => {
193
+ if (typeof e != "object" || !e) return;
194
+ let t = e[H];
195
+ return t === void 0 ? void 0 : t;
196
+ }, xe = (e) => {
197
+ let t = /* @__PURE__ */ new Map();
198
+ for (let n of e) t.set(n.stepName, (t.get(n.stepName) ?? 0) + 1);
199
+ let n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Set(), i = (e) => !r.has(e) && (r.add(e), !0);
200
+ return {
201
+ reach: (e) => {
202
+ let r = (n.get(e) ?? 0) + 1;
203
+ n.set(e, r);
204
+ let a = t.get(e) ?? 0;
205
+ if (a !== 0 && !(r <= a) && i(`extra:${e}`)) return {
206
+ kind: "extra-step-occurrence",
207
+ stepName: e,
208
+ recorded: a,
209
+ reached: r,
210
+ detail: `step '${e}' was reached ${r} time(s) on this attempt but this run only ever recorded ${a} — the body may have changed how often it runs this step (a loop bound), so the journal replays a result for an iteration the code no longer means.`
211
+ };
212
+ },
213
+ settle: () => {
214
+ let e = [];
215
+ for (let [r, a] of t) (n.get(r) ?? 0) > 0 || i(`unreached:${r}`) && e.push({
216
+ kind: "unreached-step",
217
+ stepName: r,
218
+ recorded: a,
219
+ reached: 0,
220
+ detail: `step '${r}' ran on an earlier attempt of this run but the current code never reaches it — the step was renamed, removed, or moved behind a branch, so its journaled result is orphaned and any step that replaced it executed fresh.`
221
+ });
222
+ return e;
223
+ }
224
+ };
225
+ }, U = x.unsafeMake(void 0), W = (e) => ({
226
+ kind: e.kind,
227
+ stepName: e.stepName,
228
+ recorded: e.recorded,
229
+ reached: e.reached,
230
+ detail: e.detail
231
+ }), G = (e) => y.gen(function* () {
232
+ if (e.length === 0) return;
233
+ let t = yield* x.get(N), n = yield* y.serviceOption(M);
234
+ if (t === void 0 || n._tag !== "Some") return;
235
+ let r = n.value;
236
+ for (let n of e) yield* y.promise(() => r.recordEvent({
237
+ runId: t,
238
+ eventType: "nondeterminism-suspected",
239
+ payload: W(n)
240
+ }).then(() => void 0).catch(() => void 0));
241
+ }), Se = (e) => {
242
+ if (typeof e != "object" || !e) return;
243
+ let t = e.retryAfterMillis;
244
+ if (typeof t == "number" && Number.isFinite(t) && t >= 0) return v.millis(t);
245
+ let n = e.retryAfter;
246
+ if (typeof n == "number" && Number.isFinite(n) && n >= 0) return v.seconds(n);
247
+ }, K = (e) => v.decode(e), Ce = (e) => typeof e == "object" && e && "_tag" in e ? String(e._tag) : void 0, we = (e) => e !== void 0 && (e.maxAttempts ?? 3) > 1, Te = (e) => {
248
+ if (e.retryable) return e.retryable;
249
+ if (e.retryableErrors && e.retryableErrors.length > 0) {
250
+ let t = new Set(e.retryableErrors);
251
+ return (e) => {
252
+ let n = Ce(e);
253
+ return n !== void 0 && t.has(n);
254
+ };
255
+ }
256
+ return () => !0;
257
+ }, Ee = (e) => {
258
+ let t = e.baseDelay ?? "200 millis", n = e.strategy ?? "exponential", r = e.maxAttempts ?? 3, i = n === "linear" ? S.linear(K(e.step ?? t)) : n === "fixed" ? S.spaced(K(t)) : S.exponential(K(t), e.factor ?? 2);
259
+ if (e.maxDelay !== void 0) {
260
+ let t = K(e.maxDelay);
261
+ i = S.modifyDelay(i, (e, n) => v.min(n, t));
262
+ }
263
+ (e.jitter ?? !0) === !0 && (i = S.jittered(i));
264
+ let a = S.intersect(i, S.recurs(Math.max(0, r - 1)));
265
+ return e.maxElapsed !== void 0 && (a = S.upTo(K(e.maxElapsed))(a)), a;
266
+ }, De = (e, t) => {
267
+ let n = v.toMillis(K(e.baseDelay ?? "200 millis")), r = e.strategy ?? "exponential", i = r === "fixed" ? n : r === "linear" ? n + v.toMillis(K(e.step ?? e.baseDelay ?? "200 millis")) * (t - 1) : n * (e.factor ?? 2) ** (t - 1);
268
+ return e.maxDelay !== void 0 && (i = Math.min(i, v.toMillis(K(e.maxDelay)))), (e.jitter ?? !0) === !0 && (i *= Math.random()), i;
269
+ }, Oe = (e, t, n, r) => y.gen(function* () {
270
+ let i = t.maxAttempts ?? 3, a = t.maxElapsed === void 0 ? Infinity : v.toMillis(K(t.maxElapsed)), o = yield* y.sync(() => Date.now()), s = 0;
271
+ for (;;) {
272
+ let c = yield* y.exit(e);
273
+ if (b.isSuccess(c)) return c.value;
274
+ let l = c.cause, u = g.failureOption(l), d = ae.isSome(u) ? u.value : g.squash(l);
275
+ s += 1;
276
+ let f = (yield* y.sync(() => Date.now())) - o;
277
+ if (s >= i || !n(d) || f >= a) return yield* y.failCause(l);
278
+ r?.(d);
279
+ let p = Se(d), m = p === void 0 ? De(t, s) : v.toMillis(p);
280
+ yield* y.sleep(v.millis(m));
281
+ }
282
+ }), ke = (e, t, n) => {
283
+ if (!we(t)) return e;
284
+ let r = Te(t);
285
+ if (t.respectRetryAfter === !0) return Oe(e, t, r, n);
286
+ let i = Ee(t), a = n ? y.tapError(e, (e) => y.sync(() => {
287
+ r(e) && n(e);
288
+ })) : e;
289
+ return y.retry(a, {
290
+ schedule: i,
291
+ while: r
292
+ });
293
+ }, q = Symbol.for("voltro.workflow.workerLayer"), J = Symbol.for("voltro.workflow.version"), Y = "__voltroWorkflowMessages", X = "__voltroWorkflowFlowControl", Ae = (e) => typeof e == "object" && !!e && e[q] === !0, je = (e) => typeof e == "object" && e && e[J] !== void 0 ? e[J] : {
294
+ version: "1",
295
+ compatibleWith: ["1"],
296
+ patches: []
297
+ }, Z = (e) => {
298
+ let t = e, n = `workflow '${e.name}' defect`;
299
+ return Object.assign(Object.create(Object.getPrototypeOf(e)), e, {
300
+ toLayer: (e) => t.toLayer((t, r) => y.locally(e(t, r), a, !0).pipe(y.catchAllDefect((e) => y.die(s(e, n))))),
301
+ execute: (...e) => t.execute(...e).pipe(y.catchAllDefect((e) => y.die(r(e, n)))),
302
+ annotate: (...e) => Z(t.annotate(...e)),
303
+ annotateContext: (...e) => Z(t.annotateContext(...e))
304
+ });
305
+ }, Me = ((t) => {
306
+ let r = t.encrypted === !0, i = h.make(r ? {
307
+ ...t,
308
+ payload: o(t.name, t.payload),
309
+ ...t.success === void 0 ? {} : { success: e(t.success, `workflow '${t.name}' result`) },
310
+ ...t.error === void 0 ? {} : { error: e(t.error, `workflow '${t.name}' failure`) }
311
+ } : t);
312
+ r && n(t.name);
313
+ let a = t.suspendOnFailure === !0 ? i.annotate(h.SuspendOnFailure, !0) : i, s = r ? Z(a) : a;
314
+ E(s, (e, n, r) => y.gen(function* () {
315
+ let i = s.payloadSchema.make(e), a = yield* ne.WorkflowEngine;
316
+ return yield* y.annotateCurrentSpan({ executionId: n }), yield* a.execute(s, {
317
+ executionId: n,
318
+ payload: i,
319
+ discard: r,
320
+ suspendedRetrySchedule: t.suspendedRetrySchedule
321
+ });
322
+ }).pipe(y.withSpan(`${t.name}.execute`, { captureStackTrace: !1 })));
323
+ let c = String(t.version ?? "1"), l = {
324
+ version: c,
325
+ compatibleWith: (t.compatibleWith ?? [c]).map(String),
326
+ patches: [...t.patches ?? []]
327
+ }, u = {
328
+ signals: { ...t.messages?.signals ?? {} },
329
+ updates: { ...t.messages?.updates ?? {} }
330
+ };
331
+ Object.defineProperty(s, J, {
332
+ value: l,
333
+ enumerable: !1
334
+ }), Object.defineProperty(s, Y, {
335
+ value: u,
336
+ enumerable: !1
337
+ }), w(s, oe(t.name, t));
338
+ let d = j({
339
+ workflowName: t.name,
340
+ payloadSchema: C.isSchema(t.payload) ? t.payload : C.Struct(t.payload),
341
+ control: t
342
+ });
343
+ d !== void 0 && Object.defineProperty(s, X, {
344
+ value: d,
345
+ enumerable: !1
346
+ });
347
+ let f = V(t.name, t.schedule);
348
+ return f !== void 0 && Object.defineProperty(s, H, {
349
+ value: f,
350
+ enumerable: !1
351
+ }), s;
352
+ }), Ne = (e) => {
353
+ if (typeof e != "object" || !e) return;
354
+ let t = e[X];
355
+ return t === void 0 ? void 0 : t;
356
+ }, Q = ((n) => {
357
+ if (n.encrypted !== !0) return f.make(n);
358
+ t(n.name);
359
+ let r = C.isSchema(n.payload) ? n.payload : C.Struct(n.payload);
360
+ return f.make({
361
+ ...n,
362
+ payload: e(r, `queue '${n.name}' payload`),
363
+ success: e(n.success ?? C.Void, `queue '${n.name}' result`),
364
+ error: e(n.error ?? C.Never, `queue '${n.name}' failure`)
365
+ });
366
+ }), Pe = ((e, ...t) => y.flatMap(x.get(a), (n) => n && !i(e.name) ? y.die(/* @__PURE__ */ Error(`an encrypted workflow enqueued into durable queue '${e.name}', which is not encrypted: the queue's journal would hold the value in plaintext. Declare \`durableQueue({ …, encrypted: true })\`.`)) : f.process(e, ...t))), Fe = ((...e) => {
367
+ let t = f.worker(...e);
368
+ return Object.defineProperty(t, q, {
369
+ value: !0,
370
+ enumerable: !1
371
+ }), t;
372
+ }), Ie = m.rateLimit, $ = (e) => y.gen(function* () {
373
+ let t = yield* N, n = yield* y.serviceOption(M), r = v.toMillis(v.decode(e.duration));
374
+ if (t !== void 0 && n._tag === "Some") {
375
+ let i = n.value, a = new Date(Date.now() + r).toISOString(), o = yield* I;
376
+ yield* y.promise(() => i.recordEvent({
377
+ runId: t,
378
+ eventType: "timer-set",
379
+ payload: {
380
+ name: e.name,
381
+ durationMs: r,
382
+ scheduledWakeAt: a,
383
+ ...o === void 0 ? {} : { tenantId: o }
384
+ }
385
+ }).catch(() => {}));
386
+ let s = Date.now();
387
+ yield* u.sleep(e), yield* y.promise(() => i.recordEvent({
388
+ runId: t,
389
+ eventType: "timer-fired",
390
+ payload: {
391
+ name: e.name,
392
+ actualDurationMs: Date.now() - s,
393
+ ...o === void 0 ? {} : { tenantId: o }
394
+ }
395
+ }).catch(() => {}));
396
+ return;
397
+ }
398
+ yield* u.sleep(e);
399
+ }), Le = (e) => y.gen(function* () {
400
+ let t = (yield* c.make({
401
+ name: `sleepUntil:${e.name}`,
402
+ success: C.Number,
403
+ execute: y.sync(() => e.until instanceof Date ? e.until.getTime() : e.until)
404
+ })) - Date.now();
405
+ t <= 0 || (yield* $({
406
+ name: e.name,
407
+ duration: `${t} millis`
408
+ }));
409
+ }), Re = (e, t, n, r) => y.gen(function* () {
410
+ let i = yield* N, o = yield* c.CurrentAttempt, s = yield* y.serviceOption(M), l = yield* y.serviceOption(z), u = l._tag === "Some" ? l.value.interceptor(t, {
411
+ runId: i ?? "unrecorded",
412
+ stepName: e,
413
+ attempt: o
414
+ }) : t, d, f, p = !1;
415
+ if (i !== void 0 && s._tag === "Some") {
416
+ f = s.value;
417
+ let t = r === void 0 ? void 0 : (({ retryable: e, ...t }) => t)(r);
418
+ p = yield* x.get(a);
419
+ try {
420
+ d = yield* y.promise(() => f.startStep({
421
+ runId: i,
422
+ stepName: e,
423
+ attempt: o,
424
+ stepInput: n,
425
+ sealed: p,
426
+ ...t === void 0 ? {} : { retryPolicy: t }
427
+ }));
428
+ } catch {
429
+ d = void 0;
430
+ }
431
+ }
432
+ let m = Date.now();
433
+ return yield* y.locally(ke(u, r), F, e).pipe(y.tap((e) => d && f ? y.promise(async () => {
434
+ try {
435
+ await f.endStepSuccess({
436
+ stepRecordId: d,
437
+ sealed: p,
438
+ output: e,
439
+ durationMs: Date.now() - m
440
+ });
441
+ } catch {}
442
+ }) : y.void), y.tapErrorCause((e) => d && f && !g.isInterruptedOnly(e) ? y.promise(async () => {
443
+ try {
444
+ let t = g.failureOption(e), n = Array.from(g.failures(e)), r = Array.from(g.defects(e)), i = t._tag === "Some" ? t.value : r[0] ?? g.squash(e), a = i?.message ?? String(i), o = i && typeof i == "object" && "_tag" in i ? String(i._tag) : null;
445
+ await f.endStepFailure({
446
+ stepRecordId: d,
447
+ sealed: p,
448
+ errorTag: o,
449
+ errorMessage: a,
450
+ errorCause: {
451
+ pretty: g.pretty(e),
452
+ failures: n,
453
+ defects: r
454
+ },
455
+ durationMs: Date.now() - m
456
+ });
457
+ } catch {}
458
+ }) : y.void));
459
+ }), ze = (e) => y.gen(function* () {
460
+ let t = yield* x.get(U);
461
+ if (t === void 0) return;
462
+ let n = t.reach(e);
463
+ n !== void 0 && (yield* G([n]));
464
+ }), Be = (e, t, n) => {
465
+ let i = e;
466
+ return {
467
+ ...e,
468
+ commit: () => y.zipRight(ze(t), n === void 0 ? i : y.flatMap(x.get(a), (e) => e ? n().pipe(y.catchAllDefect((e) => y.die(r(e, `step '${t}' defect`)))) : i))
469
+ };
470
+ }, Ve = (t) => {
471
+ let { input: n, retry: r, ...i } = t, a = Re(i.name, i.execute, n, r), o = c.make({
472
+ ...i,
473
+ execute: a
474
+ }), l, u = `step '${i.name}' defect`;
475
+ return Be(o, i.name, () => l ??= c.make({
476
+ ...i,
477
+ success: e(i.success ?? C.Void, `step '${i.name}' result`),
478
+ error: e(i.error ?? C.Never, `step '${i.name}' failure`),
479
+ execute: a.pipe(y.catchAllDefect((e) => y.die(s(e, u))))
480
+ }));
481
+ }, He = (e) => y.map(x.get(L), (t) => t.includes(e)), Ue = h.withCompensation, We = c.idempotencyKey;
482
+ //#endregion
483
+ export { be as A, me as B, te as C, G as D, W as E, N as F, D as G, R as H, F as I, j as J, ue as K, I as L, P as M, he as N, B as O, L as P, M as R, Me as S, xe as T, de as U, ge as V, pe as W, ce as X, E as Y, se as Z, Le as _, d as a, l as b, ee as c, Ae as d, He as f, $ as g, Ie as h, q as i, V as j, H as k, Ne as l, Fe as m, Y as n, Q as o, Pe as p, le as q, J as r, p as s, X as t, je as u, Ve as v, U as w, Ue as x, We as y, z };
@@ -65,7 +65,9 @@ export declare const deferringControlsOf: (control: ResolvedFlowControl | undefi
65
65
  export { durableClock }
66
66
 
67
67
  /** Define a durable queue for concurrency-controlled side work. */
68
- export declare const durableQueue: typeof durableQueueModule.make;
68
+ export declare const durableQueue: typeof durableQueueModule.make & (<Options extends Parameters<typeof durableQueueModule.make>[0] & {
69
+ readonly encrypted?: boolean;
70
+ }>(options: Options) => ReturnType<typeof durableQueueModule.make>);
69
71
 
70
72
  export { durableQueueModule }
71
73
 
@@ -194,7 +196,13 @@ export declare const patch: (id: string) => Effect.Effect<boolean>;
194
196
  /** The SCHEMA a payload input denotes — `Fields` lifted into a `Struct`. */
195
197
  declare type PayloadSchemaOf<P> = P extends Schema.Struct.Fields ? Schema.Struct<P> : P;
196
198
 
197
- /** Enqueue work and await the durable queue worker's result. */
199
+ /**
200
+ * Enqueue work and await the durable queue worker's result.
201
+ *
202
+ * From inside an encrypted workflow the queue must be encrypted too — its
203
+ * journal would otherwise hold the workflow's data in plaintext one hop away,
204
+ * the same reason an encrypted workflow's `onFailure` handler must be.
205
+ */
198
206
  export declare const processQueue: typeof durableQueueModule.process;
199
207
 
200
208
  /** Start a durable queue worker layer with bounded concurrency.
@@ -306,7 +314,7 @@ export declare interface ResolvedFlowControl {
306
314
  readonly finishMs?: number;
307
315
  };
308
316
  readonly onFailure?: string;
309
- readonly encryptSteps?: boolean;
317
+ readonly encrypted?: boolean;
310
318
  readonly cancelOn?: ReadonlyArray<ResolvedCancelOn>;
311
319
  }
312
320
 
@@ -508,58 +516,6 @@ export declare const validateWorkflowSchedule: (workflowName: string, declaratio
508
516
  */
509
517
  export declare const withCompensation: typeof workflowModule.withCompensation;
510
518
 
511
- /**
512
- * Define a durable workflow.
513
- *
514
- * ── `idempotencyKey` is the execution's IDENTITY, permanently ──────────────
515
- *
516
- * Read this before choosing one. It is the single most expensive
517
- * misunderstanding this API has produced — a downstream project lost a day and
518
- * shipped a design around the wrong model, and the wording they read
519
- * ("deduplicates concurrent invocations with the same input") is what led them
520
- * there. It sounds like a WINDOW: dedupe while a run is in flight. It is not.
521
- *
522
- * The key IS the execution. A second start with the same key REPLAYS the first
523
- * run's journaled result — forever, not just while it is running. Once the run
524
- * completes the key is SPENT: a later, genuinely new invocation under that key
525
- * is a silent no-op that returns the old output. Nothing errors and nothing
526
- * logs, because from the engine's point of view you asked for a run it already
527
- * has.
528
- *
529
- * ```ts
530
- * const a = yield* wf.execute({ id: 'same' })
531
- * const b = yield* wf.execute({ id: 'same' }) // ← does NOT run; replays `a`
532
- * ```
533
- *
534
- * So a key must be **unique per unit of work you want to happen**, not per
535
- * logical subject. `tour:${rowId}` is wrong if the tour can be re-narrated;
536
- * `tour:${rowId}:${updatedAt}` is right, because every edit is a new unit of
537
- * work with its own identity.
538
- *
539
- * ── And a flow-control key is a DIFFERENT thing ────────────────────────────
540
- *
541
- * The two get conflated, and conflating them is what makes "I need to re-arm a
542
- * key" feel like a missing feature. It is not missing; it is two fields:
543
- *
544
- * • `idempotencyKey` — the execution's identity. Vary it per unit of work.
545
- * • `debounce.key` / `singleton.key` / `concurrency.key` — the RESOURCE runs
546
- * compete for. Keep it stable.
547
- *
548
- * "One job, fifteen minutes after the last edit, latest state wins" is then:
549
- *
550
- * ```ts
551
- * workflow({
552
- * name: 'tourNarration',
553
- * payload: TourPayload,
554
- * idempotencyKey: ({ rowId, editedAt }) => `tour:${rowId}:${editedAt}`, // varies
555
- * debounce: { key: (p) => `tour:${p.rowId}`, period: '15 minutes' }, // stable
556
- * })
557
- * ```
558
- *
559
- * Twenty edits mint twenty identities; exactly one is ever admitted. There is
560
- * no `restart: true` and no generation counter in this API because separating
561
- * the two keys is the mechanism those would have been.
562
- */
563
519
  export declare const workflow: WorkflowFn;
564
520
 
565
521
  declare type WorkflowBaseOptions = Parameters<WorkflowMake>[0];
@@ -792,27 +748,27 @@ export declare interface WorkflowFlowControl<Payload> {
792
748
  */
793
749
  readonly onFailure?: string;
794
750
  /**
795
- * Encrypt this workflow's journaled step `input` / `output` / `errorCause`.
751
+ * Encrypt every durable copy of this workflow's data.
796
752
  *
797
- * `step({ input })` is written to `_voltro_workflow_run_steps` and rendered in
798
- * the dashboard — a feature, and the reason people pass rich input. For a step
799
- * carrying personal data it is also a SECOND COPY, outside the `.encrypted()`
800
- * boundary the governance plugin establishes for tables. An app that carefully
801
- * encrypted a column and then handed the same value to a step has it in
802
- * plaintext one table over.
753
+ * Covered: the payload, the result and a typed failure in the engine journal
754
+ * (`cluster_messages`, `cluster_replies`), every step's result and typed
755
+ * failure there, the run row (`_voltro_workflow_runs.payload` / `.output`),
756
+ * the step journal (`_voltro_workflow_run_steps`), a queued start's payload
757
+ * and a failure report's copy of the payload. The body sees the values it was
758
+ * given, and a run replays unchanged.
803
759
  *
804
- * Reuses the cipher `governancePlugin({ fieldEncryption })` already registers,
805
- * so there is ONE key and one rotation story rather than a second scheme that
806
- * only workflows know about. It is therefore a BOOT ERROR to declare this
807
- * without that plugin configured: silently falling back to plaintext would be
808
- * the worst outcome available — the declaration reads as protection and the
809
- * rows are readable.
760
+ * Not covered, and said here so it is not assumed: the `idempotencyKey`
761
+ * (hashed into the execution id, but a primary key built from free text is
762
+ * still that text in the engine's key column — derive it from ids), signal
763
+ * and update payloads, and anything a step writes elsewhere itself.
810
764
  *
811
- * Only the journal is affected. The values the workflow BODY sees are
812
- * unchanged, and the durable execution journal `@effect/workflow` keeps is
813
- * untouched (encrypting that would break replay).
765
+ * Reuses the cipher `governancePlugin({ fieldEncryption })` registers — one
766
+ * key, one rotation story. Declaring it without that plugin configured is a
767
+ * BOOT ERROR: a plaintext fallback would leave the declaration reading as
768
+ * protection while the rows are readable. A plaintext value written before
769
+ * the declaration (a run already in flight) is still read.
814
770
  */
815
- readonly encryptSteps?: boolean;
771
+ readonly encrypted?: boolean;
816
772
  }
817
773
 
818
774
  /** Where the RESOLVED flow-control declaration hangs off a definition. A
@@ -1,2 +1,2 @@
1
- import { A as e, C as t, D as n, E as r, G as i, J as a, K as o, O as s, S as c, T as l, U as u, W as d, _ as f, a as p, b as m, c as h, d as g, f as _, g as v, h as y, i as b, j as x, k as S, l as C, m as w, n as T, o as E, p as D, q as O, r as k, s as A, t as j, u as M, v as N, w as P, x as F, y as I } from "./primitives-BJBn8tiH.js";
1
+ import { A as e, C as t, D as n, E as r, G as i, J as a, K as o, O as s, S as c, T as l, U as u, W as d, _ as f, a as p, b as m, c as h, d as g, f as _, g as v, h as y, i as b, j as x, k as S, l as C, m as w, n as T, o as E, p as D, q as O, r as k, s as A, t as j, u as M, v as N, w as P, x as F, y as I } from "./primitives-Btjhjm8P.js";
2
2
  export { P as CurrentWorkflowReplayShape, s as DEFAULT_WORKFLOW_SCHEDULE_MAX_RUNTIME_MS, u as DEFERRING_CONTROLS, j as WorkflowFlowControlProperty, T as WorkflowMessagesProperty, S as WorkflowScheduleProperty, k as WorkflowVersionTypeId, b as WorkflowWorkerLayerTypeId, d as assertConsistentPools, i as deferringControlsOf, p as durableClock, E as durableQueue, A as durableQueueModule, h as durableRateLimiterModule, C as getWorkflowFlowControl, e as getWorkflowSchedule, M as getWorkflowVersionMetadata, o as hasCancelOn, O as hasFlowControl, g as isWorkflowWorkerLayer, l as makeReplayShapeTracker, r as nondeterminismEventPayload, _ as patch, D as processQueue, w as queueWorker, y as rateLimit, n as reportNondeterminism, v as sleep, f as sleepUntil, N as step, I as stepIdempotencyKey, m as stepModule, a as validateFlowControl, x as validateWorkflowSchedule, F as withCompensation, c as workflow, t as workflowModule };