cursedops 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/smoke.ts CHANGED
@@ -31,9 +31,19 @@
31
31
  * |---|---|---|
32
32
  * | `0` | every check passed | keep the release |
33
33
  * | `1` | the APP is wrong | roll back |
34
- * | `2` | the EDGE is wrong — DNS, tunnel, hostname, policy | do **not** roll back |
34
+ * | `2` | NOT the app — the edge (DNS, tunnel, hostname, policy), the NETWORK the smoke ran on, or the smoke's OWN environment | do **not** roll back |
35
35
  *
36
36
  * Two is the load-bearing one and it is why {@link Smoke.edgeFault} exists.
37
+ *
38
+ * 🔴 **0.5.0 widened what a `2` can mean and did NOT add a code**, deliberately. Every deploy
39
+ * script in the fleet reads this contract as `0 → keep`, `2 → stop without reverting`, anything
40
+ * else → `git revert` (`apps/collections/scripts/deploy.ts:413-424` and its four siblings). A new
41
+ * `3` for "the smoke's credential is missing" would fall into the revert branch of every one of
42
+ * them — the exact failure it exists to prevent — so the distinction lives in {@link Smoke.verdict}
43
+ * and in the sentence {@link Smoke.report} prints, and the CODE stays one every reader already
44
+ * handles. The one direction that is never excused: a dead ORIGIN ({@link probeOrigin}) is `1`
45
+ * whatever else the run saw, because a live edge over a dead origin is precisely the green lie
46
+ * (incident 5 above) this module exists to refuse.
37
47
  * Reverting good application code cannot restore a DNS record, a tunnel route or
38
48
  * an access policy: a rollback there discards work while leaving the real fault
39
49
  * exactly where it is. {@link Smoke.finish} is the single place that code is
@@ -71,12 +81,29 @@
71
81
  */
72
82
 
73
83
  import { spawnSync } from "node:child_process";
84
+ import { lookup } from "node:dns/promises";
85
+ import { isIP } from "node:net";
74
86
 
75
87
  export interface Failure {
76
88
  check: string;
77
89
  detail: string;
78
90
  }
79
91
 
