@voltro/workflow 0.86.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 +5321 -0
- package/THIRD-PARTY-NOTICES.md +1130 -11
- package/dist/cluster.d.ts +111 -27
- package/dist/cluster.js +12 -8
- package/dist/clusterCron.js +17 -9
- package/dist/clusterLayer.js +104 -69
- package/dist/clusterRunnerLayer.js +26 -16
- package/dist/clusterShardGroups.js +33 -0
- package/dist/clusterTestSuite.d.ts +23 -0
- package/dist/clusterTestSuite.js +142 -112
- package/dist/index.d.ts +47 -22
- package/dist/index.js +24 -23
- 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/workflowExecutorRouting.js +33 -0
- package/dist/workflowRestartDispatch.js +29 -23
- package/dist/workflowRestartReservation.js +156 -155
- package/dist/workflowResumeDispatch.js +17 -16
- 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/clusterTestSuite.js
CHANGED
|
@@ -6,11 +6,12 @@ import { awaitSignalSuspending as c, completeSuspendingSignal as l } from "./sig
|
|
|
6
6
|
import { makeWorkflowRunRecorder as u } from "./runRecording.js";
|
|
7
7
|
import { workflowEngineLayer as d } from "./clusterLayer.js";
|
|
8
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,
|
|
10
|
-
import { SqlClient as
|
|
11
|
-
import {
|
|
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";
|
|
12
13
|
//#region src/clusterTestSuite.ts
|
|
13
|
-
var
|
|
14
|
+
var A = async (e, t, n = 750) => {
|
|
14
15
|
try {
|
|
15
16
|
let r = await import("node:net");
|
|
16
17
|
return await new Promise((i) => {
|
|
@@ -26,26 +27,26 @@ var D = async (e, t, n = 750) => {
|
|
|
26
27
|
} catch {
|
|
27
28
|
return !1;
|
|
28
29
|
}
|
|
29
|
-
},
|
|
30
|
+
}, j = [
|
|
30
31
|
"cluster_messages",
|
|
31
32
|
"cluster_replies",
|
|
32
33
|
"cluster_locks",
|
|
33
34
|
"cluster_runners"
|
|
34
|
-
],
|
|
35
|
+
], M = async (e) => {
|
|
35
36
|
try {
|
|
36
37
|
await g.runPromise(g.gen(function* () {
|
|
37
|
-
let e = yield*
|
|
38
|
-
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);
|
|
39
40
|
}).pipe(g.scoped, g.provide(e.sqlClientLayer)));
|
|
40
41
|
} catch {}
|
|
41
|
-
},
|
|
42
|
+
}, N = "10 seconds", P = "30 seconds", F = (e, t) => g.gen(function* () {
|
|
42
43
|
let n = yield* g.sync(() => Date.now() + t);
|
|
43
44
|
for (; !e();) {
|
|
44
45
|
if (Date.now() > n) return !1;
|
|
45
46
|
yield* g.sleep("250 millis");
|
|
46
47
|
}
|
|
47
48
|
return !0;
|
|
48
|
-
}),
|
|
49
|
+
}), I = (e) => {
|
|
49
50
|
let t = `${e.name}-${Date.now()}`, n = (t, n) => d({
|
|
50
51
|
sqlClientLayer: e.sqlClientLayer,
|
|
51
52
|
runnerStorage: e.runnerStorage,
|
|
@@ -54,46 +55,75 @@ var D = async (e, t, n = 750) => {
|
|
|
54
55
|
...e.shardLockMode ? { shardLockMode: e.shardLockMode } : {},
|
|
55
56
|
...n && n.length > 0 ? { extraShardingLayers: n } : {}
|
|
56
57
|
});
|
|
57
|
-
|
|
58
|
+
D(`cluster workflow engine — ${e.name}`, () => {
|
|
58
59
|
let c = !1;
|
|
59
|
-
|
|
60
|
-
c = await e.reachable(), c ? await
|
|
61
|
-
}, 3e4),
|
|
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();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
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
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
98
|
}), a = i.toLayer(() => g.gen(function* () {
|
|
69
99
|
let e = yield* o({
|
|
70
100
|
name: "first",
|
|
71
|
-
success:
|
|
101
|
+
success: x.Number,
|
|
72
102
|
execute: g.succeed(40)
|
|
73
103
|
});
|
|
74
104
|
return yield* o({
|
|
75
105
|
name: "second",
|
|
76
|
-
success:
|
|
106
|
+
success: x.Number,
|
|
77
107
|
execute: g.succeed(e + 2)
|
|
78
108
|
});
|
|
79
109
|
})), l = await g.runPromise(g.scoped(i.execute({ id: `e2e-${t}` }).pipe(g.provide(a.pipe(v.provideMerge(n(e.runnerPort)))))));
|
|
80
|
-
|
|
81
|
-
}, 3e4),
|
|
110
|
+
O(l).toBe(42);
|
|
111
|
+
}, 3e4), k("idempotency-key journal — same payload returns same result", async () => {
|
|
82
112
|
if (!c) return;
|
|
83
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
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
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
128
|
let r = await g.runPromise(h.make()), i = 0, a = f({
|
|
99
129
|
name: `clusterCron_${t}`,
|
|
@@ -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 l = e.clusterResume ?
|
|
139
|
+
let l = e.clusterResume ? k : (() => {});
|
|
110
140
|
l("a sleeping workflow resumes on a fresh runner after the first is torn down", async () => {
|
|
111
141
|
if (!c) return;
|
|
112
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
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
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
160
|
}), yield* a({
|
|
131
161
|
name: "nap",
|
|
132
|
-
duration:
|
|
162
|
+
duration: N
|
|
133
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,14 +175,14 @@ 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
|
-
|
|
185
|
+
O(b).toBe("done"), O(i).toEqual(["A"]), O(l).toEqual(["B"]);
|
|
156
186
|
}, 48e4), l("dormancy loop: a wake signal survives teardown and is cleared on resume", async () => {
|
|
157
187
|
if (!c) return;
|
|
158
188
|
let l = `dormancyLoop_${t}`, d = `wr_${l}`, f = [], p = /* @__PURE__ */ new Map(), m = u({
|
|
@@ -171,62 +201,62 @@ var D = async (e, t, n = 750) => {
|
|
|
171
201
|
}
|
|
172
202
|
}), h = () => s({
|
|
173
203
|
name: l,
|
|
174
|
-
payload: { id:
|
|
175
|
-
success:
|
|
204
|
+
payload: { id: x.String },
|
|
205
|
+
success: x.String,
|
|
176
206
|
idempotencyKey: ({ id: e }) => `${l}:${e}`
|
|
177
207
|
}), y = (e, t) => e.toLayer(() => g.gen(function* () {
|
|
178
208
|
return yield* o({
|
|
179
209
|
name: "step1",
|
|
180
|
-
success:
|
|
210
|
+
success: x.Void,
|
|
181
211
|
execute: g.void
|
|
182
212
|
}), yield* a({
|
|
183
213
|
name: "nap",
|
|
184
|
-
duration:
|
|
214
|
+
duration: P
|
|
185
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
|
-
},
|
|
243
|
+
}, R = (r) => {
|
|
214
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
249
|
}), u = (e) => g.runPromise(g.gen(function* () {
|
|
220
|
-
let t = yield*
|
|
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(
|
|
252
|
+
}).pipe(g.provideService(S.SafeIntegers, !0), g.scoped, g.provide(r.sqlClientLayer))), f = (e, t) => {
|
|
223
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;
|
|
@@ -235,29 +265,29 @@ var D = async (e, t, n = 750) => {
|
|
|
235
265
|
handler: n.toLayer(({ note: e }) => g.gen(function* () {
|
|
236
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
274
|
}, m = (e, t) => g.runPromise(g.scoped(e.wf.execute(t).pipe(g.provide(e.handler.pipe(v.provideMerge(a()))))));
|
|
245
|
-
|
|
275
|
+
D(`encrypted workflow journal — ${r.name}`, () => {
|
|
246
276
|
let d = !1;
|
|
247
|
-
|
|
248
|
-
d = await r.reachable(), d && await
|
|
249
|
-
}, 3e4),
|
|
250
|
-
d || e.skip(), n(
|
|
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);
|
|
251
281
|
let t = f(`journalPlain_${i}`, !1), r = f(`journalSealed_${i}`, !0);
|
|
252
|
-
|
|
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
|
-
d || e.skip(), n(
|
|
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);
|
|
261
291
|
let t = f(`journalReplay_${i}`, !0), r = await m(t, {
|
|
262
292
|
note: "REPLAYMARK",
|
|
263
293
|
count: 7
|
|
@@ -265,60 +295,60 @@ var D = async (e, t, n = 750) => {
|
|
|
265
295
|
note: "REPLAYMARK",
|
|
266
296
|
count: 7
|
|
267
297
|
});
|
|
268
|
-
|
|
269
|
-
}, 6e4),
|
|
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) => {
|
|
270
300
|
d || e.skip();
|
|
271
301
|
let t = `journalBefore_${i}`, r = await m(f(t, !1), {
|
|
272
302
|
note: "OLDMARK",
|
|
273
303
|
count: 3
|
|
274
304
|
});
|
|
275
|
-
n(
|
|
305
|
+
n(L);
|
|
276
306
|
let a = f(t, !0);
|
|
277
|
-
|
|
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
|
|
313
|
+
class r extends x.TaggedError()("Refused", { detail: x.String }) {}
|
|
284
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
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
326
|
return g.runPromiseExit(g.scoped(i.execute({ note: e }).pipe(g.provide(c.pipe(v.provideMerge(a()))))));
|
|
297
327
|
};
|
|
298
|
-
for (let t of ["defect", "typed"])
|
|
299
|
-
d || r.skip(), n(
|
|
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);
|
|
300
330
|
let a = await h(`journalFailPlain_${t}_${i}`, !1, t);
|
|
301
|
-
|
|
331
|
+
O(a._tag).toBe("Failure"), O(await u(`FAILMARK-journalFailPlain_${t}`), "the probe finds plaintext").toBeGreaterThan(0);
|
|
302
332
|
let o = await h(`journalFailSealed_${t}_${i}`, !0, t);
|
|
303
|
-
|
|
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
|
-
d || e.skip(), n(
|
|
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);
|
|
307
337
|
let r = (e, n) => {
|
|
308
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
343
|
encrypted: n
|
|
314
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
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 e of [!1, !0])
|
|
332
|
-
d || t.skip(), n(
|
|
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);
|
|
333
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
368
|
encrypted: e
|
|
339
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
374
|
let e = yield* o.executionId({ id: "one" });
|
|
345
|
-
return yield* o.execute({ id: "one" }, { discard: !0 }), yield* _(o, e, (e) =>
|
|
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* _(o, e, (e) =>
|
|
382
|
+
}), yield* _(o, e, (e) => y(e) === "Complete");
|
|
353
383
|
}).pipe(g.provide(f.pipe(v.provideMerge(a()))))));
|
|
354
|
-
|
|
384
|
+
O(JSON.stringify(b.getOrThrow(m).exit), "the body received the value").toContain(p);
|
|
355
385
|
let h = await u(p);
|
|
356
|
-
e ?
|
|
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
|
-
d || e.skip(), n(
|
|
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);
|
|
360
390
|
let t = `journalChild_${i}`, r = `journalParent_${i}`, o = `CHILDMARK-${i}`, c = s({
|
|
361
391
|
name: t,
|
|
362
|
-
payload: { id:
|
|
363
|
-
success:
|
|
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
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()))))));
|
|
373
|
-
|
|
374
|
-
}, 6e4),
|
|
375
|
-
d || e.skip(), n(
|
|
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);
|
|
376
406
|
let t = `journalSuspendOnFailure_${i}`, r = `SOFMARK-${i}`, c = s({
|
|
377
407
|
name: t,
|
|
378
|
-
payload: { id:
|
|
379
|
-
success:
|
|
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(l.pipe(v.provideMerge(a())))))),
|
|
392
|
-
}, 6e4),
|
|
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) => {
|
|
393
423
|
d || e.skip();
|
|
394
424
|
let t = f(`journalNoCipher_${i}`, !0);
|
|
395
|
-
await
|
|
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 };
|