@voltro/workflow 0.85.0 → 0.86.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 (85) hide show
  1. package/CHANGELOG.md +666 -0
  2. package/THIRD-PARTY-NOTICES.md +5 -33
  3. package/dist/admission.js +140 -0
  4. package/dist/admissionDrainer.js +705 -0
  5. package/dist/admissionGate.js +115 -0
  6. package/dist/admissionStore.js +347 -0
  7. package/dist/admissionSubmission.js +297 -0
  8. package/dist/admissionTables.js +137 -0
  9. package/dist/admissionTransaction.js +21 -0
  10. package/dist/awaitEvent.js +82 -0
  11. package/dist/budgetHold.js +136 -0
  12. package/dist/cancelOnPlan.js +75 -0
  13. package/dist/cancelOnSweep.js +188 -0
  14. package/dist/cancelPendingStart.js +50 -0
  15. package/dist/cluster.js +8 -2
  16. package/dist/clusterCron.js +11 -0
  17. package/dist/clusterDiagnostics.js +20 -0
  18. package/dist/clusterDiagnosticsSql.js +145 -0
  19. package/dist/clusterLayer.js +110 -0
  20. package/dist/clusterMigrationGate.js +61 -0
  21. package/dist/clusterRunnerLayer.js +21 -0
  22. package/dist/clusterTestSuite.js +101 -101
  23. package/dist/controlMonitor.js +13 -0
  24. package/dist/controlRequest.js +367 -0
  25. package/dist/executionRefusal.js +162 -0
  26. package/dist/failureHandoff.js +183 -0
  27. package/dist/failureMessage.js +12 -0
  28. package/dist/failureRecovery.js +50 -0
  29. package/dist/failureReportStore.js +60 -0
  30. package/dist/flowControl.js +142 -0
  31. package/dist/forwardStartReceipts.js +41 -0
  32. package/dist/inMemoryLayer.js +7 -0
  33. package/dist/inMemoryRecorder.js +56 -0
  34. package/dist/index.d.ts +82 -8
  35. package/dist/index.js +49 -5
  36. package/dist/inspectWorkflow.js +101 -0
  37. package/dist/{journalEncryption-DTyhUzaS.js → journalEncryption.js} +30 -37
  38. package/dist/messages.js +92 -0
  39. package/dist/nondeterminism.js +51 -0
  40. package/dist/parentClosure.js +141 -0
  41. package/dist/parentClosureRecovery.js +54 -0
  42. package/dist/pendingSubmission.js +85 -0
  43. package/dist/prepareSubmissionDispatch.js +17 -0
  44. package/dist/preparedExecution.js +14 -0
  45. package/dist/primitives.js +225 -2
  46. package/dist/queuedCancellationRequest.js +364 -0
  47. package/dist/queuedControl.js +21 -0
  48. package/dist/recorder.js +11 -0
  49. package/dist/residentExecutionEvidence.js +64 -0
  50. package/dist/retryPolicy.js +57 -0
  51. package/dist/runEventsTable.js +17 -0
  52. package/dist/runGeneration.js +345 -0
  53. package/dist/runRecording.js +615 -0
  54. package/dist/runStepsTable.js +28 -0
  55. package/dist/runnerStorageGuard.js +29 -0
  56. package/dist/runsTable.js +42 -0
  57. package/dist/scheduleDeclaration.js +44 -0
  58. package/dist/signalHint.js +23 -0
  59. package/dist/signals.js +70 -0
  60. package/dist/signalsSuspending.js +57 -0
  61. package/dist/sqliteWorkflowOwnership.js +26 -0
  62. package/dist/stalenessSweep.js +191 -0
  63. package/dist/startContextsTable.js +20 -0
  64. package/dist/startReceipt.js +143 -0
  65. package/dist/submissionRecovery.js +122 -0
  66. package/dist/submissionRejection.js +10 -0
  67. package/dist/submissionRetry.js +71 -0
  68. package/dist/suspensionUnavailable.js +9 -0
  69. package/dist/updates.js +87 -0
  70. package/dist/watermarkTable.js +9 -0
  71. package/dist/webCrypto.js +17 -0
  72. package/dist/workflowDelivery.js +24 -0
  73. package/dist/workflowJournal.js +124 -0
  74. package/dist/workflowJournalReset.js +111 -0
  75. package/dist/workflowReadSelections.js +12 -0
  76. package/dist/workflowRestartDispatch.js +29 -0
  77. package/dist/workflowRestartObservation.js +60 -0
  78. package/dist/workflowRestartPreparation.js +109 -0
  79. package/dist/workflowRestartReservation.js +426 -0
  80. package/dist/workflowResumeDispatch.js +29 -0
  81. package/package.json +4 -4
  82. package/dist/cluster-F_bhqVP7.js +0 -679
  83. package/dist/primitives-DX04WTWh.js +0 -507
  84. package/dist/src-C2NEyUM8.js +0 -3925
  85. package/dist/workflowReadSelections-CZAUjP7f.js +0 -2096
