@voltro/workflow 0.28.0 → 0.30.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.
@@ -0,0 +1,159 @@
1
+ import { Context as e, Duration as t, Effect as n, Layer as r, Option as i } from "effect";
2
+ import { ClusterCron as a, ClusterSchema as o, ClusterWorkflowEngine as s, EntityAddress as c, EntityId as l, EntityType as u, MessageStorage as d, RunnerAddress as f, Sharding as p, ShardingConfig as m, SingleRunner as h, SqlMessageStorage as g, SqlRunnerStorage as _ } from "@effect/cluster";
3
+ import { SqlClient as v } from "@effect/sql";
4
+ //#region src/clusterMigrationGate.ts
5
+ var y = 6322741009312601n, b = `voltro_cluster_migration_${y.toString(36)}`, x = () => {
6
+ let e = Number(process.env.VOLTRO_CLUSTER_MIGRATION_LOCK_TIMEOUT_MS);
7
+ return Number.isFinite(e) && e > 0 ? e : 6e4;
8
+ }, S = 250, C = (e) => e[0]?.[0], w = (e, t) => e.onDialectOrElse({
9
+ sqlite: () => n.void,
10
+ mysql: () => t.executeValues(`SELECT GET_LOCK('${b}', ${Math.ceil(x() / 1e3)})`, []).pipe(n.flatMap((e) => C(e) === 1 || C(e) === "1" ? n.void : n.die(/* @__PURE__ */ Error(`could not acquire cluster migration lock '${b}' (GET_LOCK timed out)`)))),
11
+ mssql: () => t.executeValues(`DECLARE @res int; EXEC @res = sp_getapplock @Resource = '${b}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = ${x()}; SELECT @res`, []).pipe(n.flatMap((e) => Number(C(e) ?? -999) >= 0 ? n.void : n.die(/* @__PURE__ */ Error(`could not acquire cluster migration lock '${b}' (sp_getapplock ${String(C(e))})`)))),
12
+ orElse: () => {
13
+ let e = y.toString(), r = Date.now(), i = t.executeValues(`SELECT pg_try_advisory_lock(${e}::bigint)`, []).pipe(n.flatMap((e) => {
14
+ let t = C(e);
15
+ return t === !0 || t === "t" || t === 1 ? n.void : Date.now() - r >= x() ? n.die(/* @__PURE__ */ Error(`could not acquire the cluster migration advisory lock within ${Math.round(x() / 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.)`)) : n.sleep(`${S} millis`).pipe(n.zipRight(i));
16
+ }));
17
+ return i;
18
+ }
19
+ }), T = (e, t) => e.onDialectOrElse({
20
+ sqlite: () => n.void,
21
+ mysql: () => n.asVoid(t.executeValues(`SELECT RELEASE_LOCK('${b}')`, [])),
22
+ mssql: () => n.asVoid(t.executeValues(`EXEC sp_releaseapplock @Resource = '${b}', @LockOwner = 'Session'`, [])),
23
+ orElse: () => n.asVoid(t.executeValues(`SELECT pg_advisory_unlock(${y.toString()}::bigint)`, []))
24
+ }), E = (e, t) => e.onDialectOrElse({
25
+ sqlite: () => !0,
26
+ orElse: () => !1
27
+ }) ? t : n.scoped(n.gen(function* () {
28
+ let r = yield* e.reserve;
29
+ return yield* n.acquireRelease(w(e, r), () => T(e, r).pipe(n.orDie)), yield* t;
30
+ })), D = (e) => e === "sql" ? ["cluster_messages", "cluster_runners"] : ["cluster_messages"], O = (e, t) => n.forEach(D(t), (t) => e`SELECT 1 FROM ${e(t)}`.pipe(n.as(!0), n.catchAll(() => n.succeed(!1))), { concurrency: 1 }).pipe(n.map((e) => e.every(Boolean))), k = (e, t) => n.scoped(n.asVoid(r.build(t.pipe(r.provide(m.layerDefaults), r.provide(r.succeed(v.SqlClient, e)))))), A = (e, t) => k(e, g.layer).pipe(n.zipRight(t === "sql" ? k(e, _.layer) : n.void)), j = (e) => r.effect(v.SqlClient, n.gen(function* () {
31
+ let t = yield* v.SqlClient;
32
+ return (yield* O(t, e.runnerStorage)) || (yield* E(t, A(t, e.runnerStorage))), t;
33
+ })), M = 34e3, N = (e) => {
34
+ let t = e.runnerListenHost ?? process.env.POD_IP ?? process.env.VOLTRO_WORKFLOW_RUNNER_HOST ?? "localhost";
35
+ return {
36
+ host: t,
37
+ port: e.runnerListenPort ?? 34e3,
38
+ localhostRisk: e.runnerStorage === "sql" && (t === "localhost" || t === "127.0.0.1")
39
+ };
40
+ }, P = (e) => {
41
+ let t = e.sqliteFamily ? "memory" : "sql", n = e.override ?? process.env.VOLTRO_WORKFLOW_RUNNER_STORAGE;
42
+ if (n === void 0 || n.trim() === "") return t;
43
+ let r = n.trim().toLowerCase();
44
+ if (r !== "memory" && r !== "sql") throw Error(`VOLTRO_WORKFLOW_RUNNER_STORAGE must be 'memory' or 'sql', got '${n}'.`);
45
+ if (r === "sql" && e.sqliteFamily) throw Error("VOLTRO_WORKFLOW_RUNNER_STORAGE='sql' is unsupported on sqlite / turso — @effect/cluster has no sqlite advisory-lock branch. Use 'memory'.");
46
+ return r;
47
+ }, F = (e = process.env.VOLTRO_WORKFLOW_SHARD_LOCK) => {
48
+ if (e === void 0 || e.trim() === "") return "auto";
49
+ let t = e.trim().toLowerCase();
50
+ if (t === "auto" || t === "row" || t === "advisory") return t;
51
+ throw Error(`VOLTRO_WORKFLOW_SHARD_LOCK must be 'auto', 'row', or 'advisory', got '${e}'.`);
52
+ }, I = n.gen(function* () {
53
+ let e = yield* (yield* v.SqlClient)`SELECT @@wsrep_on AS wsrep`.pipe(n.catchAll(() => n.succeed([])));
54
+ if (e.length === 0) return !1;
55
+ let t = e[0].wsrep;
56
+ return t === 1 || t === "1" || t === !0 || t === "ON" || t === "on";
57
+ }), L = (e) => n.gen(function* () {
58
+ return e.runnerStorage === "memory" ? !1 : e.mode === "row" ? !0 : e.mode === "advisory" || e.dialectId !== "mysql" && e.dialectId !== "mariadb" ? !1 : yield* I;
59
+ }), R = () => {
60
+ let e = (e) => {
61
+ if (e === void 0) return;
62
+ let t = Number(e);
63
+ return Number.isFinite(t) && t > 0 ? t : void 0;
64
+ }, t = e(process.env.VOLTRO_WORKFLOW_FAILOVER_LEASE), n = e(process.env.VOLTRO_WORKFLOW_FAILOVER_HEARTBEAT), r = e(process.env.VOLTRO_WORKFLOW_POLL_INTERVAL);
65
+ return {
66
+ ...t === void 0 ? {} : { failoverLeaseSeconds: t },
67
+ ...n === void 0 ? {} : { failoverHeartbeatSeconds: n },
68
+ ...r === void 0 ? {} : { messagePollSeconds: r }
69
+ };
70
+ }, z = n.flatMap(p.Sharding, (e) => e.pollStorage), B = /* @__PURE__ */ new Set(), V = (e) => {
71
+ let a = N({
72
+ runnerStorage: e.runnerStorage,
73
+ runnerListenHost: e.runnerListenHost,
74
+ runnerListenPort: e.runnerListenPort
75
+ }), o = a.host, c = a.port;
76
+ a.localhostRisk && !B.has(o) && (B.add(o), process.stderr.write(`[voltro:workflow] runner host is '${o}' with SQL cluster storage — other pods cannot reach this runner, so a workflow cannot resume on another pod after a reschedule. Inject POD_IP via the K8s downward API (fieldRef: status.podIP) or set runnerListenHost / VOLTRO_WORKFLOW_RUNNER_HOST.
77
+ `));
78
+ let l = j({ runnerStorage: e.runnerStorage }).pipe(r.provide(e.sqlClientLayer)), u = e.shardLockMode ?? F(), d = r.unwrapEffect(L({
79
+ dialectId: e.dialectId,
80
+ runnerStorage: e.runnerStorage,
81
+ mode: u
82
+ }).pipe(n.map((n) => (e.runnerStorage === "sql" && process.stderr.write(`[voltro:workflow] shard-lock coordination: ${n ? "row-based" : "advisory"} (dialect=${e.dialectId ?? "unknown"}, mode=${u}` + (n && u === "auto" ? ", wsrep/Galera cluster detected" : "") + ")\n"), h.layer({
83
+ runnerStorage: e.runnerStorage,
84
+ shardingConfig: {
85
+ runnerAddress: i.some(f.make(o, c)),
86
+ runnerListenAddress: i.some(f.make(o, c)),
87
+ ...n ? { shardLockDisableAdvisory: !0 } : {},
88
+ ...e.failoverLeaseSeconds === void 0 ? {} : { shardLockExpiration: t.seconds(e.failoverLeaseSeconds) },
89
+ ...e.failoverHeartbeatSeconds === void 0 ? {} : { shardLockRefreshInterval: t.seconds(e.failoverHeartbeatSeconds) },
90
+ ...e.messagePollSeconds === void 0 ? {} : { entityMessagePollInterval: t.seconds(e.messagePollSeconds) }
91
+ }
92
+ }))))).pipe(r.provide(l)), p = e.extraShardingLayers ?? [];
93
+ return (p.length === 0 ? s.layer : r.mergeAll(s.layer, ...p)).pipe(r.provideMerge(d));
94
+ }, H = (e) => a.make({
95
+ name: e.name,
96
+ cron: e.cron,
97
+ execute: n.promise(() => e.execute()),
98
+ skipIfOlderThan: e.skipIfOlderThan ?? t.days(1)
99
+ }), U = (e, t) => `${e}/${t}`, W = (t, n, r) => {
100
+ let i = l.make(n), a = e.get(t.annotations, o.ShardGroup)(i);
101
+ return new c.EntityAddress({
102
+ entityType: u.EntityType.make(`Workflow/${t.name}`),
103
+ entityId: i,
104
+ shardId: r.getShardId(i, a)
105
+ });
106
+ }, G = (e) => {
107
+ let t = (e) => e && typeof e == "object" && "_tag" in e ? String(e._tag) : void 0, n = t(e);
108
+ if (n === "Failure" || n !== "Success") return "failed";
109
+ let r = e.value, i = t(r);
110
+ if (i === "Suspended") return "suspended";
111
+ if (i === "Complete") {
112
+ let e = r.exit;
113
+ return t(e) === "Success" ? "succeeded" : "failed";
114
+ }
115
+ return "failed";
116
+ }, K = (e) => J(e.workflow, e.executionId, e.failedActivities), q = (e) => J(e.workflow, e.executionId, e.resetActivities), J = (e, t, r) => n.gen(function* () {
117
+ let a = yield* p.Sharding, o = yield* d.MessageStorage, s = W(e, t, a), c = yield* o.requestIdForPrimaryKey({
118
+ address: s,
119
+ tag: "run",
120
+ id: ""
121
+ }).pipe(n.orElseSucceed(() => i.none()));
122
+ if (i.isNone(c)) return {
123
+ redriven: !1,
124
+ runReplyCleared: !1,
125
+ activitiesReset: 0,
126
+ reason: "no-journal"
127
+ };
128
+ let l = (yield* o.repliesForUnfiltered([c.value]).pipe(n.orElseSucceed(() => []))).filter((e) => e._tag === "WithExit").at(-1);
129
+ if (l === void 0) return {
130
+ redriven: !1,
131
+ runReplyCleared: !1,
132
+ activitiesReset: 0,
133
+ reason: "no-reply"
134
+ };
135
+ if (G(l.exit) === "succeeded") return {
136
+ redriven: !1,
137
+ runReplyCleared: !1,
138
+ activitiesReset: 0,
139
+ reason: "succeeded"
140
+ };
141
+ let u = 0;
142
+ for (let e of r) {
143
+ let t = yield* o.requestIdForPrimaryKey({
144
+ address: s,
145
+ tag: "activity",
146
+ id: U(e.name, e.attempt)
147
+ }).pipe(n.orElseSucceed(() => i.none()));
148
+ i.isSome(t) && (yield* a.reset(t.value)) && (u += 1);
149
+ }
150
+ let f = yield* a.reset(c.value);
151
+ return yield* a.pollStorage, {
152
+ redriven: !0,
153
+ runReplyCleared: f,
154
+ activitiesReset: u,
155
+ reason: null
156
+ };
157
+ });
158
+ //#endregion
159
+ export { M as a, z as c, P as d, L as f, H as i, R as l, K as n, I as o, V as p, q as r, F as s, U as t, N as u };
package/dist/cluster.d.ts CHANGED
@@ -206,6 +206,47 @@ export declare const resolveShardLockDisableAdvisory: (opts: {
206
206
  readonly mode: ShardLockMode;
207
207
  }) => Effect.Effect<boolean, never, SqlClient.SqlClient>;
