@nanobpm/nano-workforce 0.120.0 → 0.120.2

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 (38) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/app/capabilityNeed.test.ts +4 -2
  3. package/app/capabilityNeed.ts +3 -1
  4. package/app/deliveryGraphCompiler.test.ts +72 -44
  5. package/app/deliveryGraphCompiler.ts +121 -19
  6. package/app/deliveryGraphRun.test.ts +2 -2
  7. package/app/deliveryRunner.test.ts +29 -17
  8. package/app/deliveryRunner.ts +31 -10
  9. package/app/feature.test.ts +3 -1
  10. package/app/feature.ts +9 -0
  11. package/app/featureReadiness.test.ts +7 -4
  12. package/app/featureReadiness.ts +8 -3
  13. package/app/plan.test.ts +1 -1
  14. package/app/plan.ts +9 -0
  15. package/app/planFanoutPreflight.test.ts +12 -12
  16. package/app/planLowering.test.ts +2 -0
  17. package/app/planLowering.ts +7 -2
  18. package/app/pollUserTasks.test.ts +49 -1
  19. package/app/readiness.test.ts +9 -0
  20. package/app/readiness.ts +25 -0
  21. package/app/service.ts +24 -6
  22. package/biome.json +24 -1
  23. package/e2e/delivery-graph.e2e.ts +2 -1
  24. package/e2e/feature-preflight.e2e.ts +2 -0
  25. package/e2e/inter-epic-dependency.e2e.ts +7 -1
  26. package/e2e/plan-fanout-preflight.e2e.ts +2 -0
  27. package/e2e/readiness-gate.e2e.ts +27 -11
  28. package/operations/compileDeliveryGraph.test.ts +3 -0
  29. package/operations/compileDeliveryGraph.ts +1 -1
  30. package/operations/dispatchDeliveryGraph.ts +1 -1
  31. package/operations/previewDeliveryGraph.ts +1 -1
  32. package/operations/startDeliveryGraph.ts +1 -1
  33. package/package.json +3 -2
  34. package/resources/processes/feature.bpmn +168 -52
  35. package/resources/processes/plan-fanout.bpmn +168 -52
  36. package/resources/processes/readiness-gate.bpmn +194 -84
  37. package/workers/readiness-probe/worker.test.ts +82 -238
  38. package/workers/readiness-probe/worker.ts +46 -128
@@ -1,32 +1,15 @@
1
- // Unit coverage for the pr.readiness-probe poll loop (workers/readiness-probe/worker.ts, #258).
2
- //
3
- // The loop is factored out (`pollUntilReady`) with injectable I/O, clock, and publisher so the two
4
- // load-bearing behaviours are proven without a network, a subprocess, or a real timer:
5
- // • ready → it publishes the `readiness-ready` message exactly once and returns ready;
6
- // • never-ready → it EXHAUSTS its local budget and returns not-ready WITHOUT publishing (the
7
- // engine timer, not the worker, is the authoritative bound) — and it must not loop forever.
1
+ // Unit coverage for the pr.readiness-probe single-shot worker (workers/readiness-probe/worker.ts, #428).
8
2
  import { test } from "node:test";
9
3
  import { assert, assertEquals, assertRejects } from "#test-assert";
10
4
  import type { CommandResult, HttpResponse, ProbeExec, ReadinessProbe } from "../../app/readiness.ts";
11
5
  import { parseProbe } from "../../app/readiness.ts";
12
- import handler, { pollUntilReady, READINESS_READY_MESSAGE, readGateVars, safeBind } from "./worker.ts";
6
+ import handler, { probeSingleShot, READINESS_READY_MESSAGE, readGateVars, safeBind } from "./worker.ts";
13
7
 
