@voltro/workflow 0.84.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 (89) hide show
  1. package/CHANGELOG.md +1215 -0
  2. package/THIRD-PARTY-NOTICES.md +5 -151
  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.d.ts +304 -280
  16. package/dist/cluster.js +8 -2
  17. package/dist/clusterCron.js +11 -0
  18. package/dist/clusterDiagnostics.js +20 -0
  19. package/dist/clusterDiagnosticsSql.js +145 -0
  20. package/dist/clusterLayer.js +110 -0
  21. package/dist/clusterMigrationGate.js +61 -0
  22. package/dist/clusterRunnerLayer.js +21 -0
  23. package/dist/clusterTestSuite.d.ts +11 -11
  24. package/dist/clusterTestSuite.js +257 -257
  25. package/dist/controlMonitor.js +13 -0
  26. package/dist/controlRequest.js +367 -0
  27. package/dist/executionRefusal.js +162 -0
  28. package/dist/failureHandoff.js +183 -0
  29. package/dist/failureMessage.js +12 -0
  30. package/dist/failureRecovery.js +50 -0
  31. package/dist/failureReportStore.js +60 -0
  32. package/dist/flowControl.js +142 -0
  33. package/dist/forwardStartReceipts.js +41 -0
  34. package/dist/inMemoryLayer.js +7 -0
  35. package/dist/inMemoryRecorder.js +56 -0
  36. package/dist/index.d.ts +456 -360
  37. package/dist/index.js +49 -5
  38. package/dist/inspectWorkflow.js +101 -0
  39. package/dist/journalEncryption.js +147 -0
  40. package/dist/messages.js +92 -0
  41. package/dist/nondeterminism.js +51 -0
  42. package/dist/parentClosure.js +141 -0
  43. package/dist/parentClosureRecovery.js +54 -0
  44. package/dist/pendingSubmission.js +85 -0
  45. package/dist/prepareSubmissionDispatch.js +17 -0
  46. package/dist/preparedExecution.js +14 -0
  47. package/dist/primitives.d.ts +56 -31
  48. package/dist/primitives.js +225 -2
  49. package/dist/queuedCancellationRequest.js +364 -0
  50. package/dist/queuedControl.js +21 -0
  51. package/dist/recorder.js +11 -0
  52. package/dist/residentExecutionEvidence.js +64 -0
  53. package/dist/retryPolicy.js +57 -0
  54. package/dist/runEventsTable.js +17 -0
  55. package/dist/runGeneration.js +345 -0
  56. package/dist/runRecording.js +615 -0
  57. package/dist/runStepsTable.js +28 -0
  58. package/dist/runnerStorageGuard.js +29 -0
  59. package/dist/runsTable.js +42 -0
  60. package/dist/scheduleDeclaration.js +44 -0
  61. package/dist/signalHint.js +23 -0
  62. package/dist/signals.js +70 -0
  63. package/dist/signalsSuspending.js +57 -0
  64. package/dist/sqliteWorkflowOwnership.js +26 -0
  65. package/dist/stalenessSweep.js +191 -0
  66. package/dist/startContextsTable.js +20 -0
  67. package/dist/startReceipt.js +143 -0
  68. package/dist/submissionRecovery.js +122 -0
  69. package/dist/submissionRejection.js +10 -0
  70. package/dist/submissionRetry.js +71 -0
  71. package/dist/suspensionUnavailable.js +9 -0
  72. package/dist/updates.js +87 -0
  73. package/dist/watermarkTable.js +9 -0
  74. package/dist/webCrypto.js +17 -0
  75. package/dist/workflowDelivery.js +24 -0
  76. package/dist/workflowJournal.js +124 -0
  77. package/dist/workflowJournalReset.js +111 -0
  78. package/dist/workflowReadSelections.js +12 -0
  79. package/dist/workflowRestartDispatch.js +29 -0
  80. package/dist/workflowRestartObservation.js +60 -0
  81. package/dist/workflowRestartPreparation.js +109 -0
  82. package/dist/workflowRestartReservation.js +426 -0
  83. package/dist/workflowResumeDispatch.js +29 -0
  84. package/package.json +6 -9
  85. package/dist/cluster-i3frC3Da.js +0 -646
  86. package/dist/clusterDiagnostics-CGUtnJkX.js +0 -2084
  87. package/dist/journalEncryption--n5VDdYO.js +0 -148
  88. package/dist/primitives-Btjhjm8P.js +0 -483
  89. package/dist/src-DP-lKqaG.js +0 -3867