@@ -0,0 +1,61 @@
1
+ import { webCryptoLayer as e } from "./webCrypto.js";
2
+ import { Effect as t, Layer as n } from "effect";
3
+ import { SqlClient as r } from "effect/sql";
4
+ import { ShardingConfig as i, SqlMessageStorage as a, SqlRunnerStorage as o } from "effect/cluster";
5
+ import { applyUndeclaredCharsetWork as s, fnv1a64 as c, readUndeclaredCharsetColumns as l, resolveMigrationLockSchema as u, sqlPoolCapacity as d, undeclaredCharsetWork as f } from "@voltro/database/sql";
6
+ //#region src/clusterMigrationGate.ts
7
+ var p = 6322741009312601n, m = () => {
8
+ let e = u();
9
+ return e === void 0 ? p : c(`voltro_cluster_migration_lock:${e}`) & 9223372036854775807n;
10
+ }, h = () => {
11
+ let e = `voltro_cluster_migration_${p.toString(36)}`, t = u();
12
+ if (t === void 0) return e;
13
+ let n = `${e}_${t}`;
14
+ return /^[A-Za-z0-9_]+$/.test(t) && n.length <= 64 ? n : `${e}_h${c(`voltro_cluster_migration_lock:${t}`).toString(16)}`;
15
+ }, g = () => {
16
+ let e = Number(process.env.VOLTRO_CLUSTER_MIGRATION_LOCK_TIMEOUT_MS);
17
+ return Number.isFinite(e) && e > 0 ? e : 6e4;
18
+ }, _ = 250, v = (e) => e[0]?.[0], y = (e, n) => e.onDialectOrElse({
19
+ sqlite: () => t.void,
20
+ mysql: () => n.executeValues(`SELECT GET_LOCK('${h()}', ${Math.ceil(g() / 1e3)})`, []).pipe(t.flatMap((e) => v(e) === 1 || v(e) === "1" ? t.void : t.die(/* @__PURE__ */ Error(`could not acquire cluster migration lock '${h()}' (GET_LOCK timed out)`)))),
21
+ mssql: () => n.executeValues(`DECLARE @res int; EXEC @res = sp_getapplock @Resource = '${h()}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = ${g()}; SELECT @res`, []).pipe(t.flatMap((e) => Number(v(e) ?? -999) >= 0 ? t.void : t.die(/* @__PURE__ */ Error(`could not acquire cluster migration lock '${h()}' (sp_getapplock ${String(v(e))})`)))),
22
+ orElse: () => {
23
+ let e = m().toString(), r = Date.now(), i = n.executeValues(`SELECT pg_try_advisory_lock(${e}::bigint)`, []).pipe(t.flatMap((e) => {
24
+ let n = v(e);
25
+ return n === !0 || n === "t" || n === 1 ? t.void : Date.now() - r >= g() ? t.die(/* @__PURE__ */ Error(`could not acquire the cluster migration advisory lock within ${Math.round(g() / 1e3)}s. Another runner is migrating the cluster schema, or a prior boot wedged while holding it. (Raise VOLTRO_CLUSTER_MIGRATION_LOCK_TIMEOUT_MS, or inspect pg_locks for the holder.)`)) : t.sleep(`${_} millis`).pipe(t.andThen(i));
26
+ }));
27
+ return i;
28
+ }
29
+ }), b = (e, n) => e.onDialectOrElse({
30
+ sqlite: () => t.void,
31
+ mysql: () => t.asVoid(n.executeValues(`SELECT RELEASE_LOCK('${h()}')`, [])),
32
+ mssql: () => t.asVoid(n.executeValues(`EXEC sp_releaseapplock @Resource = '${h()}', @LockOwner = 'Session'`, [])),
33
+ orElse: () => t.asVoid(n.executeValues(`SELECT pg_advisory_unlock(${m().toString()}::bigint)`, []))
34
+ }), x = (e, n) => e.onDialectOrElse({
35
+ sqlite: () => !0,
36
+ orElse: () => !1
37
+ }) ? n : t.scoped(t.gen(function* () {
38
+ let r = yield* e.reserve;
39
+ return yield* t.acquireRelease(y(e, r), () => b(e, r).pipe(t.orDie)), yield* n;
40
+ })), S = (e) => e === "sql" ? ["cluster_messages", "cluster_runners"] : ["cluster_messages"], C = (e, n) => e.onDialectOrElse({
41
+ pg: () => e`SELECT (to_regclass(${n}) IS NOT NULL) AS present`,
42
+ mysql: () => e`SELECT (COUNT(*) > 0) AS present FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ${n}`,
43
+ mssql: () => e`SELECT CASE WHEN OBJECT_ID(${n}, 'U') IS NULL THEN 0 ELSE 1 END AS present`,
44
+ orElse: () => e`SELECT (COUNT(*) > 0) AS present FROM sqlite_master WHERE type = 'table' AND name = ${n}`
45
+ }).pipe(t.map((e) => {
46
+ let t = e[0]?.present;
47
+ return t === !0 || t === 1 || t === "1" || t === "t" || typeof t == "bigint" && t === 1n;
48
+ }), t.catch(() => t.succeed(!1))), w = (e, n) => t.forEach(S(n), (t) => C(e, t), { concurrency: 1 }).pipe(t.map((e) => e.every(Boolean))), T = (a, o) => t.scoped(t.asVoid(n.build(o.pipe(n.provide(i.layerDefaults), n.provide(n.succeed(r.SqlClient, a)), n.provide(e))))), E = (e, n) => T(e, a.layer).pipe(t.andThen(n === "sql" ? T(e, o.layer) : t.void)), D = (e) => t.map(e.unsafe("SELECT table_name AS t FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name LIKE 'cluster\\_%'"), (e) => new Set(e.map((e) => String(e.t ?? e.T)))), O = (e, n, r) => r === void 0 || !e.onDialectOrElse({
49
+ mysql: () => !0,
50
+ orElse: () => !1
51
+ }) ? E(e, n) : t.gen(function* () {
52
+ let t = yield* D(e);
53
+ yield* E(e, n);
54
+ let i = new Set([...yield* D(e)].filter((e) => !t.has(e)));
55
+ i.size !== 0 && (yield* s(e, f(yield* l(e), /* @__PURE__ */ new Set(), r, i)));
56
+ }), k = (e) => e === "sql" ? 3 : 2, A = (e) => n.effect(r.SqlClient, t.gen(function* () {
57
+ let n = yield* r.SqlClient, i = k(e.runnerStorage), a = d(n);
58
+ return a !== void 0 && a < i ? yield* t.die(/* @__PURE__ */ Error(`the workflow runtime needs ${i} database connections at once and the pool has ${a}: ` + (e.runnerStorage === "sql" ? "on a first boot it holds its migration lock and its runner lock while the migration runs on a third, and once running it keeps the runner lock and needs one more to work. " : "on a first boot it holds its migration lock while the migration runs on a second. ") + `Set DB_MAX_CONNECTIONS (or PG_MAX_CONNECTIONS) to at least ${i}` + (e.runnerStorage === "sql" ? ", or VOLTRO_WORKFLOW_RUNNER_STORAGE=memory for a single-process runtime that needs 2 (no cross-pod shard handoff)." : "."))) : ((yield* w(n, e.runnerStorage)) || (yield* x(n, O(n, e.runnerStorage, e.charset))), n);
59
+ }));
60
+ //#endregion
61
+ export { k as clusterConnectionFloor, A as clusterMigrationGateLayer, C as tableExists, x as withClusterMigrationLock };
@@ -0,0 +1,21 @@
1
+ import { webCryptoLayer as e } from "./webCrypto.js";
2
+ import { guardRunnerStorage as t } from "./runnerStorageGuard.js";
3
+ import { Effect as n, Layer as r, Semaphore as i } from "effect";
4
+ import { SqlClient as a } from "effect/sql";
5
+ import { RunnerHealth as o, RunnerStorage as s, Runners as c, Sharding as l, ShardingConfig as u, SqlMessageStorage as d, SqlRunnerStorage as f } from "effect/cluster";
6
+ //#region src/clusterRunnerLayer.ts
7
+ var p = (e) => n.map(i.make(1), (t) => ({
8
+ ...e,
9
+ release: (r, i) => t.withPermits(1)(n.suspend(() => e.release(r, i)))
10
+ })), m = r.unwrap(n.map(a.SqlClient, (e) => e.onDialectOrElse({
11
+ sqlite: () => r.effect(s.RunnerStorage, n.flatMap(s.RunnerStorage, p)).pipe(r.provide(f.layer)),
12
+ orElse: () => f.layer
13
+ }))), h = (e) => r.effect(s.RunnerStorage, n.gen(function* () {
14
+ let n = yield* a.SqlClient, r = yield* s.RunnerStorage;
15
+ return yield* t(r, {
16
+ ...e,
17
+ probe: n`SELECT 1`
18
+ });
19
+ })).pipe(r.provide(r.orDie(m))), g = (t) => l.layer.pipe(r.provideMerge(c.layerNoop), r.provideMerge(d.layer), r.provide([t.runnerStorage === "memory" ? s.layerMemory : h(t.guard), o.layerNoop]), r.provide(u.layerFromEnv(t.shardingConfig)), r.provide(e));
20
+ //#endregion
21
+ export { g as clusterRunnerLayer, p as limitSqliteShardReleases };
@@ -1,7 +1,11 @@
1
- import { E as e, O as t, a as n } from "./journalEncryption-DTyhUzaS.js";
2
- import { F as r, R as i, S as a, g as o, v as s } from "./primitives-DX04WTWh.js";
3
- import { C as c, r as l, w as u } from "./src-C2NEyUM8.js";
4
- import { t as d, u as f } from "./cluster-F_bhqVP7.js";
1
+ import { failureMessage as e } from "./failureMessage.js";
2
+ import { encryptionAwareActivity as t, setWorkflowJournalCipher as n } from "./journalEncryption.js";
3
+ import { CurrentWorkflowRunId as r, WorkflowRunRecorder as i } from "./recorder.js";
4
+ import { sleep as a, step as o, workflow as s } from "./primitives.js";
5
+ import { awaitSignalSuspending as c, completeSuspendingSignal as l } from "./signalsSuspending.js";
6
+ import { makeWorkflowRunRecorder as u } from "./runRecording.js";
7
+ import { workflowEngineLayer as d } from "./clusterLayer.js";
8
+ import { makeClusterCronLayer as f } from "./clusterCron.js";
5
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";
6
10
  import { SqlClient as x } from "effect/sql";