14
- // A virtual clock: `now()` advances only when the loop's `wait(ms)` is called, so a never-ready
15
- // probe races to its deadline in zero real time (no setTimeout) and the test can never hang.
16
- function fakeClock() {
17
- let t = 0;
18
- return {
19
- now: () => t,
20
- wait: async (ms: number) => {
21
- t += ms;
22
- },
23
- };
24
- }
25
-
26
- function execReturning(seq: Array<HttpResponse>): ProbeExec {
8
+ function execReturning(seq: Array<HttpResponse>, counts: { http: number } = { http: 0 }): ProbeExec {
27
9
  let i = 0;
28
10
  return {
29
11
  async httpGet() {
12
+ counts.http += 1;
30
13
  const r = seq[Math.min(i, seq.length - 1)];
31
14
  i += 1;
32
15
  return r;
@@ -37,8 +20,8 @@ function execReturning(seq: Array<HttpResponse>): ProbeExec {
37
20
  };
38
21
  }
39
22
 
40
- const httpProbe = (poll: ReadinessProbe["poll"]): ReadinessProbe =>
41
- parseProbe({ kind: "http", target: "https://x/health", poll });
23
+ const httpProbe = (poll?: ReadinessProbe["poll"]): ReadinessProbe =>
24
+ parseProbe({ kind: "http", target: "https://x/health?token=s3cr3t", poll });
42
25
 
43
26
  test("safeBind: strips reserved keys (ready/detail) so a bind can only ADD outputs, never shadow the payload", () => {
44
27
  const cleaned = safeBind({ resolvedArtifact: "@nanobpm/urban@0.54.0", ready: "false", detail: "spoofed" });
@@ -48,99 +31,46 @@ test("safeBind: strips reserved keys (ready/detail) so a bind can only ADD outpu
48
31
  assertEquals(Object.keys(safeBind(undefined)).length, 0, "an absent bind yields an empty object");
49
32
  });
50
33
 
51
- test("pollUntilReady: a fallback that throws is caught, logged by class name (no leak), and stays not-ready", async () => {
52
- const clock = fakeClock();
53
- const seen: string[] = [];
54
- let publishes = 0;
55
- const res = await pollUntilReady({
56
- probe: httpProbe({ everyMs: 5, timeoutMs: 30, backoff: "fixed" }),
57
- gateKey: "gate-fallback-throws",
58
- exec: execReturning([{ status: 503, body: "" }]),
59
- env: {},
60
- now: clock.now,
61
- wait: clock.wait,
62
- publish: async () => {
63
- publishes += 1;
64
- },
65
- fallback: async () => {
66
- throw new Error("boom at https://h/p?token=s3cr3t");
67
- },
68
- log: (msg) => seen.push(msg),
69
- });
70
- assert(!res.ready, "a throwing fallback keeps the not-ready outcome for the engine timer");
71
- assertEquals(publishes, 0, "nothing is published when the fallback throws");
72
- const all = seen.join("\n");
73
- assert(all.includes("fallback error: Error"), "the fallback error is logged by class name");
74
- assert(!all.includes("s3cr3t"), "the raw error message (with its secret) must not leak");
75
- });
76
-
77
- test("pollUntilReady: publishes readiness-ready once and returns ready when a probe goes green", async () => {
78
- const clock = fakeClock();
34
+ test("probeSingleShot: publishes readiness-ready once and returns ready when a probe is green", async () => {
79
35
  const published: Array<{ detail: string }> = [];
80
- const exec = execReturning([{ status: 503, body: "" }, { status: 200, body: "ok" }]);
81
- const res = await pollUntilReady({
82
- probe: httpProbe({ everyMs: 10, timeoutMs: 10_000, backoff: "fixed" }),
83
- gateKey: "gate-1",
84
- exec,
36
+ const counts = { http: 0 };
37
+ const res = await probeSingleShot({
38
+ probe: httpProbe({ everyMs: 1, timeoutMs: 60_000, backoff: "fixed" }),
39
+ exec: execReturning([{ status: 200, body: "ok" }], counts),
85
40
  env: {},
86
- now: clock.now,
87
- wait: clock.wait,
88
41
  publish: async (detail) => {
89
42
  published.push({ detail });
90
43
  },
91
44
  });
92
- assert(res.ready, "the probe eventually reported ready");
45
+ assert(res.ready, "the probe reported ready");
46
+ assertEquals(counts.http, 1, "a single activation performs exactly one probe I/O");
93
47
  assertEquals(published.length, 1, "exactly one readiness message was published");
94
48
  });
95
49
 
96
- test("pollUntilReady: forwards a matcher's bind through publish into the message variables (#274 Gap B)", async () => {
97
- // A capability probe resolves a version; its bind must flow through publish so the gate can surface
98
- // resolvedArtifact as an output. The gh-api stub returns a release whose provenance carries #274.
99
- const clock = fakeClock();
100
- const published: Array<{ detail: string; bind?: Record<string, string> }> = [];
101
- const payload = JSON.stringify([{ tag_name: "@nanobpm/urban@0.54.0", body: "## Provenance\n- #274\n" }]);
102
- const exec: ProbeExec = {
103
- async httpGet() {
104
- return { status: 0, body: "" };
105
- },
106
- async run(): Promise<CommandResult> {
107
- return { code: 0, stdout: payload, stderr: "" };
108
- },
109
- };
110
- const res = await pollUntilReady({
111
- probe: parseProbe({
112
- kind: "capability",
113
- target: "github-releases:nanobpm/nano-ide",
114
- match: { capabilityRef: "nano-ide#274", package: "@nanobpm/urban" },
115
- poll: { everyMs: 5, timeoutMs: 5000, backoff: "fixed" },
116
- }),
117
- gateKey: "gate-cap",
118
- exec,
50
+ test("probeSingleShot: not-ready and not lastAttempt returns without publishing", async () => {
51
+ let publishes = 0;
52
+ const counts = { http: 0 };
53
+ const res = await probeSingleShot({
54
+ probe: httpProbe({ everyMs: 1, timeoutMs: 60_000, backoff: "fixed" }),
55
+ exec: execReturning([{ status: 503, body: "" }], counts),
119
56
  env: {},
120
- now: clock.now,
121
- wait: clock.wait,
122
- publish: async (detail, bind) => {
123
- published.push({ detail, bind });
57
+ publish: async () => {
58
+ publishes += 1;
124
59
  },
125
60
  });
126
- assert(res.ready, "the capability edge resolved");
127
- assertEquals(published.length, 1, "exactly one readiness message was published");
128
- assertEquals(published[0]?.bind?.resolvedArtifact, "@nanobpm/urban@0.54.0", "the resolved artifact flowed through the bind");
61
+ assert(!res.ready, "the probe reported not-ready");
62
+ assertEquals(counts.http, 1, "regression guard: the worker has no wall-clock retry loop");
63
+ assertEquals(publishes, 0, "a not-ready non-final activation never publishes");
129
64
  });
130
65
 
131
- test("pollUntilReady: the gated fallback fires ONCE at budget exhaustion and can still resolve+publish", async () => {
132
- // Deterministic provenance never resolves (no matching release), so the loop exhausts its budget —
133
- // the gate boundary. The fallback thunk then verifies empirically and publishes a bound version.
134
- const clock = fakeClock();
66
+ test("probeSingleShot: lastAttempt fallback fires once, can resolve ready, and publishes bound outputs", async () => {
135
67
  const published: Array<{ bind?: Record<string, string> }> = [];
136
68
  let fallbackCalls = 0;
137
- const res = await pollUntilReady({
138
- probe: httpProbe({ everyMs: 5, timeoutMs: 30, backoff: "fixed" }),
139
- gateKey: "gate-fallback",
69
+ const res = await probeSingleShot({
70
+ probe: httpProbe(),
140
71
  exec: execReturning([{ status: 503, body: "" }]),
141
72
  env: {},
142
- now: clock.now,
143
- wait: clock.wait,
73
+ lastAttempt: true,
144
74
  publish: async (_detail, bind) => {
145
75
  published.push({ bind });
146
76
  },
@@ -150,110 +80,103 @@ test("pollUntilReady: the gated fallback fires ONCE at budget exhaustion and can
150
80
  },
151
81
  });
152
82
  assert(res.ready, "the boundary fallback resolved the edge");
153
- assertEquals(fallbackCalls, 1, "the fallback fires exactly once, at the boundary — never per attempt");
83
+ assertEquals(fallbackCalls, 1, "the fallback fires exactly once at the boundary");
84
+ assertEquals(published.length, 1, "the fallback ready result publishes once");
154
85
  assertEquals(published[0]?.bind?.resolvedArtifact, "@nanobpm/urban@0.60.0");
155
86
  });
156
87
 
157
- test("pollUntilReady: a fallback that does not resolve leaves the not-ready outcome for the engine timer", async () => {
158
- const clock = fakeClock();
88
+ test("probeSingleShot: lastAttempt fallback throwing is caught by class name and stays not-ready", async () => {
89
+ const seen: string[] = [];
159
90
  let publishes = 0;
160
- const res = await pollUntilReady({
161
- probe: httpProbe({ everyMs: 5, timeoutMs: 30, backoff: "fixed" }),
162
- gateKey: "gate-fallback-noop",
91
+ const res = await probeSingleShot({
92
+ probe: httpProbe(),
163
93
  exec: execReturning([{ status: 503, body: "" }]),
164
94
  env: {},
165
- now: clock.now,
166
- wait: clock.wait,
95
+ lastAttempt: true,
167
96
  publish: async () => {
168
97
  publishes += 1;
169
98
  },
170
- fallback: async () => ({ ready: false, detail: "still nothing" }),
99
+ fallback: async () => {
100
+ throw new Error("boom at https://h/p?token=fallback-secret");
101
+ },
102
+ log: (msg) => seen.push(msg),
171
103
  });
172
- assert(!res.ready, "an inconclusive fallback keeps the wait bounded by the engine timer");
173
- assertEquals(publishes, 0, "no readiness signal is published when the fallback does not resolve");
174
- assert(
175
- res.detail.includes("still nothing"),
176
- "the inconclusive fallback's (redacted) diagnostic is surfaced in the returned detail, not discarded",
177
- );
104
+ assert(!res.ready, "a throwing fallback keeps the not-ready outcome for the engine timer");
105
+ assertEquals(publishes, 0, "nothing is published when the fallback throws");
106
+ const all = [res.detail, ...seen].join("\n");
107
+ assert(all.includes("fallback error: Error"), "the fallback error is logged by class name");
108
+ assert(!all.includes("fallback-secret"), "the raw error message must not leak");
178
109
  });
179
110
 
180
- test("pollUntilReady: a never-green probe exhausts its budget and returns not-ready WITHOUT publishing", async () => {
181
- const clock = fakeClock();
182
- let publishes = 0;
183
- const exec = execReturning([{ status: 503, body: "" }]);
184
- const res = await pollUntilReady({
185
- probe: httpProbe({ everyMs: 5, timeoutMs: 100, backoff: "fixed" }),
186
- gateKey: "gate-2",
187
- exec,
111
+ test("probeSingleShot: inconclusive lastAttempt fallback surfaces its redacted detail", async () => {
112
+ const res = await probeSingleShot({
113
+ probe: httpProbe(),
114
+ exec: execReturning([{ status: 503, body: "" }]),
188
115
  env: {},
189
- now: clock.now,
190
- wait: clock.wait,
191
- publish: async () => {
192
- publishes += 1;
193
- },
116
+ lastAttempt: true,
117
+ publish: async () => {},
118
+ fallback: async () => ({ ready: false, detail: "still nothing" }),
194
119
  });
195
- assert(!res.ready, "the wait was bounded — the loop gave up instead of hanging");
196
- assertEquals(publishes, 0, "a not-ready probe never publishes a readiness signal");
197
- assert(clock.now() <= 100, "the loop stopped at (or before) its declared budget");
120
+ assert(!res.ready, "an inconclusive fallback keeps not-ready");
121
+ assert(res.detail.includes("fallback: still nothing"), "the fallback diagnostic is preserved");
198
122
  });
199
123
 
200
- test("pollUntilReady: keeps probing up to the deadline a flip-to-ready in the final backoff window is caught, not missed", async () => {
201
- // everyMs 10, budget 25: three deterministic probes land at t=0,10,20. A full-backoff sleep from
202
- // t=20 would jump to t=30 (past the 25ms bound) and stop probing early, missing a green at t=25 and
203
- // forcing a spurious timeout escalation. The clamp keeps probing to the same bound the engine holds.
204
- const clock = fakeClock();
205
- let publishes = 0;
206
- const exec = execReturning([
207
- { status: 503, body: "" },
208
- { status: 503, body: "" },
209
- { status: 503, body: "" },
210
- { status: 200, body: "ok" },
211
- ]);
212
- const res = await pollUntilReady({
213
- probe: httpProbe({ everyMs: 10, timeoutMs: 25, backoff: "fixed" }),
214
- gateKey: "gate-final-window",
124
+ test("probeSingleShot: forwards a matcher's bind through publish into the message variables (#274 Gap B)", async () => {
125
+ const published: Array<{ detail: string; bind?: Record<string, string> }> = [];
126
+ const payload = JSON.stringify([{ tag_name: "@nanobpm/urban@0.54.0", body: "## Provenance\n- #274\n" }]);
127
+ const exec: ProbeExec = {
128
+ async httpGet() {
129
+ return { status: 0, body: "" };
130
+ },
131
+ async run(): Promise<CommandResult> {
132
+ return { code: 0, stdout: payload, stderr: "" };
133
+ },
134
+ };
135
+ const res = await probeSingleShot({
136
+ probe: parseProbe({
137
+ kind: "capability",
138
+ target: "github-releases:nanobpm/nano-ide",
139
+ match: { capabilityRef: "nano-ide#274", package: "@nanobpm/urban" },
140
+ }),
215
141
  exec,
216
142
  env: {},
217
- now: clock.now,
218
- wait: clock.wait,
219
- publish: async () => {
220
- publishes += 1;
143
+ publish: async (detail, bind) => {
144
+ published.push({ detail, bind });
221
145
  },
222
146
  });
223
- assert(res.ready, "the flip-to-ready inside the final backoff window was probed and caught");
224
- assertEquals(publishes, 1, "the readiness signal was published exactly once");
225
- assert(clock.now() <= 25, "the worker never probed past the engine-enforced deadline");
147
+ assert(res.ready, "the capability edge resolved");
148
+ assertEquals(published.length, 1, "exactly one readiness message was published");
149
+ assertEquals(published[0]?.bind?.resolvedArtifact, "@nanobpm/urban@0.54.0", "the resolved artifact flowed through the bind");
226
150
  });
227
151
 
228
- test("pollUntilReady: an I/O throw is caught and treated as not-ready (never rejects), and its raw message is not leaked", async () => {
229
- const clock = fakeClock();
152
+ test("probeSingleShot: I/O throw is caught, raw message not leaked, and no retry loop runs", async () => {
230
153
  const seen: string[] = [];
154
+ let calls = 0;
231
155
  const exec: ProbeExec = {
232
156
  async httpGet() {
157
+ calls += 1;
233
158
  throw new Error("connection refused to https://h/p?token=s3cr3t");
234
159
  },
235
160
  async run(): Promise<CommandResult> {
236
161
  return { code: 1, stdout: "", stderr: "" };
237
162
  },
238
163
  };
239
- const res = await pollUntilReady({
240
- probe: httpProbe({ everyMs: 5, timeoutMs: 30, backoff: "fixed" }),
241
- gateKey: "gate-3",
164
+ const res = await probeSingleShot({
165
+ probe: httpProbe({ everyMs: 1, timeoutMs: 60_000, backoff: "fixed" }),
242
166
  exec,
243
167
  env: {},
244
- now: clock.now,
245
- wait: clock.wait,
246
168
  publish: async () => {},
247
169
  log: (msg) => seen.push(msg),
248
170
  });
249
171
  assert(!res.ready, "a transport failure is a transient not-ready, not a crash");
172
+ assertEquals(calls, 1, "regression guard: an I/O throw does not enter an internal retry loop");
250
173
  const all = [res.detail, ...seen].join("\n");
251
174
  assert(!all.includes("s3cr3t"), "the raw error message (with its secret) must not leak");
252
175
  assert(!all.includes("connection refused"), "only the error class is surfaced, not the message");
253
176
  });
254
177
 
255
178
  test("handler: a blank gateKey fails fast (an empty correlationKey would never release the gate)", async () => {
256
- const job = { variables: { probe: { kind: "http", target: "https://x/health" }, gateKey: " " } };
179
+ const job = { variables: { probe: { kind: "http", target: "https://x/health" }, gateKey: " ", probeTimeout: "PT30M" } };
257
180
  await assertRejects(
258
181
  // biome-ignore lint/suspicious/noExplicitAny: minimal job/app stub for the fail-fast guard.
259
182
  () => handler(job as any, {} as any),
@@ -262,87 +185,11 @@ test("handler: a blank gateKey fails fast (an empty correlationKey would never r
262
185
  );
263
186
  });
264
187
 
265
- test("pollUntilReady: the poll cadence reads NANO_READINESS_POLL_EVERY_MS from the injected env, not ambient process.env", async () => {
266
- const clock = fakeClock();
267
- // Descriptor omits everyMs, so the cadence falls back to the env contract. A small injected value
268
- // (25ms) makes the loop step through its 100ms budget; the ambient default (15_000ms) would blow
269
- // the budget on the first wait and bail at now()=0. Asserting the clock advanced proves the
270
- // injected env — not process.env — drove the cadence.
271
- const exec = execReturning([{ status: 503, body: "" }]);
272
- const res = await pollUntilReady({
273
- probe: httpProbe({ timeoutMs: 100, backoff: "fixed" }),
274
- gateKey: "gate-env",
275
- exec,
276
- env: { NANO_READINESS_POLL_EVERY_MS: "25" },
277
- now: clock.now,
278
- wait: clock.wait,
279
- publish: async () => {},
280
- });
281
- assert(!res.ready, "the never-green probe exhausted its budget");
282
- assert(clock.now() > 0, "the injected env's 25ms cadence stepped the clock (ambient default would bail at 0)");
283
- });
284
-
285
- test("pollUntilReady: an omitted poll.timeoutMs takes the budget from NANO_READINESS_POLL_TIMEOUT, not the built-in 30m", async () => {
286
- const clock = fakeClock();
287
- // Regression for the worker/gate timeout drift: the gate's engine timer derives from
288
- // NANO_READINESS_POLL_TIMEOUT when the descriptor omits poll.timeoutMs, but the worker used to
289
- // fall back to the hard-coded 30m default. With a 45m env budget and no descriptor timeout, the
290
- // never-green probe must keep polling PAST 30m (up to the 45m the gate itself waits) so it can't
291
- // go silent while the gate is still waiting. everyMs is clamped to MAX_EVERY_MS (5m), so ~9 waits
292
- // land the clock beyond 30m only if the env budget — not the 30m default — is in force.
293
- const exec = execReturning([{ status: 503, body: "" }]);
294
- const THIRTY_MIN = 30 * 60_000;
295
- const res = await pollUntilReady({
296
- probe: httpProbe({ everyMs: 5 * 60_000, backoff: "fixed" }),
297
- gateKey: "gate-timeout-env",
298
- exec,
299
- env: { NANO_READINESS_POLL_TIMEOUT: "PT45M" },
300
- now: clock.now,
301
- wait: clock.wait,
302
- publish: async () => {},
303
- });
304
- assert(!res.ready, "the never-green probe exhausted its budget");
305
- assert(
306
- clock.now() > THIRTY_MIN,
307
- `the worker honored the 45m env budget (probed past 30m); stopped at ${clock.now()}ms`,
308
- );
309
- });
310
-
311
- test("pollUntilReady: the seeded probeTimeout binds the worker to the gate per-instance, overriding ambient env", async () => {
312
- const clock = fakeClock();
313
- // Regression for the per-instance worker/gate drift: the gate's engine timers fire off the seeded
314
- // `probeTimeout` process variable, so the worker must adopt THAT value, not recompute from the
315
- // ambient env. Here the seeded bound (45m) is generous while the ambient env (1m) is stale/smaller.
316
- // The env-recomputing code would bail after 1m — going silent while the engine still waits 45m —
317
- // so the never-green probe must instead keep polling PAST 30m to prove the seeded value is in force.
318
- const exec = execReturning([{ status: 503, body: "" }]);
319
- const THIRTY_MIN = 30 * 60_000;
320
- const res = await pollUntilReady({
321
- probe: httpProbe({ everyMs: 5 * 60_000, backoff: "fixed" }),
322
- gateKey: "gate-seeded",
323
- probeTimeout: "PT45M",
324
- exec,
325
- env: { NANO_READINESS_POLL_TIMEOUT: "PT1M" },
326
- now: clock.now,
327
- wait: clock.wait,
328
- publish: async () => {},
329
- });
330
- assert(!res.ready, "the never-green probe exhausted its (seeded) budget");
331
- assert(
332
- clock.now() > THIRTY_MIN,
333
- `the worker honored the seeded 45m probeTimeout over the 1m env; stopped at ${clock.now()}ms`,
334
- );
335
- });
336
-
337
188
  test("READINESS_READY_MESSAGE is the name the gate correlates", () => {
338
189
  assertEquals(READINESS_READY_MESSAGE, "readiness-ready");
339
190
  });
340
191
 
341
192
  test("readGateVars: returns the RAW (untrimmed) gateKey so the publish key matches the gate's =gateKey subscription", () => {
342
- // The gate's message subscription binds correlationKey="=gateKey" (the untrimmed process variable).
343
- // If a caller seeds a whitespace-padded gateKey, trimming the publish key would desync it from the
344
- // subscription — a green probe would then release the wait only via the timeout arm. So the key we
345
- // publish on must be the raw value, byte-for-byte.
346
193
  const { gateKey } = readGateVars({ gateKey: " gate-x ", probeTimeout: "PT30M" });
347
194
  assertEquals(gateKey, " gate-x ", "the raw gateKey is preserved for byte-for-byte correlation");
348
195
  });
@@ -361,9 +208,6 @@ test("readGateVars: a blank/whitespace gateKey fails fast (an empty correlationK
361
208
  });
362
209
 
363
210
  test("readGateVars: a missing/blank probeTimeout fails fast rather than silently falling back to env", () => {
364
- // probeTimeout drives BOTH the gate's engine timers (=probeTimeout). A missing/non-string value used
365
- // to fall back to the env-derived twin, breaking the per-instance bound and masking a mis-seeded
366
- // instance until it escalated. It must now fail fast.
367
211
  for (const probeTimeout of [undefined, "", " ", 42, null]) {
368
212
  let threw = false;
369
213
  try {