208
208
 
209
+ export declare interface ResumeFromStepTarget {
210
+ /** The workflow definition — for its `name` (entity type) + `annotations` (shard group). */
211
+ readonly workflow: Workflow.Any;
212
+ /** The engine-assigned execution id (= cluster `entityId`) of the run to resume. */
213
+ readonly executionId: string;
214
+ /**
215
+ * The step attempts to reset so they re-execute fresh: the operator's
216
+ * chosen rewind target AND every step ordered at/after it — regardless
217
+ * of whether those steps SUCCEEDED. This is the one difference from
218
+ * {@link redriveFailedRun}, which resets only the failed attempts: resume
219
+ * rewinds PAST the failure point, so a step that completed cleanly but
220
+ * whose result is now stale re-runs too. Steps BEFORE the target keep
221
+ * their journaled replies and replay. Typically every
222
+ * `_voltro_workflow_run_steps` row (all attempts) whose `startedAt` is at
223
+ * or after the target step's first attempt.
224
+ */
225
+ readonly resetActivities: ReadonlyArray<FailedActivityRef>;
226
+ }
227
+
228
+ /**
229
+ * Resume a terminally-`failed` workflow run from an operator-chosen STEP,
230
+ * rather than from the step it happened to die on.
231
+ *
232
+ * The generalisation of {@link redriveFailedRun}: it resets the target step
233
+ * AND every step ordered at/after it (all attempts, succeeded ones included),
234
+ * so those re-execute while the steps BEFORE the target replay from the
235
+ * journal. Use it when the failure point is not the right rewind point — a
236
+ * step completed cleanly but on stale/wrong external state, and the operator
237
+ * wants to re-run from there once the cause is fixed.
238
+ *
239
+ * The mechanism is identical to redrive (clear the chosen replies + the run
240
+ * reply, then re-poll); only the SET of replies cleared differs, and that set
241
+ * is computed by the caller from `_voltro_workflow_run_steps`. Refuses (no
242
+ * mutation) for the same reasons — no journal, still running, or already
243
+ * succeeded.
244
+ *
245
+ * Requires the SAME cluster instance's `Sharding` + `MessageStorage` the
246
+ * workflow engine was built from (see `workflowEngineLayer`).
247
+ */
248
+ export declare const resumeRunFromStep: (target: ResumeFromStepTarget) => Effect.Effect<RedriveOutcome, never, Sharding.Sharding | MessageStorage.MessageStorage>;
249
+
209
250
  /** Resolved cluster-runner address + the silent-resume-breaker flag.
210
251
  * Single source of truth for both the engine layer and the
211
252
  * `/_voltro/inspect/cluster` snapshot, so the CLI never reports an
package/dist/cluster.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "./cluster-BQ0H5M9n.js";
2
- export { i as DEFAULT_RUNNER_LISTEN_PORT, u as activityPrimaryKey, e as detectWsrepCluster, c as makeClusterCronLayer, s as parseShardLockMode, l as pollWorkflowStorage, o as redriveFailedRun, t as resolveFailoverTuning, a as resolveRunnerIdentity, d as resolveRunnerStorage, n as resolveShardLockDisableAdvisory, r as workflowEngineLayer };
1
+ import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, p as c, r as l, s as u, t as d, u as f } from "./cluster-CyIyBO39.js";
2
+ export { e as DEFAULT_RUNNER_LISTEN_PORT, d as activityPrimaryKey, s as detectWsrepCluster, i as makeClusterCronLayer, u as parseShardLockMode, t as pollWorkflowStorage, o as redriveFailedRun, a as resolveFailoverTuning, f as resolveRunnerIdentity, n as resolveRunnerStorage, r as resolveShardLockDisableAdvisory, l as resumeRunFromStep, c as workflowEngineLayer };
@@ -35,6 +35,35 @@ export declare interface ClusterEngineFixture {
35
35
  readonly reachable: () => Promise<boolean>;
36
36
  }
37
37
 
38
+ /**
39
+ * Clear cluster state left behind by EARLIER runs of this suite.
40
+ *
41
+ * Nothing did this, and the consequence was a suite that got less reliable the
42
+ * more you ran it. Every scenario names its workflows with a per-run `SUFFIX`,
43
+ * so a run's cron entities never collide with the next run's — they ACCUMULATE
44
+ * instead. A fresh runner then finds messages addressed to
45
+ * `ClusterCron/clusterCron_<oldSuffix>` entity types that no process registers a
46
+ * handler for any more, and retries them every 10 seconds, forever, holding a
47
+ * connection each time. Eventually the pool cannot be acquired and whichever
48
+ * scenario is running dies with `SqlError: Failed to acquire connection` — a
49
+ * failure that names the victim and not the cause, in a file that passes
50
+ * perfectly in isolation against a fresh database.
51
+ *
52
+ * That is the mechanism behind the "rotating victims" this repo has a
53
+ * maintainer note about. It reads as machine saturation because the symptom is
54
+ * timing-shaped, but the load is manufactured by the suite's own leftovers, and
55
+ * it survives every `docker compose down`-less rerun.
56
+ *
57
+ * Purging at the START rather than the end is deliberate: an `afterAll` cannot
58
+ * clean up after a run that crashed, and the leftovers of a crashed run are
59
+ * exactly the ones most likely to be there. Same reasoning as the boot path's
60
+ * `reapTestFixtures`.
61
+ *
62
+ * Best-effort per table — on the first ever run against a database the cluster
63
+ * tables do not exist yet, and that is not a failure.
64
+ */
65
+ export declare const purgeClusterState: (fixture: ClusterEngineFixture) => Promise<void>;
66
+
38
67
  /**
39
68
  * Register the cluster-engine suite for one dialect. Each
40
69
  * `@voltro/sql-*` (or the workflow package) calls this from a thin
@@ -1,6 +1,6 @@
1
- import { C as e, m as t, p as n, v as r, x as i } from "./primitives-Dgu3O55Q.js";
2
- import { i as a } from "./src-KDx7NTsa.js";
3
- import { f as o, r as s } from "./cluster-BQ0H5M9n.js";
1
+ import { E as e, _ as t, h as n, w as r, x as i } from "./primitives-Cwy2zQpU.js";
2
+ import { i as a } from "./src-CMapid4w.js";
3
+ import { i as o, p as s } from "./cluster-CyIyBO39.js";
4
4
  import { Cron as c, Deferred as l, Effect as u, Fiber as d, Layer as f, Schema as p } from "effect";
5
5
  import { SqlClient as m } from "@effect/sql";
6
6
  import { beforeAll as h, describe as g, expect as _, test as v } from "vitest";
@@ -41,7 +41,7 @@ var y = async (e, t, n = 750) => {
41
41
  }
42
42
  return !0;
43
43
  }), T = (m) => {
44
- let y = `${m.name}-${Date.now()}`, b = (e, t) => o({
44
+ let y = `${m.name}-${Date.now()}`, b = (e, t) => s({
45
45
  sqlClientLayer: m.sqlClientLayer,
46
46
  runnerStorage: m.runnerStorage,
47
47
  runnerListenPort: e,
@@ -50,21 +50,21 @@ var y = async (e, t, n = 750) => {
50
50
  ...t && t.length > 0 ? { extraShardingLayers: t } : {}
51
51
  });
52
52
  g(`cluster workflow engine — ${m.name}`, () => {
53
- let o = !1;
53
+ let s = !1;
54
54
  h(async () => {
55
- if (o = await m.reachable(), !o) {
55
+ if (s = await m.reachable(), !s) {
56
56
  console.warn(`[skip] cluster suite — ${m.name} not reachable`);
57
57
  return;
58
58
  }
59
59
  await x(m);
60
60
  }, 3e4), v("a two-step workflow runs end-to-end", async () => {
61
- if (!o) return;
62
- let e = `clusterTwoStep_${y}`, n = r({
61
+ if (!s) return;
62
+ let e = `clusterTwoStep_${y}`, n = i({
63
63
  name: e,
64
64
  payload: { id: p.String },
65
65
  success: p.Number,
66
66
  idempotencyKey: ({ id: t }) => `${e}:${t}`
67
- }), i = n.toLayer(() => u.gen(function* () {
67
+ }), r = n.toLayer(() => u.gen(function* () {
68
68
  let e = yield* t({
69
69
  name: "first",
70
70
  success: p.Number,
@@ -76,25 +76,25 @@ var y = async (e, t, n = 750) => {
76
76
  execute: u.succeed(e + 2)
77
77
  });
78
78
  }));
79
- _(await u.runPromise(u.scoped(n.execute({ id: `e2e-${y}` }).pipe(u.provide(i.pipe(f.provideMerge(b(m.runnerPort)))))))).toBe(42);
79
+ _(await u.runPromise(u.scoped(n.execute({ id: `e2e-${y}` }).pipe(u.provide(r.pipe(f.provideMerge(b(m.runnerPort)))))))).toBe(42);
80
80
  }, 3e4), v("idempotency-key journal — same payload returns same result", async () => {
81
- if (!o) return;
82
- let e = `clusterIdempotency_${y}`, n = 0, i = r({
81
+ if (!s) return;
82
+ let e = `clusterIdempotency_${y}`, n = 0, r = i({
83
83
  name: e,
84
84
  payload: { id: p.String },
85
85
  success: p.Number,
86
86
  idempotencyKey: ({ id: t }) => `${e}:${t}`
87
- }), a = i.toLayer(() => t({
87
+ }), a = r.toLayer(() => t({
88
88
  name: "compute",
89
89
  success: p.Number,
90
90
  execute: u.sync(() => (n++, n * 100))
91
- })).pipe(f.provideMerge(b(m.runnerPort))), [s, c] = await u.runPromise(u.scoped(u.gen(function* () {
92
- return [yield* i.execute({ id: `same-${y}` }), yield* i.execute({ id: `same-${y}` })];
91
+ })).pipe(f.provideMerge(b(m.runnerPort))), [o, c] = await u.runPromise(u.scoped(u.gen(function* () {
92
+ return [yield* r.execute({ id: `same-${y}` }), yield* r.execute({ id: `same-${y}` })];
93
93
  }).pipe(u.provide(a))));
94
- _(s).toBe(c), _(n).toBe(1);
94
+ _(o).toBe(c), _(n).toBe(1);
95
95
  }, 3e4), v("ClusterCron fires its execute on the shard owner", async () => {
96
- if (!o) return;
97
- let e = await u.runPromise(l.make()), t = 0, n = s({
96
+ if (!s) return;
97
+ let e = await u.runPromise(l.make()), t = 0, n = o({
98
98
  name: `clusterCron_${y}`,
99
99
  cron: c.unsafeParse("*/1 * * * * *", "UTC"),
