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