@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.
Files changed (99) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/THIRD-PARTY-NOTICES.md +3 -227
  3. package/dist/admission.js +1 -2
  4. package/dist/admissionDrainer.js +5 -3
  5. package/dist/admissionGate.js +1 -2
  6. package/dist/admissionStore.js +1 -2
  7. package/dist/admissionSubmission.js +1 -2
  8. package/dist/admissionTables.js +4 -4
  9. package/dist/admissionTransaction.js +1 -2
  10. package/dist/awaitEvent.js +1 -2
  11. package/dist/budgetHold.js +1 -2
  12. package/dist/cancelOnPlan.js +1 -2
  13. package/dist/cancelOnSweep.js +1 -2
  14. package/dist/cancelPendingStart.js +1 -2
  15. package/dist/cluster.d.ts +111 -27
  16. package/dist/cluster.js +12 -8
  17. package/dist/clusterCron.js +18 -11
  18. package/dist/clusterDiagnostics.js +1 -2
  19. package/dist/clusterDiagnosticsSql.js +1 -2
  20. package/dist/clusterLayer.js +105 -71
  21. package/dist/clusterMigrationGate.js +1 -2
  22. package/dist/clusterRunnerLayer.js +27 -18
  23. package/dist/clusterShardGroups.js +32 -0
  24. package/dist/clusterTestSuite.d.ts +45 -2
  25. package/dist/clusterTestSuite.js +225 -197
  26. package/dist/controlMonitor.js +1 -2
  27. package/dist/controlRequest.js +1 -2
  28. package/dist/executionRefusal.js +1 -2
  29. package/dist/failureHandoff.js +1 -2
  30. package/dist/failureMessage.js +1 -2
  31. package/dist/failureRecovery.js +1 -2
  32. package/dist/failureReportStore.js +1 -2
  33. package/dist/flowControl.js +1 -2
  34. package/dist/forwardStartReceipts.js +1 -2
  35. package/dist/inMemoryLayer.js +1 -2
  36. package/dist/inMemoryRecorder.js +1 -2
  37. package/dist/index.d.ts +50 -23
  38. package/dist/index.js +24 -23
  39. package/dist/inspectWorkflow.js +2 -3
  40. package/dist/journalEncryption.js +1 -2
  41. package/dist/journalRoutingGuard.js +49 -0
  42. package/dist/journalRoutingLayout.js +46 -0
  43. package/dist/journalRoutingLock.js +38 -0
  44. package/dist/journalRoutingMaintenance.js +23 -0
  45. package/dist/journalRoutingMessageStorage.js +31 -0
  46. package/dist/journalRoutingNamespace.js +22 -0
  47. package/dist/journalRoutingRuntime.js +32 -0
  48. package/dist/journalRoutingSharding.js +27 -0
  49. package/dist/journalRoutingState.js +92 -0
  50. package/dist/journalRoutingTable.js +10 -0
  51. package/dist/journalRoutingTransaction.js +15 -0
  52. package/dist/messages.js +1 -2
  53. package/dist/nondeterminism.js +1 -2
  54. package/dist/parentClosure.js +1 -2
  55. package/dist/parentClosureRecovery.js +1 -2
  56. package/dist/pendingSubmission.js +1 -2
  57. package/dist/prepareSubmissionDispatch.js +1 -2
  58. package/dist/preparedExecution.js +1 -2
  59. package/dist/primitives.js +1 -2
  60. package/dist/queuedCancellationRequest.js +1 -2
  61. package/dist/queuedControl.js +1 -2
  62. package/dist/recorder.js +1 -2
  63. package/dist/residentExecutionEvidence.js +1 -2
  64. package/dist/retryPolicy.js +1 -2
  65. package/dist/runEventsTable.js +1 -2
  66. package/dist/runGeneration.js +1 -2
  67. package/dist/runRecording.js +1 -2
  68. package/dist/runStepsTable.js +1 -2
  69. package/dist/runnerStorageGuard.js +1 -2
  70. package/dist/runsTable.js +1 -2
  71. package/dist/scheduleDeclaration.js +1 -2
  72. package/dist/signalHint.js +1 -2
  73. package/dist/signals.js +1 -2
  74. package/dist/signalsSuspending.js +1 -2
  75. package/dist/sqliteWorkflowOwnership.js +1 -2
  76. package/dist/stalenessSweep.js +1 -2
  77. package/dist/startContextsTable.js +1 -2
  78. package/dist/startReceipt.js +1 -2
  79. package/dist/submissionRecovery.js +1 -2
  80. package/dist/submissionRejection.js +1 -2
  81. package/dist/submissionRetry.js +1 -2
  82. package/dist/suspensionUnavailable.js +1 -2
  83. package/dist/updates.js +1 -2
  84. package/dist/watermarkTable.js +1 -2
  85. package/dist/webCrypto.js +4 -17
  86. package/dist/workflowDelivery.js +1 -2
  87. package/dist/workflowExecutorRouting.js +32 -0
  88. package/dist/workflowJournal.js +1 -2
  89. package/dist/workflowJournalReset.js +1 -2
  90. package/dist/workflowReadSelections.js +1 -2
  91. package/dist/workflowRestartDispatch.js +30 -25
  92. package/dist/workflowRestartObservation.js +1 -2
  93. package/dist/workflowRestartPreparation.js +1 -2
  94. package/dist/workflowRestartReservation.js +158 -157
  95. package/dist/workflowResumeDispatch.js +18 -18
  96. package/dist/workflowRuntimeReadiness.js +11 -0
  97. package/dist/workflowShardRelocation.js +57 -0
  98. package/dist/workflowShardRelocationSql.js +71 -0
  99. package/package.json +5 -11
