@voltro/workflow 0.87.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +169 -6
- package/THIRD-PARTY-NOTICES.md +3 -1346
- package/dist/admission.js +1 -2
- package/dist/admissionDrainer.js +5 -3
- package/dist/admissionGate.js +1 -2
- package/dist/admissionStore.js +1 -2
- package/dist/admissionSubmission.js +1 -2
- package/dist/admissionTables.js +4 -4
- package/dist/admissionTransaction.js +1 -2
- package/dist/awaitEvent.js +1 -2
- package/dist/budgetHold.js +1 -2
- package/dist/cancelOnPlan.js +1 -2
- package/dist/cancelOnSweep.js +1 -2
- package/dist/cancelPendingStart.js +1 -2
- package/dist/clusterCron.js +1 -2
- package/dist/clusterDiagnostics.js +1 -2
- package/dist/clusterDiagnosticsSql.js +1 -2
- package/dist/clusterLayer.js +1 -2
- package/dist/clusterMigrationGate.js +1 -2
- package/dist/clusterRunnerLayer.js +1 -2
- package/dist/clusterShardGroups.js +1 -2
- package/dist/clusterTestSuite.d.ts +22 -2
- package/dist/clusterTestSuite.js +170 -172
- package/dist/controlMonitor.js +1 -2
- package/dist/controlRequest.js +1 -2
- package/dist/executionRefusal.js +1 -2
- package/dist/failureHandoff.js +1 -2
- package/dist/failureMessage.js +1 -2
- package/dist/failureRecovery.js +1 -2
- package/dist/failureReportStore.js +1 -2
- package/dist/flowControl.js +1 -2
- package/dist/forwardStartReceipts.js +1 -2
- package/dist/inMemoryLayer.js +1 -2
- package/dist/inMemoryRecorder.js +1 -2
- package/dist/index.d.ts +3 -1
- package/dist/inspectWorkflow.js +2 -3
- package/dist/journalEncryption.js +1 -2
- package/dist/journalRoutingGuard.js +1 -2
- package/dist/journalRoutingLayout.js +1 -2
- package/dist/journalRoutingLock.js +1 -2
- package/dist/journalRoutingMaintenance.js +1 -2
- package/dist/journalRoutingMessageStorage.js +1 -2
- package/dist/journalRoutingNamespace.js +1 -2
- package/dist/journalRoutingRuntime.js +1 -2
- package/dist/journalRoutingSharding.js +1 -2
- package/dist/journalRoutingState.js +1 -2
- package/dist/journalRoutingTable.js +1 -2
- package/dist/journalRoutingTransaction.js +2 -3
- package/dist/messages.js +1 -2
- package/dist/nondeterminism.js +1 -2
- package/dist/parentClosure.js +1 -2
- package/dist/parentClosureRecovery.js +1 -2
- package/dist/pendingSubmission.js +1 -2
- package/dist/prepareSubmissionDispatch.js +1 -2
- package/dist/preparedExecution.js +1 -2
- package/dist/primitives.js +1 -2
- package/dist/queuedCancellationRequest.js +1 -2
- package/dist/queuedControl.js +1 -2
- package/dist/recorder.js +1 -2
- package/dist/residentExecutionEvidence.js +1 -2
- package/dist/retryPolicy.js +1 -2
- package/dist/runEventsTable.js +1 -2
- package/dist/runGeneration.js +1 -2
- package/dist/runRecording.js +1 -2
- package/dist/runStepsTable.js +1 -2
- package/dist/runnerStorageGuard.js +1 -2
- package/dist/runsTable.js +1 -2
- package/dist/scheduleDeclaration.js +1 -2
- package/dist/signalHint.js +1 -2
- package/dist/signals.js +1 -2
- package/dist/signalsSuspending.js +1 -2
- package/dist/sqliteWorkflowOwnership.js +1 -2
- package/dist/stalenessSweep.js +1 -2
- package/dist/startContextsTable.js +1 -2
- package/dist/startReceipt.js +1 -2
- package/dist/submissionRecovery.js +1 -2
- package/dist/submissionRejection.js +1 -2
- package/dist/submissionRetry.js +1 -2
- package/dist/suspensionUnavailable.js +1 -2
- package/dist/updates.js +1 -2
- package/dist/watermarkTable.js +1 -2
- package/dist/webCrypto.js +4 -17
- package/dist/workflowDelivery.js +1 -2
- package/dist/workflowExecutorRouting.js +1 -2
- package/dist/workflowJournal.js +1 -2
- package/dist/workflowJournalReset.js +1 -2
- package/dist/workflowReadSelections.js +1 -2
- package/dist/workflowRestartDispatch.js +1 -2
- package/dist/workflowRestartObservation.js +1 -2
- package/dist/workflowRestartPreparation.js +1 -2
- package/dist/workflowRestartReservation.js +4 -4
- package/dist/workflowResumeDispatch.js +1 -2
- package/dist/workflowRuntimeReadiness.js +1 -2
- package/dist/workflowShardRelocation.js +1 -2
- package/dist/workflowShardRelocationSql.js +1 -2
- package/package.json +5 -11
package/dist/clusterTestSuite.js
CHANGED
|
@@ -9,9 +9,8 @@ import { makeClusterCronLayer as f } from "./clusterCron.js";
|
|
|
9
9
|
import { Cause as p, Cron as m, Deferred as h, Effect as g, Fiber as _, Layer as v, ManagedRuntime as y, Option as b, Schema as x } from "effect";
|
|
10
10
|
import { SqlClient as S } from "effect/sql";
|
|
11
11
|
import { EntityId as C, Sharding as w } from "effect/cluster";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
var A = async (e, t, n = 750) => {
|
|
12
|
+
// src/clusterTestSuite.ts
|
|
13
|
+
var T = async (e, t, n = 750) => {
|
|
15
14
|
try {
|
|
16
15
|
let r = await import("node:net");
|
|
17
16
|
return await new Promise((i) => {
|
|
@@ -27,27 +26,27 @@ var A = async (e, t, n = 750) => {
|
|
|
27
26
|
} catch {
|
|
28
27
|
return !1;
|
|
29
28
|
}
|
|
30
|
-
},
|
|
29
|
+
}, E = [
|
|
31
30
|
"cluster_messages",
|
|
32
31
|
"cluster_replies",
|
|
33
32
|
"cluster_locks",
|
|
34
33
|
"cluster_runners"
|
|
35
|
-
],
|
|
34
|
+
], D = async (e) => {
|
|
36
35
|
try {
|
|
37
36
|
await g.runPromise(g.gen(function* () {
|
|
38
37
|
let e = yield* S.SqlClient;
|
|
39
|
-
for (let t of
|
|
38
|
+
for (let t of E) yield* e.unsafe(`DELETE FROM ${t}`).pipe(g.ignore);
|
|
40
39
|
}).pipe(g.scoped, g.provide(e.sqlClientLayer)));
|
|
41
40
|
} catch {}
|
|
42
|
-
},
|
|
41
|
+
}, O = "10 seconds", k = "30 seconds", A = (e, t) => g.gen(function* () {
|
|
43
42
|
let n = yield* g.sync(() => Date.now() + t);
|
|
44
43
|
for (; !e();) {
|
|
45
44
|
if (Date.now() > n) return !1;
|
|
46
45
|
yield* g.sleep("250 millis");
|
|
47
46
|
}
|
|
48
47
|
return !0;
|
|
49
|
-
}),
|
|
50
|
-
let t = `${e.name}-${Date.now()}`,
|
|
48
|
+
}), j = (e, t) => {
|
|
49
|
+
let { describe: n, test: c, expect: l, beforeAll: p } = t, b = `${e.name}-${Date.now()}`, S = (t, n) => d({
|
|
51
50
|
sqlClientLayer: e.sqlClientLayer,
|
|
52
51
|
runnerStorage: e.runnerStorage,
|
|
53
52
|
runnerListenPort: t,
|
|
@@ -55,21 +54,21 @@ var A = async (e, t, n = 750) => {
|
|
|
55
54
|
...e.shardLockMode ? { shardLockMode: e.shardLockMode } : {},
|
|
56
55
|
...n && n.length > 0 ? { extraShardingLayers: n } : {}
|
|
57
56
|
});
|
|
58
|
-
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}, 3e4), (e.runnerStorage === "sql" && e.dialectId !== "sqlite" ?
|
|
63
|
-
if (!
|
|
64
|
-
|
|
57
|
+
n(`cluster workflow engine — ${e.name}`, () => {
|
|
58
|
+
let t = !1;
|
|
59
|
+
p(async () => {
|
|
60
|
+
t = await e.reachable(), t ? await D(e) : console.warn(`[skip] cluster suite — ${e.name} not reachable`);
|
|
61
|
+
}, 3e4), (e.runnerStorage === "sql" && e.dialectId !== "sqlite" ? c : (() => {}))("client-only submission executes on a distinct worker and never owns its shard", async (n) => {
|
|
62
|
+
if (!t) {
|
|
63
|
+
n.skip();
|
|
65
64
|
return;
|
|
66
65
|
}
|
|
67
|
-
let
|
|
68
|
-
name:
|
|
66
|
+
let r = `roleClient_${b}`, i = s({
|
|
67
|
+
name: r,
|
|
69
68
|
payload: { id: x.String },
|
|
70
69
|
success: x.Number,
|
|
71
|
-
idempotencyKey: ({ id: e }) => `${
|
|
72
|
-
}),
|
|
70
|
+
idempotencyKey: ({ id: e }) => `${r}:${e}`
|
|
71
|
+
}), a = 0, o = 0, c = y.make(i.toLayer(() => g.sync(() => (a++, 42))).pipe(v.provideMerge(S(e.runnerPort)))), u = y.make(i.toLayer(() => g.sync(() => (o++, -1))).pipe(v.provideMerge(d({
|
|
73
72
|
sqlClientLayer: e.sqlClientLayer,
|
|
74
73
|
runnerStorage: "sql",
|
|
75
74
|
runnerMode: "client",
|
|
@@ -78,24 +77,24 @@ var A = async (e, t, n = 750) => {
|
|
|
78
77
|
...e.shardLockMode ? { shardLockMode: e.shardLockMode } : {}
|
|
79
78
|
}))));
|
|
80
79
|
try {
|
|
81
|
-
await
|
|
82
|
-
let e = await
|
|
83
|
-
|
|
80
|
+
await c.runPromise(g.void), await u.runPromise(g.void);
|
|
81
|
+
let e = await u.runPromise(i.execute({ id: "from-client" }).pipe(g.timeout("60 seconds")));
|
|
82
|
+
l(e).toBe(42), l(a).toBe(1), l(o).toBe(0), l(await u.runPromise(g.map(w.Sharding, (e) => e.hasShardId(e.getShardId(C.make("client-probe"), "default"))))).toBe(!1), l(await u.runPromise(i.execute({ id: "from-client" }).pipe(g.timeout("60 seconds")))).toBe(42), l(a).toBe(1), l(o).toBe(0);
|
|
84
83
|
} finally {
|
|
85
84
|
try {
|
|
86
|
-
await f.dispose();
|
|
87
|
-
} finally {
|
|
88
85
|
await u.dispose();
|
|
86
|
+
} finally {
|
|
87
|
+
await c.dispose();
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
|
-
}, 15e4),
|
|
92
|
-
if (!
|
|
93
|
-
let
|
|
94
|
-
name:
|
|
90
|
+
}, 15e4), c("a two-step workflow runs end-to-end", async () => {
|
|
91
|
+
if (!t) return;
|
|
92
|
+
let n = `clusterTwoStep_${b}`, r = s({
|
|
93
|
+
name: n,
|
|
95
94
|
payload: { id: x.String },
|
|
96
95
|
success: x.Number,
|
|
97
|
-
idempotencyKey: ({ id: e }) => `${
|
|
98
|
-
}),
|
|
96
|
+
idempotencyKey: ({ id: e }) => `${n}:${e}`
|
|
97
|
+
}), i = r.toLayer(() => g.gen(function* () {
|
|
99
98
|
let e = yield* o({
|
|
100
99
|
name: "first",
|
|
101
100
|
success: x.Number,
|
|
@@ -106,86 +105,86 @@ var A = async (e, t, n = 750) => {
|
|
|
106
105
|
success: x.Number,
|
|
107
106
|
execute: g.succeed(e + 2)
|
|
108
107
|
});
|
|
109
|
-
})),
|
|
110
|
-
|
|
111
|
-
}, 3e4),
|
|
112
|
-
if (!
|
|
113
|
-
let
|
|
114
|
-
name:
|
|
108
|
+
})), a = await g.runPromise(g.scoped(r.execute({ id: `e2e-${b}` }).pipe(g.provide(i.pipe(v.provideMerge(S(e.runnerPort)))))));
|
|
109
|
+
l(a).toBe(42);
|
|
110
|
+
}, 3e4), c("idempotency-key journal — same payload returns same result", async () => {
|
|
111
|
+
if (!t) return;
|
|
112
|
+
let n = `clusterIdempotency_${b}`, r = 0, i = s({
|
|
113
|
+
name: n,
|
|
115
114
|
payload: { id: x.String },
|
|
116
115
|
success: x.Number,
|
|
117
|
-
idempotencyKey: ({ id: e }) => `${
|
|
118
|
-
}),
|
|
116
|
+
idempotencyKey: ({ id: e }) => `${n}:${e}`
|
|
117
|
+
}), a = i.toLayer(() => o({
|
|
119
118
|
name: "compute",
|
|
120
119
|
success: x.Number,
|
|
121
|
-
execute: g.sync(() => (
|
|
122
|
-
})).pipe(v.provideMerge(
|
|
123
|
-
return [yield*
|
|
124
|
-
}).pipe(g.provide(
|
|
125
|
-
|
|
126
|
-
}, 3e4),
|
|
127
|
-
if (!
|
|
128
|
-
let
|
|
129
|
-
name: `clusterCron_${
|
|
120
|
+
execute: g.sync(() => (r++, r * 100))
|
|
121
|
+
})).pipe(v.provideMerge(S(e.runnerPort))), [c, u] = await g.runPromise(g.scoped(g.gen(function* () {
|
|
122
|
+
return [yield* i.execute({ id: `same-${b}` }), yield* i.execute({ id: `same-${b}` })];
|
|
123
|
+
}).pipe(g.provide(a))));
|
|
124
|
+
l(c).toBe(u), l(r).toBe(1);
|
|
125
|
+
}, 3e4), c("ClusterCron fires its execute on the shard owner", async () => {
|
|
126
|
+
if (!t) return;
|
|
127
|
+
let n = await g.runPromise(h.make()), r = 0, i = f({
|
|
128
|
+
name: `clusterCron_${b}`,
|
|
130
129
|
cron: m.parseUnsafe("*/1 * * * * *", "UTC"),
|
|
131
130
|
execute: async () => {
|
|
132
|
-
|
|
131
|
+
r += 1, await g.runPromise(h.succeed(n, !0)).catch(() => {});
|
|
133
132
|
}
|
|
134
|
-
}),
|
|
135
|
-
return yield* v.build(
|
|
133
|
+
}), a = await g.runPromise(g.scoped(g.gen(function* () {
|
|
134
|
+
return yield* v.build(S(e.runnerPort + 1, [i])), yield* h.await(n).pipe(g.timeout("25 seconds"), g.orElseSucceed(() => !1));
|
|
136
135
|
})));
|
|
137
|
-
|
|
136
|
+
l(a).toBe(!0), l(r).toBeGreaterThanOrEqual(1);
|
|
138
137
|
}, 35e3);
|
|
139
|
-
let
|
|
140
|
-
|
|
141
|
-
if (!
|
|
142
|
-
let
|
|
143
|
-
name:
|
|
138
|
+
let n = e.clusterResume ? c : (() => {});
|
|
139
|
+
n("a sleeping workflow resumes on a fresh runner after the first is torn down", async () => {
|
|
140
|
+
if (!t) return;
|
|
141
|
+
let n = `dormancyResume_${b}`, r = [], i = [], c = [], u = () => s({
|
|
142
|
+
name: n,
|
|
144
143
|
payload: { id: x.String },
|
|
145
144
|
success: x.String,
|
|
146
|
-
idempotencyKey: ({ id: e }) => `${
|
|
147
|
-
}),
|
|
145
|
+
idempotencyKey: ({ id: e }) => `${n}:${e}`
|
|
146
|
+
}), d = (e, t) => e.toLayer(() => g.gen(function* () {
|
|
148
147
|
return yield* o({
|
|
149
148
|
name: "step1",
|
|
150
149
|
success: x.Void,
|
|
151
150
|
execute: g.sync(() => {
|
|
152
|
-
|
|
151
|
+
r.push(t);
|
|
153
152
|
})
|
|
154
153
|
}), yield* o({
|
|
155
154
|
name: "armed",
|
|
156
155
|
success: x.Void,
|
|
157
156
|
execute: g.sync(() => {
|
|
158
|
-
|
|
157
|
+
c.push(t);
|
|
159
158
|
})
|
|
160
159
|
}), yield* a({
|
|
161
160
|
name: "nap",
|
|
162
|
-
duration:
|
|
161
|
+
duration: O
|
|
163
162
|
}), yield* o({
|
|
164
163
|
name: "step2",
|
|
165
164
|
success: x.Void,
|
|
166
165
|
execute: g.sync(() => {
|
|
167
|
-
|
|
166
|
+
i.push(t);
|
|
168
167
|
})
|
|
169
168
|
}), "done";
|
|
170
|
-
})),
|
|
171
|
-
let t =
|
|
169
|
+
})), f = e.runnerPort + 2, p = (e) => {
|
|
170
|
+
let t = u();
|
|
172
171
|
return {
|
|
173
172
|
wf: t,
|
|
174
|
-
layer:
|
|
173
|
+
layer: d(t, e).pipe(v.provideMerge(S(f)))
|
|
175
174
|
};
|
|
176
|
-
},
|
|
177
|
-
let { wf: e, layer: t } =
|
|
178
|
-
return (yield*
|
|
175
|
+
}, m = { id: `resume-${b}` }, h = await g.runPromise(g.scoped(g.gen(function* () {
|
|
176
|
+
let { wf: e, layer: t } = p("A"), n = yield* v.build(t), r = yield* g.forkDetach(e.execute(m).pipe(g.provide(n), g.ignore));
|
|
177
|
+
return (yield* A(() => c.length > 0, 6e4)) ? r : yield* g.die(/* @__PURE__ */ Error("runner A never journaled step1"));
|
|
179
178
|
})));
|
|
180
|
-
await g.runPromise(_.interrupt(
|
|
181
|
-
let
|
|
182
|
-
let { wf: e, layer: t } =
|
|
183
|
-
return yield* e.execute(
|
|
179
|
+
await g.runPromise(_.interrupt(h).pipe(g.ignore)), l(r).toEqual(["A"]), l(i).toEqual([]);
|
|
180
|
+
let y = await g.runPromise(g.scoped(g.gen(function* () {
|
|
181
|
+
let { wf: e, layer: t } = p("B"), n = yield* v.build(t);
|
|
182
|
+
return yield* e.execute(m).pipe(g.provide(n), g.timeout("240 seconds"), g.orElseSucceed(() => "TIMEOUT"));
|
|
184
183
|
})));
|
|
185
|
-
|
|
186
|
-
}, 48e4),
|
|
187
|
-
if (!
|
|
188
|
-
let
|
|
184
|
+
l(y).toBe("done"), l(r).toEqual(["A"]), l(i).toEqual(["B"]);
|
|
185
|
+
}, 48e4), n("dormancy loop: a wake signal survives teardown and is cleared on resume", async () => {
|
|
186
|
+
if (!t) return;
|
|
187
|
+
let n = `dormancyLoop_${b}`, c = `wr_${n}`, d = [], f = /* @__PURE__ */ new Map(), p = u({
|
|
189
188
|
store: {
|
|
190
189
|
insert: async (e, t) => t,
|
|
191
190
|
update: async () => null,
|
|
@@ -193,63 +192,63 @@ var A = async (e, t, n = 750) => {
|
|
|
193
192
|
},
|
|
194
193
|
wakeups: {
|
|
195
194
|
register: async (e, t) => {
|
|
196
|
-
|
|
195
|
+
f.set(e, { wakeAt: t });
|
|
197
196
|
},
|
|
198
197
|
cancel: async (e) => {
|
|
199
|
-
|
|
198
|
+
f.delete(e);
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
|
-
}),
|
|
203
|
-
name:
|
|
201
|
+
}), m = () => s({
|
|
202
|
+
name: n,
|
|
204
203
|
payload: { id: x.String },
|
|
205
204
|
success: x.String,
|
|
206
|
-
idempotencyKey: ({ id: e }) => `${
|
|
207
|
-
}),
|
|
205
|
+
idempotencyKey: ({ id: e }) => `${n}:${e}`
|
|
206
|
+
}), h = (e, t) => e.toLayer(() => g.gen(function* () {
|
|
208
207
|
return yield* o({
|
|
209
208
|
name: "step1",
|
|
210
209
|
success: x.Void,
|
|
211
210
|
execute: g.void
|
|
212
211
|
}), yield* a({
|
|
213
212
|
name: "nap",
|
|
214
|
-
duration:
|
|
213
|
+
duration: k
|
|
215
214
|
}), yield* o({
|
|
216
215
|
name: "step2",
|
|
217
216
|
success: x.Void,
|
|
218
217
|
execute: g.sync(() => {
|
|
219
|
-
|
|
218
|
+
d.push(t);
|
|
220
219
|
})
|
|
221
220
|
}), "done";
|
|
222
|
-
}).pipe(g.provideService(r,
|
|
223
|
-
let t =
|
|
221
|
+
}).pipe(g.provideService(r, c), g.provideService(i, p))), y = e.runnerPort + 2, C = (e) => {
|
|
222
|
+
let t = m();
|
|
224
223
|
return {
|
|
225
224
|
wf: t,
|
|
226
|
-
layer:
|
|
225
|
+
layer: h(t, e).pipe(v.provideMerge(S(y)))
|
|
227
226
|
};
|
|
228
|
-
},
|
|
229
|
-
let { wf: e, layer: t } =
|
|
230
|
-
return (yield*
|
|
227
|
+
}, w = { id: `loop-${b}` }, T = await g.runPromise(g.scoped(g.gen(function* () {
|
|
228
|
+
let { wf: e, layer: t } = C("A"), n = yield* v.build(t), r = yield* g.forkDetach(e.execute(w).pipe(g.provide(n), g.ignore));
|
|
229
|
+
return (yield* A(() => f.has(c), 6e4)) ? (yield* g.sleep("1 second"), r) : yield* g.die(/* @__PURE__ */ Error("workflow never registered a wakeup"));
|
|
231
230
|
})));
|
|
232
|
-
await g.runPromise(_.interrupt(
|
|
233
|
-
let
|
|
234
|
-
let { wf: e, layer: t } =
|
|
235
|
-
return yield* e.execute(
|
|
231
|
+
await g.runPromise(_.interrupt(T).pipe(g.ignore)), l(f.has(c)).toBe(!0), l(d).toEqual([]);
|
|
232
|
+
let E = await g.runPromise(g.scoped(g.gen(function* () {
|
|
233
|
+
let { wf: e, layer: t } = C("B"), n = yield* v.build(t);
|
|
234
|
+
return yield* e.execute(w).pipe(g.provide(n), g.timeout("600 seconds"), g.orElseSucceed(() => "TIMEOUT"));
|
|
236
235
|
})));
|
|
237
|
-
|
|
236
|
+
l(E).toBe("done"), l(d).toEqual(["B"]), l(f.has(c)).toBe(!1);
|
|
238
237
|
}, 9e5);
|
|
239
238
|
});
|
|
240
|
-
},
|
|
239
|
+
}, M = {
|
|
241
240
|
encrypt: (e) => `enc:v2:c:${Buffer.from(e, "utf8").toString("base64")}`,
|
|
242
241
|
decrypt: (e) => Buffer.from(e.slice(9), "base64").toString("utf8")
|
|
243
|
-
},
|
|
244
|
-
let i = `${r.name}-enc-${Date.now()}`,
|
|
242
|
+
}, N = (r, i) => {
|
|
243
|
+
let { describe: a, test: u, expect: f, beforeAll: m, afterEach: h } = i, _ = `${r.name}-enc-${Date.now()}`, y = () => d({
|
|
245
244
|
sqlClientLayer: r.sqlClientLayer,
|
|
246
245
|
runnerStorage: r.runnerStorage,
|
|
247
246
|
runnerListenPort: r.runnerPort + 7,
|
|
248
247
|
...r.dialectId ? { dialectId: r.dialectId } : {}
|
|
249
|
-
}),
|
|
248
|
+
}), C = (e) => g.runPromise(g.gen(function* () {
|
|
250
249
|
let t = yield* S.SqlClient, n = yield* t.unsafe("SELECT * FROM cluster_messages"), r = yield* t.unsafe("SELECT * FROM cluster_replies"), i = (e) => JSON.stringify(e, (e, t) => typeof t == "bigint" ? String(t) : t);
|
|
251
250
|
return [...n, ...r].filter((t) => i(t).includes(e)).length;
|
|
252
|
-
}).pipe(g.provideService(S.SafeIntegers, !0), g.scoped, g.provide(r.sqlClientLayer))),
|
|
251
|
+
}).pipe(g.provideService(S.SafeIntegers, !0), g.scoped, g.provide(r.sqlClientLayer))), w = (e, t) => {
|
|
253
252
|
let n = s({
|
|
254
253
|
name: e,
|
|
255
254
|
payload: {
|
|
@@ -271,45 +270,45 @@ var A = async (e, t, n = 750) => {
|
|
|
271
270
|
})),
|
|
272
271
|
executions: () => r
|
|
273
272
|
};
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
let
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}, 3e4),
|
|
280
|
-
|
|
281
|
-
let t =
|
|
282
|
-
|
|
273
|
+
}, T = (e, t) => g.runPromise(g.scoped(e.wf.execute(t).pipe(g.provide(e.handler.pipe(v.provideMerge(y()))))));
|
|
274
|
+
a(`encrypted workflow journal — ${r.name}`, () => {
|
|
275
|
+
let i = !1;
|
|
276
|
+
m(async () => {
|
|
277
|
+
i = await r.reachable(), i && await D(r);
|
|
278
|
+
}, 3e4), h(() => n(void 0)), u("payload, step result and result are ciphertext; the unencrypted twin is found (control)", async (e) => {
|
|
279
|
+
i || e.skip(), n(M);
|
|
280
|
+
let t = w(`journalPlain_${_}`, !1), r = w(`journalSealed_${_}`, !0);
|
|
281
|
+
f(await T(t, {
|
|
283
282
|
note: "PLAINMARK",
|
|
284
283
|
count: 1
|
|
285
|
-
})).toEqual({ summary: "RESULT-STEP-PLAINMARK" }),
|
|
284
|
+
})).toEqual({ summary: "RESULT-STEP-PLAINMARK" }), f(await T(r, {
|
|
286
285
|
note: "SEALMARK",
|
|
287
286
|
count: 1
|
|
288
|
-
})).toEqual({ summary: "RESULT-STEP-SEALMARK" }),
|
|
289
|
-
}, 6e4),
|
|
290
|
-
|
|
291
|
-
let t =
|
|
287
|
+
})).toEqual({ summary: "RESULT-STEP-SEALMARK" }), f(await C("PLAINMARK"), "the probe finds plaintext").toBeGreaterThan(0), f(await C("SEALMARK"), "no journal row carries the encrypted run in plaintext").toBe(0), f(await C("enc:v2:c:"), "and the ciphertext is there").toBeGreaterThan(0);
|
|
288
|
+
}, 6e4), u("a second execute replays the result from the encrypted journal without re-running the step", async (e) => {
|
|
289
|
+
i || e.skip(), n(M);
|
|
290
|
+
let t = w(`journalReplay_${_}`, !0), r = await T(t, {
|
|
292
291
|
note: "REPLAYMARK",
|
|
293
292
|
count: 7
|
|
294
|
-
}), a = await
|
|
293
|
+
}), a = await T(t, {
|
|
295
294
|
note: "REPLAYMARK",
|
|
296
295
|
count: 7
|
|
297
296
|
});
|
|
298
|
-
|
|
299
|
-
}, 6e4),
|
|
300
|
-
|
|
301
|
-
let t = `journalBefore_${
|
|
297
|
+
f(a).toEqual(r), f(t.executions()).toBe(1);
|
|
298
|
+
}, 6e4), u("a run journaled in plaintext before the declaration is still read", async (e) => {
|
|
299
|
+
i || e.skip();
|
|
300
|
+
let t = `journalBefore_${_}`, r = await T(w(t, !1), {
|
|
302
301
|
note: "OLDMARK",
|
|
303
302
|
count: 3
|
|
304
303
|
});
|
|
305
|
-
n(
|
|
306
|
-
let a =
|
|
307
|
-
|
|
304
|
+
n(M);
|
|
305
|
+
let a = w(t, !0);
|
|
306
|
+
f(await T(a, {
|
|
308
307
|
note: "OLDMARK",
|
|
309
308
|
count: 3
|
|
310
|
-
})).toEqual(r),
|
|
309
|
+
})).toEqual(r), f(a.executions(), "answered from the plaintext journal, not re-run").toBe(0);
|
|
311
310
|
}, 6e4);
|
|
312
|
-
let
|
|
311
|
+
let a = (e, t, n) => {
|
|
313
312
|
class r extends x.TaggedError()("Refused", { detail: x.String }) {}
|
|
314
313
|
let i = s({
|
|
315
314
|
name: e,
|
|
@@ -317,23 +316,23 @@ var A = async (e, t, n = 750) => {
|
|
|
317
316
|
error: r,
|
|
318
317
|
idempotencyKey: ({ note: t }) => `${e}:${t}`,
|
|
319
318
|
encrypted: t
|
|
320
|
-
}),
|
|
319
|
+
}), a = i.toLayer(({ note: e }) => o({
|
|
321
320
|
name: "call-upstream",
|
|
322
321
|
success: x.String,
|
|
323
322
|
error: r,
|
|
324
323
|
execute: n === "defect" ? g.die(/* @__PURE__ */ Error(`upstream rejected FAILMARK-${e}`)) : g.fail(new r({ detail: `FAILMARK-${e}` }))
|
|
325
324
|
}));
|
|
326
|
-
return g.runPromiseExit(g.scoped(i.execute({ note: e }).pipe(g.provide(
|
|
325
|
+
return g.runPromiseExit(g.scoped(i.execute({ note: e }).pipe(g.provide(a.pipe(v.provideMerge(y()))))));
|
|
327
326
|
};
|
|
328
|
-
for (let t of ["defect", "typed"])
|
|
329
|
-
|
|
330
|
-
let
|
|
331
|
-
|
|
332
|
-
let
|
|
333
|
-
|
|
327
|
+
for (let t of ["defect", "typed"]) u(`a ${t} failure of an encrypted run leaves no plaintext; the unencrypted twin does (control)`, async (r) => {
|
|
328
|
+
i || r.skip(), n(M);
|
|
329
|
+
let o = await a(`journalFailPlain_${t}_${_}`, !1, t);
|
|
330
|
+
f(o._tag).toBe("Failure"), f(await C(`FAILMARK-journalFailPlain_${t}`), "the probe finds plaintext").toBeGreaterThan(0);
|
|
331
|
+
let s = await a(`journalFailSealed_${t}_${_}`, !0, t);
|
|
332
|
+
f(s._tag).toBe("Failure"), f(await C(`FAILMARK-journalFailSealed_${t}`), "no journal row carries the failure in plaintext").toBe(0), f(e(p.squash(s.cause))).toContain(`FAILMARK-journalFailSealed_${t}`);
|
|
334
333
|
}, 6e4);
|
|
335
|
-
|
|
336
|
-
|
|
334
|
+
u("a journaled wait value of an encrypted run is ciphertext; the unencrypted twin is found (control)", async (e) => {
|
|
335
|
+
i || e.skip(), n(M);
|
|
337
336
|
let r = (e, n) => {
|
|
338
337
|
let r = s({
|
|
339
338
|
name: e,
|
|
@@ -346,88 +345,87 @@ var A = async (e, t, n = 750) => {
|
|
|
346
345
|
success: x.String,
|
|
347
346
|
execute: g.succeed(`WAITMARK-${e}`)
|
|
348
347
|
}));
|
|
349
|
-
return g.runPromise(g.scoped(r.execute({ note: e }).pipe(g.provide(i.pipe(v.provideMerge(
|
|
348
|
+
return g.runPromise(g.scoped(r.execute({ note: e }).pipe(g.provide(i.pipe(v.provideMerge(y()))))));
|
|
350
349
|
};
|
|
351
|
-
|
|
350
|
+
f(await r(`journalWaitPlain_${_}`, !1)).toBe(`WAITMARK-journalWaitPlain_${_}`), f(await C(`WAITMARK-journalWaitPlain_${_}`), "the probe finds plaintext").toBeGreaterThan(0), f(await r(`journalWaitSealed_${_}`, !0)).toBe(`WAITMARK-journalWaitSealed_${_}`), f(await C(`WAITMARK-journalWaitSealed_${_}`), "no plaintext wait value in the journal").toBe(0);
|
|
352
351
|
}, 6e4);
|
|
353
|
-
let
|
|
352
|
+
let d = (e, t, n) => g.gen(function* () {
|
|
354
353
|
for (let r = 0; r < 400; r++) {
|
|
355
354
|
let r = yield* e.poll(t);
|
|
356
355
|
if (n(r)) return r;
|
|
357
356
|
yield* g.sleep("50 millis");
|
|
358
357
|
}
|
|
359
358
|
return yield* g.die(/* @__PURE__ */ Error("pollUntil: never satisfied"));
|
|
360
|
-
}),
|
|
361
|
-
for (let e of [!1, !0])
|
|
362
|
-
|
|
363
|
-
let r = `journalSignal_${e ? "sealed" : "plain"}_${
|
|
359
|
+
}), S = (e) => b.isOption(e) && b.isSome(e) ? e.value._tag : void 0;
|
|
360
|
+
for (let e of [!1, !0]) u(`a suspending signal's value is ${e ? "ciphertext" : "found (control)"} in the journal, and the run resumes with it`, async (t) => {
|
|
361
|
+
i || t.skip(), n(M);
|
|
362
|
+
let r = `journalSignal_${e ? "sealed" : "plain"}_${_}`, a = s({
|
|
364
363
|
name: r,
|
|
365
364
|
payload: { id: x.String },
|
|
366
365
|
success: x.Unknown,
|
|
367
366
|
idempotencyKey: ({ id: e }) => `${r}:${e}`,
|
|
368
367
|
encrypted: e
|
|
369
|
-
}),
|
|
368
|
+
}), o = a.toLayer(() => c({ store: { query: () => Promise.resolve([]) } }, {
|
|
370
369
|
name: "approval",
|
|
371
370
|
schema: x.Struct({ comment: x.String }),
|
|
372
371
|
timeoutMs: 36e5
|
|
373
|
-
})),
|
|
374
|
-
let e = yield*
|
|
375
|
-
return yield*
|
|
372
|
+
})), u = `SIGNALMARK-${r}`, p = await g.runPromise(g.scoped(g.gen(function* () {
|
|
373
|
+
let e = yield* a.executionId({ id: "one" });
|
|
374
|
+
return yield* a.execute({ id: "one" }, { discard: !0 }), yield* d(a, e, (e) => S(e) === "Suspended"), yield* l({
|
|
376
375
|
target: {
|
|
377
376
|
executionId: e,
|
|
378
377
|
workflowName: r
|
|
379
378
|
},
|
|
380
379
|
signalName: "approval",
|
|
381
|
-
payload: { comment:
|
|
382
|
-
}), yield*
|
|
383
|
-
}).pipe(g.provide(
|
|
384
|
-
|
|
385
|
-
let
|
|
386
|
-
e ?
|
|
380
|
+
payload: { comment: u }
|
|
381
|
+
}), yield* d(a, e, (e) => S(e) === "Complete");
|
|
382
|
+
}).pipe(g.provide(o.pipe(v.provideMerge(y()))))));
|
|
383
|
+
f(JSON.stringify(b.getOrThrow(p).exit), "the body received the value").toContain(u);
|
|
384
|
+
let m = await C(u);
|
|
385
|
+
e ? f(m, "no plaintext signal value in the journal").toBe(0) : f(m, "the probe finds plaintext").toBeGreaterThan(0);
|
|
387
386
|
}, 9e4);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
let t = `journalChild_${
|
|
387
|
+
u("an encrypted child's defect reaches its encrypted parent as thrown, and no journal row carries it", async (e) => {
|
|
388
|
+
i || e.skip(), n(M);
|
|
389
|
+
let t = `journalChild_${_}`, r = `journalParent_${_}`, a = `CHILDMARK-${_}`, o = s({
|
|
391
390
|
name: t,
|
|
392
391
|
payload: { id: x.String },
|
|
393
392
|
success: x.String,
|
|
394
393
|
idempotencyKey: ({ id: e }) => e,
|
|
395
394
|
encrypted: !0
|
|
396
|
-
}),
|
|
395
|
+
}), c = s({
|
|
397
396
|
name: r,
|
|
398
397
|
payload: { id: x.String },
|
|
399
398
|
success: x.String,
|
|
400
399
|
idempotencyKey: ({ id: e }) => e,
|
|
401
400
|
encrypted: !0
|
|
402
|
-
}),
|
|
403
|
-
|
|
404
|
-
}, 6e4),
|
|
405
|
-
|
|
406
|
-
let t = `journalSuspendOnFailure_${
|
|
401
|
+
}), l = o.toLayer(() => g.die(/* @__PURE__ */ Error(`child failed ${a}`))), u = c.toLayer(({ id: e }) => o.execute({ id: `${e}-child` }).pipe(g.catchDefect((e) => g.succeed(`caught: ${e.message}`)))), d = await g.runPromise(g.scoped(c.execute({ id: "p1" }).pipe(g.provide(v.mergeAll(u, l).pipe(v.provideMerge(y()))))));
|
|
402
|
+
f(d).toBe(`caught: child failed ${a}`), f(await C(a)).toBe(0);
|
|
403
|
+
}, 6e4), u("an encrypted run that suspends on a failed step journals no plaintext", async (e) => {
|
|
404
|
+
i || e.skip(), n(M);
|
|
405
|
+
let t = `journalSuspendOnFailure_${_}`, r = `SOFMARK-${_}`, a = s({
|
|
407
406
|
name: t,
|
|
408
407
|
payload: { id: x.String },
|
|
409
408
|
success: x.String,
|
|
410
409
|
idempotencyKey: ({ id: e }) => e,
|
|
411
410
|
encrypted: !0,
|
|
412
411
|
suspendOnFailure: !0
|
|
413
|
-
}),
|
|
412
|
+
}), c = a.toLayer(() => o({
|
|
414
413
|
name: "call",
|
|
415
414
|
success: x.String,
|
|
416
415
|
execute: g.die(/* @__PURE__ */ Error(`step failed ${r}`))
|
|
417
416
|
}));
|
|
418
417
|
await g.runPromise(g.scoped(g.gen(function* () {
|
|
419
|
-
let e = yield*
|
|
420
|
-
yield*
|
|
421
|
-
}).pipe(g.provide(
|
|
422
|
-
}, 6e4),
|
|
423
|
-
|
|
424
|
-
let t =
|
|
425
|
-
await
|
|
418
|
+
let e = yield* a.executionId({ id: "s1" });
|
|
419
|
+
yield* a.execute({ id: "s1" }, { discard: !0 }), yield* d(a, e, (e) => S(e) === "Suspended");
|
|
420
|
+
}).pipe(g.provide(c.pipe(v.provideMerge(y())))))), f(await C(r)).toBe(0);
|
|
421
|
+
}, 6e4), u("without a registered cipher an encrypted run fails instead of writing plaintext", async (e) => {
|
|
422
|
+
i || e.skip();
|
|
423
|
+
let t = w(`journalNoCipher_${_}`, !0);
|
|
424
|
+
await f(T(t, {
|
|
426
425
|
note: "NOCIPHERMARK",
|
|
427
426
|
count: 1
|
|
428
|
-
})).rejects.toThrow(),
|
|
427
|
+
})).rejects.toThrow(), f(await C("NOCIPHERMARK")).toBe(0);
|
|
429
428
|
}, 6e4);
|
|
430
429
|
});
|
|
431
430
|
};
|
|
432
|
-
|
|
433
|
-
export { M as purgeClusterState, I as runClusterEngineSuite, R as runJournalEncryptionSuite, A as tcpReachable };
|
|
431
|
+
export { D as purgeClusterState, j as runClusterEngineSuite, N as runJournalEncryptionSuite, T as tcpReachable };
|
package/dist/controlMonitor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readPendingWorkflowCancellation as e } from "./controlRequest.js";
|
|
2
2
|
import { Effect as t } from "effect";
|
|
3
3
|
import { WorkflowInstance as n } from "effect/workflow/WorkflowEngine";
|
|
4
|
-
|
|
4
|
+
// src/controlMonitor.ts
|
|
5
5
|
var r = (r, i, a) => t.gen(function* () {
|
|
6
6
|
let o = yield* n;
|
|
7
7
|
for (;;) {
|
|
@@ -9,5 +9,4 @@ var r = (r, i, a) => t.gen(function* () {
|
|
|
9
9
|
yield* t.sleep(1e3);
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
|
-
//#endregion
|
|
13
12
|
export { r as awaitWorkflowCancellation };
|
package/dist/controlRequest.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isWorkflowEncrypted as e, sealErrorText as t, sealJournalValue as n } from "./journalEncryption.js";
|
|
2
2
|
import { Schema as r } from "effect";
|
|
3
3
|
import { and as i, derivedRowId as a, eq as o, gt as s, or as c } from "@voltro/database";
|
|
4
|
-
|
|
4
|
+
// src/controlRequest.ts
|
|
5
5
|
var l = "_voltro_workflow_control_requests", u = r.String.pipe(r.check(r.isMinLength(1))), d = r.Struct({
|
|
6
6
|
kind: r.Literals(["confirmed", "already-terminal"]),
|
|
7
7
|
status: r.Literals([
|
|
@@ -363,5 +363,4 @@ var l = "_voltro_workflow_control_requests", u = r.String.pipe(r.check(r.isMinLe
|
|
|
363
363
|
u = r.id;
|
|
364
364
|
}
|
|
365
365
|
};
|
|
366
|
-
//#endregion
|
|
367
366
|
export { l as CONTROL_REQUESTS_TABLE, w as FLOW_CONTROL_FINISH_TIMEOUT, k as acceptWorkflowControlInTransaction, j as beginWorkflowControlDispatchInTransaction, A as bindWorkflowResumeJournalInTransaction, E as finishTimeoutCause, C as hasPendingWorkflowCancellation, O as hasUnsettledParentClosureControls, S as readPendingWorkflowCancellation, x as readPendingWorkflowCancellations, D as readWorkflowControlInTransaction, M as settleWorkflowControlsInTransaction, N as settleWorkflowResumesInTransaction };
|
package/dist/executionRefusal.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Cause as o, Effect as s, Schema as c } from "effect";
|
|
|
6
6
|
import { SubjectIdentity as l, publishServerError as u } from "@voltro/protocol";
|
|
7
7
|
import { randomUUID as d } from "node:crypto";
|
|
8
8
|
import { WorkflowInstance as f } from "effect/workflow/WorkflowEngine";
|
|
9
|
-
|
|
9
|
+
// src/executionRefusal.ts
|
|
10
10
|
var p = (p, m) => s.gen(function* () {
|
|
11
11
|
let h = yield* s.sync(() => c.decodeUnknownSync(l)(p.subject));
|
|
12
12
|
if (h.tenantId !== p.failureContext.tenantId) return yield* s.die(/* @__PURE__ */ Error("Workflow refusal has inconsistent caller tenant"));
|
|
@@ -158,5 +158,4 @@ var p = (p, m) => s.gen(function* () {
|
|
|
158
158
|
});
|
|
159
159
|
}), yield* s.failCause(m.cause);
|
|
160
160
|
});
|
|
161
|
-
//#endregion
|
|
162
161
|
export { p as recordWorkflowExecutionRefusal };
|
package/dist/failureHandoff.js
CHANGED
|
@@ -6,7 +6,7 @@ import { PENDING_TABLE as f, parkDelayedStart as p, retryBackoffMs as m } from "
|
|
|
6
6
|
import { Schema as h } from "effect";
|
|
7
7
|
import { and as g, eq as _, gt as v, isNull as y } from "@voltro/database";
|
|
8
8
|
import { createHash as b } from "node:crypto";
|
|
9
|
-
|
|
9
|
+
// src/failureHandoff.ts
|
|
10
10
|
var x = async (e, t, n) => {
|
|
11
11
|
let r, a = t === "submissionId" ? _(t, n) : g(_(t, n), y("submissionId"), y("terminal"));
|
|
12
12
|
for (;;) {
|
|
@@ -179,5 +179,4 @@ var x = async (e, t, n) => {
|
|
|
179
179
|
executionId: c.executionId
|
|
180
180
|
};
|
|
181
181
|
};
|
|
182
|
-
//#endregion
|
|
183
182
|
export { j as FailureReconciliationError, A as handoffWorkflowFailureInTransaction, w as isFailureDeliveryExecution, C as isFailureDeliveryPending, S as isFailureDeliverySubmission, M as reconcileWorkflowFailureInTransaction };
|