@@ -0,0 +1,297 @@
1
+ import { sealErrorText as e } from "./journalEncryption.js";
2
+ import { withAdmissionTransaction as t } from "./admissionTransaction.js";
3
+ import { resolveAdmissionKeys as n } from "./admission.js";
4
+ import { readWorkflowFailureRecord as r, recordWorkflowFailureInTransaction as i } from "./failureReportStore.js";
5
+ import { handoffWorkflowFailureInTransaction as a, isFailureDeliverySubmission as o } from "./failureHandoff.js";
6
+ import { readResidentExecutionEvidence as s } from "./residentExecutionEvidence.js";
7
+ import { ADMISSIONS_TABLE as c, evaluateAdmission as l, recordAdmission as u, retryBackoffMs as d } from "./admissionStore.js";
8
+ import { Schema as f } from "effect";
9
+ import { and as p, eq as m, gt as h, isNull as g } from "@voltro/database";
10
+ import { createHash as _, randomUUID as v } from "node:crypto";
11
+ import { isDeepStrictEqual as y } from "node:util";
12
+ //#region src/admissionSubmission.ts
13
+ var b = "_voltro_workflow_submissions", x = "wfsub", S = () => `${x}_${v()}`, C = f.Struct({
14
+ id: f.String,
15
+ workflowName: f.String,
16
+ tenantId: f.NullOr(f.String),
17
+ ledgerId: f.String,
18
+ preparedExecutionId: f.NullOr(f.String),
19
+ executionId: f.NullOr(f.String),
20
+ runId: f.NullOr(f.String),
21
+ receiptsBound: f.Boolean,
22
+ dispatchAttempts: f.Number.check(f.isInt(), f.isGreaterThanOrEqualTo(0)),
23
+ failureId: f.NullOr(f.String),
24
+ retry: f.Struct({
25
+ attempts: f.Int,
26
+ after: f.Number,
27
+ lastError: f.NullOr(f.String),
28
+ stoppedAt: f.NullOr(f.Number),
29
+ recovery: f.optional(f.Struct({
30
+ attempts: f.Int,
31
+ stoppedAt: f.Number,
32
+ retriedAt: f.Number
33
+ }))
34
+ }),
35
+ payload: f.Unknown,
36
+ callerContext: f.Unknown,
37
+ intentIds: f.Array(f.String),
38
+ eviction: f.NullOr(f.Struct({
39
+ executionId: f.String,
40
+ runId: f.String
41
+ }))
42
+ }), w = f.decodeUnknownSync(C);
43
+ async function* T(e, t, n) {
44
+ let r;
45
+ for (;;) {
46
+ let i = p(m("preparedExecutionId", t), h("dispatchAttempts", 0)), a = await e.query({
47
+ table: b,
48
+ predicate: r === void 0 ? i : p(i, h("id", r)),
49
+ order: [{
50
+ column: "id",
51
+ direction: "asc"
52
+ }],
53
+ take: 100,
54
+ skip: void 0,
55
+ projection: void 0
56
+ });
57
+ if (a.length === 0) return;
58
+ for (let e of a) {
59
+ let i = w(e);
60
+ if (i.workflowName !== n || !i.receiptsBound || i.failureId !== null || i.executionId !== null && i.executionId !== t) throw Error("Workflow completion has an inconsistent resident submission owner");
61
+ yield i, r = i.id;
62
+ }
63
+ }
64
+ }
65
+ var E = async (e, t, n) => (await D(e, t, n)).map((e) => w(e)), D = async (e, t, n) => {
66
+ if (!Number.isSafeInteger(t) || t < 1) throw Error("Submission recovery requires a positive page size");
67
+ return e.query({
68
+ table: b,
69
+ predicate: n === void 0 ? p(m("receiptsBound", !0), g("executionId"), g("failureId")) : p(m("receiptsBound", !0), g("executionId"), g("failureId"), h("id", n)),
70
+ order: [{
71
+ column: "id",
72
+ direction: "asc"
73
+ }],
74
+ take: t,
75
+ skip: void 0,
76
+ projection: void 0
77
+ });
78
+ }, O = async (e, t) => {
79
+ let n = await e.query({
80
+ table: b,
81
+ predicate: m("id", t),
82
+ order: [],
83
+ take: 1,
84
+ skip: void 0,
85
+ projection: void 0
86
+ });
87
+ return n[0] === void 0 ? void 0 : f.decodeUnknownSync(C)(n[0]);
88
+ }, k = async (e, n) => t(e, (e) => A(e, n)), A = async (e, t) => {
89
+ if (t.id.length === 0) throw Error("Admission submission requires a stable non-empty identity");
90
+ let r = await e.query({
91
+ table: b,
92
+ predicate: m("id", t.id),
93
+ order: [],
94
+ take: 1,
95
+ skip: void 0,
96
+ projection: void 0
97
+ });
98
+ if (r[0] !== void 0) {
99
+ let e = f.decodeUnknownSync(C)(r[0]);
100
+ if (e.workflowName !== t.control.workflowName || e.tenantId !== t.tenantId) throw Error("Admission submission identity belongs to a different workflow or tenant");
101
+ let n = JSON.parse(JSON.stringify(e.payload) ?? "null"), i = JSON.parse(JSON.stringify(t.encodedPayload) ?? "null");
102
+ if (!y(n, i)) throw Error("Admission submission identity is already bound to a different payload");
103
+ return {
104
+ kind: "reserved",
105
+ submission: e,
106
+ reused: !0
107
+ };
108
+ }
109
+ let i = n(t.control, t.keyPayload), a = await l({
110
+ store: e,
111
+ control: t.control,
112
+ keys: i,
113
+ paused: t.paused,
114
+ now: t.now,
115
+ reconsidering: !0
116
+ });
117
+ if (a.kind !== "admit") return {
118
+ kind: "not-admitted",
119
+ decision: a
120
+ };
121
+ let o = await u({
122
+ store: e,
123
+ control: t.control,
124
+ keys: i,
125
+ outcome: "admitted",
126
+ mode: t.mode,
127
+ reason: a.reason,
128
+ collapsed: t.collapsed,
129
+ waitedMs: t.now - t.firstSeenAt,
130
+ executionId: null,
131
+ runId: null,
132
+ tenantId: t.tenantId,
133
+ now: t.now
134
+ });
135
+ if (o === void 0) throw Error("Admission reservation has no ledger identity");
136
+ return {
137
+ kind: "reserved",
138
+ submission: await j(e, {
139
+ id: t.id,
140
+ workflowName: t.control.workflowName,
141
+ tenantId: t.tenantId,
142
+ ledgerId: o,
143
+ payload: t.encodedPayload,
144
+ callerContext: t.callerContext,
145
+ intentIds: t.intentIds,
146
+ eviction: a.evict ?? null
147
+ }),
148
+ reused: !1
149
+ };
150
+ }, j = async (e, t) => {
151
+ if (t.id.length === 0 || t.ledgerId.length === 0) throw Error("Submission requires its reserved admission identity");
152
+ let n = {
153
+ ...t,
154
+ callerContext: t.callerContext ?? null,
155
+ intentIds: [...t.intentIds],
156
+ preparedExecutionId: null,
157
+ executionId: null,
158
+ runId: null,
159
+ receiptsBound: !1,
160
+ dispatchAttempts: 0,
161
+ failureId: null,
162
+ retry: {
163
+ attempts: 0,
164
+ after: 0,
165
+ lastError: null,
166
+ stoppedAt: null
167
+ }
168
+ };
169
+ return await e.insert(b, n), n;
170
+ }, M = async (e, n, r) => t(e, (e) => N(e, n, r)), N = async (e, t, n) => {
171
+ if (n.executionId.length === 0) throw Error("Engine acknowledgement requires an execution identity");
172
+ let r = await e.query({
173
+ table: b,
174
+ predicate: m("id", t),
175
+ order: [],
176
+ take: 1,
177
+ skip: void 0,
178
+ projection: void 0
179
+ });
180
+ if (r[0] === void 0) throw Error("Cannot acknowledge an unknown admission submission");
181
+ let i = f.decodeUnknownSync(C)(r[0]);
182
+ if (i.failureId !== null) throw Error("Cannot acknowledge a permanently rejected admission submission");
183
+ if (i.preparedExecutionId !== null && i.preparedExecutionId !== n.executionId) throw Error("Engine acknowledgement contradicts the prepared execution identity");
184
+ if (i.executionId !== null && i.executionId !== n.executionId || i.runId !== null && n.runId !== null && i.runId !== n.runId) throw Error("Admission submission already belongs to a different execution or run");
185
+ let a = await s(e, i.workflowName, n.executionId), o = i.runId ?? n.runId;
186
+ if (a !== void 0 && o !== null && a.runId !== o) throw Error("Admission acknowledgement belongs to a different resident run");
187
+ let c = {
188
+ executionId: n.executionId,
189
+ runId: o ?? a?.runId ?? null
190
+ }, l = a?.completedAt ?? void 0;
191
+ if (await e.update("_voltro_workflow_admissions", i.ledgerId, {
192
+ ...c,
193
+ ...l === void 0 ? {} : { releasedAt: l }
194
+ }) === null) throw Error("Admission submission has no lease to acknowledge");
195
+ if (await e.update("_voltro_workflow_submissions", t, c) === null) throw Error("Admission submission disappeared during acknowledgement");
196
+ return {
197
+ ...i,
198
+ ...c
199
+ };
200
+ }, P = async (e, t, n) => {
201
+ if (n.length === 0) throw Error("Prepared submission requires a non-empty execution identity");
202
+ let r = await O(e, t);
203
+ if (r === void 0) throw Error("Cannot prepare an unknown admission submission");
204
+ if (!r.receiptsBound || r.failureId !== null) throw Error("Cannot prepare an incomplete or terminal admission submission");
205
+ if (r.preparedExecutionId !== null) {
206
+ if (r.executionId !== null && r.executionId !== r.preparedExecutionId) throw Error("Submission acceptance contradicts its prepared identity");
207
+ return r;
208
+ }
209
+ if (r.executionId !== null) throw Error("Cannot prepare an accepted submission without its prepared identity");
210
+ if (r.dispatchAttempts !== 0) throw Error("Cannot choose an execution identity after an unbound dispatch");
211
+ if (r.retry.stoppedAt !== null) throw Error("Cannot prepare a stopped admission submission");
212
+ if (await e.update("_voltro_workflow_submissions", t, { preparedExecutionId: n }) === null) throw Error("Admission submission disappeared while preparing execution");
213
+ return {
214
+ ...r,
215
+ preparedExecutionId: n
216
+ };
217
+ }, F = async (t, n, r, i, a = !1, o = 0) => {
218
+ if (a !== !1 && (!Number.isSafeInteger(a) || a < 1)) throw Error("Submission retry limit must be a positive integer or false");
219
+ if (!Number.isSafeInteger(o) || o < 0) throw Error("Submission failure requires a valid observed recovery epoch");
220
+ let s = await O(t, n);
221
+ if (s === void 0) throw Error("Cannot record failure for an unknown submission");
222
+ if (s.executionId !== null || s.failureId !== null || s.retry.stoppedAt !== null || (s.retry.recovery?.attempts ?? 0) !== o) return s;
223
+ let c = s.retry.attempts + 1, l = c - (s.retry.recovery?.attempts ?? 0), u = {
224
+ ...s.retry,
225
+ attempts: c,
226
+ lastError: e(s.workflowName, r),
227
+ after: i + d(l),
228
+ stoppedAt: a !== !1 && l >= a ? i : null
229
+ };
230
+ if (await t.update("_voltro_workflow_submissions", n, { retry: u }) === null) throw Error("Admission submission disappeared while recording failure");
231
+ return {
232
+ ...s,
233
+ retry: u
234
+ };
235
+ }, I = async (e, t, n) => {
236
+ if (!Number.isFinite(n)) throw Error("Submission dispatch requires a finite timestamp");
237
+ let r = await O(e, t);
238
+ if (r === void 0) throw Error("Cannot dispatch an unknown admission submission");
239
+ if (!r.receiptsBound || r.executionId !== null || r.failureId !== null || r.retry.stoppedAt !== null || r.retry.after > n) return null;
240
+ let i = r.dispatchAttempts + 1;
241
+ if (!Number.isSafeInteger(i)) throw Error("Submission dispatch counter exhausted");
242
+ if (r.dispatchAttempts === 0) {
243
+ let [t] = await e.query({
244
+ table: c,
245
+ predicate: m("id", r.ledgerId),
246
+ lock: "update",
247
+ order: [],
248
+ take: 1,
249
+ skip: void 0,
250
+ projection: void 0
251
+ });
252
+ if (t === void 0 || t.workflowName !== r.workflowName || t.tenantId !== r.tenantId || t.outcome !== "admitted" || t.executionId !== null || t.runId !== null || t.releasedAt !== null) throw Error("First submission dispatch requires its matching unbound and unreleased admission lease");
253
+ let i = t.admittedAt instanceof Date ? t.admittedAt.getTime() : NaN, a = t.expiresAt instanceof Date ? t.expiresAt.getTime() : NaN, o = a - i, s = new Date(n + o);
254
+ if (!Number.isFinite(i) || !Number.isFinite(a) || o <= 0 || !Number.isFinite(s.getTime())) throw Error("First submission dispatch requires a valid finite original admission lease interval");
255
+ if (await e.update("_voltro_workflow_admissions", r.ledgerId, { expiresAt: s }) === null) throw Error("Admission submission lost its lease before first dispatch");
256
+ }
257
+ if (await e.update("_voltro_workflow_submissions", t, { dispatchAttempts: i }) === null) throw Error("Admission submission disappeared before dispatch");
258
+ return {
259
+ ...r,
260
+ dispatchAttempts: i
261
+ };
262
+ }, L = async (e, t, n, s, c = 0) => {
263
+ if (!Number.isSafeInteger(n) || n < 0) throw Error("Invalid submission dispatch attempt");
264
+ if (!Number.isSafeInteger(c) || c < 0) throw Error("Submission rejection requires a valid observed recovery epoch");
265
+ let l = await O(e, t);
266
+ if (l === void 0) throw Error("Cannot reject an unknown admission submission");
267
+ if (l.failureId !== null) {
268
+ let n = await r(e, l.failureId);
269
+ if (n === void 0 || n.sourceKind !== "submission" || n.sourceId !== t || n.workflowName !== l.workflowName || n.tenantId !== l.tenantId) throw Error("Rejected submission has no matching durable failure report");
270
+ return await a(e, n.id, s.now), n;
271
+ }
272
+ if ((l.retry.recovery?.attempts ?? 0) !== c || !l.receiptsBound || l.executionId !== null || l.dispatchAttempts !== n || n > 1) return;
273
+ let u = `wffr_${_("sha256").update(JSON.stringify(["submission", t])).digest("hex").slice(0, 40)}`, d = await i(e, {
274
+ id: u,
275
+ sourceKind: "submission",
276
+ sourceId: t,
277
+ workflowName: l.workflowName,
278
+ tenantId: l.tenantId,
279
+ handler: await o(e, t) ? null : s.handler,
280
+ callerContext: l.callerContext,
281
+ report: {
282
+ workflow: l.workflowName,
283
+ payload: l.payload,
284
+ errorTag: "WorkflowPayloadError",
285
+ errorMessage: s.errorMessage,
286
+ reason: "permanent input rejection before engine acceptance",
287
+ failedAt: s.now,
288
+ runId: null,
289
+ executionId: null
290
+ }
291
+ });
292
+ if (await e.update("_voltro_workflow_admissions", l.ledgerId, { releasedAt: new Date(s.now) }) === null) throw Error("Rejected admission submission has no lease");
293
+ if (await e.update("_voltro_workflow_submissions", t, { failureId: u }) === null) throw Error("Admission submission disappeared during rejection");
294
+ return await a(e, d.id, s.now), d;
295
+ };
296
+ //#endregion
297
+ export { x as ADMISSION_SUBMISSION_ID_PREFIX, b as SUBMISSIONS_TABLE, M as acknowledgeAdmissionSubmission, N as acknowledgeAdmissionSubmissionInTransaction, I as beginAdmissionSubmissionDispatchInTransaction, w as decodeAdmissionSubmission, T as dispatchedExecutionSubmissions, S as newAdmissionSubmissionId, F as noteAdmissionSubmissionFailure, E as pendingAdmissionSubmissions, j as persistReservedSubmission, P as prepareAdmissionSubmissionExecutionInTransaction, O as readAdmissionSubmission, D as readPendingAdmissionSubmissionRows, L as rejectAdmissionSubmissionInTransaction, k as reserveAdmissionSubmission, A as reserveAdmissionSubmissionInTransaction };
@@ -0,0 +1,137 @@
1
+ import { boolean as e, id as t, integer as n, json as r, real as i, table as a, text as o, timestamp as s } from "@voltro/database";
2
+ //#region src/admissionTables.ts
3
+ var c = a("_voltro_workflow_admission_locks", {
4
+ id: t({ prefix: "wfalk" }),
5
+ nonce: o()
6
+ }).nonReactive(), l = a("_voltro_workflow_parent_closures", {
7
+ id: t({ prefix: "wfpc" }),
8
+ runId: o(),
9
+ workflowName: o(),
10
+ executionId: o(),
11
+ generation: o(),
12
+ parentStatus: o().oneOf([
13
+ "succeeded",
14
+ "failed",
15
+ "cancelled"
16
+ ]),
17
+ closedAt: s(),
18
+ afterChildId: o().nullable(),
19
+ scanHasOpenChildren: e().default(!1),
20
+ completedAt: s().nullable()
21
+ }).index(["executionId"]).index(["completedAt", "id"]).nonReactive(), u = a("_voltro_workflow_control_requests", {
22
+ id: t({ prefix: "wfctl" }),
23
+ runId: o(),
24
+ workflowName: o(),
25
+ executionId: o(),
26
+ generation: o(),
27
+ operation: o().oneOf([
28
+ "cancel",
29
+ "terminate",
30
+ "resume"
31
+ ]),
32
+ requestedAt: s(),
33
+ owner: r().nullable(),
34
+ cause: r().nullable(),
35
+ parentClosureId: o().nullable(),
36
+ resumeJournal: r().nullable(),
37
+ dispatchAttempts: n().default(0),
38
+ result: r().nullable()
39
+ }).index(["runId"]).index(["parentClosureId", "id"]).nonReactive(), d = a("_voltro_workflow_failure_reports", {
40
+ id: t({ prefix: "wffr" }),
41
+ workflowName: o(),
42
+ tenantId: o().nullable(),
43
+ sourceId: o(),
44
+ sourceKind: o().oneOf([
45
+ "submission",
46
+ "intent",
47
+ "run"
48
+ ]),
49
+ handler: o().nullable(),
50
+ callerContext: r().nullable(),
51
+ report: r(),
52
+ handoffReceiptId: o().nullable(),
53
+ handoffAttempt: n().default(0),
54
+ acceptedExecutionId: o().nullable()
55
+ }).index(["sourceKind", "sourceId"]).nonReactive(), f = a("_voltro_workflow_submissions", {
56
+ id: t({ prefix: "wfsub" }),
57
+ workflowName: o(),
58
+ tenantId: o().nullable(),
59
+ ledgerId: o(),
60
+ executionId: o().nullable(),
61
+ runId: o().nullable(),
62
+ receiptsBound: e().default(!1),
63
+ preparedExecutionId: o().nullable(),
64
+ dispatchAttempts: n().default(0),
65
+ failureId: o().nullable(),
66
+ retry: r(),
67
+ payload: r(),
68
+ callerContext: r().nullable(),
69
+ intentIds: r(),
70
+ eviction: r().nullable()
71
+ }).nonReactive(), p = a("_voltro_workflow_start_receipts", {
72
+ id: t({ prefix: "wfrc" }),
73
+ workflowName: o(),
74
+ tenantId: o().nullable(),
75
+ pendingId: o().maxLength(64),
76
+ submissionId: o().maxLength(64).nullable(),
77
+ terminal: r().nullable(),
78
+ requestFingerprint: o().maxLength(64).nullable()
79
+ }).index(["pendingId"]).nonReactive(), m = a("_voltro_workflow_pending", {
80
+ id: t({ prefix: "wfp" }),
81
+ tenantId: o().nullable(),
82
+ workflowName: o(),
83
+ controlKey: o(),
84
+ mode: o().oneOf([
85
+ "debounce",
86
+ "batch",
87
+ "throttle",
88
+ "concurrency",
89
+ "paused",
90
+ "delayed"
91
+ ]),
92
+ payload: r(),
93
+ callerContext: r().nullable(),
94
+ queuedAppVersion: o().nullable(),
95
+ queuedSchemaFingerprint: o().nullable(),
96
+ priority: n().default(0),
97
+ dueAt: s(),
98
+ deadlineAt: s().nullable(),
99
+ collapsed: n().default(1),
100
+ firstSeenAt: s().default("now"),
101
+ updatedAt: s().default("now"),
102
+ attempts: n().default(0),
103
+ lastError: o().nullable(),
104
+ deadLetteredAt: s().nullable()
105
+ }).index(["workflowName", "controlKey"]).index(["dueAt"]).index(["workflowName", "mode"]).index(["tenantId"]), h = a("_voltro_workflow_admissions", {
106
+ id: t({ prefix: "wfadm" }),
107
+ tenantId: o().nullable(),
108
+ workflowName: o(),
109
+ outcome: o().oneOf([
110
+ "admitted",
111
+ "dropped",
112
+ "skipped",
113
+ "evicted",
114
+ "expired",
115
+ "discarded"
116
+ ]),
117
+ mode: o().nullable(),
118
+ reason: o().nullable(),
119
+ collapsed: n().default(1),
120
+ waitedMs: i().nullable().narrowedFrom("integer"),
121
+ executionId: o().nullable(),
122
+ runId: o().nullable(),
123
+ singletonKey: o().nullable(),
124
+ concurrencyKey: o().nullable(),
125
+ concurrencyReservation: r().nullable(),
126
+ rateKey: o().nullable(),
127
+ admittedAt: s().default("now"),
128
+ expiresAt: s().nullable(),
129
+ releasedAt: s().nullable()
130
+ }).index(["workflowName", "admittedAt"]).index(["concurrencyKey", "releasedAt"]).index(["singletonKey", "releasedAt"]).index(["rateKey", "admittedAt"]).index(["executionId"]).index(["tenantId"]), g = a("_voltro_workflow_pauses", {
131
+ id: t({ prefix: "wfpause" }),
132
+ pausedAt: s().default("now"),
133
+ pausedBy: o().nullable(),
134
+ reason: o().nullable()
135
+ });
136
+ //#endregion
137
+ export { c as _voltroWorkflowAdmissionLocksTable, h as _voltroWorkflowAdmissionsTable, u as _voltroWorkflowControlRequestsTable, d as _voltroWorkflowFailureReportsTable, l as _voltroWorkflowParentClosuresTable, g as _voltroWorkflowPausesTable, m as _voltroWorkflowPendingTable, p as _voltroWorkflowStartReceiptsTable, f as _voltroWorkflowSubmissionsTable };
@@ -0,0 +1,21 @@
1
+ import { eq as e } from "@voltro/database";
2
+ import { randomUUID as t } from "node:crypto";
3
+ //#region src/admissionTransaction.ts
4
+ var n = "_voltro_workflow_admission_locks", r = "deployment", i = async (i, a) => (await i.insertIgnoreWithOutcome(n, {
5
+ id: r,
6
+ nonce: t()
7
+ }, { conflictColumns: ["id"] }), i.transactional(async (n) => {
8
+ if ((await n.query({
9
+ table: "_voltro_workflow_admission_locks",
10
+ predicate: e("id", r),
11
+ lock: "update",
12
+ order: [],
13
+ take: 1,
14
+ skip: void 0,
15
+ projection: void 0
16
+ })).length !== 1) throw Error("Admission coordinator row is missing; refusing an unlocked decision");
17
+ if (await n.update("_voltro_workflow_admission_locks", r, { nonce: t() }) === null) throw Error("Admission coordinator row disappeared; refusing an unlocked decision");
18
+ return a(n);
19
+ }));
20
+ //#endregion
21
+ export { n as ADMISSION_LOCKS_TABLE, i as withAdmissionTransaction };
@@ -0,0 +1,82 @@
1
+ import { encryptionAwareActivity as e } from "./journalEncryption.js";
2
+ import { CurrentWorkflowRunId as t, WorkflowRunRecorder as n } from "./recorder.js";
3
+ import { Effect as r, Schema as i } from "effect";
4
+ import { and as a, eq as o, gte as s } from "@voltro/database";
5
+ //#region src/awaitEvent.ts
6
+ var c = async (e, t) => {
7
+ if (e !== void 0) try {
8
+ await e.recordEvent(t);
9
+ } catch {}
10
+ }, l = (l, d) => {
11
+ let f = r.gen(function* () {
12
+ let e = d.pollIntervalMs ?? 200, f = d.maxPollIntervalMs ?? 5e3, p = d.timeoutMs ?? 864e5, m = d.take ?? 500, h = yield* t, g = yield* r.serviceOption(n), _ = g._tag === "Some" ? g.value : void 0;
13
+ if (h === void 0) return yield* r.die(/* @__PURE__ */ Error("awaitEvent: no active workflow run id in scope"));
14
+ let v = h, y = yield* d.since === "run-start" ? u(l, v) : r.succeed(/* @__PURE__ */ new Date());
15
+ yield* r.promise(() => c(_, {
16
+ runId: v,
17
+ eventType: "event-awaited",
18
+ payload: {
19
+ event: d.event,
20
+ since: d.since ?? "wait-start",
21
+ cutoff: y.toISOString()
22
+ }
23
+ }));
24
+ let b = Date.now(), x = e, S = 0;
25
+ for (;;) {
26
+ let e = Date.now() - b;
27
+ if (e > p) return yield* r.die(/* @__PURE__ */ Error(`awaitEvent('${d.event}') timed out after ${e}ms having examined ${S} candidate event(s). ${S === 0 ? "NONE were published under that name in the window — check the spelling against ctx.events.publish, and consider since: 'run-start' if the event may have arrived before this step began." : "The predicate rejected all of them — log one payload and compare it with what `match` expects."}`));
28
+ let t = {
29
+ table: "_voltro_workflow_events",
30
+ predicate: a(o("name", d.event), s("occurredAt", y)),
31
+ order: [{
32
+ column: "occurredAt",
33
+ direction: "asc"
34
+ }],
35
+ take: m,
36
+ skip: void 0,
37
+ projection: void 0
38
+ }, n = yield* r.tryPromise({
39
+ try: () => l.store.query(t),
40
+ catch: (e) => e
41
+ });
42
+ S = n.length;
43
+ for (let e of n) {
44
+ let t = yield* i.decodeUnknownEffect(d.schema)(e.payload);
45
+ if (d.match(t)) return yield* r.promise(() => c(_, {
46
+ runId: v,
47
+ eventType: "event-received",
48
+ payload: {
49
+ event: d.event,
50
+ eventId: e.id
51
+ }
52
+ })), t;
53
+ }
54
+ yield* r.sleep(`${x} millis`), x = Math.min(x * 2, f);
55
+ }
56
+ });
57
+ return e({
58
+ name: `awaitEvent:${d.event}`,
59
+ success: d.schema,
60
+ execute: f
61
+ });
62
+ }, u = (e, t) => r.gen(function* () {
63
+ let n = (yield* r.tryPromise({
64
+ try: () => e.store.query({
65
+ table: "_voltro_workflow_runs",
66
+ predicate: o("id", t),
67
+ order: [],
68
+ take: 1,
69
+ skip: void 0,
70
+ projection: void 0
71
+ }),
72
+ catch: (e) => e
73
+ }))[0]?.startedAt;
74
+ if (n instanceof Date) return n;
75
+ if (typeof n == "string" || typeof n == "number") {
76
+ let e = new Date(n);
77
+ if (!Number.isNaN(e.getTime())) return e;
78
+ }
79
+ return /* @__PURE__ */ new Date(0);
80
+ });
81
+ //#endregion
82
+ export { l as awaitEvent };
@@ -0,0 +1,136 @@
1
+ import { awaitSignalSuspending as e, completeSuspendingSignal as t } from "./signalsSuspending.js";
2
+ import { Cause as n, Effect as r, Schema as i } from "effect";
3
+ import { and as a, eq as o, generateId as s, id as c, isNull as l, registerRetention as u, retentionTtlMsFromEnv as d, table as f, text as p, timestamp as m } from "@voltro/database";
4
+ //#region src/budgetHold.ts
5
+ var h = "_voltro_budget_holds", g = "budget-hold", _ = (e) => `${g}:${e}`, v = (e) => `${e.executionId}/${e.stepName}/${e.budget}#${e.generation}`, y = async (e, t) => (await e.query({
6
+ table: h,
7
+ predicate: a(o("executionId", t.executionId), o("stepName", t.stepName), o("budget", t.budget)),
8
+ order: []
9
+ })).length, b = f(h, {
10
+ id: c({ prefix: "bhd" }),
11
+ holdKey: p().maxLength(191).unique(),
12
+ budget: p(),
13
+ tenantId: p().nullable(),
14
+ executionId: p(),
15
+ workflowName: p(),
16
+ signalName: p(),
17
+ stepName: p(),
18
+ spentMicroUsd: p().nullable(),
19
+ limitMicroUsd: p().nullable(),
20
+ heldAt: m().default("now"),
21
+ releasedAt: m().nullable(),
22
+ outcome: p().nullable()
23
+ }).index(["budget", "releasedAt"]), x = () => {
24
+ u({
25
+ source: "framework",
26
+ table: h,
27
+ timeColumn: "releasedAt",
28
+ ttlMs: d(process.env.VOLTRO_BUDGET_HOLDS_TTL_HOURS, 720)
29
+ });
30
+ }, S = class extends Error {
31
+ budget;
32
+ holdKey;
33
+ constructor(e, t, n) {
34
+ super(`budget '${e}': the run was suspended waiting for headroom and gave up after ${n}ms (hold ${t}). Nothing was spent. Raise the budget, reset its window, or lift the hold, then re-drive the run.`), this.name = "BudgetHoldExpired", this.budget = e, this.holdKey = t;
35
+ }
36
+ }, C = 6048e5, w = 9e5, T = (t) => r.gen(function* () {
37
+ x();
38
+ let c = t.timeoutMs ?? 6048e5, u = t.recheckEveryMs ?? 9e5, d = {
39
+ executionId: t.executionId,
40
+ stepName: t.stepName,
41
+ budget: t.budget
42
+ };
43
+ for (let f = 0;; f++) {
44
+ let p = yield* r.promise(() => D(t.store, d)), m = p.length === 0 ? Date.now() : E(p[0]?.heldAt), g = Number.isFinite(c) ? c - (Date.now() - m) : Infinity, y = v({
45
+ ...d,
46
+ generation: f
47
+ });
48
+ if (g <= 0) return yield* r.promise(() => O(t.store, d, "expired")), yield* r.fail(new S(t.budget, y, c));
49
+ let b = _(y);
50
+ yield* r.promise(() => t.store.insertIgnore(h, {
51
+ id: s({
52
+ kind: "typeid",
53
+ prefix: "bhd"
54
+ }, h),
55
+ holdKey: y,
56
+ budget: t.budget,
57
+ tenantId: t.tenantId,
58
+ executionId: t.executionId,
59
+ workflowName: t.workflowName,
60
+ signalName: b,
61
+ stepName: t.stepName,
62
+ spentMicroUsd: t.spentMicroUsd === void 0 ? null : String(Math.round(t.spentMicroUsd)),
63
+ limitMicroUsd: t.limitMicroUsd === void 0 ? null : String(Math.round(t.limitMicroUsd)),
64
+ heldAt: /* @__PURE__ */ new Date(),
65
+ releasedAt: null,
66
+ outcome: null
67
+ }, { conflictColumns: ["holdKey"] }));
68
+ let x = Math.min(u, g);
69
+ if (yield* e({ store: t.store }, {
70
+ name: b,
71
+ schema: i.Unknown,
72
+ timeoutMs: x
73
+ }).pipe(r.catchCause((e) => n.hasInterruptsOnly(e) ? r.failCause(e) : r.void)), yield* r.promise(() => t.store.updateMany(h, {
74
+ releasedAt: /* @__PURE__ */ new Date(),
75
+ outcome: "recheck"
76
+ }, { where: a(o("holdKey", y), l("releasedAt")) })), yield* t.hasHeadroom) return;
77
+ }
78
+ }), E = (e) => e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : Date.now(), D = async (e, t) => [...await e.query({
79
+ table: h,
80
+ predicate: a(o("executionId", t.executionId), o("stepName", t.stepName), o("budget", t.budget)),
81
+ order: []
82
+ })].sort((e, t) => String(e.id).localeCompare(String(t.id))), O = async (e, t, n) => {
83
+ await e.updateMany(h, {
84
+ releasedAt: /* @__PURE__ */ new Date(),
85
+ outcome: n
86
+ }, { where: a(o("executionId", t.executionId), o("stepName", t.stepName), o("budget", t.budget), l("releasedAt")) });
87
+ }, k = (e) => r.gen(function* () {
88
+ let n = e.tenantId === void 0 ? a(o("budget", e.budget), l("releasedAt")) : a(o("budget", e.budget), o("tenantId", e.tenantId), l("releasedAt")), i = yield* r.promise(() => e.store.query({
89
+ table: h,
90
+ predicate: n,
91
+ order: []
92
+ })), s = 0;
93
+ for (let n of i) (yield* r.promise(() => e.store.updateMany("_voltro_budget_holds", {
94
+ releasedAt: /* @__PURE__ */ new Date(),
95
+ outcome: e.outcome ?? "released"
96
+ }, { where: a(o("id", String(n.id)), l("releasedAt")) }))) === 1 && (yield* t({
97
+ target: {
98
+ executionId: String(n.executionId),
99
+ workflowName: String(n.workflowName)
100
+ },
101
+ signalName: String(n.signalName),
102
+ payload: {
103
+ released: !0,
104
+ budget: e.budget
105
+ }
106
+ }).pipe(r.catch(() => r.succeed({ completed: !1 }))), s += 1);
107
+ return { released: s };
108
+ }), A = async (e, t) => {
109
+ let n = t?.budget === void 0 ? l("releasedAt") : a(o("budget", t.budget), l("releasedAt"));
110
+ return (await e.query({
111
+ table: h,
112
+ predicate: n,
113
+ order: []
114
+ })).map((e) => ({
115
+ id: String(e.id),
116
+ holdKey: String(e.holdKey),
117
+ budget: String(e.budget),
118
+ tenantId: e.tenantId ?? null,
119
+ executionId: String(e.executionId),
120
+ workflowName: String(e.workflowName),
121
+ stepName: String(e.stepName),
122
+ heldAt: e.heldAt instanceof Date ? e.heldAt.toISOString() : String(e.heldAt ?? "")
123
+ }));
124
+ }, j = async (e) => {
125
+ let t = await A(e), n = /* @__PURE__ */ new Map();
126
+ for (let e of t) {
127
+ let t = `${e.budget}\u0000${e.tenantId ?? ""}`;
128
+ n.has(t) || n.set(t, {
129
+ budget: e.budget,
130
+ tenantId: e.tenantId
131
+ });
132
+ }
133
+ return [...n.values()];
134
+ };
135
+ //#endregion
136
+ export { h as BUDGET_HOLDS_TABLE, S as BudgetHoldExpired, C as DEFAULT_BUDGET_HOLD_TIMEOUT_MS, w as DEFAULT_BUDGET_RECHECK_MS, b as _voltroBudgetHoldsTable, y as budgetHoldGeneration, v as budgetHoldKey, _ as budgetHoldSignalName, j as heldBudgets, A as pendingBudgetHolds, k as releaseBudgetHolds, T as suspendForBudget };