@voltro/workflow 0.80.0 → 0.82.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 +1610 -6
- package/README.md +4 -0
- package/dist/cluster-MboOAwcv.js +604 -0
- package/dist/cluster.d.ts +576 -26
- package/dist/cluster.js +2 -2
- package/dist/clusterDiagnostics-Dc7yBP3b.js +2069 -0
- package/dist/clusterTestSuite.d.ts +10 -0
- package/dist/clusterTestSuite.js +309 -117
- package/dist/index.d.ts +279 -115
- package/dist/index.js +5 -4
- package/dist/journalEncryption--n5VDdYO.js +148 -0
- package/dist/primitives-Btjhjm8P.js +483 -0
- package/dist/primitives.d.ts +28 -72
- package/dist/primitives.js +2 -2
- package/dist/src-BAN5Xovf.js +3860 -0
- package/package.json +4 -4
- package/dist/cluster-DJittOqj.js +0 -489
- package/dist/clusterDiagnostics-Cwt-WFec.js +0 -395
- package/dist/primitives-bv2JHUfk.js +0 -442
- package/dist/src-C8oxXvy6.js +0 -4974
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConfigError } from 'effect';
|
|
2
2
|
import { Layer } from 'effect';
|
|
3
|
+
import { MysqlCharset } from '@voltro/database/sql';
|
|
3
4
|
import { Sharding } from '@effect/cluster';
|
|
4
5
|
import { SqlClient } from '@effect/sql';
|
|
5
6
|
import { SqlError } from '@effect/sql';
|
|
@@ -71,6 +72,8 @@ export declare const purgeClusterState: (fixture: ClusterEngineFixture) => Promi
|
|
|
71
72
|
*/
|
|
72
73
|
export declare const runClusterEngineSuite: (fixture: ClusterEngineFixture) => void;
|
|
73
74
|
|
|
75
|
+
export declare const runJournalEncryptionSuite: (fixture: ClusterEngineFixture) => void;
|
|
76
|
+
|
|
74
77
|
/**
|
|
75
78
|
* Runner-storage backend for `@effect/cluster.SingleRunner`.
|
|
76
79
|
*
|
|
@@ -145,6 +148,13 @@ declare interface WorkflowEngineLayerOptions {
|
|
|
145
148
|
readonly shardLockMode?: ShardLockMode;
|
|
146
149
|
readonly runnerListenHost?: string;
|
|
147
150
|
readonly runnerListenPort?: number;
|
|
151
|
+
/**
|
|
152
|
+
* The declared store charset (`store: { dialect, charset }`, mysql family).
|
|
153
|
+
* The engine's tables are created by the library with no character set named;
|
|
154
|
+
* the migration gate converts the ones it creates to this, so a workflow input
|
|
155
|
+
* with a 4-byte character is not refused on a `utf8mb3` server.
|
|
156
|
+
*/
|
|
157
|
+
readonly charset?: MysqlCharset | undefined;
|
|
148
158
|
/**
|
|
149
159
|
* FAILOVER TUNING — the two knobs that govern how fast a SURVIVING replica
|
|
150
160
|
* takes over a CRASHED replica's in-flight workflows. Reclaim is a
|
package/dist/clusterTestSuite.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { F as e,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { F as e, R as t, S as n, g as r, v as i } from "./primitives-Btjhjm8P.js";
|
|
2
|
+
import { E as a, a as o } from "./journalEncryption--n5VDdYO.js";
|
|
3
|
+
import { C as s, r as c, w as l } from "./src-BAN5Xovf.js";
|
|
4
|
+
import { t as u, u as d } from "./cluster-MboOAwcv.js";
|
|
5
|
+
import { Cause as f, Cron as p, Deferred as m, Effect as h, Fiber as g, Layer as _, Schema as v } from "effect";
|
|
6
|
+
import { SqlClient as y } from "@effect/sql";
|
|
7
|
+
import { afterEach as b, beforeAll as x, describe as S, expect as C, test as w } from "vitest";
|
|
7
8
|
//#region src/clusterTestSuite.ts
|
|
8
|
-
var
|
|
9
|
+
var T = async (e, t, n = 750) => {
|
|
9
10
|
try {
|
|
10
11
|
let r = await import("node:net");
|
|
11
12
|
return await new Promise((i) => {
|
|
@@ -21,140 +22,140 @@ var y = async (e, t, n = 750) => {
|
|
|
21
22
|
} catch {
|
|
22
23
|
return !1;
|
|
23
24
|
}
|
|
24
|
-
},
|
|
25
|
+
}, E = [
|
|
25
26
|
"cluster_messages",
|
|
26
27
|
"cluster_replies",
|
|
27
28
|
"cluster_locks",
|
|
28
29
|
"cluster_runners"
|
|
29
|
-
],
|
|
30
|
+
], D = async (e) => {
|
|
30
31
|
try {
|
|
31
|
-
await
|
|
32
|
-
let e = yield*
|
|
33
|
-
for (let t of
|
|
34
|
-
}).pipe(
|
|
32
|
+
await h.runPromise(h.gen(function* () {
|
|
33
|
+
let e = yield* y.SqlClient;
|
|
34
|
+
for (let t of E) yield* e.unsafe(`DELETE FROM ${t}`).pipe(h.ignore);
|
|
35
|
+
}).pipe(h.scoped, h.provide(e.sqlClientLayer)));
|
|
35
36
|
} catch {}
|
|
36
|
-
},
|
|
37
|
-
let n = yield*
|
|
37
|
+
}, O = "10 seconds", k = "30 seconds", A = (e, t) => h.gen(function* () {
|
|
38
|
+
let n = yield* h.sync(() => Date.now() + t);
|
|
38
39
|
for (; !e();) {
|
|
39
40
|
if (Date.now() > n) return !1;
|
|
40
|
-
yield*
|
|
41
|
+
yield* h.sleep("250 millis");
|
|
41
42
|
}
|
|
42
43
|
return !0;
|
|
43
|
-
}),
|
|
44
|
-
let
|
|
45
|
-
sqlClientLayer:
|
|
46
|
-
runnerStorage:
|
|
44
|
+
}), j = (a) => {
|
|
45
|
+
let o = `${a.name}-${Date.now()}`, s = (e, t) => d({
|
|
46
|
+
sqlClientLayer: a.sqlClientLayer,
|
|
47
|
+
runnerStorage: a.runnerStorage,
|
|
47
48
|
runnerListenPort: e,
|
|
48
|
-
...
|
|
49
|
-
...
|
|
49
|
+
...a.dialectId ? { dialectId: a.dialectId } : {},
|
|
50
|
+
...a.shardLockMode ? { shardLockMode: a.shardLockMode } : {},
|
|
50
51
|
...t && t.length > 0 ? { extraShardingLayers: t } : {}
|
|
51
52
|
});
|
|
52
|
-
|
|
53
|
-
let
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
console.warn(`[skip] cluster suite — ${
|
|
53
|
+
S(`cluster workflow engine — ${a.name}`, () => {
|
|
54
|
+
let l = !1;
|
|
55
|
+
x(async () => {
|
|
56
|
+
if (l = await a.reachable(), !l) {
|
|
57
|
+
console.warn(`[skip] cluster suite — ${a.name} not reachable`);
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
59
|
-
await
|
|
60
|
-
}, 3e4),
|
|
61
|
-
if (!
|
|
62
|
-
let e = `clusterTwoStep_${
|
|
60
|
+
await D(a);
|
|
61
|
+
}, 3e4), w("a two-step workflow runs end-to-end", async () => {
|
|
62
|
+
if (!l) return;
|
|
63
|
+
let e = `clusterTwoStep_${o}`, t = n({
|
|
63
64
|
name: e,
|
|
64
|
-
payload: { id:
|
|
65
|
-
success:
|
|
65
|
+
payload: { id: v.String },
|
|
66
|
+
success: v.Number,
|
|
66
67
|
idempotencyKey: ({ id: t }) => `${e}:${t}`
|
|
67
|
-
}), r = t.toLayer(() =>
|
|
68
|
+
}), r = t.toLayer(() => h.gen(function* () {
|
|
68
69
|
let e = yield* i({
|
|
69
70
|
name: "first",
|
|
70
|
-
success:
|
|
71
|
-
execute:
|
|
71
|
+
success: v.Number,
|
|
72
|
+
execute: h.succeed(40)
|
|
72
73
|
});
|
|
73
74
|
return yield* i({
|
|
74
75
|
name: "second",
|
|
75
|
-
success:
|
|
76
|
-
execute:
|
|
76
|
+
success: v.Number,
|
|
77
|
+
execute: h.succeed(e + 2)
|
|
77
78
|
});
|
|
78
|
-
})),
|
|
79
|
-
|
|
80
|
-
}, 3e4),
|
|
81
|
-
if (!
|
|
82
|
-
let e = `clusterIdempotency_${
|
|
79
|
+
})), c = await h.runPromise(h.scoped(t.execute({ id: `e2e-${o}` }).pipe(h.provide(r.pipe(_.provideMerge(s(a.runnerPort)))))));
|
|
80
|
+
C(c).toBe(42);
|
|
81
|
+
}, 3e4), w("idempotency-key journal — same payload returns same result", async () => {
|
|
82
|
+
if (!l) return;
|
|
83
|
+
let e = `clusterIdempotency_${o}`, t = 0, r = n({
|
|
83
84
|
name: e,
|
|
84
|
-
payload: { id:
|
|
85
|
-
success:
|
|
85
|
+
payload: { id: v.String },
|
|
86
|
+
success: v.Number,
|
|
86
87
|
idempotencyKey: ({ id: t }) => `${e}:${t}`
|
|
87
|
-
}),
|
|
88
|
+
}), c = r.toLayer(() => i({
|
|
88
89
|
name: "compute",
|
|
89
|
-
success:
|
|
90
|
-
execute:
|
|
91
|
-
})).pipe(
|
|
92
|
-
return [yield* r.execute({ id: `same-${
|
|
93
|
-
}).pipe(
|
|
94
|
-
|
|
95
|
-
}, 3e4),
|
|
96
|
-
if (!
|
|
97
|
-
let e = await
|
|
98
|
-
name: `clusterCron_${
|
|
99
|
-
cron:
|
|
90
|
+
success: v.Number,
|
|
91
|
+
execute: h.sync(() => (t++, t * 100))
|
|
92
|
+
})).pipe(_.provideMerge(s(a.runnerPort))), [u, d] = await h.runPromise(h.scoped(h.gen(function* () {
|
|
93
|
+
return [yield* r.execute({ id: `same-${o}` }), yield* r.execute({ id: `same-${o}` })];
|
|
94
|
+
}).pipe(h.provide(c))));
|
|
95
|
+
C(u).toBe(d), C(t).toBe(1);
|
|
96
|
+
}, 3e4), w("ClusterCron fires its execute on the shard owner", async () => {
|
|
97
|
+
if (!l) return;
|
|
98
|
+
let e = await h.runPromise(m.make()), t = 0, n = u({
|
|
99
|
+
name: `clusterCron_${o}`,
|
|
100
|
+
cron: p.unsafeParse("*/1 * * * * *", "UTC"),
|
|
100
101
|
execute: async () => {
|
|
101
|
-
t += 1, await
|
|
102
|
+
t += 1, await h.runPromise(m.succeed(e, !0)).catch(() => {});
|
|
102
103
|
}
|
|
103
|
-
}), r = await
|
|
104
|
-
return yield*
|
|
104
|
+
}), r = await h.runPromise(h.scoped(h.gen(function* () {
|
|
105
|
+
return yield* _.build(s(a.runnerPort + 1, [n])), yield* m.await(e).pipe(h.timeout("25 seconds"), h.orElseSucceed(() => !1));
|
|
105
106
|
})));
|
|
106
|
-
|
|
107
|
+
C(r).toBe(!0), C(t).toBeGreaterThanOrEqual(1);
|
|
107
108
|
}, 35e3);
|
|
108
|
-
let
|
|
109
|
-
|
|
110
|
-
if (!
|
|
111
|
-
let e = `dormancyResume_${
|
|
109
|
+
let d = a.clusterResume ? w : (() => {});
|
|
110
|
+
d("a sleeping workflow resumes on a fresh runner after the first is torn down", async () => {
|
|
111
|
+
if (!l) return;
|
|
112
|
+
let e = `dormancyResume_${o}`, t = [], c = [], u = [], d = () => n({
|
|
112
113
|
name: e,
|
|
113
|
-
payload: { id:
|
|
114
|
-
success:
|
|
114
|
+
payload: { id: v.String },
|
|
115
|
+
success: v.String,
|
|
115
116
|
idempotencyKey: ({ id: t }) => `${e}:${t}`
|
|
116
|
-
}),
|
|
117
|
+
}), f = (e, n) => e.toLayer(() => h.gen(function* () {
|
|
117
118
|
return yield* i({
|
|
118
119
|
name: "step1",
|
|
119
|
-
success:
|
|
120
|
-
execute:
|
|
120
|
+
success: v.Void,
|
|
121
|
+
execute: h.sync(() => {
|
|
121
122
|
t.push(n);
|
|
122
123
|
})
|
|
123
124
|
}), yield* i({
|
|
124
125
|
name: "armed",
|
|
125
|
-
success:
|
|
126
|
-
execute:
|
|
127
|
-
|
|
126
|
+
success: v.Void,
|
|
127
|
+
execute: h.sync(() => {
|
|
128
|
+
u.push(n);
|
|
128
129
|
})
|
|
129
130
|
}), yield* r({
|
|
130
131
|
name: "nap",
|
|
131
|
-
duration:
|
|
132
|
+
duration: O
|
|
132
133
|
}), yield* i({
|
|
133
134
|
name: "step2",
|
|
134
|
-
success:
|
|
135
|
-
execute:
|
|
136
|
-
|
|
135
|
+
success: v.Void,
|
|
136
|
+
execute: h.sync(() => {
|
|
137
|
+
c.push(n);
|
|
137
138
|
})
|
|
138
139
|
}), "done";
|
|
139
|
-
})),
|
|
140
|
-
let t =
|
|
140
|
+
})), p = a.runnerPort + 2, m = (e) => {
|
|
141
|
+
let t = d();
|
|
141
142
|
return {
|
|
142
143
|
wf: t,
|
|
143
|
-
layer:
|
|
144
|
+
layer: f(t, e).pipe(_.provideMerge(s(p)))
|
|
144
145
|
};
|
|
145
|
-
},
|
|
146
|
-
let { wf: e, layer: t } =
|
|
147
|
-
return (yield*
|
|
146
|
+
}, y = { id: `resume-${o}` }, b = await h.runPromise(h.scoped(h.gen(function* () {
|
|
147
|
+
let { wf: e, layer: t } = m("A"), n = yield* _.build(t), r = yield* h.forkDaemon(e.execute(y).pipe(h.provide(n), h.ignore));
|
|
148
|
+
return (yield* A(() => u.length > 0, 6e4)) ? r : yield* h.die(/* @__PURE__ */ Error("runner A never journaled step1"));
|
|
148
149
|
})));
|
|
149
|
-
await
|
|
150
|
-
let
|
|
151
|
-
let { wf: e, layer: t } =
|
|
152
|
-
return yield* e.execute(
|
|
150
|
+
await h.runPromise(g.interrupt(b).pipe(h.ignore)), C(t).toEqual(["A"]), C(c).toEqual([]);
|
|
151
|
+
let x = await h.runPromise(h.scoped(h.gen(function* () {
|
|
152
|
+
let { wf: e, layer: t } = m("B"), n = yield* _.build(t);
|
|
153
|
+
return yield* e.execute(y).pipe(h.provide(n), h.timeout("240 seconds"), h.orElseSucceed(() => "TIMEOUT"));
|
|
153
154
|
})));
|
|
154
|
-
|
|
155
|
-
}, 48e4),
|
|
156
|
-
if (!
|
|
157
|
-
let
|
|
155
|
+
C(x).toBe("done"), C(t).toEqual(["A"]), C(c).toEqual(["B"]);
|
|
156
|
+
}, 48e4), d("dormancy loop: a wake signal survives teardown and is cleared on resume", async () => {
|
|
157
|
+
if (!l) return;
|
|
158
|
+
let u = `dormancyLoop_${o}`, d = `wr_${u}`, f = [], p = /* @__PURE__ */ new Map(), m = c({
|
|
158
159
|
store: {
|
|
159
160
|
insert: async (e, t) => t,
|
|
160
161
|
update: async () => null,
|
|
@@ -162,50 +163,241 @@ var y = async (e, t, n = 750) => {
|
|
|
162
163
|
},
|
|
163
164
|
wakeups: {
|
|
164
165
|
register: async (e, t) => {
|
|
165
|
-
|
|
166
|
+
p.set(e, { wakeAt: t });
|
|
166
167
|
},
|
|
167
168
|
cancel: async (e) => {
|
|
168
|
-
|
|
169
|
+
p.delete(e);
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
|
-
}),
|
|
172
|
-
name:
|
|
173
|
-
payload: { id:
|
|
174
|
-
success:
|
|
175
|
-
idempotencyKey: ({ id: e }) => `${
|
|
176
|
-
}),
|
|
172
|
+
}), y = () => n({
|
|
173
|
+
name: u,
|
|
174
|
+
payload: { id: v.String },
|
|
175
|
+
success: v.String,
|
|
176
|
+
idempotencyKey: ({ id: e }) => `${u}:${e}`
|
|
177
|
+
}), b = (n, a) => n.toLayer(() => h.gen(function* () {
|
|
177
178
|
return yield* i({
|
|
178
179
|
name: "step1",
|
|
179
|
-
success:
|
|
180
|
-
execute:
|
|
180
|
+
success: v.Void,
|
|
181
|
+
execute: h.void
|
|
181
182
|
}), yield* r({
|
|
182
183
|
name: "nap",
|
|
183
|
-
duration:
|
|
184
|
+
duration: k
|
|
184
185
|
}), yield* i({
|
|
185
186
|
name: "step2",
|
|
186
|
-
success:
|
|
187
|
-
execute:
|
|
188
|
-
|
|
187
|
+
success: v.Void,
|
|
188
|
+
execute: h.sync(() => {
|
|
189
|
+
f.push(a);
|
|
189
190
|
})
|
|
190
191
|
}), "done";
|
|
191
|
-
}).pipe(
|
|
192
|
-
let t =
|
|
192
|
+
}).pipe(h.locally(e, d), h.provideService(t, m))), x = a.runnerPort + 2, S = (e) => {
|
|
193
|
+
let t = y();
|
|
193
194
|
return {
|
|
194
195
|
wf: t,
|
|
195
|
-
layer:
|
|
196
|
+
layer: b(t, e).pipe(_.provideMerge(s(x)))
|
|
196
197
|
};
|
|
197
|
-
},
|
|
198
|
-
let { wf: e, layer: t } =
|
|
199
|
-
return (yield*
|
|
198
|
+
}, w = { id: `loop-${o}` }, T = await h.runPromise(h.scoped(h.gen(function* () {
|
|
199
|
+
let { wf: e, layer: t } = S("A"), n = yield* _.build(t), r = yield* h.forkDaemon(e.execute(w).pipe(h.provide(n), h.ignore));
|
|
200
|
+
return (yield* A(() => p.has(d), 6e4)) ? (yield* h.sleep("1 second"), r) : yield* h.die(/* @__PURE__ */ Error("workflow never registered a wakeup"));
|
|
200
201
|
})));
|
|
201
|
-
await
|
|
202
|
-
let
|
|
203
|
-
let { wf: e, layer: t } =
|
|
204
|
-
return yield* e.execute(
|
|
202
|
+
await h.runPromise(g.interrupt(T).pipe(h.ignore)), C(p.has(d)).toBe(!0), C(f).toEqual([]);
|
|
203
|
+
let E = await h.runPromise(h.scoped(h.gen(function* () {
|
|
204
|
+
let { wf: e, layer: t } = S("B"), n = yield* _.build(t);
|
|
205
|
+
return yield* e.execute(w).pipe(h.provide(n), h.timeout("600 seconds"), h.orElseSucceed(() => "TIMEOUT"));
|
|
205
206
|
})));
|
|
206
|
-
|
|
207
|
+
C(E).toBe("done"), C(f).toEqual(["B"]), C(p.has(d)).toBe(!1);
|
|
207
208
|
}, 9e5);
|
|
208
209
|
});
|
|
210
|
+
}, M = {
|
|
211
|
+
encrypt: (e) => `enc:v2:c:${Buffer.from(e, "utf8").toString("base64")}`,
|
|
212
|
+
decrypt: (e) => Buffer.from(e.slice(9), "base64").toString("utf8")
|
|
213
|
+
}, N = (e) => {
|
|
214
|
+
let t = `${e.name}-enc-${Date.now()}`, r = () => d({
|
|
215
|
+
sqlClientLayer: e.sqlClientLayer,
|
|
216
|
+
runnerStorage: e.runnerStorage,
|
|
217
|
+
runnerListenPort: e.runnerPort + 7,
|
|
218
|
+
...e.dialectId ? { dialectId: e.dialectId } : {}
|
|
219
|
+
}), c = (t) => h.runPromise(h.gen(function* () {
|
|
220
|
+
let e = yield* y.SqlClient, n = yield* e.unsafe("SELECT * FROM cluster_messages"), r = yield* e.unsafe("SELECT * FROM cluster_replies");
|
|
221
|
+
return [...n, ...r].filter((e) => JSON.stringify(e).includes(t)).length;
|
|
222
|
+
}).pipe(h.scoped, h.provide(e.sqlClientLayer))), u = (e, t) => {
|
|
223
|
+
let r = n({
|
|
224
|
+
name: e,
|
|
225
|
+
payload: {
|
|
226
|
+
note: v.String,
|
|
227
|
+
count: v.Number
|
|
228
|
+
},
|
|
229
|
+
success: v.Struct({ summary: v.String }),
|
|
230
|
+
idempotencyKey: ({ count: t }) => `${e}:${t}`,
|
|
231
|
+
encrypted: t
|
|
232
|
+
}), a = 0;
|
|
233
|
+
return {
|
|
234
|
+
wf: r,
|
|
235
|
+
handler: r.toLayer(({ note: e }) => h.gen(function* () {
|
|
236
|
+
return { summary: `RESULT-${yield* i({
|
|
237
|
+
name: "summarise",
|
|
238
|
+
success: v.String,
|
|
239
|
+
execute: h.sync(() => (a++, `STEP-${e}`))
|
|
240
|
+
})}` };
|
|
241
|
+
})),
|
|
242
|
+
executions: () => a
|
|
243
|
+
};
|
|
244
|
+
}, p = (e, t) => h.runPromise(h.scoped(e.wf.execute(t).pipe(h.provide(e.handler.pipe(_.provideMerge(r()))))));
|
|
245
|
+
S(`encrypted workflow journal — ${e.name}`, () => {
|
|
246
|
+
let d = !1;
|
|
247
|
+
x(async () => {
|
|
248
|
+
d = await e.reachable(), d && await D(e);
|
|
249
|
+
}, 3e4), b(() => a(void 0)), w("payload, step result and result are ciphertext; the unencrypted twin is found (control)", async (e) => {
|
|
250
|
+
d || e.skip(), a(M);
|
|
251
|
+
let n = u(`journalPlain_${t}`, !1), r = u(`journalSealed_${t}`, !0);
|
|
252
|
+
C(await p(n, {
|
|
253
|
+
note: "PLAINMARK",
|
|
254
|
+
count: 1
|
|
255
|
+
})).toEqual({ summary: "RESULT-STEP-PLAINMARK" }), C(await p(r, {
|
|
256
|
+
note: "SEALMARK",
|
|
257
|
+
count: 1
|
|
258
|
+
})).toEqual({ summary: "RESULT-STEP-SEALMARK" }), C(await c("PLAINMARK"), "the probe finds plaintext").toBeGreaterThan(0), C(await c("SEALMARK"), "no journal row carries the encrypted run in plaintext").toBe(0), C(await c("enc:v2:c:"), "and the ciphertext is there").toBeGreaterThan(0);
|
|
259
|
+
}, 6e4), w("a second execute replays the result from the encrypted journal without re-running the step", async (e) => {
|
|
260
|
+
d || e.skip(), a(M);
|
|
261
|
+
let n = u(`journalReplay_${t}`, !0), r = await p(n, {
|
|
262
|
+
note: "REPLAYMARK",
|
|
263
|
+
count: 7
|
|
264
|
+
}), i = await p(n, {
|
|
265
|
+
note: "REPLAYMARK",
|
|
266
|
+
count: 7
|
|
267
|
+
});
|
|
268
|
+
C(i).toEqual(r), C(n.executions()).toBe(1);
|
|
269
|
+
}, 6e4), w("a run journaled in plaintext before the declaration is still read", async (e) => {
|
|
270
|
+
d || e.skip();
|
|
271
|
+
let n = `journalBefore_${t}`, r = await p(u(n, !1), {
|
|
272
|
+
note: "OLDMARK",
|
|
273
|
+
count: 3
|
|
274
|
+
});
|
|
275
|
+
a(M);
|
|
276
|
+
let i = u(n, !0);
|
|
277
|
+
C(await p(i, {
|
|
278
|
+
note: "OLDMARK",
|
|
279
|
+
count: 3
|
|
280
|
+
})).toEqual(r), C(i.executions(), "answered from the plaintext journal, not re-run").toBe(0);
|
|
281
|
+
}, 6e4);
|
|
282
|
+
let m = (e, t, a) => {
|
|
283
|
+
class o extends v.TaggedError()("Refused", { detail: v.String }) {}
|
|
284
|
+
let s = n({
|
|
285
|
+
name: e,
|
|
286
|
+
payload: { note: v.String },
|
|
287
|
+
error: o,
|
|
288
|
+
idempotencyKey: ({ note: t }) => `${e}:${t}`,
|
|
289
|
+
encrypted: t
|
|
290
|
+
}), c = s.toLayer(({ note: e }) => i({
|
|
291
|
+
name: "call-upstream",
|
|
292
|
+
success: v.String,
|
|
293
|
+
error: o,
|
|
294
|
+
execute: a === "defect" ? h.die(/* @__PURE__ */ Error(`upstream rejected FAILMARK-${e}`)) : h.fail(new o({ detail: `FAILMARK-${e}` }))
|
|
295
|
+
}));
|
|
296
|
+
return h.runPromiseExit(h.scoped(s.execute({ note: e }).pipe(h.provide(c.pipe(_.provideMerge(r()))))));
|
|
297
|
+
};
|
|
298
|
+
for (let e of ["defect", "typed"]) w(`a ${e} failure of an encrypted run leaves no plaintext; the unencrypted twin does (control)`, async (n) => {
|
|
299
|
+
d || n.skip(), a(M);
|
|
300
|
+
let r = await m(`journalFailPlain_${e}_${t}`, !1, e);
|
|
301
|
+
C(r._tag).toBe("Failure"), C(await c(`FAILMARK-journalFailPlain_${e}`), "the probe finds plaintext").toBeGreaterThan(0);
|
|
302
|
+
let i = await m(`journalFailSealed_${e}_${t}`, !0, e);
|
|
303
|
+
C(i._tag).toBe("Failure"), C(await c(`FAILMARK-journalFailSealed_${e}`), "no journal row carries the failure in plaintext").toBe(0), C(f.pretty(i.cause)).toContain(`FAILMARK-journalFailSealed_${e}`);
|
|
304
|
+
}, 6e4);
|
|
305
|
+
w("a journaled wait value of an encrypted run is ciphertext; the unencrypted twin is found (control)", async (e) => {
|
|
306
|
+
d || e.skip(), a(M);
|
|
307
|
+
let i = (e, t) => {
|
|
308
|
+
let i = n({
|
|
309
|
+
name: e,
|
|
310
|
+
payload: { note: v.String },
|
|
311
|
+
success: v.String,
|
|
312
|
+
idempotencyKey: ({ note: t }) => `${e}:${t}`,
|
|
313
|
+
encrypted: t
|
|
314
|
+
}), a = i.toLayer(({ note: e }) => o({
|
|
315
|
+
name: "await-signal:approval",
|
|
316
|
+
success: v.String,
|
|
317
|
+
execute: h.succeed(`WAITMARK-${e}`)
|
|
318
|
+
}));
|
|
319
|
+
return h.runPromise(h.scoped(i.execute({ note: e }).pipe(h.provide(a.pipe(_.provideMerge(r()))))));
|
|
320
|
+
};
|
|
321
|
+
C(await i(`journalWaitPlain_${t}`, !1)).toBe(`WAITMARK-journalWaitPlain_${t}`), C(await c(`WAITMARK-journalWaitPlain_${t}`), "the probe finds plaintext").toBeGreaterThan(0), C(await i(`journalWaitSealed_${t}`, !0)).toBe(`WAITMARK-journalWaitSealed_${t}`), C(await c(`WAITMARK-journalWaitSealed_${t}`), "no plaintext wait value in the journal").toBe(0);
|
|
322
|
+
}, 6e4);
|
|
323
|
+
let g = (e, t, n) => h.gen(function* () {
|
|
324
|
+
for (let r = 0; r < 400; r++) {
|
|
325
|
+
let r = yield* e.poll(t);
|
|
326
|
+
if (n(r)) return r;
|
|
327
|
+
yield* h.sleep("50 millis");
|
|
328
|
+
}
|
|
329
|
+
return yield* h.dieMessage("pollUntil: never satisfied");
|
|
330
|
+
}), y = (e) => e?._tag;
|
|
331
|
+
for (let e of [!1, !0]) w(`a suspending signal's value is ${e ? "ciphertext" : "found (control)"} in the journal, and the run resumes with it`, async (i) => {
|
|
332
|
+
d || i.skip(), a(M);
|
|
333
|
+
let o = `journalSignal_${e ? "sealed" : "plain"}_${t}`, u = n({
|
|
334
|
+
name: o,
|
|
335
|
+
payload: { id: v.String },
|
|
336
|
+
success: v.Unknown,
|
|
337
|
+
idempotencyKey: ({ id: e }) => `${o}:${e}`,
|
|
338
|
+
encrypted: e
|
|
339
|
+
}), f = u.toLayer(() => s({ store: { query: () => Promise.resolve([]) } }, {
|
|
340
|
+
name: "approval",
|
|
341
|
+
schema: v.Struct({ comment: v.String }),
|
|
342
|
+
timeoutMs: 36e5
|
|
343
|
+
})), p = `SIGNALMARK-${o}`, m = await h.runPromise(h.scoped(h.gen(function* () {
|
|
344
|
+
let e = yield* u.executionId({ id: "one" });
|
|
345
|
+
return yield* u.execute({ id: "one" }, { discard: !0 }), yield* g(u, e, (e) => y(e) === "Suspended"), yield* l({
|
|
346
|
+
target: {
|
|
347
|
+
executionId: e,
|
|
348
|
+
workflowName: o
|
|
349
|
+
},
|
|
350
|
+
signalName: "approval",
|
|
351
|
+
payload: { comment: p }
|
|
352
|
+
}), yield* g(u, e, (e) => y(e) === "Complete");
|
|
353
|
+
}).pipe(h.provide(f.pipe(_.provideMerge(r()))))));
|
|
354
|
+
C(JSON.stringify(m.exit), "the body received the value").toContain(p);
|
|
355
|
+
let b = await c(p);
|
|
356
|
+
e ? C(b, "no plaintext signal value in the journal").toBe(0) : C(b, "the probe finds plaintext").toBeGreaterThan(0);
|
|
357
|
+
}, 9e4);
|
|
358
|
+
w("an encrypted child's defect reaches its encrypted parent as thrown, and no journal row carries it", async (e) => {
|
|
359
|
+
d || e.skip(), a(M);
|
|
360
|
+
let i = `journalChild_${t}`, o = `journalParent_${t}`, s = `CHILDMARK-${t}`, l = n({
|
|
361
|
+
name: i,
|
|
362
|
+
payload: { id: v.String },
|
|
363
|
+
success: v.String,
|
|
364
|
+
idempotencyKey: ({ id: e }) => e,
|
|
365
|
+
encrypted: !0
|
|
366
|
+
}), u = n({
|
|
367
|
+
name: o,
|
|
368
|
+
payload: { id: v.String },
|
|
369
|
+
success: v.String,
|
|
370
|
+
idempotencyKey: ({ id: e }) => e,
|
|
371
|
+
encrypted: !0
|
|
372
|
+
}), f = l.toLayer(() => h.die(/* @__PURE__ */ Error(`child failed ${s}`))), p = u.toLayer(({ id: e }) => l.execute({ id: `${e}-child` }).pipe(h.catchAllDefect((e) => h.succeed(`caught: ${e.message}`)))), m = await h.runPromise(h.scoped(u.execute({ id: "p1" }).pipe(h.provide(_.mergeAll(p, f).pipe(_.provideMerge(r()))))));
|
|
373
|
+
C(m).toBe(`caught: child failed ${s}`), C(await c(s)).toBe(0);
|
|
374
|
+
}, 6e4), w("an encrypted run that suspends on a failed step journals no plaintext", async (e) => {
|
|
375
|
+
d || e.skip(), a(M);
|
|
376
|
+
let o = `journalSuspendOnFailure_${t}`, s = `SOFMARK-${t}`, l = n({
|
|
377
|
+
name: o,
|
|
378
|
+
payload: { id: v.String },
|
|
379
|
+
success: v.String,
|
|
380
|
+
idempotencyKey: ({ id: e }) => e,
|
|
381
|
+
encrypted: !0,
|
|
382
|
+
suspendOnFailure: !0
|
|
383
|
+
}), u = l.toLayer(() => i({
|
|
384
|
+
name: "call",
|
|
385
|
+
success: v.String,
|
|
386
|
+
execute: h.die(/* @__PURE__ */ Error(`step failed ${s}`))
|
|
387
|
+
}));
|
|
388
|
+
await h.runPromise(h.scoped(h.gen(function* () {
|
|
389
|
+
let e = yield* l.executionId({ id: "s1" });
|
|
390
|
+
yield* l.execute({ id: "s1" }, { discard: !0 }), yield* g(l, e, (e) => y(e) === "Suspended");
|
|
391
|
+
}).pipe(h.provide(u.pipe(_.provideMerge(r())))))), C(await c(s)).toBe(0);
|
|
392
|
+
}, 6e4), w("without a registered cipher an encrypted run fails instead of writing plaintext", async (e) => {
|
|
393
|
+
d || e.skip();
|
|
394
|
+
let n = u(`journalNoCipher_${t}`, !0);
|
|
395
|
+
await C(p(n, {
|
|
396
|
+
note: "NOCIPHERMARK",
|
|
397
|
+
count: 1
|
|
398
|
+
})).rejects.toThrow(), C(await c("NOCIPHERMARK")).toBe(0);
|
|
399
|
+
}, 6e4);
|
|
400
|
+
});
|
|
209
401
|
};
|
|
210
402
|
//#endregion
|
|
211
|
-
export {
|
|
403
|
+
export { D as purgeClusterState, j as runClusterEngineSuite, N as runJournalEncryptionSuite, T as tcpReachable };
|