@voltro/workflow 0.85.0 → 0.86.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +666 -0
  2. package/THIRD-PARTY-NOTICES.md +5 -33
  3. package/dist/admission.js +140 -0
  4. package/dist/admissionDrainer.js +705 -0
  5. package/dist/admissionGate.js +115 -0
  6. package/dist/admissionStore.js +347 -0
  7. package/dist/admissionSubmission.js +297 -0
  8. package/dist/admissionTables.js +137 -0
  9. package/dist/admissionTransaction.js +21 -0
  10. package/dist/awaitEvent.js +82 -0
  11. package/dist/budgetHold.js +136 -0
  12. package/dist/cancelOnPlan.js +75 -0
  13. package/dist/cancelOnSweep.js +188 -0
  14. package/dist/cancelPendingStart.js +50 -0
  15. package/dist/cluster.js +8 -2
  16. package/dist/clusterCron.js +11 -0
  17. package/dist/clusterDiagnostics.js +20 -0
  18. package/dist/clusterDiagnosticsSql.js +145 -0
  19. package/dist/clusterLayer.js +110 -0
  20. package/dist/clusterMigrationGate.js +61 -0
  21. package/dist/clusterRunnerLayer.js +21 -0
  22. package/dist/clusterTestSuite.js +101 -101
  23. package/dist/controlMonitor.js +13 -0
  24. package/dist/controlRequest.js +367 -0
  25. package/dist/executionRefusal.js +162 -0
  26. package/dist/failureHandoff.js +183 -0
  27. package/dist/failureMessage.js +12 -0
  28. package/dist/failureRecovery.js +50 -0
  29. package/dist/failureReportStore.js +60 -0
  30. package/dist/flowControl.js +142 -0
  31. package/dist/forwardStartReceipts.js +41 -0
  32. package/dist/inMemoryLayer.js +7 -0
  33. package/dist/inMemoryRecorder.js +56 -0
  34. package/dist/index.d.ts +82 -8
  35. package/dist/index.js +49 -5
  36. package/dist/inspectWorkflow.js +101 -0
  37. package/dist/{journalEncryption-DTyhUzaS.js → journalEncryption.js} +30 -37
  38. package/dist/messages.js +92 -0
  39. package/dist/nondeterminism.js +51 -0
  40. package/dist/parentClosure.js +141 -0
  41. package/dist/parentClosureRecovery.js +54 -0
  42. package/dist/pendingSubmission.js +85 -0
  43. package/dist/prepareSubmissionDispatch.js +17 -0
  44. package/dist/preparedExecution.js +14 -0
  45. package/dist/primitives.js +225 -2
  46. package/dist/queuedCancellationRequest.js +364 -0
  47. package/dist/queuedControl.js +21 -0
  48. package/dist/recorder.js +11 -0
  49. package/dist/residentExecutionEvidence.js +64 -0
  50. package/dist/retryPolicy.js +57 -0
  51. package/dist/runEventsTable.js +17 -0
  52. package/dist/runGeneration.js +345 -0
  53. package/dist/runRecording.js +615 -0
  54. package/dist/runStepsTable.js +28 -0
  55. package/dist/runnerStorageGuard.js +29 -0
  56. package/dist/runsTable.js +42 -0
  57. package/dist/scheduleDeclaration.js +44 -0
  58. package/dist/signalHint.js +23 -0
  59. package/dist/signals.js +70 -0
  60. package/dist/signalsSuspending.js +57 -0
  61. package/dist/sqliteWorkflowOwnership.js +26 -0
  62. package/dist/stalenessSweep.js +191 -0
  63. package/dist/startContextsTable.js +20 -0
  64. package/dist/startReceipt.js +143 -0
  65. package/dist/submissionRecovery.js +122 -0
  66. package/dist/submissionRejection.js +10 -0
  67. package/dist/submissionRetry.js +71 -0
  68. package/dist/suspensionUnavailable.js +9 -0
  69. package/dist/updates.js +87 -0
  70. package/dist/watermarkTable.js +9 -0
  71. package/dist/webCrypto.js +17 -0
  72. package/dist/workflowDelivery.js +24 -0
  73. package/dist/workflowJournal.js +124 -0
  74. package/dist/workflowJournalReset.js +111 -0
  75. package/dist/workflowReadSelections.js +12 -0
  76. package/dist/workflowRestartDispatch.js +29 -0
  77. package/dist/workflowRestartObservation.js +60 -0
  78. package/dist/workflowRestartPreparation.js +109 -0
  79. package/dist/workflowRestartReservation.js +426 -0
  80. package/dist/workflowResumeDispatch.js +29 -0
  81. package/package.json +4 -4
  82. package/dist/cluster-F_bhqVP7.js +0 -679
  83. package/dist/primitives-DX04WTWh.js +0 -507
  84. package/dist/src-C2NEyUM8.js +0 -3925
  85. package/dist/workflowReadSelections-CZAUjP7f.js +0 -2096
