@voltro/workflow 0.86.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 +5484 -0
- package/THIRD-PARTY-NOTICES.md +3 -227
- 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/cluster.d.ts +111 -27
- package/dist/cluster.js +12 -8
- package/dist/clusterCron.js +18 -11
- package/dist/clusterDiagnostics.js +1 -2
- package/dist/clusterDiagnosticsSql.js +1 -2
- package/dist/clusterLayer.js +105 -71
- package/dist/clusterMigrationGate.js +1 -2
- package/dist/clusterRunnerLayer.js +27 -18
- package/dist/clusterShardGroups.js +32 -0
- package/dist/clusterTestSuite.d.ts +45 -2
- package/dist/clusterTestSuite.js +225 -197
- 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 +50 -23
- package/dist/index.js +24 -23
- package/dist/inspectWorkflow.js +2 -3
- package/dist/journalEncryption.js +1 -2
- package/dist/journalRoutingGuard.js +49 -0
- package/dist/journalRoutingLayout.js +46 -0
- package/dist/journalRoutingLock.js +38 -0
- package/dist/journalRoutingMaintenance.js +23 -0
- package/dist/journalRoutingMessageStorage.js +31 -0
- package/dist/journalRoutingNamespace.js +22 -0
- package/dist/journalRoutingRuntime.js +32 -0
- package/dist/journalRoutingSharding.js +27 -0
- package/dist/journalRoutingState.js +92 -0
- package/dist/journalRoutingTable.js +10 -0
- package/dist/journalRoutingTransaction.js +15 -0
- 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 +32 -0
- package/dist/workflowJournal.js +1 -2
- package/dist/workflowJournalReset.js +1 -2
- package/dist/workflowReadSelections.js +1 -2
- package/dist/workflowRestartDispatch.js +30 -25
- package/dist/workflowRestartObservation.js +1 -2
- package/dist/workflowRestartPreparation.js +1 -2
- package/dist/workflowRestartReservation.js +158 -157
- package/dist/workflowResumeDispatch.js +18 -18
- package/dist/workflowRuntimeReadiness.js +11 -0
- package/dist/workflowShardRelocation.js +57 -0
- package/dist/workflowShardRelocationSql.js +71 -0
- package/package.json +5 -11
package/dist/clusterTestSuite.js
CHANGED
|
@@ -6,11 +6,11 @@ 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 {
|
|
12
|
-
|
|
13
|
-
var
|
|
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
|
+
// src/clusterTestSuite.ts
|
|
13
|
+
var T = async (e, t, n = 750) => {
|
|
14
14
|
try {
|
|
15
15
|
let r = await import("node:net");
|
|
16
16
|
return await new Promise((i) => {
|
|
@@ -26,27 +26,27 @@ var D = async (e, t, n = 750) => {
|
|
|
26
26
|
} catch {
|
|
27
27
|
return !1;
|
|
28
28
|
}
|
|
29
|
-
},
|
|
29
|
+
}, E = [
|
|
30
30
|
"cluster_messages",
|
|
31
31
|
"cluster_replies",
|
|
32
32
|
"cluster_locks",
|
|
33
33
|
"cluster_runners"
|
|
34
|
-
],
|
|
34
|
+
], D = async (e) => {
|
|
35
35
|
try {
|
|
36
36
|
await g.runPromise(g.gen(function* () {
|
|
37
|
-
let e = yield*
|
|
38
|
-
for (let t of
|
|
37
|
+
let e = yield* S.SqlClient;
|
|
38
|
+
for (let t of E) yield* e.unsafe(`DELETE FROM ${t}`).pipe(g.ignore);
|
|
39
39
|
}).pipe(g.scoped, g.provide(e.sqlClientLayer)));
|
|
40
40
|
} catch {}
|
|
41
|
-
},
|
|
41
|
+
}, O = "10 seconds", k = "30 seconds", A = (e, t) => g.gen(function* () {
|
|
42
42
|
let n = yield* g.sync(() => Date.now() + t);
|
|
43
43
|
for (; !e();) {
|
|
44
44
|
if (Date.now() > n) return !1;
|
|
45
45
|
yield* g.sleep("250 millis");
|
|
46
46
|
}
|
|
47
47
|
return !0;
|
|
48
|
-
}),
|
|
49
|
-
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({
|
|
50
50
|
sqlClientLayer: e.sqlClientLayer,
|
|
51
51
|
runnerStorage: e.runnerStorage,
|
|
52
52
|
runnerListenPort: t,
|
|
@@ -54,108 +54,137 @@ var D = async (e, t, n = 750) => {
|
|
|
54
54
|
...e.shardLockMode ? { shardLockMode: e.shardLockMode } : {},
|
|
55
55
|
...n && n.length > 0 ? { extraShardingLayers: n } : {}
|
|
56
56
|
});
|
|
57
|
-
|
|
58
|
-
let
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}, 3e4),
|
|
62
|
-
if (!
|
|
63
|
-
|
|
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();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
let r = `roleClient_${b}`, i = s({
|
|
64
67
|
name: r,
|
|
65
|
-
payload: { id:
|
|
66
|
-
success:
|
|
68
|
+
payload: { id: x.String },
|
|
69
|
+
success: x.Number,
|
|
67
70
|
idempotencyKey: ({ id: e }) => `${r}:${e}`
|
|
68
|
-
}), a = i.toLayer(() => g.
|
|
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({
|
|
72
|
+
sqlClientLayer: e.sqlClientLayer,
|
|
73
|
+
runnerStorage: "sql",
|
|
74
|
+
runnerMode: "client",
|
|
75
|
+
runnerListenPort: e.runnerPort + 19,
|
|
76
|
+
...e.dialectId ? { dialectId: e.dialectId } : {},
|
|
77
|
+
...e.shardLockMode ? { shardLockMode: e.shardLockMode } : {}
|
|
78
|
+
}))));
|
|
79
|
+
try {
|
|
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);
|
|
83
|
+
} finally {
|
|
84
|
+
try {
|
|
85
|
+
await u.dispose();
|
|
86
|
+
} finally {
|
|
87
|
+
await c.dispose();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
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,
|
|
94
|
+
payload: { id: x.String },
|
|
95
|
+
success: x.Number,
|
|
96
|
+
idempotencyKey: ({ id: e }) => `${n}:${e}`
|
|
97
|
+
}), i = r.toLayer(() => g.gen(function* () {
|
|
69
98
|
let e = yield* o({
|
|
70
99
|
name: "first",
|
|
71
|
-
success:
|
|
100
|
+
success: x.Number,
|
|
72
101
|
execute: g.succeed(40)
|
|
73
102
|
});
|
|
74
103
|
return yield* o({
|
|
75
104
|
name: "second",
|
|
76
|
-
success:
|
|
105
|
+
success: x.Number,
|
|
77
106
|
execute: g.succeed(e + 2)
|
|
78
107
|
});
|
|
79
|
-
})),
|
|
80
|
-
|
|
81
|
-
}, 3e4),
|
|
82
|
-
if (!
|
|
83
|
-
let
|
|
84
|
-
name:
|
|
85
|
-
payload: { id:
|
|
86
|
-
success:
|
|
87
|
-
idempotencyKey: ({ id: e }) => `${
|
|
88
|
-
}),
|
|
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,
|
|
114
|
+
payload: { id: x.String },
|
|
115
|
+
success: x.Number,
|
|
116
|
+
idempotencyKey: ({ id: e }) => `${n}:${e}`
|
|
117
|
+
}), a = i.toLayer(() => o({
|
|
89
118
|
name: "compute",
|
|
90
|
-
success:
|
|
91
|
-
execute: g.sync(() => (
|
|
92
|
-
})).pipe(v.provideMerge(
|
|
93
|
-
return [yield*
|
|
94
|
-
}).pipe(g.provide(
|
|
95
|
-
|
|
96
|
-
}, 3e4),
|
|
97
|
-
if (!
|
|
98
|
-
let
|
|
99
|
-
name: `clusterCron_${
|
|
119
|
+
success: x.Number,
|
|
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}`,
|
|
100
129
|
cron: m.parseUnsafe("*/1 * * * * *", "UTC"),
|
|
101
130
|
execute: async () => {
|
|
102
|
-
|
|
131
|
+
r += 1, await g.runPromise(h.succeed(n, !0)).catch(() => {});
|
|
103
132
|
}
|
|
104
|
-
}),
|
|
105
|
-
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));
|
|
106
135
|
})));
|
|
107
|
-
|
|
136
|
+
l(a).toBe(!0), l(r).toBeGreaterThanOrEqual(1);
|
|
108
137
|
}, 35e3);
|
|
109
|
-
let
|
|
110
|
-
|
|
111
|
-
if (!
|
|
112
|
-
let
|
|
113
|
-
name:
|
|
114
|
-
payload: { id:
|
|
115
|
-
success:
|
|
116
|
-
idempotencyKey: ({ id: e }) => `${
|
|
117
|
-
}),
|
|
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,
|
|
143
|
+
payload: { id: x.String },
|
|
144
|
+
success: x.String,
|
|
145
|
+
idempotencyKey: ({ id: e }) => `${n}:${e}`
|
|
146
|
+
}), d = (e, t) => e.toLayer(() => g.gen(function* () {
|
|
118
147
|
return yield* o({
|
|
119
148
|
name: "step1",
|
|
120
|
-
success:
|
|
149
|
+
success: x.Void,
|
|
121
150
|
execute: g.sync(() => {
|
|
122
|
-
|
|
151
|
+
r.push(t);
|
|
123
152
|
})
|
|
124
153
|
}), yield* o({
|
|
125
154
|
name: "armed",
|
|
126
|
-
success:
|
|
155
|
+
success: x.Void,
|
|
127
156
|
execute: g.sync(() => {
|
|
128
|
-
|
|
157
|
+
c.push(t);
|
|
129
158
|
})
|
|
130
159
|
}), yield* a({
|
|
131
160
|
name: "nap",
|
|
132
|
-
duration:
|
|
161
|
+
duration: O
|
|
133
162
|
}), yield* o({
|
|
134
163
|
name: "step2",
|
|
135
|
-
success:
|
|
164
|
+
success: x.Void,
|
|
136
165
|
execute: g.sync(() => {
|
|
137
|
-
|
|
166
|
+
i.push(t);
|
|
138
167
|
})
|
|
139
168
|
}), "done";
|
|
140
|
-
})),
|
|
141
|
-
let t =
|
|
169
|
+
})), f = e.runnerPort + 2, p = (e) => {
|
|
170
|
+
let t = u();
|
|
142
171
|
return {
|
|
143
172
|
wf: t,
|
|
144
|
-
layer:
|
|
173
|
+
layer: d(t, e).pipe(v.provideMerge(S(f)))
|
|
145
174
|
};
|
|
146
|
-
},
|
|
147
|
-
let { wf: e, layer: t } =
|
|
148
|
-
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"));
|
|
149
178
|
})));
|
|
150
|
-
await g.runPromise(_.interrupt(
|
|
151
|
-
let
|
|
152
|
-
let { wf: e, layer: t } =
|
|
153
|
-
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"));
|
|
154
183
|
})));
|
|
155
|
-
|
|
156
|
-
}, 48e4),
|
|
157
|
-
if (!
|
|
158
|
-
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({
|
|
159
188
|
store: {
|
|
160
189
|
insert: async (e, t) => t,
|
|
161
190
|
update: async () => null,
|
|
@@ -163,70 +192,70 @@ var D = async (e, t, n = 750) => {
|
|
|
163
192
|
},
|
|
164
193
|
wakeups: {
|
|
165
194
|
register: async (e, t) => {
|
|
166
|
-
|
|
195
|
+
f.set(e, { wakeAt: t });
|
|
167
196
|
},
|
|
168
197
|
cancel: async (e) => {
|
|
169
|
-
|
|
198
|
+
f.delete(e);
|
|
170
199
|
}
|
|
171
200
|
}
|
|
172
|
-
}),
|
|
173
|
-
name:
|
|
174
|
-
payload: { id:
|
|
175
|
-
success:
|
|
176
|
-
idempotencyKey: ({ id: e }) => `${
|
|
177
|
-
}),
|
|
201
|
+
}), m = () => s({
|
|
202
|
+
name: n,
|
|
203
|
+
payload: { id: x.String },
|
|
204
|
+
success: x.String,
|
|
205
|
+
idempotencyKey: ({ id: e }) => `${n}:${e}`
|
|
206
|
+
}), h = (e, t) => e.toLayer(() => g.gen(function* () {
|
|
178
207
|
return yield* o({
|
|
179
208
|
name: "step1",
|
|
180
|
-
success:
|
|
209
|
+
success: x.Void,
|
|
181
210
|
execute: g.void
|
|
182
211
|
}), yield* a({
|
|
183
212
|
name: "nap",
|
|
184
|
-
duration:
|
|
213
|
+
duration: k
|
|
185
214
|
}), yield* o({
|
|
186
215
|
name: "step2",
|
|
187
|
-
success:
|
|
216
|
+
success: x.Void,
|
|
188
217
|
execute: g.sync(() => {
|
|
189
|
-
|
|
218
|
+
d.push(t);
|
|
190
219
|
})
|
|
191
220
|
}), "done";
|
|
192
|
-
}).pipe(g.provideService(r,
|
|
193
|
-
let t =
|
|
221
|
+
}).pipe(g.provideService(r, c), g.provideService(i, p))), y = e.runnerPort + 2, C = (e) => {
|
|
222
|
+
let t = m();
|
|
194
223
|
return {
|
|
195
224
|
wf: t,
|
|
196
|
-
layer:
|
|
225
|
+
layer: h(t, e).pipe(v.provideMerge(S(y)))
|
|
197
226
|
};
|
|
198
|
-
},
|
|
199
|
-
let { wf: e, layer: t } =
|
|
200
|
-
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"));
|
|
201
230
|
})));
|
|
202
|
-
await g.runPromise(_.interrupt(
|
|
231
|
+
await g.runPromise(_.interrupt(T).pipe(g.ignore)), l(f.has(c)).toBe(!0), l(d).toEqual([]);
|
|
203
232
|
let E = await g.runPromise(g.scoped(g.gen(function* () {
|
|
204
|
-
let { wf: e, layer: t } =
|
|
205
|
-
return yield* e.execute(
|
|
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"));
|
|
206
235
|
})));
|
|
207
|
-
|
|
236
|
+
l(E).toBe("done"), l(d).toEqual(["B"]), l(f.has(c)).toBe(!1);
|
|
208
237
|
}, 9e5);
|
|
209
238
|
});
|
|
210
|
-
},
|
|
239
|
+
}, M = {
|
|
211
240
|
encrypt: (e) => `enc:v2:c:${Buffer.from(e, "utf8").toString("base64")}`,
|
|
212
241
|
decrypt: (e) => Buffer.from(e.slice(9), "base64").toString("utf8")
|
|
213
|
-
},
|
|
214
|
-
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({
|
|
215
244
|
sqlClientLayer: r.sqlClientLayer,
|
|
216
245
|
runnerStorage: r.runnerStorage,
|
|
217
246
|
runnerListenPort: r.runnerPort + 7,
|
|
218
247
|
...r.dialectId ? { dialectId: r.dialectId } : {}
|
|
219
|
-
}),
|
|
220
|
-
let t = yield*
|
|
248
|
+
}), C = (e) => g.runPromise(g.gen(function* () {
|
|
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);
|
|
221
250
|
return [...n, ...r].filter((t) => i(t).includes(e)).length;
|
|
222
|
-
}).pipe(g.provideService(
|
|
251
|
+
}).pipe(g.provideService(S.SafeIntegers, !0), g.scoped, g.provide(r.sqlClientLayer))), w = (e, t) => {
|
|
223
252
|
let n = s({
|
|
224
253
|
name: e,
|
|
225
254
|
payload: {
|
|
226
|
-
note:
|
|
227
|
-
count:
|
|
255
|
+
note: x.String,
|
|
256
|
+
count: x.Number
|
|
228
257
|
},
|
|
229
|
-
success:
|
|
258
|
+
success: x.Struct({ summary: x.String }),
|
|
230
259
|
idempotencyKey: ({ count: t }) => `${e}:${t}`,
|
|
231
260
|
encrypted: t
|
|
232
261
|
}), r = 0;
|
|
@@ -235,169 +264,168 @@ var D = async (e, t, n = 750) => {
|
|
|
235
264
|
handler: n.toLayer(({ note: e }) => g.gen(function* () {
|
|
236
265
|
return { summary: `RESULT-${yield* o({
|
|
237
266
|
name: "summarise",
|
|
238
|
-
success:
|
|
267
|
+
success: x.String,
|
|
239
268
|
execute: g.sync(() => (r++, `STEP-${e}`))
|
|
240
269
|
})}` };
|
|
241
270
|
})),
|
|
242
271
|
executions: () => r
|
|
243
272
|
};
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
let
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}, 3e4),
|
|
250
|
-
|
|
251
|
-
let t =
|
|
252
|
-
|
|
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, {
|
|
253
282
|
note: "PLAINMARK",
|
|
254
283
|
count: 1
|
|
255
|
-
})).toEqual({ summary: "RESULT-STEP-PLAINMARK" }),
|
|
284
|
+
})).toEqual({ summary: "RESULT-STEP-PLAINMARK" }), f(await T(r, {
|
|
256
285
|
note: "SEALMARK",
|
|
257
286
|
count: 1
|
|
258
|
-
})).toEqual({ summary: "RESULT-STEP-SEALMARK" }),
|
|
259
|
-
}, 6e4),
|
|
260
|
-
|
|
261
|
-
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, {
|
|
262
291
|
note: "REPLAYMARK",
|
|
263
292
|
count: 7
|
|
264
|
-
}), a = await
|
|
293
|
+
}), a = await T(t, {
|
|
265
294
|
note: "REPLAYMARK",
|
|
266
295
|
count: 7
|
|
267
296
|
});
|
|
268
|
-
|
|
269
|
-
}, 6e4),
|
|
270
|
-
|
|
271
|
-
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), {
|
|
272
301
|
note: "OLDMARK",
|
|
273
302
|
count: 3
|
|
274
303
|
});
|
|
275
|
-
n(
|
|
276
|
-
let a =
|
|
277
|
-
|
|
304
|
+
n(M);
|
|
305
|
+
let a = w(t, !0);
|
|
306
|
+
f(await T(a, {
|
|
278
307
|
note: "OLDMARK",
|
|
279
308
|
count: 3
|
|
280
|
-
})).toEqual(r),
|
|
309
|
+
})).toEqual(r), f(a.executions(), "answered from the plaintext journal, not re-run").toBe(0);
|
|
281
310
|
}, 6e4);
|
|
282
|
-
let
|
|
283
|
-
class r extends
|
|
311
|
+
let a = (e, t, n) => {
|
|
312
|
+
class r extends x.TaggedError()("Refused", { detail: x.String }) {}
|
|
284
313
|
let i = s({
|
|
285
314
|
name: e,
|
|
286
|
-
payload: { note:
|
|
315
|
+
payload: { note: x.String },
|
|
287
316
|
error: r,
|
|
288
317
|
idempotencyKey: ({ note: t }) => `${e}:${t}`,
|
|
289
318
|
encrypted: t
|
|
290
|
-
}),
|
|
319
|
+
}), a = i.toLayer(({ note: e }) => o({
|
|
291
320
|
name: "call-upstream",
|
|
292
|
-
success:
|
|
321
|
+
success: x.String,
|
|
293
322
|
error: r,
|
|
294
323
|
execute: n === "defect" ? g.die(/* @__PURE__ */ Error(`upstream rejected FAILMARK-${e}`)) : g.fail(new r({ detail: `FAILMARK-${e}` }))
|
|
295
324
|
}));
|
|
296
|
-
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()))))));
|
|
297
326
|
};
|
|
298
|
-
for (let t of ["defect", "typed"])
|
|
299
|
-
|
|
300
|
-
let
|
|
301
|
-
|
|
302
|
-
let
|
|
303
|
-
|
|
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}`);
|
|
304
333
|
}, 6e4);
|
|
305
|
-
|
|
306
|
-
|
|
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);
|
|
307
336
|
let r = (e, n) => {
|
|
308
337
|
let r = s({
|
|
309
338
|
name: e,
|
|
310
|
-
payload: { note:
|
|
311
|
-
success:
|
|
339
|
+
payload: { note: x.String },
|
|
340
|
+
success: x.String,
|
|
312
341
|
idempotencyKey: ({ note: t }) => `${e}:${t}`,
|
|
313
342
|
encrypted: n
|
|
314
343
|
}), i = r.toLayer(({ note: e }) => t({
|
|
315
344
|
name: "await-signal:approval",
|
|
316
|
-
success:
|
|
345
|
+
success: x.String,
|
|
317
346
|
execute: g.succeed(`WAITMARK-${e}`)
|
|
318
347
|
}));
|
|
319
|
-
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()))))));
|
|
320
349
|
};
|
|
321
|
-
|
|
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);
|
|
322
351
|
}, 6e4);
|
|
323
|
-
let
|
|
352
|
+
let d = (e, t, n) => g.gen(function* () {
|
|
324
353
|
for (let r = 0; r < 400; r++) {
|
|
325
354
|
let r = yield* e.poll(t);
|
|
326
355
|
if (n(r)) return r;
|
|
327
356
|
yield* g.sleep("50 millis");
|
|
328
357
|
}
|
|
329
358
|
return yield* g.die(/* @__PURE__ */ Error("pollUntil: never satisfied"));
|
|
330
|
-
}),
|
|
331
|
-
for (let e of [!1, !0])
|
|
332
|
-
|
|
333
|
-
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({
|
|
334
363
|
name: r,
|
|
335
|
-
payload: { id:
|
|
336
|
-
success:
|
|
364
|
+
payload: { id: x.String },
|
|
365
|
+
success: x.Unknown,
|
|
337
366
|
idempotencyKey: ({ id: e }) => `${r}:${e}`,
|
|
338
367
|
encrypted: e
|
|
339
|
-
}),
|
|
368
|
+
}), o = a.toLayer(() => c({ store: { query: () => Promise.resolve([]) } }, {
|
|
340
369
|
name: "approval",
|
|
341
|
-
schema:
|
|
370
|
+
schema: x.Struct({ comment: x.String }),
|
|
342
371
|
timeoutMs: 36e5
|
|
343
|
-
})),
|
|
344
|
-
let e = yield*
|
|
345
|
-
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({
|
|
346
375
|
target: {
|
|
347
376
|
executionId: e,
|
|
348
377
|
workflowName: r
|
|
349
378
|
},
|
|
350
379
|
signalName: "approval",
|
|
351
|
-
payload: { comment:
|
|
352
|
-
}), yield*
|
|
353
|
-
}).pipe(g.provide(
|
|
354
|
-
|
|
355
|
-
let
|
|
356
|
-
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);
|
|
357
386
|
}, 9e4);
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
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({
|
|
361
390
|
name: t,
|
|
362
|
-
payload: { id:
|
|
363
|
-
success:
|
|
391
|
+
payload: { id: x.String },
|
|
392
|
+
success: x.String,
|
|
364
393
|
idempotencyKey: ({ id: e }) => e,
|
|
365
394
|
encrypted: !0
|
|
366
|
-
}),
|
|
395
|
+
}), c = s({
|
|
367
396
|
name: r,
|
|
368
|
-
payload: { id:
|
|
369
|
-
success:
|
|
397
|
+
payload: { id: x.String },
|
|
398
|
+
success: x.String,
|
|
370
399
|
idempotencyKey: ({ id: e }) => e,
|
|
371
400
|
encrypted: !0
|
|
372
|
-
}),
|
|
373
|
-
|
|
374
|
-
}, 6e4),
|
|
375
|
-
|
|
376
|
-
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({
|
|
377
406
|
name: t,
|
|
378
|
-
payload: { id:
|
|
379
|
-
success:
|
|
407
|
+
payload: { id: x.String },
|
|
408
|
+
success: x.String,
|
|
380
409
|
idempotencyKey: ({ id: e }) => e,
|
|
381
410
|
encrypted: !0,
|
|
382
411
|
suspendOnFailure: !0
|
|
383
|
-
}),
|
|
412
|
+
}), c = a.toLayer(() => o({
|
|
384
413
|
name: "call",
|
|
385
|
-
success:
|
|
414
|
+
success: x.String,
|
|
386
415
|
execute: g.die(/* @__PURE__ */ Error(`step failed ${r}`))
|
|
387
416
|
}));
|
|
388
417
|
await g.runPromise(g.scoped(g.gen(function* () {
|
|
389
|
-
let e = yield*
|
|
390
|
-
yield*
|
|
391
|
-
}).pipe(g.provide(
|
|
392
|
-
}, 6e4),
|
|
393
|
-
|
|
394
|
-
let t =
|
|
395
|
-
await T(
|
|
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, {
|
|
396
425
|
note: "NOCIPHERMARK",
|
|
397
426
|
count: 1
|
|
398
|
-
})).rejects.toThrow(),
|
|
427
|
+
})).rejects.toThrow(), f(await C("NOCIPHERMARK")).toBe(0);
|
|
399
428
|
}, 6e4);
|
|
400
429
|
});
|
|
401
430
|
};
|
|
402
|
-
|
|
403
|
-
export { k as purgeClusterState, N as runClusterEngineSuite, F as runJournalEncryptionSuite, D 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 };
|