92
+ /**
93
+ * What a finished smoke concluded, in the order {@link Smoke.report} decides it:
94
+ *
95
+ * · `origin` — the process behind the edge did not answer on loopback. Exit 1, ALWAYS: a cache
96
+ * in front of a dead origin is the one thing no other verdict may excuse.
97
+ * · `environment` — the smoke could not judge the app, because something IT needs (a
98
+ * credential, a file) is missing here. Exit 2: nothing about the app was measured.
99
+ * · `network` — the public checks failed and this machine's resolver is substituting answers
100
+ * for a Cloudflare-proxied hostname. Exit 2: the app is not at fault.
101
+ * · `edge` — something in front of the app could not be reached or disagrees. Exit 2.
102
+ * · `app` — the app answered wrongly. Exit 1, roll back.
103
+ * · `pass` — exit 0.
104
+ */
105
+ export type SmokeVerdict = "pass" | "app" | "origin" | "edge" | "network" | "environment";
106
+
80
107
  export interface SmokeOptions {
81
108
  /** The origin every relative path is asked against. A trailing slash is trimmed. */
82
109
  base: string;
@@ -91,6 +118,8 @@ export interface SmokeOptions {
91
118
  headers?: () => Record<string, string>;
92
119
  /** Injected for tests. Resolves a hostname against a public resolver. */
93
120
  resolve?: (host: string) => string;
121
+ /** Injected for tests. How {@link Smoke.checkNetwork} asks the resolvers — see {@link diagnoseNetwork}. */
122
+ network?: NetworkProbe;
94
123
  log?: (line: string) => void;
95
124
  error?: (line: string) => void;
96
125
  }
@@ -109,12 +138,47 @@ export interface Smoke {
109
138
  anonymous(path: string, init?: RequestInit): Promise<Response>;
110
139
  /** Mark the run as an edge fault: reached nothing, so code 2 rather than 1. */
111
140
  markEdgeFault(): void;
141
+ /**
142
+ * Record that the ORIGIN is dead — code 1 whatever else happened. {@link probeOrigin} is the
143
+ * one caller that should need this; it is public so an app with its own origin check can say so.
144
+ */
145
+ markOriginFault(check: string, detail: string): void;
146
+ /** True once {@link markOriginFault} has been called. */
147
+ readonly originFault: boolean;
148
+ /**
149
+ * Assert the SMOKE's own environment before asserting the app: every name in `needs` whose
150
+ * value is empty is refused with `how` (what to do about it), and the run can no longer pass
151
+ * or fail the app — it exits 2 with the sentence. Returns true when nothing was missing.
152
+ *
153
+ * 🔴 Measured 2026-09-17 on `desk`: `✗ FAILED — 5 of 6` about a deploy that was serving
154
+ * correctly, because only the deploy script loaded the Access token and the smoke, run by a
155
+ * person, hit the Access wall and charged it to the app.
156
+ */
157
+ requireEnvironment(needs: Record<string, string | null | undefined>, how: string): boolean;
158
+ /** One free-form environment refusal — for a need that is not a variable (a file, a socket). */
159
+ refuseEnvironment(what: string, how: string): void;
160
+ /** Every environment refusal so far. */
161
+ readonly refusals: readonly Failure[];
162
+ /**
163
+ * Ask whether this machine's NETWORK is lying about {@link base}'s hostname — see
164
+ * {@link diagnoseNetwork}. When it is, the run is marked a network fault (exit 2) and the
165
+ * lines say where the fault lives. Returns the diagnosis.
166
+ */
167
+ checkNetwork(): Promise<NetworkDiagnosis>;
168
+ /** The conclusion {@link report} will print, without printing it. */
169
+ verdict(): SmokeVerdict;
112
170
  /** A sentence to append to a fetch failure, or `""`. See {@link createSmoke}. */
113
171
  dnsHint(error: Error): string;
114
172
  /** Print the ledger and return the exit code. Does not exit. */
115
173
  report(): number;
116
174
  /** {@link report}, then `process.exit` with it. */
117
175
  finish(): never;
176
+ /**
177
+ * {@link checkNetwork} when the run failed for a reason other than a dead origin, then
178
+ * {@link report}. Returns the exit code; does not exit. The recommended ending of a smoke
179
+ * that asks a public hostname: `process.exit(await smoke.settle())`.
180
+ */
181
+ settle(): Promise<number>;
118
182
  }
119
183
 
120
184
  /**
@@ -147,7 +211,10 @@ export function createSmoke(options: SmokeOptions): Smoke {
147
211
 
148
212
  const passed: string[] = [];
149
213
  const failures: Failure[] = [];
214
+ const refusals: Failure[] = [];
150
215
  let edgeFault = false;
216
+ let originFault = false;
217
+ let network: NetworkDiagnosis | null = null;
151
218
 
152
219
  const smoke: Smoke = {
153
220
  base,
@@ -157,9 +224,15 @@ export function createSmoke(options: SmokeOptions): Smoke {
157
224
  get failures() {
158
225
  return failures;
159
226
  },
227
+ get refusals() {
228
+ return refusals;
229
+ },
160
230
  get edgeFault() {
161
231
  return edgeFault;
162
232
  },
233
+ get originFault() {
234
+ return originFault;
235
+ },
163
236
 
164
237
  record(check, ok, detail) {
165
238
  if (ok) passed.push(`${check} — ${detail}`);
@@ -187,6 +260,44 @@ export function createSmoke(options: SmokeOptions): Smoke {
187
260
  edgeFault = true;
188
261
  },
189
262
 
263
+ markOriginFault(check, detail) {
264
+ originFault = true;
265
+ failures.push({ check, detail });
266
+ },
267
+
268
+ requireEnvironment(needs, how) {
269
+ const missing = Object.entries(needs)
270
+ .filter(([, value]) => !value?.trim())
271
+ .map(([name]) => name);
272
+ if (missing.length === 0) return true;
273
+ smoke.refuseEnvironment(`${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not set for this smoke`, how);
274
+ return false;
275
+ },
276
+
277
+ refuseEnvironment(what, how) {
278
+ refusals.push({ check: what, detail: how });
279
+ },
280
+
281
+ async checkNetwork() {
282
+ let host: string;
283
+ try {
284
+ host = new URL(base).hostname;
285
+ } catch {
286
+ return { verdict: "not-applicable", lines: [`${base} is not a URL`] };
287
+ }
288
+ network = await diagnoseNetwork(host, options.network);
289
+ return network;
290
+ },
291
+
292
+ verdict() {
293
+ if (originFault) return "origin";
294
+ if (refusals.length > 0) return "environment";
295
+ if (failures.length === 0) return "pass";
296
+ if (network?.verdict === "intercepting") return "network";
297
+ if (edgeFault) return "edge";
298
+ return "app";
299
+ },
300
+
190
301
  dnsHint(failed: Error) {
191
302
  if (!LOOKS_LIKE_DNS.test(failed.message)) return "";
192
303
  const host = new URL(base).hostname;
@@ -204,19 +315,50 @@ export function createSmoke(options: SmokeOptions): Smoke {
204
315
  report() {
205
316
  for (const line of passed) log(` ✓ ${line}`);
206
317
  for (const failure of failures) error(` ✗ ${failure.check} — ${failure.detail}`);
318
+ for (const refusal of refusals) error(` ⚠ ${refusal.check} — ${refusal.detail}`);
207
319
  const total = passed.length + failures.length;
208
- if (failures.length === 0) {
320
+ const verdict = smoke.verdict();
321
+ if (verdict === "pass") {
209
322
  log(`\n✓ deploy smoke passed — ${total} checks against ${base}`);
210
323
  return 0;
211
324
  }
325
+ if (verdict === "environment") {
326
+ error(`\n⚠ this smoke could not judge the app at ${base} — its OWN environment is missing something (above).`);
327
+ error(" (exit 2 — not an app failure: nothing about the app was measured. Rolling back would not change it.)");
328
+ return 2;
329
+ }
212
330
  error(`\n✗ deploy smoke FAILED — ${failures.length} of ${total} checks against ${base}`);
213
- if (edgeFault) error(" (exit 2 — an EDGE fault. Rolling the app back would not change it.)");
214
- return edgeFault ? 2 : 1;
331
+ if (verdict === "origin") {
332
+ error(" (exit 1 — the ORIGIN is dead. Whatever the public hostname said came from something in front of it.)");
333
+ return 1;
334
+ }
335
+ if (verdict === "network") {
336
+ error("\n🛜 this network is intercepting DNS — the app is not at fault:");
337
+ for (const line of network?.lines ?? []) error(` ${line}`);
338
+ error(" (exit 2 — the NETWORK this ran on. Re-run from another network, or over encrypted DNS.)");
339
+ return 2;
340
+ }
341
+ if (verdict === "edge") {
342
+ error(" (exit 2 — an EDGE fault. Rolling the app back would not change it.)");
343
+ return 2;
344
+ }
345
+ return 1;
215
346
  },
216
347
 
217
348
  finish() {
218
349
  process.exit(smoke.report());
219
350
  },
351
+
352
+ async settle() {
353
+ if (failures.length > 0 && !originFault && refusals.length === 0) {
354
+ try {
355
+ await smoke.checkNetwork();
356
+ } catch {
357
+ // A diagnosis that could not run is not a verdict; the ledger stands as it is.
358
+ }
359
+ }
360
+ return smoke.report();
361
+ },
220
362
  };
221
363
 
222
364
  return smoke;
@@ -421,3 +563,333 @@ export async function sameDeployedShell(smoke: Smoke, addresses: string[], optio
421
563
  });
422
564
  return agreed;
423
565
  }
566
+
567
+ // ── The ORIGIN, asked directly ────────────────────────────────────────────────────────────
568
+
569
+ /** The origin probe's name in the ledger. */
570
+ export const ORIGIN_CHECK = "the origin answers on loopback";
571
+
572
+ export interface OriginOptions {
573
+ /** What the port was read from, for the sentence when there is none — usually the launchd label. */
574
+ label?: string;
575
+ /** The path to ask. `/healthz` unless the app's liveness answer lives elsewhere. */
576
+ path?: string;
577
+ /** What counts as alive. Default: a `200`. */
578
+ accept?: (response: Response) => boolean | Promise<boolean>;
579
+ timeoutMs?: number;
580
+ /** Injected for tests. Defaults to the global `fetch`. */
581
+ fetch?: (url: string, init: RequestInit) => Promise<Response>;
582
+ }
583
+
584
+ /**
585
+ * Ask the process itself, on `127.0.0.1:<port>`, and record a dead one as an ORIGIN fault —
586
+ * exit 1 whatever the public hostname said.
587
+ *
588
+ * 🔴 **A deployed smoke that only asks the public hostname is asking Cloudflare, not the app.**
589
+ * Measured 2026-09-15/17: `patterns` and `collections` each served a cached `200` over a `503`
590
+ * origin with every check green, and four apps' smokes (`patterns`, `roms`, `family`, `desk`)
591
+ * made no loopback request at all. Loopback cannot be cached by anything, so this is the one
592
+ * question whose answer the edge cannot forge.
593
+ *
594
+ * Pass the port from `livePort(LABEL)` in `cursedops/launchd` — the LIVE declaration, never
595
+ * `package.json`, which advertised the wrong port in eight of eight apps. A `null` port is
596
+ * recorded as the fault it is: no job, no origin, and a public `200` can only be a cache.
597
+ */
598
+ export async function probeOrigin(smoke: Smoke, port: number | null, options: OriginOptions = {}): Promise<boolean> {
599
+ const path = options.path ?? "/healthz";
600
+ const accept = options.accept ?? ((response: Response) => response.status === 200);
601
+ const send = options.fetch ?? ((url: string, init: RequestInit) => fetch(url, init));
602
+ const where = options.label ? ` (${options.label})` : "";
603
+ if (port === null) {
604
+ smoke.markOriginFault(
605
+ ORIGIN_CHECK,
606
+ `there is no origin to ask — no live port${where}. The job is not loaded or declares no PORT, so any 200 the public hostname gave came from a cache.`,
607
+ );
608
+ return false;
609
+ }
610
+ const url = `http://127.0.0.1:${port}${path}${path.includes("?") ? "&" : "?"}cb=${Date.now().toString(36)}`;
611
+ try {
612
+ const response = await send(url, { redirect: "manual", signal: AbortSignal.timeout(options.timeoutMs ?? 10_000) });
613
+ if (await accept(response)) return smoke.record(ORIGIN_CHECK, true, `GET 127.0.0.1:${port}${path} → ${response.status}`);
614
+ smoke.markOriginFault(ORIGIN_CHECK, `GET 127.0.0.1:${port}${path} → ${response.status}${where} — the process behind the edge is not healthy`);
615
+ } catch (thrown) {
616
+ smoke.markOriginFault(ORIGIN_CHECK, `GET 127.0.0.1:${port}${path}${where} — ${(thrown as Error).message}. Nothing is serving behind the edge.`);
617
+ }
618
+ return false;
619
+ }
620
+
621
+ // ── The NETWORK the smoke ran on ──────────────────────────────────────────────────────────
622
+
623
+ /**
624
+ * Cloudflare's published proxy ranges (`https://www.cloudflare.com/ips-v4` and `/ips-v6`), as of
625
+ * 2026-09-19. The fallback, never the first answer: {@link diagnoseNetwork} asks for the live
626
+ * list first, and uses this only when it cannot be fetched — which is most likely on the very
627
+ * network this exists to describe.
628
+ */
629
+ export const CLOUDFLARE_RANGES_SNAPSHOT: readonly string[] = [
630
+ "173.245.48.0/20",
631
+ "103.21.244.0/22",
632
+ "103.22.200.0/22",
633
+ "103.31.4.0/22",
634
+ "141.101.64.0/18",
635
+ "108.162.192.0/18",
636
+ "190.93.240.0/20",
637
+ "188.114.96.0/20",
638
+ "197.234.240.0/22",
639
+ "198.41.128.0/17",
640
+ "162.158.0.0/15",
641
+ "104.16.0.0/13",
642
+ "104.24.0.0/14",
643
+ "172.64.0.0/13",
644
+ "131.0.72.0/22",
645
+ "2400:cb00::/32",
646
+ "2606:4700::/32",
647
+ "2803:f800::/32",
648
+ "2405:b500::/32",
649
+ "2405:8100::/32",
650
+ "2a06:98c0::/29",
651
+ "2c0f:f248::/32",
652
+ ];
653
+
654
+ function ipToBig(ip: string): bigint | null {
655
+ if (isIP(ip) === 4) return ip.split(".").reduce((a, o) => (a << 8n) + BigInt(Number(o)), 0n);
656
+ if (isIP(ip) !== 6) return null;
657
+ const bare = ip.split("%")[0] as string;
658
+ // `::ffff:1.2.3.4` — the tail is dotted and is two groups, not one.
659
+ const dotted = /(\d+\.\d+\.\d+\.\d+)$/.exec(bare);
660
+ const text = dotted
661
+ ? bare.replace(dotted[1] as string, (() => {
662
+ const n = ipToBig(dotted[1] as string) as bigint;
663
+ return `${(n >> 16n).toString(16)}:${(n & 0xffffn).toString(16)}`;
664
+ })())
665
+ : bare;
666
+ const [head, tail] = text.split("::");
667
+ const h = head ? head.split(":") : [];
668
+ const t = tail !== undefined ? (tail ? tail.split(":") : []) : [];
669
+ const mid = tail !== undefined ? Array(8 - h.length - t.length).fill("0") : [];
670
+ return [...h, ...mid, ...t].reduce((a, g) => (a << 16n) + BigInt(Number.parseInt(g || "0", 16)), 0n);
671
+ }
672
+
673
+ /** Is `ip` inside one of `ranges` (CIDR strings)? Lifted from `tools/check-public-dns.ts`. */
674
+ export function inRanges(ip: string, ranges: readonly string[]): boolean {
675
+ const addr = ipToBig(ip);
676
+ if (addr === null) return false;
677
+ const v6 = isIP(ip) === 6;
678
+ for (const cidr of ranges) {
679
+ const [net, bitsRaw] = cidr.split("/");
680
+ if (!net || !bitsRaw || (isIP(net) === 6) !== v6) continue;
681
+ const base = ipToBig(net);
682
+ if (base === null) continue;
683
+ const width = v6 ? 128n : 32n;
684
+ const bits = BigInt(bitsRaw);
685
+ const mask = ((1n << bits) - 1n) << (width - bits);
686
+ if ((addr & mask) === (base & mask)) return true;
687
+ }
688
+ return false;
689
+ }
690
+
691
+ /** One independent resolver's answer; `null` when it could not be reached for this host. */
692
+ export interface IndependentAnswer {
693
+ name: string;
694
+ answers: string[] | null;
695
+ }
696
+
697
+ /** How {@link diagnoseNetwork} asks. Every member is injectable, so a test never touches a network. */
698
+ export interface NetworkProbe {
699
+ /** What THIS machine's resolver answers — the path a `fetch` really takes. Default: `dns.lookup`, all addresses. */
700
+ system?: (host: string) => Promise<string[]>;
701
+ /** What resolvers that cannot be silently substituted answer. Default: DoH to Cloudflare and Google. */
702
+ independent?: (host: string) => Promise<IndependentAnswer[]>;
703
+ /** Cloudflare's proxy ranges. Default: the published list, else {@link CLOUDFLARE_RANGES_SNAPSHOT}. */
704
+ ranges?: () => Promise<readonly string[]>;
705
+ }
706
+
707
+ export interface NetworkDiagnosis {
708
+ /**
709
+ * `intercepting` — this machine's answer for a Cloudflare-proxied hostname is outside every
710
+ * published range, or is one no independent resolver gives: the NETWORK is at fault.
711
+ * `clean` — the network is honest; whatever failed, it was not this. `not-proxied` — the
712
+ * hostname is not behind Cloudflare's proxy, so the range rule says nothing. `offline` —
713
+ * nothing resolved it at all. `not-applicable` — an IP literal or a loopback name.
714
+ */
715
+ verdict: "intercepting" | "clean" | "not-proxied" | "offline" | "not-applicable";
716
+ lines: string[];
717
+ }
718
+
719
+ async function systemAnswers(host: string): Promise<string[]> {
720
+ try {
721
+ return (await lookup(host, { all: true })).map((entry) => entry.address);
722
+ } catch {
723
+ return [];
724
+ }
725
+ }
726
+
727
+ const DOH_ENDPOINTS = [
728
+ ["cloudflare-dns.com", "https://cloudflare-dns.com/dns-query"],
729
+ ["dns.google", "https://dns.google/resolve"],
730
+ ] as const;
731
+
732
+ async function dohAnswers(host: string): Promise<IndependentAnswer[]> {
733
+ const ask = async (endpoint: string, type: "A" | "AAAA"): Promise<string[] | null> => {
734
+ try {
735
+ const response = await fetch(`${endpoint}?name=${encodeURIComponent(host)}&type=${type}`, {
736
+ headers: { accept: "application/dns-json" },
737
+ signal: AbortSignal.timeout(8_000),
738
+ });
739
+ if (!response.ok) return null;
740
+ const body = (await response.json()) as { Answer?: Array<{ type: number; data: string }> };
741
+ return (body.Answer ?? []).filter((a) => a.type === 1 || a.type === 28).map((a) => a.data);
742
+ } catch {
743
+ return null;
744
+ }
745
+ };
746
+ return await Promise.all(
747
+ DOH_ENDPOINTS.map(async ([name, endpoint]) => {
748
+ const both = await Promise.all([ask(endpoint, "A"), ask(endpoint, "AAAA")]);
749
+ // 🔴 Either family failing means this endpoint did not answer — a partial answer read as
750
+ // complete is how a check reports "everything resolved" while blind to v6.
751
+ return { name, answers: both.some((b) => b === null) ? null : both.flatMap((b) => b ?? []) };
752
+ }),
753
+ );
754
+ }
755
+
756
+ async function publishedRanges(): Promise<readonly string[]> {
757
+ try {
758
+ const out: string[] = [];
759
+ for (const url of ["https://www.cloudflare.com/ips-v4", "https://www.cloudflare.com/ips-v6"]) {
760
+ const response = await fetch(url, { signal: AbortSignal.timeout(8_000) });
761
+ if (!response.ok) return CLOUDFLARE_RANGES_SNAPSHOT;
762
+ out.push(...(await response.text()).split("\n").map((l) => l.trim()).filter((l) => l.includes("/")));
763
+ }
764
+ return out.length > 0 ? out : CLOUDFLARE_RANGES_SNAPSHOT;
765
+ } catch {
766
+ return CLOUDFLARE_RANGES_SNAPSHOT;
767
+ }
768
+ }
769
+
770
+ /**
771
+ * Is the network this smoke is running on LYING about `host`?
772
+ *
773
+ * 🔴 Measured 2026-09-19 over an airline's wifi: the aircraft's resolver answered
774
+ * `desk.cursedalchemy.com` with `1.1.1.1` — Cloudflare's public RESOLVER, not an edge that serves
775
+ * customer zones — and Cloudflare refused the SNI with `403 · Error 1034`. Nothing in the fleet
776
+ * was wrong and nothing in the fleet could say so; every deployed smoke run there charges that
777
+ * 403 to the app, because a smoke's verdict is a function of whatever resolver its machine has.
778
+ *
779
+ * The mechanism is `tools/check-public-dns.ts`'s, lifted: a Cloudflare-proxied hostname MUST
780
+ * resolve inside the ranges Cloudflare publishes, so an answer outside them, or one no
781
+ * independent resolver gives, is the network substituting answers — true everywhere, and
782
+ * checkable from the bad network itself. Whether the hostname is proxied at all is read off the
783
+ * independent answers, so a caller never has to say.
784
+ *
785
+ * 🔴 **Diagnosis only. It never retries and never pins an address** — the fault is that a
786
+ * network-caused red was indistinguishable from an app-caused one, and the deliverable is the
787
+ * distinction, not a way around the network.
788
+ */
789
+ export async function diagnoseNetwork(host: string, probe: NetworkProbe = {}): Promise<NetworkDiagnosis> {
790
+ if (isIP(host) !== 0 || host === "localhost" || host.endsWith(".localhost")) {
791
+ return { verdict: "not-applicable", lines: [`${host} is not a public hostname`] };
792
+ }
793
+ const [system, independent, ranges] = await Promise.all([
794
+ (probe.system ?? systemAnswers)(host),
795
+ (probe.independent ?? dohAnswers)(host),
796
+ (probe.ranges ?? publishedRanges)(),
797
+ ]);
798
+ const reached = independent.filter((entry) => entry.answers !== null);
799
+ const world = [...new Set(reached.flatMap((entry) => entry.answers ?? []))];
800
+
801
+ if (reached.length === 0) {
802
+ if (system.length === 0) return { verdict: "offline", lines: [`nothing resolved ${host} — not this machine, not any independent resolver`] };
803
+ const outside = system.filter((ip) => !inRanges(ip, ranges));
804
+ return {
805
+ verdict: "intercepting",
806
+ lines: [
807
+ `no independent resolver (${DOH_ENDPOINTS.map(([name]) => name).join(", ")}) could be reached, while this machine's resolver answers ${system.join(", ")}.`,
808
+ ...(outside.length > 0 ? [`${outside.join(", ")} is OUTSIDE every range Cloudflare publishes.`] : []),
809
+ "A network that blocks both encrypted resolvers is already answering the question.",
810
+ ],
811
+ };
812
+ }
813
+ if (world.length > 0 && world.every((ip) => !inRanges(ip, ranges))) {
814
+ return { verdict: "not-proxied", lines: [`${host} is not served through Cloudflare's proxy (${world.join(", ")}), so the range rule says nothing`] };
815
+ }
816
+
817
+ const lines: string[] = [];
818
+ for (const ip of system) {
819
+ if (inRanges(ip, ranges)) continue;
820
+ lines.push(
821
+ `${host} resolved to ${ip} via this machine's resolver — OUTSIDE every range Cloudflare publishes. Connecting there with this SNI is what returns "403 · Error 1034 · Edge IP Restricted".`,
822
+ );
823
+ }
824
+ const strangers = system.filter((ip) => inRanges(ip, ranges) && !world.includes(ip));
825
+ if (strangers.length > 0) {
826
+ lines.push(`this machine's resolver answers ${strangers.join(", ")}, which NO independent resolver returned (they say ${world.join(", ")}).`);
827
+ }
828
+ if (system.length === 0 && world.length > 0) {
829
+ lines.push(
830
+ `this machine cannot resolve ${host}, which the independent resolvers answer (${world.join(", ")}) — a resolver in the way, or this Mac's negative DNS cache.`,
831
+ );
832
+ }
833
+ return lines.length > 0 ? { verdict: "intercepting", lines } : { verdict: "clean", lines: [`${host}: this machine and ${reached.length} independent resolver(s) agree`] };
834
+ }
835
+
836
+ // ── A version that has SETTLED ────────────────────────────────────────────────────────────
837
+
838
+ export interface SteadyOptions {
839
+ /** Consecutive good reads before the answer is trusted. Default 3. */
840
+ reads?: number;
841
+ /** Reads before giving up and judging whatever answered. Default 40. */
842
+ tries?: number;
843
+ /** Pause after a GOOD read, before the next. Default 2 s. */
844
+ pauseMs?: number;
845
+ /** Pause after a BAD read. Default 5 s. */
846
+ retryMs?: number;
847
+ /** Injected for tests. */
848
+ sleep?: (ms: number) => Promise<void>;
849
+ }
850
+
851
+ export interface SteadyRead<T> {
852
+ /** The LAST response read, body unconsumed — judge this, never the wait. */
853
+ response: Response;
854
+ /** Its body as JSON, or `{}` when it was not JSON. */
855
+ body: T;
856
+ /** Consecutive good reads at the end. `>= reads` means it settled. */
857
+ steady: number;
858
+ /** How many times it asked. */
859
+ asked: number;
860
+ }
861
+
862
+ /**
863
+ * Ask until `ready` holds for `reads` reads IN A ROW, then hand back the last answer.
864
+ *
865
+ * 🔴 **Several reads, not one.** A deploy and each secret upload are separate Worker versions,
866
+ * and for some seconds consecutive requests land on different ones. Measured on `music`'s first
867
+ * deploy (2026-09-23, `a54ef71`): ONE good `/healthz` read was followed by thirteen gated routes
868
+ * answering `503` (the pre-secrets version) and `404`, because one isolate had the new version
869
+ * and the next did not. The copies in `vault` and `collections` still judged after one read.
870
+ *
871
+ * It never decides pass or fail: it returns what answered, and the caller judges that — a wait
872
+ * that swallowed its own timeout would be a check that cannot go red.
873
+ */
874
+ export async function steadyHealth<T = Record<string, unknown>>(
875
+ ask: () => Promise<Response>,
876
+ ready: (body: T, response: Response) => boolean,
877
+ options: SteadyOptions = {},
878
+ ): Promise<SteadyRead<T>> {
879
+ const reads = options.reads ?? 3;
880
+ const tries = options.tries ?? 40;
881
+ const sleep = options.sleep ?? ((ms: number) => new Promise<void>((done) => setTimeout(done, ms)));
882
+ let steady = 0;
883
+ let asked = 0;
884
+ for (;;) {
885
+ const response = await ask();
886
+ asked++;
887
+ const body = (await response
888
+ .clone()
889
+ .json()
890
+ .catch(() => ({}))) as T;
891
+ steady = ready(body, response) ? steady + 1 : 0;
892
+ if (steady >= reads || asked >= tries) return { response, body, steady, asked };
893
+ await sleep(steady > 0 ? (options.pauseMs ?? 2_000) : (options.retryMs ?? 5_000));
894
+ }
895
+ }