@@ -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, Option as y, Schema as b } from "effect";
10
- import { SqlClient as x } from "effect/sql";
11
- import { afterEach as S, beforeAll as C, describe as w, expect as T, test as E } from "vitest";
12
- //#region src/clusterTestSuite.ts
13
- var D = async (e, t, n = 750) => {
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
- }, O = [
29
+ }, E = [
30
30
  "cluster_messages",
31
31
  "cluster_replies",
32
32
  "cluster_locks",
33
33
  "cluster_runners"
34
- ], k = async (e) => {
34
+ ], D = async (e) => {
35
35
  try {
36
36
  await g.runPromise(g.gen(function* () {
37
- let e = yield* x.SqlClient;
38
- for (let t of O) yield* e.unsafe(`DELETE FROM ${t}`).pipe(g.ignore);
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
- }, A = "10 seconds", j = "30 seconds", M = (e, t) => g.gen(function* () {
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
- }), N = (e) => {
49
- let t = `${e.name}-${Date.now()}`, n = (t, n) => d({
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
- w(`cluster workflow engine — ${e.name}`, () => {
58
- let c = !1;
59
- C(async () => {
60
- c = await e.reachable(), c ? await k(e) : console.warn(`[skip] cluster suite — ${e.name} not reachable`);
61
- }, 3e4), E("a two-step workflow runs end-to-end", async () => {
62
- if (!c) return;
63
- let r = `clusterTwoStep_${t}`, i = s({
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: b.String },
66
- success: b.Number,
68
+ payload: { id: x.String },
69
+ success: x.Number,
67
70
  idempotencyKey: ({ id: e }) => `${r}:${e}`
68
- }), a = i.toLayer(() => g.gen(function* () {
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: b.Number,
100
+ success: x.Number,
72
101
  execute: g.succeed(40)
73
102
  });
74
103
  return yield* o({
75
104
  name: "second",
76
- success: b.Number,
105
+ success: x.Number,
77
106
  execute: g.succeed(e + 2)
78
107
  });
79
- })), l = await g.runPromise(g.scoped(i.execute({ id: `e2e-${t}` }).pipe(g.provide(a.pipe(v.provideMerge(n(e.runnerPort)))))));
80
- T(l).toBe(42);
81
- }, 3e4), E("idempotency-key journal — same payload returns same result", async () => {
82
- if (!c) return;
83
- let r = `clusterIdempotency_${t}`, i = 0, a = s({
84
- name: r,
85
- payload: { id: b.String },
86
- success: b.Number,
87
- idempotencyKey: ({ id: e }) => `${r}:${e}`
88
- }), l = a.toLayer(() => o({
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: b.Number,
91
- execute: g.sync(() => (i++, i * 100))
92
- })).pipe(v.provideMerge(n(e.runnerPort))), [u, d] = await g.runPromise(g.scoped(g.gen(function* () {
93
- return [yield* a.execute({ id: `same-${t}` }), yield* a.execute({ id: `same-${t}` })];
94
- }).pipe(g.provide(l))));
95
- T(u).toBe(d), T(i).toBe(1);
96
- }, 3e4), E("ClusterCron fires its execute on the shard owner", async () => {
97
- if (!c) return;
98
- let r = await g.runPromise(h.make()), i = 0, a = f({
99
- name: `clusterCron_${t}`,
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
- i += 1, await g.runPromise(h.succeed(r, !0)).catch(() => {});
131
+ r += 1, await g.runPromise(h.succeed(n, !0)).catch(() => {});
103
132
  }
104
- }), o = await g.runPromise(g.scoped(g.gen(function* () {
105
- return yield* v.build(n(e.runnerPort + 1, [a])), yield* h.await(r).pipe(g.timeout("25 seconds"), g.orElseSucceed(() => !1));
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
- T(o).toBe(!0), T(i).toBeGreaterThanOrEqual(1);
136
+ l(a).toBe(!0), l(r).toBeGreaterThanOrEqual(1);
108
137
  }, 35e3);
109
- let l = e.clusterResume ? E : (() => {});
110
- l("a sleeping workflow resumes on a fresh runner after the first is torn down", async () => {
111
- if (!c) return;
112
- let r = `dormancyResume_${t}`, i = [], l = [], u = [], d = () => s({
113
- name: r,
114
- payload: { id: b.String },
115
- success: b.String,
116
- idempotencyKey: ({ id: e }) => `${r}:${e}`
117
- }), f = (e, t) => e.toLayer(() => g.gen(function* () {
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: b.Void,
149
+ success: x.Void,
121
150
  execute: g.sync(() => {
122
- i.push(t);
151
+ r.push(t);
123
152
  })
124
153
  }), yield* o({
125
154
  name: "armed",
126
- success: b.Void,
155
+ success: x.Void,
127
156
  execute: g.sync(() => {
128
- u.push(t);
157
+ c.push(t);
129
158
  })
130
159
  }), yield* a({
131
160
  name: "nap",
132
- duration: A
161
+ duration: O
133
162
  }), yield* o({
134
163
  name: "step2",
135
- success: b.Void,
164
+ success: x.Void,
136
165
  execute: g.sync(() => {
137
- l.push(t);
166
+ i.push(t);
138
167
  })
139
168
  }), "done";
140
- })), p = e.runnerPort + 2, m = (e) => {
141
- let t = d();
169
+ })), f = e.runnerPort + 2, p = (e) => {
170
+ let t = u();
142
171
  return {
143
172
  wf: t,
144
- layer: f(t, e).pipe(v.provideMerge(n(p)))
173
+ layer: d(t, e).pipe(v.provideMerge(S(f)))
145
174
  };
146
- }, h = { id: `resume-${t}` }, y = await g.runPromise(g.scoped(g.gen(function* () {
147
- 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* M(() => u.length > 0, 6e4)) ? r : yield* g.die(/* @__PURE__ */ Error("runner A never journaled step1"));
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(y).pipe(g.ignore)), T(i).toEqual(["A"]), T(l).toEqual([]);
151
- let x = await g.runPromise(g.scoped(g.gen(function* () {
152
- let { wf: e, layer: t } = m("B"), n = yield* v.build(t);
153
- return yield* e.execute(h).pipe(g.provide(n), g.timeout("240 seconds"), g.orElseSucceed(() => "TIMEOUT"));
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
- T(x).toBe("done"), T(i).toEqual(["A"]), T(l).toEqual(["B"]);
156
- }, 48e4), l("dormancy loop: a wake signal survives teardown and is cleared on resume", async () => {
157
- if (!c) return;
158
- let l = `dormancyLoop_${t}`, d = `wr_${l}`, f = [], p = /* @__PURE__ */ new Map(), m = u({
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
- p.set(e, { wakeAt: t });
195
+ f.set(e, { wakeAt: t });
167
196
  },
168
197
  cancel: async (e) => {
169
- p.delete(e);
198
+ f.delete(e);
170
199
  }
171
200
  }
172
- }), h = () => s({
173
- name: l,
174
- payload: { id: b.String },
175
- success: b.String,
176
- idempotencyKey: ({ id: e }) => `${l}:${e}`
177
- }), y = (e, t) => e.toLayer(() => g.gen(function* () {
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: b.Void,
209
+ success: x.Void,
181
210
  execute: g.void
182
211
  }), yield* a({
183
212
  name: "nap",
184
- duration: j
213
+ duration: k
185
214
  }), yield* o({
186
215
  name: "step2",
187
- success: b.Void,
216
+ success: x.Void,
188
217
  execute: g.sync(() => {
189
- f.push(t);
218
+ d.push(t);
190
219
  })
191
220
  }), "done";
192
- }).pipe(g.provideService(r, d), g.provideService(i, m))), x = e.runnerPort + 2, S = (e) => {
193
- let t = h();
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: y(t, e).pipe(v.provideMerge(n(x)))
225
+ layer: h(t, e).pipe(v.provideMerge(S(y)))
197
226
  };
198
- }, C = { id: `loop-${t}` }, w = await g.runPromise(g.scoped(g.gen(function* () {
199
- 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* M(() => p.has(d), 6e4)) ? (yield* g.sleep("1 second"), r) : yield* g.die(/* @__PURE__ */ Error("workflow never registered a wakeup"));
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(w).pipe(g.ignore)), T(p.has(d)).toBe(!0), T(f).toEqual([]);
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 } = S("B"), n = yield* v.build(t);
205
- return yield* e.execute(C).pipe(g.provide(n), g.timeout("600 seconds"), g.orElseSucceed(() => "TIMEOUT"));
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
- T(E).toBe("done"), T(f).toEqual(["B"]), T(p.has(d)).toBe(!1);
236
+ l(E).toBe("done"), l(d).toEqual(["B"]), l(f.has(c)).toBe(!1);
208
237
  }, 9e5);
209
238
  });
210
- }, P = {
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
- }, F = (r) => {
214
- let i = `${r.name}-enc-${Date.now()}`, a = () => d({
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
- }), u = (e) => g.runPromise(g.gen(function* () {
220
- let t = yield* x.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);
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(x.SafeIntegers, !0), g.scoped, g.provide(r.sqlClientLayer))), f = (e, t) => {
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: b.String,
227
- count: b.Number
255
+ note: x.String,
256
+ count: x.Number
228
257
  },
229
- success: b.Struct({ summary: b.String }),
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: b.String,
267
+ success: x.String,
239
268
  execute: g.sync(() => (r++, `STEP-${e}`))
240
269
  })}` };
241
270
  })),
242
271
  executions: () => r
243
272
  };
244
- }, m = (e, t) => g.runPromise(g.scoped(e.wf.execute(t).pipe(g.provide(e.handler.pipe(v.provideMerge(a()))))));
245
- w(`encrypted workflow journal — ${r.name}`, () => {
246
- let d = !1;
247
- C(async () => {
248
- d = await r.reachable(), d && await k(r);
249
- }, 3e4), S(() => n(void 0)), E("payload, step result and result are ciphertext; the unencrypted twin is found (control)", async (e) => {
250
- d || e.skip(), n(P);
251
- let t = f(`journalPlain_${i}`, !1), r = f(`journalSealed_${i}`, !0);
252
- T(await m(t, {
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" }), T(await m(r, {
284
+ })).toEqual({ summary: "RESULT-STEP-PLAINMARK" }), f(await T(r, {
256
285
  note: "SEALMARK",
257
286
  count: 1
258
- })).toEqual({ summary: "RESULT-STEP-SEALMARK" }), T(await u("PLAINMARK"), "the probe finds plaintext").toBeGreaterThan(0), T(await u("SEALMARK"), "no journal row carries the encrypted run in plaintext").toBe(0), T(await u("enc:v2:c:"), "and the ciphertext is there").toBeGreaterThan(0);
259
- }, 6e4), E("a second execute replays the result from the encrypted journal without re-running the step", async (e) => {
260
- d || e.skip(), n(P);
261
- let t = f(`journalReplay_${i}`, !0), r = await m(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 m(t, {
293
+ }), a = await T(t, {
265
294
  note: "REPLAYMARK",
266
295
  count: 7
267
296
  });
268
- T(a).toEqual(r), T(t.executions()).toBe(1);
269
- }, 6e4), E("a run journaled in plaintext before the declaration is still read", async (e) => {
270
- d || e.skip();
271
- let t = `journalBefore_${i}`, r = await m(f(t, !1), {
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(P);
276
- let a = f(t, !0);
277
- T(await m(a, {
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), T(a.executions(), "answered from the plaintext journal, not re-run").toBe(0);
309
+ })).toEqual(r), f(a.executions(), "answered from the plaintext journal, not re-run").toBe(0);
281
310
  }, 6e4);
282
- let h = (e, t, n) => {
283
- class r extends b.TaggedError()("Refused", { detail: b.String }) {}
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: b.String },
315
+ payload: { note: x.String },
287
316
  error: r,
288
317
  idempotencyKey: ({ note: t }) => `${e}:${t}`,
289
318
  encrypted: t
290
- }), c = i.toLayer(({ note: e }) => o({
319
+ }), a = i.toLayer(({ note: e }) => o({
291
320
  name: "call-upstream",
292
- success: b.String,
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(c.pipe(v.provideMerge(a()))))));
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"]) E(`a ${t} failure of an encrypted run leaves no plaintext; the unencrypted twin does (control)`, async (r) => {
299
- d || r.skip(), n(P);
300
- let a = await h(`journalFailPlain_${t}_${i}`, !1, t);
301
- T(a._tag).toBe("Failure"), T(await u(`FAILMARK-journalFailPlain_${t}`), "the probe finds plaintext").toBeGreaterThan(0);
302
- let o = await h(`journalFailSealed_${t}_${i}`, !0, t);
303
- T(o._tag).toBe("Failure"), T(await u(`FAILMARK-journalFailSealed_${t}`), "no journal row carries the failure in plaintext").toBe(0), T(e(p.squash(o.cause))).toContain(`FAILMARK-journalFailSealed_${t}`);
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
- E("a journaled wait value of an encrypted run is ciphertext; the unencrypted twin is found (control)", async (e) => {
306
- d || e.skip(), n(P);
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: b.String },
311
- success: b.String,
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: b.String,
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(a()))))));
348
+ return g.runPromise(g.scoped(r.execute({ note: e }).pipe(g.provide(i.pipe(v.provideMerge(y()))))));
320
349
  };
321
- T(await r(`journalWaitPlain_${i}`, !1)).toBe(`WAITMARK-journalWaitPlain_${i}`), T(await u(`WAITMARK-journalWaitPlain_${i}`), "the probe finds plaintext").toBeGreaterThan(0), T(await r(`journalWaitSealed_${i}`, !0)).toBe(`WAITMARK-journalWaitSealed_${i}`), T(await u(`WAITMARK-journalWaitSealed_${i}`), "no plaintext wait value in the journal").toBe(0);
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 _ = (e, t, n) => g.gen(function* () {
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
- }), x = (e) => y.isOption(e) && y.isSome(e) ? e.value._tag : void 0;
331
- for (let e of [!1, !0]) E(`a suspending signal's value is ${e ? "ciphertext" : "found (control)"} in the journal, and the run resumes with it`, async (t) => {
332
- d || t.skip(), n(P);
333
- let r = `journalSignal_${e ? "sealed" : "plain"}_${i}`, o = s({
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: b.String },
336
- success: b.Unknown,
364
+ payload: { id: x.String },
365
+ success: x.Unknown,
337
366
  idempotencyKey: ({ id: e }) => `${r}:${e}`,
338
367
  encrypted: e
339
- }), f = o.toLayer(() => c({ store: { query: () => Promise.resolve([]) } }, {
368
+ }), o = a.toLayer(() => c({ store: { query: () => Promise.resolve([]) } }, {
340
369
  name: "approval",
341
- schema: b.Struct({ comment: b.String }),
370
+ schema: x.Struct({ comment: x.String }),
342
371
  timeoutMs: 36e5
343
- })), p = `SIGNALMARK-${r}`, m = await g.runPromise(g.scoped(g.gen(function* () {
344
- let e = yield* o.executionId({ id: "one" });
345
- return yield* o.execute({ id: "one" }, { discard: !0 }), yield* _(o, e, (e) => x(e) === "Suspended"), yield* l({
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: p }
352
- }), yield* _(o, e, (e) => x(e) === "Complete");
353
- }).pipe(g.provide(f.pipe(v.provideMerge(a()))))));
354
- T(JSON.stringify(y.getOrThrow(m).exit), "the body received the value").toContain(p);
355
- let h = await u(p);
356
- e ? T(h, "no plaintext signal value in the journal").toBe(0) : T(h, "the probe finds plaintext").toBeGreaterThan(0);
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
- E("an encrypted child's defect reaches its encrypted parent as thrown, and no journal row carries it", async (e) => {
359
- d || e.skip(), n(P);
360
- let t = `journalChild_${i}`, r = `journalParent_${i}`, o = `CHILDMARK-${i}`, c = s({
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: b.String },
363
- success: b.String,
391
+ payload: { id: x.String },
392
+ success: x.String,
364
393
  idempotencyKey: ({ id: e }) => e,
365
394
  encrypted: !0
366
- }), l = s({
395
+ }), c = s({
367
396
  name: r,
368
- payload: { id: b.String },
369
- success: b.String,
397
+ payload: { id: x.String },
398
+ success: x.String,
370
399
  idempotencyKey: ({ id: e }) => e,
371
400
  encrypted: !0
372
- }), 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
- T(m).toBe(`caught: child failed ${o}`), T(await u(o)).toBe(0);
374
- }, 6e4), E("an encrypted run that suspends on a failed step journals no plaintext", async (e) => {
375
- d || e.skip(), n(P);
376
- let t = `journalSuspendOnFailure_${i}`, r = `SOFMARK-${i}`, c = s({
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: b.String },
379
- success: b.String,
407
+ payload: { id: x.String },
408
+ success: x.String,
380
409
  idempotencyKey: ({ id: e }) => e,
381
410
  encrypted: !0,
382
411
  suspendOnFailure: !0
383
- }), l = c.toLayer(() => o({
412
+ }), c = a.toLayer(() => o({
384
413
  name: "call",
385
- success: b.String,
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* c.executionId({ id: "s1" });
390
- yield* c.execute({ id: "s1" }, { discard: !0 }), yield* _(c, e, (e) => x(e) === "Suspended");
391
- }).pipe(g.provide(l.pipe(v.provideMerge(a())))))), T(await u(r)).toBe(0);
392
- }, 6e4), E("without a registered cipher an encrypted run fails instead of writing plaintext", async (e) => {
393
- d || e.skip();
394
- let t = f(`journalNoCipher_${i}`, !0);
395
- await T(m(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(), T(await u("NOCIPHERMARK")).toBe(0);
427
+ })).rejects.toThrow(), f(await C("NOCIPHERMARK")).toBe(0);
399
428
  }, 6e4);
400
429
  });
401
430
  };
402
- //#endregion
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 };
@@ -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
- //#region src/controlMonitor.ts
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 };