7
11
  import { afterEach as S, beforeAll as C, describe as w, expect as T, test as E } from "vitest";
@@ -42,7 +46,7 @@ var D = async (e, t, n = 750) => {
42
46
  }
43
47
  return !0;
44
48
  }), N = (e) => {
45
- let t = `${e.name}-${Date.now()}`, n = (t, n) => f({
49
+ let t = `${e.name}-${Date.now()}`, n = (t, n) => d({
46
50
  sqlClientLayer: e.sqlClientLayer,
47
51
  runnerStorage: e.runnerStorage,
48
52
  runnerListenPort: t,
@@ -53,49 +57,45 @@ var D = async (e, t, n = 750) => {
53
57
  w(`cluster workflow engine — ${e.name}`, () => {
54
58
  let c = !1;
55
59
  C(async () => {
56
- if (c = await e.reachable(), !c) {
57
- console.warn(`[skip] cluster suite — ${e.name} not reachable`);
58
- return;
59
- }
60
- await k(e);
60
+ c = await e.reachable(), c ? await k(e) : console.warn(`[skip] cluster suite — ${e.name} not reachable`);
61
61
  }, 3e4), E("a two-step workflow runs end-to-end", async () => {
62
62
  if (!c) return;
63
- let r = `clusterTwoStep_${t}`, i = a({
63
+ let r = `clusterTwoStep_${t}`, i = s({
64
64
  name: r,
65
65
  payload: { id: b.String },
66
66
  success: b.Number,
67
67
  idempotencyKey: ({ id: e }) => `${r}:${e}`
68
- }), o = i.toLayer(() => g.gen(function* () {
69
- let e = yield* s({
68
+ }), a = i.toLayer(() => g.gen(function* () {
69
+ let e = yield* o({
70
70
  name: "first",
71
71
  success: b.Number,
72
72
  execute: g.succeed(40)
73
73
  });
74
- return yield* s({
74
+ return yield* o({
75
75
  name: "second",
76
76
  success: b.Number,
77
77
  execute: g.succeed(e + 2)
78
78
  });
79
- })), l = await g.runPromise(g.scoped(i.execute({ id: `e2e-${t}` }).pipe(g.provide(o.pipe(v.provideMerge(n(e.runnerPort)))))));
79
+ })), l = await g.runPromise(g.scoped(i.execute({ id: `e2e-${t}` }).pipe(g.provide(a.pipe(v.provideMerge(n(e.runnerPort)))))));
80
80
  T(l).toBe(42);
81
81
  }, 3e4), E("idempotency-key journal — same payload returns same result", async () => {
82
82
  if (!c) return;
83
- let r = `clusterIdempotency_${t}`, i = 0, o = a({
83
+ let r = `clusterIdempotency_${t}`, i = 0, a = s({
84
84
  name: r,
85
85
  payload: { id: b.String },
86
86
  success: b.Number,
87
87
  idempotencyKey: ({ id: e }) => `${r}:${e}`
88
- }), l = o.toLayer(() => s({
88
+ }), l = a.toLayer(() => o({
89
89
  name: "compute",
90
90
  success: b.Number,
91
91
  execute: g.sync(() => (i++, i * 100))
92
92
  })).pipe(v.provideMerge(n(e.runnerPort))), [u, d] = await g.runPromise(g.scoped(g.gen(function* () {
93
- return [yield* o.execute({ id: `same-${t}` }), yield* o.execute({ id: `same-${t}` })];
93
+ return [yield* a.execute({ id: `same-${t}` }), yield* a.execute({ id: `same-${t}` })];
94
94
  }).pipe(g.provide(l))));
95
95
  T(u).toBe(d), T(i).toBe(1);
96
96
  }, 3e4), E("ClusterCron fires its execute on the shard owner", async () => {
97
97
  if (!c) return;
98
- let r = await g.runPromise(h.make()), i = 0, a = d({
98
+ let r = await g.runPromise(h.make()), i = 0, a = f({
99
99
  name: `clusterCron_${t}`,
100
100
  cron: m.parseUnsafe("*/1 * * * * *", "UTC"),
101
101
  execute: async () => {
@@ -106,31 +106,31 @@ var D = async (e, t, n = 750) => {
106
106
  })));
107
107
  T(o).toBe(!0), T(i).toBeGreaterThanOrEqual(1);
108
108
  }, 35e3);
109
- let u = e.clusterResume ? E : (() => {});
110
- u("a sleeping workflow resumes on a fresh runner after the first is torn down", async () => {
109
+ let l = e.clusterResume ? E : (() => {});
110
+ l("a sleeping workflow resumes on a fresh runner after the first is torn down", async () => {
111
111
  if (!c) return;
112
- let r = `dormancyResume_${t}`, i = [], l = [], u = [], d = () => a({
112
+ let r = `dormancyResume_${t}`, i = [], l = [], u = [], d = () => s({
113
113
  name: r,
114
114
  payload: { id: b.String },
115
115
  success: b.String,
116
116
  idempotencyKey: ({ id: e }) => `${r}:${e}`
117
117
  }), f = (e, t) => e.toLayer(() => g.gen(function* () {
118
- return yield* s({
118
+ return yield* o({
119
119
  name: "step1",
120
120
  success: b.Void,
121
121
  execute: g.sync(() => {
122
122
  i.push(t);
123
123
  })
124
- }), yield* s({
124
+ }), yield* o({
125
125
  name: "armed",
126
126
  success: b.Void,
127
127
  execute: g.sync(() => {
128
128
  u.push(t);
129
129
  })
130
- }), yield* o({
130
+ }), yield* a({
131
131
  name: "nap",
132
132
  duration: A
133
- }), yield* s({
133
+ }), yield* o({
134
134
  name: "step2",
135
135
  success: b.Void,
136
136
  execute: g.sync(() => {
@@ -153,9 +153,9 @@ var D = async (e, t, n = 750) => {
153
153
  return yield* e.execute(h).pipe(g.provide(n), g.timeout("240 seconds"), g.orElseSucceed(() => "TIMEOUT"));
154
154
  })));
155
155
  T(x).toBe("done"), T(i).toEqual(["A"]), T(l).toEqual(["B"]);
156
- }, 48e4), u("dormancy loop: a wake signal survives teardown and is cleared on resume", async () => {
156
+ }, 48e4), l("dormancy loop: a wake signal survives teardown and is cleared on resume", async () => {
157
157
  if (!c) return;
158
- let u = `dormancyLoop_${t}`, d = `wr_${u}`, f = [], p = /* @__PURE__ */ new Map(), m = l({
158
+ let l = `dormancyLoop_${t}`, d = `wr_${l}`, f = [], p = /* @__PURE__ */ new Map(), m = u({
159
159
  store: {
160
160
  insert: async (e, t) => t,
161
161
  update: async () => null,
@@ -169,20 +169,20 @@ var D = async (e, t, n = 750) => {
169
169
  p.delete(e);
170
170
  }
171
171
  }
172
- }), h = () => a({
173
- name: u,
172
+ }), h = () => s({
173
+ name: l,
174
174
  payload: { id: b.String },
175
175
  success: b.String,
176
- idempotencyKey: ({ id: e }) => `${u}:${e}`
176
+ idempotencyKey: ({ id: e }) => `${l}:${e}`
177
177
  }), y = (e, t) => e.toLayer(() => g.gen(function* () {
178
- return yield* s({
178
+ return yield* o({
179
179
  name: "step1",
180
180
  success: b.Void,
181
181
  execute: g.void
182
- }), yield* o({
182
+ }), yield* a({
183
183
  name: "nap",
184
184
  duration: j
185
- }), yield* s({
185
+ }), yield* o({
186
186
  name: "step2",
187
187
  success: b.Void,
188
188
  execute: g.sync(() => {
@@ -211,16 +211,16 @@ var D = async (e, t, n = 750) => {
211
211
  encrypt: (e) => `enc:v2:c:${Buffer.from(e, "utf8").toString("base64")}`,
212
212
  decrypt: (e) => Buffer.from(e.slice(9), "base64").toString("utf8")
213
213
  }, F = (r) => {
214
- let i = `${r.name}-enc-${Date.now()}`, o = () => f({
214
+ let i = `${r.name}-enc-${Date.now()}`, a = () => d({
215
215
  sqlClientLayer: r.sqlClientLayer,
216
216
  runnerStorage: r.runnerStorage,
217
217
  runnerListenPort: r.runnerPort + 7,
218
218
  ...r.dialectId ? { dialectId: r.dialectId } : {}
219
- }), l = (e) => g.runPromise(g.gen(function* () {
219
+ }), u = (e) => g.runPromise(g.gen(function* () {
220
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);
221
221
  return [...n, ...r].filter((t) => i(t).includes(e)).length;
222
- }).pipe(g.provideService(x.SafeIntegers, !0), g.scoped, g.provide(r.sqlClientLayer))), d = (e, t) => {
223
- let n = a({
222
+ }).pipe(g.provideService(x.SafeIntegers, !0), g.scoped, g.provide(r.sqlClientLayer))), f = (e, t) => {
223
+ let n = s({
224
224
  name: e,
225
225
  payload: {
226
226
  note: b.String,
@@ -233,7 +233,7 @@ var D = async (e, t, n = 750) => {
233
233
  return {
234
234
  wf: n,
235
235
  handler: n.toLayer(({ note: e }) => g.gen(function* () {
236
- return { summary: `RESULT-${yield* s({
236
+ return { summary: `RESULT-${yield* o({
237
237
  name: "summarise",
238
238
  success: b.String,
239
239
  execute: g.sync(() => (r++, `STEP-${e}`))
@@ -241,39 +241,39 @@ var D = async (e, t, n = 750) => {
241
241
  })),
242
242
  executions: () => r
243
243
  };
244
- }, m = (e, t) => g.runPromise(g.scoped(e.wf.execute(t).pipe(g.provide(e.handler.pipe(v.provideMerge(o()))))));
244
+ }, m = (e, t) => g.runPromise(g.scoped(e.wf.execute(t).pipe(g.provide(e.handler.pipe(v.provideMerge(a()))))));
245
245
  w(`encrypted workflow journal — ${r.name}`, () => {
246
- let f = !1;
246
+ let d = !1;
247
247
  C(async () => {
248
- f = await r.reachable(), f && await k(r);
249
- }, 3e4), S(() => e(void 0)), E("payload, step result and result are ciphertext; the unencrypted twin is found (control)", async (t) => {
250
- f || t.skip(), e(P);
251
- let n = d(`journalPlain_${i}`, !1), r = d(`journalSealed_${i}`, !0);
252
- T(await m(n, {
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, {
253
253
  note: "PLAINMARK",
254
254
  count: 1
255
255
  })).toEqual({ summary: "RESULT-STEP-PLAINMARK" }), T(await m(r, {
256
256
  note: "SEALMARK",
257
257
  count: 1
258
- })).toEqual({ summary: "RESULT-STEP-SEALMARK" }), T(await l("PLAINMARK"), "the probe finds plaintext").toBeGreaterThan(0), T(await l("SEALMARK"), "no journal row carries the encrypted run in plaintext").toBe(0), T(await l("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 (t) => {
260
- f || t.skip(), e(P);
261
- let n = d(`journalReplay_${i}`, !0), r = await m(n, {
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, {
262
262
  note: "REPLAYMARK",
263
263
  count: 7
264
- }), a = await m(n, {
264
+ }), a = await m(t, {
265
265
  note: "REPLAYMARK",
266
266
  count: 7
267
267
  });
268
- T(a).toEqual(r), T(n.executions()).toBe(1);
269
- }, 6e4), E("a run journaled in plaintext before the declaration is still read", async (t) => {
270
- f || t.skip();
271
- let n = `journalBefore_${i}`, r = await m(d(n, !1), {
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), {
272
272
  note: "OLDMARK",
273
273
  count: 3
274
274
  });
275
- e(P);
276
- let a = d(n, !0);
275
+ n(P);
276
+ let a = f(t, !0);
277
277
  T(await m(a, {
278
278
  note: "OLDMARK",
279
279
  count: 3
@@ -281,44 +281,44 @@ var D = async (e, t, n = 750) => {
281
281
  }, 6e4);
282
282
  let h = (e, t, n) => {
283
283
  class r extends b.TaggedError()("Refused", { detail: b.String }) {}
284
- let i = a({
284
+ let i = s({
285
285
  name: e,
286
286
  payload: { note: b.String },
287
287
  error: r,
288
288
  idempotencyKey: ({ note: t }) => `${e}:${t}`,
289
289
  encrypted: t
290
- }), c = i.toLayer(({ note: e }) => s({
290
+ }), c = i.toLayer(({ note: e }) => o({
291
291
  name: "call-upstream",
292
292
  success: b.String,
293
293
  error: r,
294
294
  execute: n === "defect" ? g.die(/* @__PURE__ */ Error(`upstream rejected FAILMARK-${e}`)) : g.fail(new r({ detail: `FAILMARK-${e}` }))
295
295
  }));
296
- return g.runPromiseExit(g.scoped(i.execute({ note: e }).pipe(g.provide(c.pipe(v.provideMerge(o()))))));
296
+ return g.runPromiseExit(g.scoped(i.execute({ note: e }).pipe(g.provide(c.pipe(v.provideMerge(a()))))));
297
297
  };
298
- for (let n of ["defect", "typed"]) E(`a ${n} failure of an encrypted run leaves no plaintext; the unencrypted twin does (control)`, async (r) => {
299
- f || r.skip(), e(P);
300
- let a = await h(`journalFailPlain_${n}_${i}`, !1, n);
301
- T(a._tag).toBe("Failure"), T(await l(`FAILMARK-journalFailPlain_${n}`), "the probe finds plaintext").toBeGreaterThan(0);
302
- let o = await h(`journalFailSealed_${n}_${i}`, !0, n);
303
- T(o._tag).toBe("Failure"), T(await l(`FAILMARK-journalFailSealed_${n}`), "no journal row carries the failure in plaintext").toBe(0), T(t(p.squash(o.cause))).toContain(`FAILMARK-journalFailSealed_${n}`);
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}`);
304
304
  }, 6e4);
305
- E("a journaled wait value of an encrypted run is ciphertext; the unencrypted twin is found (control)", async (t) => {
306
- f || t.skip(), e(P);
307
- let r = (e, t) => {
308
- let r = a({
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);
307
+ let r = (e, n) => {
308
+ let r = s({
309
309
  name: e,
310
310
  payload: { note: b.String },
311
311
  success: b.String,
312
312
  idempotencyKey: ({ note: t }) => `${e}:${t}`,
313
- encrypted: t
314
- }), i = r.toLayer(({ note: e }) => n({
313
+ encrypted: n
314
+ }), i = r.toLayer(({ note: e }) => t({
315
315
  name: "await-signal:approval",
316
316
  success: b.String,
317
317
  execute: g.succeed(`WAITMARK-${e}`)
318
318
  }));
319
- return g.runPromise(g.scoped(r.execute({ note: e }).pipe(g.provide(i.pipe(v.provideMerge(o()))))));
319
+ return g.runPromise(g.scoped(r.execute({ note: e }).pipe(g.provide(i.pipe(v.provideMerge(a()))))));
320
320
  };
321
- T(await r(`journalWaitPlain_${i}`, !1)).toBe(`WAITMARK-journalWaitPlain_${i}`), T(await l(`WAITMARK-journalWaitPlain_${i}`), "the probe finds plaintext").toBeGreaterThan(0), T(await r(`journalWaitSealed_${i}`, !0)).toBe(`WAITMARK-journalWaitSealed_${i}`), T(await l(`WAITMARK-journalWaitSealed_${i}`), "no plaintext wait value in the journal").toBe(0);
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);
322
322
  }, 6e4);
323
323
  let _ = (e, t, n) => g.gen(function* () {
324
324
  for (let r = 0; r < 400; r++) {
@@ -328,59 +328,59 @@ var D = async (e, t, n = 750) => {
328
328
  }
329
329
  return yield* g.die(/* @__PURE__ */ Error("pollUntil: never satisfied"));
330
330
  }), x = (e) => y.isOption(e) && y.isSome(e) ? e.value._tag : void 0;
331
- for (let t of [!1, !0]) E(`a suspending signal's value is ${t ? "ciphertext" : "found (control)"} in the journal, and the run resumes with it`, async (n) => {
332
- f || n.skip(), e(P);
333
- let r = `journalSignal_${t ? "sealed" : "plain"}_${i}`, s = a({
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({
334
334
  name: r,
335
335
  payload: { id: b.String },
336
336
  success: b.Unknown,
337
337
  idempotencyKey: ({ id: e }) => `${r}:${e}`,
338
- encrypted: t
339
- }), d = s.toLayer(() => c({ store: { query: () => Promise.resolve([]) } }, {
338
+ encrypted: e
339
+ }), f = o.toLayer(() => c({ store: { query: () => Promise.resolve([]) } }, {
340
340
  name: "approval",
341
341
  schema: b.Struct({ comment: b.String }),
342
342
  timeoutMs: 36e5
343
343
  })), p = `SIGNALMARK-${r}`, m = await g.runPromise(g.scoped(g.gen(function* () {
344
- let e = yield* s.executionId({ id: "one" });
345
- return yield* s.execute({ id: "one" }, { discard: !0 }), yield* _(s, e, (e) => x(e) === "Suspended"), yield* u({
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({
346
346
  target: {
347
347
  executionId: e,
348
348
  workflowName: r
349
349
  },
350
350
  signalName: "approval",
351
351
  payload: { comment: p }
352
- }), yield* _(s, e, (e) => x(e) === "Complete");
353
- }).pipe(g.provide(d.pipe(v.provideMerge(o()))))));
352
+ }), yield* _(o, e, (e) => x(e) === "Complete");
353
+ }).pipe(g.provide(f.pipe(v.provideMerge(a()))))));
354
354
  T(JSON.stringify(y.getOrThrow(m).exit), "the body received the value").toContain(p);
355
- let h = await l(p);
356
- t ? T(h, "no plaintext signal value in the journal").toBe(0) : T(h, "the probe finds plaintext").toBeGreaterThan(0);
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);
357
357
  }, 9e4);
358
- E("an encrypted child's defect reaches its encrypted parent as thrown, and no journal row carries it", async (t) => {
359
- f || t.skip(), e(P);
360
- let n = `journalChild_${i}`, r = `journalParent_${i}`, s = `CHILDMARK-${i}`, c = a({
361
- name: n,
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({
361
+ name: t,
362
362
  payload: { id: b.String },
363
363
  success: b.String,
364
364
  idempotencyKey: ({ id: e }) => e,
365
365
  encrypted: !0
366
- }), u = a({
366
+ }), l = s({
367
367
  name: r,
368
368
  payload: { id: b.String },
369
369
  success: b.String,
370
370
  idempotencyKey: ({ id: e }) => e,
371
371
  encrypted: !0
372
- }), d = c.toLayer(() => g.die(/* @__PURE__ */ Error(`child failed ${s}`))), p = u.toLayer(({ id: e }) => c.execute({ id: `${e}-child` }).pipe(g.catchDefect((e) => g.succeed(`caught: ${e.message}`)))), m = await g.runPromise(g.scoped(u.execute({ id: "p1" }).pipe(g.provide(v.mergeAll(p, d).pipe(v.provideMerge(o()))))));
373
- T(m).toBe(`caught: child failed ${s}`), T(await l(s)).toBe(0);
374
- }, 6e4), E("an encrypted run that suspends on a failed step journals no plaintext", async (t) => {
375
- f || t.skip(), e(P);
376
- let n = `journalSuspendOnFailure_${i}`, r = `SOFMARK-${i}`, c = a({
377
- name: n,
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({
377
+ name: t,
378
378
  payload: { id: b.String },
379
379
  success: b.String,
380
380
  idempotencyKey: ({ id: e }) => e,
381
381
  encrypted: !0,
382
382
  suspendOnFailure: !0
383
- }), u = c.toLayer(() => s({
383
+ }), l = c.toLayer(() => o({
384
384
  name: "call",
385
385
  success: b.String,
386
386
  execute: g.die(/* @__PURE__ */ Error(`step failed ${r}`))
@@ -388,14 +388,14 @@ var D = async (e, t, n = 750) => {
388
388
  await g.runPromise(g.scoped(g.gen(function* () {
389
389
  let e = yield* c.executionId({ id: "s1" });
390
390
  yield* c.execute({ id: "s1" }, { discard: !0 }), yield* _(c, e, (e) => x(e) === "Suspended");
391
- }).pipe(g.provide(u.pipe(v.provideMerge(o())))))), T(await l(r)).toBe(0);
391
+ }).pipe(g.provide(l.pipe(v.provideMerge(a())))))), T(await u(r)).toBe(0);
392
392
  }, 6e4), E("without a registered cipher an encrypted run fails instead of writing plaintext", async (e) => {
393
- f || e.skip();
394
- let t = d(`journalNoCipher_${i}`, !0);
393
+ d || e.skip();
394
+ let t = f(`journalNoCipher_${i}`, !0);
395
395
  await T(m(t, {
396
396
  note: "NOCIPHERMARK",
397
397
  count: 1
398
- })).rejects.toThrow(), T(await l("NOCIPHERMARK")).toBe(0);
398
+ })).rejects.toThrow(), T(await u("NOCIPHERMARK")).toBe(0);
399
399
  }, 6e4);
400
400
  });
401
401
  };
@@ -0,0 +1,13 @@
1
+ import { readPendingWorkflowCancellation as e } from "./controlRequest.js";
2
+ import { Effect as t } from "effect";
3
+ import { WorkflowInstance as n } from "effect/workflow/WorkflowEngine";
4
+ //#region src/controlMonitor.ts
5
+ var r = (r, i, a) => t.gen(function* () {
6
+ let o = yield* n;
7
+ for (;;) {
8
+ if (yield* t.tryPromise(() => e(i, r)).pipe(t.catch(() => t.sync(() => (a(), !1))))) return o.interrupted = !0, yield* t.interrupt;
9
+ yield* t.sleep(1e3);
10
+ }
11
+ });
12
+ //#endregion
13
+ export { r as awaitWorkflowCancellation };