100
100
  execute: async () => {
@@ -107,24 +107,24 @@ var y = async (e, t, n = 750) => {
107
107
  }, 35e3);
108
108
  let g = m.clusterResume ? v : (() => {});
109
109
  g("a sleeping workflow resumes on a fresh runner after the first is torn down", async () => {
110
- if (!o) return;
111
- let e = `dormancyResume_${y}`, i = [], a = [], s = [], c = () => r({
110
+ if (!s) return;
111
+ let e = `dormancyResume_${y}`, r = [], a = [], o = [], c = () => i({
112
112
  name: e,
113
113
  payload: { id: p.String },
114
114
  success: p.String,
115
115
  idempotencyKey: ({ id: t }) => `${e}:${t}`
116
- }), l = (e, r) => e.toLayer(() => u.gen(function* () {
116
+ }), l = (e, i) => e.toLayer(() => u.gen(function* () {
117
117
  return yield* t({
118
118
  name: "step1",
119
119
  success: p.Void,
120
120
  execute: u.sync(() => {
121
- i.push(r);
121
+ r.push(i);
122
122
  })
123
123
  }), yield* t({
124
124
  name: "armed",
125
125
  success: p.Void,
126
126
  execute: u.sync(() => {
127
- s.push(r);
127
+ o.push(i);
128
128
  })
129
129
  }), yield* n({
130
130
  name: "nap",
@@ -133,7 +133,7 @@ var y = async (e, t, n = 750) => {
133
133
  name: "step2",
134
134
  success: p.Void,
135
135
  execute: u.sync(() => {
136
- a.push(r);
136
+ a.push(i);
137
137
  })
138
138
  }), "done";
139
139
  })), h = m.runnerPort + 2, g = (e) => {
@@ -144,15 +144,15 @@ var y = async (e, t, n = 750) => {
144
144
  };
145
145
  }, v = { id: `resume-${y}` }, x = await u.runPromise(u.scoped(u.gen(function* () {
146
146
  let { wf: e, layer: t } = g("A"), n = yield* f.build(t), r = yield* u.forkDaemon(e.execute(v).pipe(u.provide(n), u.ignore));
147
- return (yield* w(() => s.length > 0, 6e4)) ? r : yield* u.die(/* @__PURE__ */ Error("runner A never journaled step1"));
147
+ return (yield* w(() => o.length > 0, 6e4)) ? r : yield* u.die(/* @__PURE__ */ Error("runner A never journaled step1"));
148
148
  })));
149
- await u.runPromise(d.interrupt(x).pipe(u.ignore)), _(i).toEqual(["A"]), _(a).toEqual([]), _(await u.runPromise(u.scoped(u.gen(function* () {
149
+ await u.runPromise(d.interrupt(x).pipe(u.ignore)), _(r).toEqual(["A"]), _(a).toEqual([]), _(await u.runPromise(u.scoped(u.gen(function* () {
150
150
  let { wf: e, layer: t } = g("B"), n = yield* f.build(t);
151
151
  return yield* e.execute(v).pipe(u.provide(n), u.timeout("240 seconds"), u.orElseSucceed(() => "TIMEOUT"));
152
- })))).toBe("done"), _(i).toEqual(["A"]), _(a).toEqual(["B"]);
152
+ })))).toBe("done"), _(r).toEqual(["A"]), _(a).toEqual(["B"]);
153
153
  }, 48e4), g("dormancy loop: a wake signal survives teardown and is cleared on resume", async () => {
154
- if (!o) return;
155
- let s = `dormancyLoop_${y}`, c = `wr_${s}`, l = [], h = /* @__PURE__ */ new Map(), g = a({
154
+ if (!s) return;
155
+ let o = `dormancyLoop_${y}`, c = `wr_${o}`, l = [], h = /* @__PURE__ */ new Map(), g = a({
156
156
  store: {
157
157
  insert: async (e, t) => t,
158
158
  update: async () => null,
@@ -166,12 +166,12 @@ var y = async (e, t, n = 750) => {
166
166
  h.delete(e);
167
167
  }
168
168
  }
169
- }), v = () => r({
170
- name: s,
169
+ }), v = () => i({
170
+ name: o,
171
171
  payload: { id: p.String },
172
172
  success: p.String,
173
- idempotencyKey: ({ id: e }) => `${s}:${e}`
174
- }), x = (r, a) => r.toLayer(() => u.gen(function* () {
173
+ idempotencyKey: ({ id: e }) => `${o}:${e}`
174
+ }), x = (i, a) => i.toLayer(() => u.gen(function* () {
175
175
  return yield* t({
176
176
  name: "step1",
177
177
  success: p.Void,
@@ -186,7 +186,7 @@ var y = async (e, t, n = 750) => {
186
186
  l.push(a);
187
187
  })
188
188
  }), "done";
189
- }).pipe(u.locally(i, c), u.provideService(e, g))), S = m.runnerPort + 2, T = (e) => {
189
+ }).pipe(u.locally(r, c), u.provideService(e, g))), S = m.runnerPort + 2, T = (e) => {
190
190
  let t = v();
191
191
  return {
192
192
  wf: t,
@@ -204,4 +204,4 @@ var y = async (e, t, n = 750) => {
204
204
  });
205
205
  };
206
206
  //#endregion
207
- export { T as runClusterEngineSuite, y as tcpReachable };
207
+ export { x as purgeClusterState, T as runClusterEngineSuite, y as tcpReachable };