@@ -0,0 +1,615 @@
1
+ import { failureMessage as e } from "./failureMessage.js";
2
+ import { STEP_JOURNAL_BINDING as t, anyWorkflowEncrypted as n, isWorkflowEncrypted as r, loggableCause as i, openWorkflowRow as a, sealEventPayload as o, sealJournalValue as s } from "./journalEncryption.js";
3
+ import { CurrentWorkflowExecutionId as c, CurrentWorkflowGeneration as l, CurrentWorkflowPatches as u, CurrentWorkflowRunId as d, CurrentWorkflowWakeTenant as f, WorkflowRunRecorder as p } from "./recorder.js";
4
+ import { CurrentWorkflowReplayShape as ee, makeReplayShapeTracker as m, nondeterminismEventPayload as te } from "./nondeterminism.js";
5
+ import { CurrentWorkflowDelivery as ne } from "./workflowDelivery.js";
6
+ import { completeWorkflowRunGenerationInTransaction as h, enterWorkflowRunGenerationInTransaction as re, suspendWorkflowRunGenerationInTransaction as g } from "./runGeneration.js";
7
+ import { awaitWorkflowCancellation as ie } from "./controlMonitor.js";
8
+ import { Workflow as _ } from "effect/workflow";
9
+ import { Cause as v, Effect as y, Exit as b, Fiber as x } from "effect";
10
+ import { publishServerError as S } from "@voltro/protocol";
11
+ import { eq as C } from "@voltro/database";
12
+ import { WorkflowInstance as w } from "effect/workflow/WorkflowEngine";
13
+ //#region src/runRecording.ts
14
+ var T = 8192, E = 3, D = 2e3, O = () => {
15
+ let e = (e) => {
16
+ if (e === void 0) return;
17
+ let t = Number(e);
18
+ return Number.isFinite(t) && t > 0 ? Math.floor(t) : void 0;
19
+ }, t = e(process.env.VOLTRO_WORKFLOW_MAX_RECLAIMS), n = e(process.env.VOLTRO_WORKFLOW_REPLAY_SHAPE_LIMIT);
20
+ return {
21
+ ...t === void 0 ? {} : { maxRunReclaims: t },
22
+ ...n === void 0 ? {} : { replayShapeLimit: n }
23
+ };
24
+ }, k = (e) => {
25
+ let t = typeof e == "string" ? (() => {
26
+ try {
27
+ return JSON.parse(e);
28
+ } catch {
29
+ return;
30
+ }
31
+ })() : e;
32
+ if (Array.isArray(t)) return t.filter((e) => typeof e == "string");
33
+ }, A = () => {
34
+ let e = globalThis.crypto;
35
+ return typeof e?.randomUUID == "function" ? e.randomUUID() : `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
36
+ }, j = (e) => `${e}_${A()}`, M = (e, t = T) => {
37
+ if (e == null) return e;
38
+ try {
39
+ let n = JSON.stringify(e);
40
+ return n.length <= t ? e : {
41
+ __truncated: !0,
42
+ bytes: n.length,
43
+ sample: n.slice(0, t)
44
+ };
45
+ } catch {
46
+ return { __unserialisable: String(e) };
47
+ }
48
+ }, N = async (e, t, r) => {
49
+ if (r === null || !n() || !("value" in r || "result" in r || "errorMessage" in r) || e.query === void 0) return r;
50
+ let [i] = await e.query({
51
+ table: "_voltro_workflow_runs",
52
+ predicate: C("id", t),
53
+ order: [],
54
+ take: 1,
55
+ skip: void 0,
56
+ projection: void 0
57
+ });
58
+ return typeof i?.tag == "string" ? o(i.tag, r) : r;
59
+ }, P = (e) => {
60
+ let t = {};
61
+ for (let [n, r] of Object.entries(e)) t[n] = r instanceof Date ? r.toISOString() : r;
62
+ return a(t);
63
+ }, F = (n) => {
64
+ let r = n.truncateBytes ?? T, i = n.makeId ?? j, a = (e, t) => n.log?.warn(e, t), o = (e, n) => n === !0 ? s(e, "workflow step value", t) : e, c = n.emit, l = n.recording === "coarse";
65
+ return {
66
+ startStep: async ({ runId: t, stepName: s, attempt: u, stepInput: d, retryPolicy: f, sealed: p }) => {
67
+ if (!l) try {
68
+ let e = await n.store.insert("_voltro_workflow_run_steps", {
69
+ id: i("ws"),
70
+ runId: t,
71
+ stepName: s,
72
+ attempt: u,
73
+ status: "running",
74
+ input: d === void 0 ? null : o(M(d, r), p),
75
+ retryPolicy: f ?? null,
76
+ output: null,
77
+ errorTag: null,
78
+ errorMessage: null,
79
+ startedAt: /* @__PURE__ */ new Date(),
80
+ completedAt: null,
81
+ durationMs: null
82
+ });
83
+ return c?.("workflowSteps", {
84
+ op: "insert",
85
+ row: P(e)
86
+ }), e.id;
87
+ } catch (n) {
88
+ a("failed to record step start (continuing)", {
89
+ runId: t,
90
+ stepName: s,
91
+ attempt: u,
92
+ reason: e(n)
93
+ });
94
+ return;
95
+ }
96
+ },
97
+ endStepSuccess: async ({ stepRecordId: t, output: i, durationMs: s, sealed: u }) => {
98
+ if (!l) try {
99
+ let e = await n.store.update("_voltro_workflow_run_steps", t, {
100
+ status: "succeeded",
101
+ output: o(M(i, r), u),
102
+ completedAt: /* @__PURE__ */ new Date(),
103
+ durationMs: s
104
+ });
105
+ e && c?.("workflowSteps", {
106
+ op: "update",
107
+ row: P(e)
108
+ });
109
+ } catch (n) {
110
+ a("failed to record step success", {
111
+ stepRecordId: t,
112
+ reason: e(n)
113
+ });
114
+ }
115
+ },
116
+ endStepFailure: async ({ stepRecordId: t, errorTag: i, errorMessage: s, errorCause: u, durationMs: d, sealed: f }) => {
117
+ if (!l) try {
118
+ let e = await n.store.update("_voltro_workflow_run_steps", t, {
119
+ status: "failed",
120
+ errorTag: i,
121
+ errorMessage: s == null ? s : o(s, f),
122
+ errorCause: u === void 0 ? null : o(M(u, r), f),
123
+ completedAt: /* @__PURE__ */ new Date(),
124
+ durationMs: d
125
+ });
126
+ e && c?.("workflowSteps", {
127
+ op: "update",
128
+ row: P(e)
129
+ });
130
+ } catch (n) {
131
+ a("failed to record step failure", {
132
+ stepRecordId: t,
133
+ reason: e(n)
134
+ });
135
+ }
136
+ },
137
+ recordEvent: async ({ runId: t, eventType: r, payload: o, stepName: s, attempt: l }) => {
138
+ let u = i("wfev"), d = !0;
139
+ try {
140
+ let e = await n.store.insert("_voltro_workflow_run_events", {
141
+ id: u,
142
+ runId: t,
143
+ eventType: r,
144
+ payload: await N(n.store, t, o ?? null),
145
+ occurredAt: /* @__PURE__ */ new Date(),
146
+ stepName: s ?? null,
147
+ attempt: l ?? null
148
+ });
149
+ c?.("workflowEvents", {
150
+ op: "insert",
151
+ row: P(e)
152
+ });
153
+ } catch (n) {
154
+ d = !1, a("failed to record workflow event (continuing)", {
155
+ runId: t,
156
+ eventType: r,
157
+ reason: e(n)
158
+ });
159
+ }
160
+ if (n.wakeups !== void 0) {
161
+ let e = o?.scheduledWakeAt, i = typeof e == "string" ? new Date(e) : void 0, a = o?.tenantId, s = typeof a == "string" ? a : void 0;
162
+ r === "timer-set" && i !== void 0 && !Number.isNaN(i.getTime()) ? await n.wakeups.register(t, i, s).catch(() => {}) : (r === "timer-fired" || r === "run-succeeded" || r === "run-failed" || r === "run-cancelled") && await n.wakeups.cancel(t, s).catch(() => {});
163
+ }
164
+ return {
165
+ id: u,
166
+ recorded: d
167
+ };
168
+ }
169
+ };
170
+ }, ae = (t, n, r) => y.gen(function* () {
171
+ let i = t.store.query;
172
+ if (i === void 0) return;
173
+ let a = t.replayShapeLimit ?? 2e3, o = yield* y.promise(() => i.call(t.store, {
174
+ table: "_voltro_workflow_run_steps",
175
+ order: [{
176
+ column: "startedAt",
177
+ direction: "asc"
178
+ }],
179
+ predicate: C("runId", n),
180
+ projection: ["stepName"],
181
+ skip: void 0,
182
+ take: a + 1
183
+ }).catch((t) => (r("failed to read the replay shape (nondeterminism tripwire disabled for this attempt)", {
184
+ runId: n,
185
+ reason: e(t)
186
+ }), [])));
187
+ if (o.length === 0) return;
188
+ if (o.length > a) {
189
+ r("replay shape exceeds the step limit — nondeterminism tripwire disabled for this run", {
190
+ runId: n,
191
+ limit: a
192
+ });
193
+ return;
194
+ }
195
+ let s = o.flatMap((e) => {
196
+ let t = e.stepName;
197
+ return typeof t == "string" ? [{ stepName: t }] : [];
198
+ });
199
+ return s.length === 0 ? void 0 : m(s);
200
+ }), I = (t, n) => {
201
+ let a = t.truncateBytes ?? T, o = t.makeId ?? j, s = t.recorder ?? F(t), m = (e, n) => t.log?.warn(e, n), C = t.emit, E = (e) => y.gen(function* () {
202
+ yield* y.sync(() => C?.("workflowEvents", {
203
+ op: "insert",
204
+ row: P(e)
205
+ }));
206
+ let n = t.wakeups, r = e.runId;
207
+ if (n !== void 0 && typeof r == "string") {
208
+ let e = t.subject?.tenantId;
209
+ yield* y.promise(() => n.cancel(r, typeof e == "string" ? e : void 0).catch(() => {
210
+ m("failed to clear terminal workflow wakeup", { runId: r });
211
+ }));
212
+ }
213
+ }), D = (n, r, i) => y.promise(async () => {
214
+ if (t.onParentClose) try {
215
+ await t.onParentClose({
216
+ workflowName: t.name,
217
+ executionId: n,
218
+ ...i === void 0 ? {} : { runId: i },
219
+ status: r
220
+ });
221
+ } catch (i) {
222
+ m("failed to apply workflow parent-close policy", {
223
+ tag: t.name,
224
+ executionId: n,
225
+ status: r,
226
+ reason: e(i)
227
+ });
228
+ }
229
+ }), O = (n, r, i, a) => y.promise(async () => {
230
+ if (t.onTerminal) try {
231
+ await t.onTerminal({
232
+ workflowName: t.name,
233
+ executionId: n,
234
+ ...i === void 0 ? {} : { runId: i },
235
+ status: r,
236
+ ...a === void 0 ? {} : a
237
+ });
238
+ } catch (i) {
239
+ m("workflow terminal observer failed", {
240
+ tag: t.name,
241
+ executionId: n,
242
+ status: r,
243
+ reason: e(i)
244
+ });
245
+ }
246
+ });
247
+ return (T, j) => y.uninterruptibleMask((N) => y.gen(function* () {
248
+ let F = Date.now(), I = yield* c, oe = yield* l, L = t.parentExecutionId ?? I ?? null, se = t.parentGeneration ?? (t.parentExecutionId == null ? oe : void 0) ?? null, R = t.parentClosePolicy ?? null, z, B = (n, r) => r.pipe(y.catchCause((r) => v.hasInterrupts(r) ? y.failCause(r) : y.sync(() => {
249
+ let i = v.squash(r);
250
+ m(n, {
251
+ tag: t.name,
252
+ executionId: j,
253
+ reason: e(i)
254
+ });
255
+ }))), V = `wfrg_${A()}`, H = yield* y.serviceOption(ne), ce = o("wr"), le = yield* y.sync(() => t.encodePayload === void 0 ? T : t.encodePayload(T)), U = yield* y.promise(() => t.transaction((e) => re(e, {
256
+ workflowName: t.name,
257
+ executionId: j,
258
+ generation: V,
259
+ now: F,
260
+ delivery: H._tag === "Some" ? H.value : null,
261
+ initialRow: {
262
+ id: ce,
263
+ payload: le,
264
+ workflowVersion: t.workflowVersion ?? null,
265
+ workflowPatches: t.workflowPatches ?? null,
266
+ output: null,
267
+ subject: t.subject ?? null,
268
+ source: t.source ?? null,
269
+ errorTag: null,
270
+ errorMessage: null,
271
+ cancelled: !1,
272
+ startedAt: new Date(F),
273
+ completedAt: null,
274
+ durationMs: null,
275
+ traceId: t.traceId ?? null,
276
+ parentExecutionId: L,
277
+ parentGeneration: se,
278
+ parentClosePolicy: R
279
+ }
280
+ })));
281
+ if (typeof U.row.id != "string") return yield* y.die(/* @__PURE__ */ Error("Recorded workflow entry has no run identity"));
282
+ if (z = U.row.id, U.restartEvent !== void 0) {
283
+ let e = U.restartEvent;
284
+ yield* B("failed to observe recorded workflow restart", y.sync(() => {
285
+ C?.("workflowEvents", {
286
+ op: "insert",
287
+ row: P(e)
288
+ });
289
+ }));
290
+ }
291
+ if (U.kind === "cancelled" || U.kind === "already-cancelled") {
292
+ let e = yield* w;
293
+ return e.interrupted = !0, U.kind === "cancelled" && (yield* B("failed to observe suspended workflow cancellation", y.gen(function* () {
294
+ if (C?.("workflowRuns", {
295
+ op: "update",
296
+ row: P(U.row)
297
+ }), U.terminalEvent === void 0) return yield* y.die(/* @__PURE__ */ Error("Cancelled entry has no terminal event"));
298
+ yield* E(U.terminalEvent), yield* D(j, "cancelled", z);
299
+ })), yield* O(j, "cancelled", z), yield* y.sync(() => t.recordRun?.({
300
+ name: t.name,
301
+ status: "cancelled",
302
+ durationMs: 0
303
+ }))), yield* y.interrupt;
304
+ }
305
+ let W = U.previous, G = W?.workflowVersion ?? null;
306
+ if (W?.id !== void 0 && G !== null && t.workflowCompatibleVersions !== void 0 && t.workflowCompatibleVersions !== null && !t.workflowCompatibleVersions.includes(G)) {
307
+ let e = `Workflow '${t.name}' run '${j}' was started on version ${G}, but current code is compatible with ${t.workflowCompatibleVersions.join(", ")}`, n = t.failureContext ?? {
308
+ handler: null,
309
+ callerContext: null,
310
+ tenantId: null
311
+ }, r = yield* y.promise(() => t.transaction((r) => h(r, {
312
+ owner: {
313
+ runId: z,
314
+ workflowName: t.name,
315
+ executionId: j,
316
+ generation: V
317
+ },
318
+ now: Date.now(),
319
+ durationMs: 0,
320
+ outcome: {
321
+ status: "failed",
322
+ errorTag: "WorkflowVersionIncompatible",
323
+ errorMessage: e,
324
+ ...n
325
+ }
326
+ })));
327
+ return r.kind === "committed" && (yield* B("failed to observe workflow version rejection", y.gen(function* () {
328
+ C?.("workflowRuns", {
329
+ op: "update",
330
+ row: P(r.row)
331
+ }), yield* E(r.terminalEvent), yield* D(j, "failed", z);
332
+ })), yield* O(j, "failed", z, {
333
+ payload: U.row.payload,
334
+ errorTag: "WorkflowVersionIncompatible",
335
+ errorMessage: e
336
+ }), yield* y.sync(() => t.recordRun?.({
337
+ name: t.name,
338
+ status: "failed",
339
+ durationMs: 0
340
+ })), m("workflow run failed", {
341
+ workflow: t.name,
342
+ executionId: j,
343
+ runId: z,
344
+ errorTag: "WorkflowVersionIncompatible",
345
+ cause: e
346
+ })), yield* y.fail(Error(e));
347
+ }
348
+ let K = t.workflowPatches ?? [], q;
349
+ if (W?.id !== void 0) {
350
+ z = W.id, K = k(W.workflowPatches) ?? K;
351
+ let e = U.row.reclaimCount;
352
+ if (typeof e != "number" || !Number.isSafeInteger(e) || e < 0) return yield* y.die(/* @__PURE__ */ Error("Recorded workflow entry has an invalid reclaim counter"));
353
+ let n = e > 0, r = t.maxRunReclaims ?? 3;
354
+ if (n && e >= r) {
355
+ let n = `Workflow '${t.name}' run '${j}' killed its runner ${e} times in a row; parked instead of reclaimed again (maxRunReclaims=${r}).`, i = yield* y.promise(() => t.transaction((e) => g(e, {
356
+ runId: z,
357
+ workflowName: t.name,
358
+ executionId: j,
359
+ generation: V
360
+ }, {
361
+ errorTag: "WorkflowCrashLooped",
362
+ errorMessage: n
363
+ })));
364
+ i.kind === "committed" && (yield* B("failed to observe workflow crash-loop park", y.gen(function* () {
365
+ C?.("workflowRuns", {
366
+ op: "update",
367
+ row: P(i.row)
368
+ }), yield* y.promise(() => s.recordEvent({
369
+ runId: z,
370
+ eventType: "run-crashlooped",
371
+ payload: {
372
+ reclaimCount: e,
373
+ maxRunReclaims: r,
374
+ executionId: j,
375
+ tag: t.name
376
+ }
377
+ }));
378
+ })), m("workflow run parked after repeated runner deaths", {
379
+ tag: t.name,
380
+ executionId: j,
381
+ runId: z,
382
+ reclaimCount: e,
383
+ maxRunReclaims: r
384
+ }));
385
+ let a = yield* w;
386
+ return yield* _.suspend(a);
387
+ }
388
+ q = yield* ae(t, z, m), yield* B("failed to record workflow re-entry", y.gen(function* () {
389
+ C?.("workflowRuns", {
390
+ op: "update",
391
+ row: P(U.row)
392
+ }), n && (yield* y.promise(() => s.recordEvent({
393
+ runId: z,
394
+ eventType: "run-resumed",
395
+ payload: {
396
+ reason: "runner-reclaim",
397
+ reclaimCount: e,
398
+ maxRunReclaims: r
399
+ }
400
+ })));
401
+ }));
402
+ } else yield* B("failed to emit recorded workflow start", y.gen(function* () {
403
+ if (C?.("workflowRuns", {
404
+ op: "insert",
405
+ row: P(U.row)
406
+ }), z) {
407
+ let e = z;
408
+ yield* y.promise(() => s.recordEvent({
409
+ runId: e,
410
+ eventType: "run-started",
411
+ payload: {
412
+ tag: t.name,
413
+ executionId: j,
414
+ source: t.source ?? null,
415
+ workflowVersion: t.workflowVersion ?? null,
416
+ workflowPatches: t.workflowPatches ?? null,
417
+ parentExecutionId: L,
418
+ parentClosePolicy: R
419
+ }
420
+ }));
421
+ }
422
+ }));
423
+ let J = t.subject?.tenantId, Y = y.suspend(() => n(T, j, V)).pipe(y.provideService(d, z), y.provideService(c, j), y.provideService(l, V), y.provideService(f, typeof J == "string" ? J : void 0), y.provideService(u, K), y.provideService(ee, q), y.provideService(p, s)), ue = t.pluginWorkflowStepLayer ? Y.pipe(y.provide(t.pluginWorkflowStepLayer)) : Y, X = yield* _.intoResult(y.gen(function* () {
424
+ let e = yield* y.forkChild(N(ue)), n = yield* y.forkChild(N(ie({
425
+ runId: z,
426
+ workflowName: t.name,
427
+ executionId: j,
428
+ generation: V
429
+ }, async (e) => t.store.query(e), () => m("workflow control read failed; retrying without claiming cancellation", {
430
+ executionId: j,
431
+ runId: z
432
+ })))), r = yield* y.exit(N(y.raceAll([y.map(x.await(e), (e) => ({
433
+ other: n,
434
+ exit: e
435
+ })), y.map(x.await(n), (t) => ({
436
+ other: e,
437
+ exit: t
438
+ }))])));
439
+ if (b.isSuccess(r)) {
440
+ yield* x.interrupt(r.value.other);
441
+ let e = r.value.exit;
442
+ if (b.isFailure(e) && v.hasInterruptsOnly(e.cause)) {
443
+ let e = yield* w;
444
+ e.suspended || (e.interrupted = !0);
445
+ }
446
+ return yield* e;
447
+ }
448
+ yield* x.interrupt(n), yield* x.interrupt(e);
449
+ let i = yield* w;
450
+ return i.interrupted = !0, yield* x.await(e);
451
+ }));
452
+ if (_.isResult(X) && X._tag === "Suspended") {
453
+ let n = X.cause, i = n !== void 0 && n.reasons.length > 0, a = i ? v.squash(n) : void 0, o = a && typeof a == "object" && "_tag" in a ? String(a._tag) : null, c = i ? e(a) : null, l = yield* y.promise(() => t.transaction((e) => g(e, {
454
+ runId: z,
455
+ workflowName: t.name,
456
+ executionId: j,
457
+ generation: V
458
+ }, {
459
+ errorTag: o,
460
+ errorMessage: c
461
+ })));
462
+ l.kind === "committed" && i && (yield* y.sync(() => S({
463
+ error: a,
464
+ source: "workflow",
465
+ name: t.name,
466
+ contentSensitive: r(t.name),
467
+ ...t.traceId ? { traceId: t.traceId } : {},
468
+ fields: {
469
+ executionId: j,
470
+ suspendedOnFailure: !0,
471
+ ...o ? { errorTag: o } : {},
472
+ ...z ? { runId: z } : {}
473
+ }
474
+ }))), l.kind === "committed" && (yield* B("failed to observe workflow suspend", y.gen(function* () {
475
+ C?.("workflowRuns", {
476
+ op: "update",
477
+ row: P(l.row)
478
+ }), yield* y.promise(() => s.recordEvent({
479
+ runId: z,
480
+ eventType: "run-suspended",
481
+ payload: i ? {
482
+ reason: "suspend-on-failure",
483
+ errorTag: o,
484
+ errorMessage: c
485
+ } : { reason: "workflow-suspend" }
486
+ }));
487
+ })));
488
+ let u = yield* w;
489
+ return yield* _.suspend(u);
490
+ }
491
+ let Z = X;
492
+ if (q !== void 0 && z !== void 0) {
493
+ let e = q.settle();
494
+ if (e.length > 0) {
495
+ let n = z;
496
+ m("workflow replay reached a different step shape than this run recorded", {
497
+ tag: t.name,
498
+ executionId: j,
499
+ runId: n,
500
+ findings: e.map((e) => `${e.kind}:${e.stepName}`)
501
+ }), yield* B("failed to record nondeterminism findings", y.promise(async () => {
502
+ for (let t of e) await s.recordEvent({
503
+ runId: n,
504
+ eventType: "nondeterminism-suspected",
505
+ payload: te(t)
506
+ }).catch(() => void 0);
507
+ }));
508
+ }
509
+ }
510
+ if (b.isFailure(Z.exit)) {
511
+ let n = Z.exit.cause;
512
+ if ((yield* w).interrupted && v.hasInterruptsOnly(n)) {
513
+ let e = Date.now(), r = yield* y.promise(() => t.transaction((n) => h(n, {
514
+ owner: {
515
+ runId: z,
516
+ workflowName: t.name,
517
+ executionId: j,
518
+ generation: V
519
+ },
520
+ now: e,
521
+ durationMs: e - F,
522
+ outcome: { status: "cancelled" }
523
+ })));
524
+ return r.kind === "committed" && (yield* B("failed to observe workflow cancellation", y.gen(function* () {
525
+ C?.("workflowRuns", {
526
+ op: "update",
527
+ row: P(r.row)
528
+ }), yield* E(r.terminalEvent), yield* D(j, "cancelled", z);
529
+ })), yield* O(j, "cancelled", z), yield* y.sync(() => t.recordRun?.({
530
+ name: t.name,
531
+ status: "cancelled",
532
+ durationMs: e - F
533
+ }))), yield* y.failCause(n);
534
+ }
535
+ let a = v.findErrorOption(n), o = a._tag === "Some" ? a.value : v.squash(n), s = o && typeof o == "object" && "_tag" in o ? String(o._tag) : null, c = e(o), l = Date.now(), u = t.failureContext ?? {
536
+ handler: null,
537
+ callerContext: null,
538
+ tenantId: null
539
+ }, d = yield* y.promise(() => t.transaction((e) => h(e, {
540
+ owner: {
541
+ runId: z,
542
+ workflowName: t.name,
543
+ executionId: j,
544
+ generation: V
545
+ },
546
+ now: l,
547
+ durationMs: l - F,
548
+ outcome: {
549
+ status: "failed",
550
+ errorTag: s,
551
+ errorMessage: c,
552
+ ...u
553
+ }
554
+ })));
555
+ return d.kind === "committed" && (m("workflow run failed", {
556
+ workflow: t.name,
557
+ executionId: j,
558
+ runId: z,
559
+ ...s ? { errorTag: s } : {},
560
+ cause: i(t.name, o)
561
+ }), yield* y.sync(() => S({
562
+ error: o,
563
+ source: "workflow",
564
+ name: t.name,
565
+ contentSensitive: r(t.name),
566
+ ...t.traceId ? { traceId: t.traceId } : {},
567
+ fields: {
568
+ executionId: j,
569
+ ...s ? { errorTag: s } : {},
570
+ ...z ? { runId: z } : {}
571
+ }
572
+ })), z && (yield* B("failed to record workflow failure", y.gen(function* () {
573
+ C?.("workflowRuns", {
574
+ op: "update",
575
+ row: P(d.row)
576
+ }), yield* E(d.terminalEvent), yield* D(j, "failed", z);
577
+ }))), yield* O(j, "failed", z, {
578
+ payload: T,
579
+ errorTag: s,
580
+ errorMessage: c
581
+ }), yield* y.sync(() => t.recordRun?.({
582
+ name: t.name,
583
+ status: "failed",
584
+ durationMs: Date.now() - F
585
+ }))), yield* y.failCause(n);
586
+ }
587
+ let Q = Z.exit.value, de = Date.now(), $ = yield* y.promise(() => t.transaction((e) => h(e, {
588
+ owner: {
589
+ runId: z,
590
+ workflowName: t.name,
591
+ executionId: j,
592
+ generation: V
593
+ },
594
+ now: de,
595
+ durationMs: de - F,
596
+ outcome: {
597
+ status: "succeeded",
598
+ output: M(Q, a)
599
+ }
600
+ })));
601
+ return $.kind === "committed" && (z && (yield* B("failed to record workflow success", y.gen(function* () {
602
+ C?.("workflowRuns", {
603
+ op: "update",
604
+ row: P($.row)
605
+ }), yield* E($.terminalEvent), yield* D(j, "succeeded", z);
606
+ }))), yield* O(j, "succeeded", z), yield* y.sync(() => t.recordRun?.({
607
+ name: t.name,
608
+ status: "succeeded",
609
+ durationMs: Date.now() - F,
610
+ completedAtSec: Date.now() / 1e3
611
+ }))), Q;
612
+ }));
613
+ };
614
+ //#endregion
615
+ export { E as DEFAULT_MAX_RUN_RECLAIMS, D as DEFAULT_REPLAY_SHAPE_LIMIT, F as makeWorkflowRunRecorder, k as normaliseWorkflowPatches, O as resolveRunGuardTuning, P as serialiseWorkflowRowForWire, M as truncateWorkflowValue, I as wrapWorkflowExecuteWithRunRecording };
@@ -0,0 +1,28 @@
1
+ import { _voltroWorkflowRunsTable as e } from "./runsTable.js";
2
+ import { id as t, integer as n, json as r, reference as i, table as a, text as o, timestamp as s } from "@voltro/database";
3
+ //#region src/runStepsTable.ts
4
+ var c = a("_voltro_workflow_run_steps", {
5
+ id: t({ prefix: "wfstep" }),
6
+ runId: i(() => e, {
7
+ index: !1,
8
+ onDelete: "cascade"
9
+ }),
10
+ stepName: o(),
11
+ attempt: n().default(1),
12
+ status: o().oneOf([
13
+ "running",
14
+ "succeeded",
15
+ "failed"
16
+ ]).default("running"),
17
+ input: r().nullable(),
18
+ retryPolicy: r().nullable(),
19
+ output: r().nullable(),
20
+ errorTag: o().nullable(),
21
+ errorMessage: o().nullable(),
22
+ errorCause: r().nullable(),
23
+ startedAt: s().default("now"),
24
+ completedAt: s().nullable(),
25
+ durationMs: n().nullable()
26
+ }).index(["runId", "stepName"]).index(["startedAt"]);
27
+ //#endregion
28
+ export { c as _voltroWorkflowRunStepsTable };
@@ -0,0 +1,29 @@
1
+ import { Duration as e, Effect as t, Schedule as n } from "effect";
2
+ import { RunnerStorage as r } from "effect/cluster";
3
+ import { PersistenceError as i } from "effect/cluster/ClusterError";
4
+ //#region src/runnerStorageGuard.ts
5
+ var a = (a, o) => t.gen(function* () {
6
+ let s = e.fromInputUnsafe(o.interval), c = e.toMillis(o.giveUpAfter), l, u, d = !1, f = !1;
7
+ yield* o.probe.pipe(t.timeout(s), t.isSuccess, t.flatMap((e) => t.sync(() => {
8
+ let t = Date.now();
9
+ if (e) {
10
+ if (l = void 0, !d || (u ??= t, t - u < c)) return;
11
+ o.log.info("workflow runner: its storage answers again — taking part in shard assignment again"), u = void 0, d = !1, f = !1;
12
+ } else u = void 0, l ??= t, !d && t - l >= c && (d = !0, o.log.warn(`workflow runner: its connection pool has not served a query for ${Math.round((t - l) / 1e3)}s — released its shards and stopped its heartbeat, so another runner takes them`, { fix: "look for what holds this process's database connections (`voltro_db_operations_in_flight` against DB_MAX_CONNECTIONS)" }));
13
+ }))).pipe(t.repeat(n.spaced(s)), t.forkScoped);
14
+ let p = () => new i({ cause: /* @__PURE__ */ Error("workflow runner gave its shards up: its connection pool does not serve queries") });
15
+ return r.RunnerStorage.of({
16
+ ...a,
17
+ register: (e, n) => t.suspend(() => d ? t.fail(p()) : a.register(e, n)),
18
+ acquire: (e, n) => t.suspend(() => d ? t.succeed([]) : a.acquire(e, n)),
19
+ refresh: (e, n) => t.suspend(() => {
20
+ if (!d) return a.refresh(e, n);
21
+ let r = f ? t.void : a.releaseAll(e).pipe(t.tap(() => t.sync(() => {
22
+ f = !0;
23
+ })), t.ignore);
24
+ return t.andThen(r, t.fail(p()));
25
+ })
26
+ });
27
+ });
28
+ //#endregion
29
+ export { a as guardRunnerStorage };