@a11ign/screenreader-fleet 0.0.0-reserved.0 → 0.2.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 (147) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +94 -2
  3. package/dist/capture-client.d.mts +49 -0
  4. package/dist/capture-client.d.mts.map +1 -0
  5. package/dist/capture-client.mjs +352 -0
  6. package/dist/capture-client.mjs.map +1 -0
  7. package/dist/check-worker-code.d.mts +34 -0
  8. package/dist/check-worker-code.d.mts.map +1 -0
  9. package/dist/check-worker-code.mjs +142 -0
  10. package/dist/check-worker-code.mjs.map +1 -0
  11. package/dist/cli-flags.d.mts +71 -0
  12. package/dist/cli-flags.d.mts.map +1 -0
  13. package/dist/cli-flags.mjs +207 -0
  14. package/dist/cli-flags.mjs.map +1 -0
  15. package/dist/code-drift.d.mts +140 -0
  16. package/dist/code-drift.d.mts.map +1 -0
  17. package/dist/code-drift.mjs +284 -0
  18. package/dist/code-drift.mjs.map +1 -0
  19. package/dist/command-line-census.d.mts +33 -0
  20. package/dist/command-line-census.d.mts.map +1 -0
  21. package/dist/command-line-census.mjs +96 -0
  22. package/dist/command-line-census.mjs.map +1 -0
  23. package/dist/compare-workers.d.mts +3 -0
  24. package/dist/compare-workers.d.mts.map +1 -0
  25. package/dist/compare-workers.mjs +332 -0
  26. package/dist/compare-workers.mjs.map +1 -0
  27. package/dist/control-plane-isolation.d.mts +45 -0
  28. package/dist/control-plane-isolation.d.mts.map +1 -0
  29. package/dist/control-plane-isolation.mjs +67 -0
  30. package/dist/control-plane-isolation.mjs.map +1 -0
  31. package/dist/deploy-worker.d.mts +3 -0
  32. package/dist/deploy-worker.d.mts.map +1 -0
  33. package/dist/deploy-worker.mjs +333 -0
  34. package/dist/deploy-worker.mjs.map +1 -0
  35. package/dist/doctor.d.mts +216 -0
  36. package/dist/doctor.d.mts.map +1 -0
  37. package/dist/doctor.mjs +962 -0
  38. package/dist/doctor.mjs.map +1 -0
  39. package/dist/fleet-consistency.d.mts +235 -0
  40. package/dist/fleet-consistency.d.mts.map +1 -0
  41. package/dist/fleet-consistency.mjs +436 -0
  42. package/dist/fleet-consistency.mjs.map +1 -0
  43. package/dist/fleet-env.d.mts +228 -0
  44. package/dist/fleet-env.d.mts.map +1 -0
  45. package/dist/fleet-env.mjs +509 -0
  46. package/dist/fleet-env.mjs.map +1 -0
  47. package/dist/fleet-scripts.d.mts +11 -0
  48. package/dist/fleet-scripts.d.mts.map +1 -0
  49. package/dist/fleet-scripts.mjs +41 -0
  50. package/dist/fleet-scripts.mjs.map +1 -0
  51. package/dist/git-safe-env.d.mts +10 -0
  52. package/dist/git-safe-env.d.mts.map +1 -0
  53. package/dist/git-safe-env.mjs +44 -0
  54. package/dist/git-safe-env.mjs.map +1 -0
  55. package/dist/guest-run.d.mts +26 -0
  56. package/dist/guest-run.d.mts.map +1 -0
  57. package/dist/guest-run.mjs +164 -0
  58. package/dist/guest-run.mjs.map +1 -0
  59. package/dist/host-address.d.mts +33 -0
  60. package/dist/host-address.d.mts.map +1 -0
  61. package/dist/host-address.mjs +105 -0
  62. package/dist/host-address.mjs.map +1 -0
  63. package/dist/host-capacity.d.mts +64 -0
  64. package/dist/host-capacity.d.mts.map +1 -0
  65. package/dist/host-capacity.mjs +152 -0
  66. package/dist/host-capacity.mjs.map +1 -0
  67. package/dist/host-metrics.d.mts +116 -0
  68. package/dist/host-metrics.d.mts.map +1 -0
  69. package/dist/host-metrics.mjs +201 -0
  70. package/dist/host-metrics.mjs.map +1 -0
  71. package/dist/index.d.ts +23 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +25 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/local-vm.d.ts +125 -0
  76. package/dist/local-vm.d.ts.map +1 -0
  77. package/dist/local-vm.js +360 -0
  78. package/dist/local-vm.js.map +1 -0
  79. package/dist/measure-guard.d.mts +34 -0
  80. package/dist/measure-guard.d.mts.map +1 -0
  81. package/dist/measure-guard.mjs +73 -0
  82. package/dist/measure-guard.mjs.map +1 -0
  83. package/dist/normalise-fleet.d.mts +2 -0
  84. package/dist/normalise-fleet.d.mts.map +1 -0
  85. package/dist/normalise-fleet.mjs +76 -0
  86. package/dist/normalise-fleet.mjs.map +1 -0
  87. package/dist/npm-cli-executable.d.mts +42 -0
  88. package/dist/npm-cli-executable.d.mts.map +1 -0
  89. package/dist/npm-cli-executable.mjs +159 -0
  90. package/dist/npm-cli-executable.mjs.map +1 -0
  91. package/dist/probe-outcome.d.mts +89 -0
  92. package/dist/probe-outcome.d.mts.map +1 -0
  93. package/dist/probe-outcome.mjs +104 -0
  94. package/dist/probe-outcome.mjs.map +1 -0
  95. package/dist/protocol-guard.d.mts +34 -0
  96. package/dist/protocol-guard.d.mts.map +1 -0
  97. package/dist/protocol-guard.mjs +121 -0
  98. package/dist/protocol-guard.mjs.map +1 -0
  99. package/dist/source-walk.d.mts +12 -0
  100. package/dist/source-walk.d.mts.map +1 -0
  101. package/dist/source-walk.mjs +56 -0
  102. package/dist/source-walk.mjs.map +1 -0
  103. package/dist/transient-fault.d.mts +6 -0
  104. package/dist/transient-fault.d.mts.map +1 -0
  105. package/dist/transient-fault.mjs +86 -0
  106. package/dist/transient-fault.mjs.map +1 -0
  107. package/dist/utm-deprecated.d.mts +6 -0
  108. package/dist/utm-deprecated.d.mts.map +1 -0
  109. package/dist/utm-deprecated.mjs +23 -0
  110. package/dist/utm-deprecated.mjs.map +1 -0
  111. package/dist/worker-code-check.d.mts +29 -0
  112. package/dist/worker-code-check.d.mts.map +1 -0
  113. package/dist/worker-code-check.mjs +85 -0
  114. package/dist/worker-code-check.mjs.map +1 -0
  115. package/dist/worker-health.d.mts +56 -0
  116. package/dist/worker-health.d.mts.map +1 -0
  117. package/dist/worker-health.mjs +73 -0
  118. package/dist/worker-health.mjs.map +1 -0
  119. package/dist/worker-http.d.mts +103 -0
  120. package/dist/worker-http.d.mts.map +1 -0
  121. package/dist/worker-http.mjs +277 -0
  122. package/dist/worker-http.mjs.map +1 -0
  123. package/dist/worker-stats.d.mts +66 -0
  124. package/dist/worker-stats.d.mts.map +1 -0
  125. package/dist/worker-stats.mjs +143 -0
  126. package/dist/worker-stats.mjs.map +1 -0
  127. package/package.json +96 -4
  128. package/src/local-worker/autounattend.xml +280 -0
  129. package/src/local-worker/build-vm.sh +218 -0
  130. package/src/local-worker/clone-worker.sh +141 -0
  131. package/src/local-worker/create-utm-vm.sh +202 -0
  132. package/src/local-worker/fetch-windows-iso.sh +238 -0
  133. package/src/local-worker/first-boot.cmd +58 -0
  134. package/src/local-worker/worker-ctl.sh +442 -0
  135. package/src/provisioning/README.md +28 -0
  136. package/src/provisioning/apply-foreground-lock-timeout.ps1 +71 -0
  137. package/src/provisioning/bare-metal/README.md +213 -0
  138. package/src/provisioning/bare-metal/a11y-bootstrap.service +58 -0
  139. package/src/provisioning/bare-metal/autounattend.xml +428 -0
  140. package/src/provisioning/bare-metal/serve-bootstrap.sh +86 -0
  141. package/src/provisioning/bootstrap-control-plane.sh +463 -0
  142. package/src/provisioning/bootstrap-windows-worker.ps1 +649 -0
  143. package/src/provisioning/build-lean-worker-image.ps1 +275 -0
  144. package/src/provisioning/diagnose-nvda-worker.ps1 +174 -0
  145. package/src/provisioning/provision-nvda-worker.ps1 +827 -0
  146. package/src/provisioning/set-display-mode.ps1 +411 -0
  147. package/src/provisioning/stamp-provision-revision.ps1 +184 -0
@@ -0,0 +1,284 @@
1
+ // @ts-check
2
+ /**
3
+ * Compare a fleet's `/health.code` readings against an EXPECTED hash, and describe the result — the half
4
+ * of `worker-code-check.mjs` that has no opinion about what "expected" means.
5
+ *
6
+ * Split out on 2026-09-05 so a SECOND caller could reach it. `worker-code-check.mjs`'s `expectedWorkerCode`
7
+ * imports `codeVersion`/`workerSourceDir` through a SUBPATH export (`@a11ign/screenreader-worker/code-version`)
8
+ * rather than a relative path, because a relative one drags `nvda-worker`'s `.mjs` files into
9
+ * `worker-fleet`'s own tsc project and the build dies with TS5055 ("would overwrite input file"). That
10
+ * subpath resolves through `node_modules` — fine for `worker-fleet`, which is a normal installed package,
11
+ * and fatal for `packages/control`, which runs from a raw git checkout with none (ADR 0012):
12
+ * `control-has-no-dependencies.test.ts` walks every import reachable from there and refuses exactly this
13
+ * shape. So the two constraints — no TS5055 here, no `node_modules` there — cannot both be satisfied by one
14
+ * import line in one file, and this file exists to be the copy neither constraint touches: every function
15
+ * below takes `expected` as a plain string, computed by whichever caller can safely reach a hasher.
16
+ *
17
+ * It imports `node:child_process` and, for `readWorkerCode`, this package's own `./worker-http.mjs` —
18
+ * relative, and itself importing nothing but `node:http`/`node:https`, so it resolves without
19
+ * `node_modules` exactly like this file already does. Verified against
20
+ * `control-has-no-dependencies.test.ts` before relying on it, since "should resolve" and "does resolve" are
21
+ * different claims and this file's whole reason for existing is being the copy the second one has to hold
22
+ * for. `fetch` (a global, needing no import) is what it replaced, kept out for the reason
23
+ * `worker-http.mjs`'s own header gives: undici silently caps the wait for response headers at ~300 s
24
+ * regardless of an `AbortSignal`, which this file's own question — does the fleet run this checkout — must
25
+ * never answer wrongly because of a truncated response.
26
+ *
27
+ * `lab-job.mjs` is that second caller — it needs this BEFORE dispatching to the lab at all, from
28
+ * `packages/control`, which cannot import `worker-code-check.mjs` directly for the reason above.
29
+ */
30
+ import { execFileSync } from "node:child_process";
31
+ import { sandboxGitEnv } from "./git-safe-env.mjs";
32
+ import { requestJson } from "./worker-http.mjs";
33
+ /** How long a worker gets to answer `/health`. Matches `check-worker-code.mjs`: a cold Windows box needs it. */
34
+ const HEALTH_TIMEOUT_MS = 15_000;
35
+ /**
36
+ * Split a set of `/health.code` readings into stale and unreachable. PURE.
37
+ *
38
+ * `null` is UNREACHABLE and is not a finding, matching `assertOneBrowserAcross`: a box that is asleep
39
+ * contributes no evidence and no mismatch, and treating silence as a fault is how a check earns a
40
+ * reputation for crying wolf. `"absent"` IS a finding — a worker predating `/health.code` is itself a
41
+ * stale deploy.
42
+ *
43
+ * `answered` is returned rather than left to be inferred from the two lists, because "nothing was wrong"
44
+ * and "nothing was examined" produce the SAME empty `stale` — and a caller with only the lists cannot
45
+ * distinguish 3 clean workers from 3 silent ones. Every number carrying what it was computed from is this
46
+ * file's parent rule; this is the one number that was missing.
47
+ *
48
+ * @param {string} expected
49
+ * @param {Array<{worker: string, code: string|null|undefined}>} readings
50
+ * @returns {{expected: string, stale: Array<{worker: string, serving: string}>, unreachable: string[],
51
+ * answered: number}}
52
+ */
53
+ export function codeDrift(expected, readings) {
54
+ const stale = [];
55
+ const unreachable = [];
56
+ let answered = 0;
57
+ for (const reading of readings ?? []) {
58
+ const code = reading?.code;
59
+ if (code === null || code === undefined) {
60
+ unreachable.push(reading?.worker);
61
+ continue;
62
+ }
63
+ answered += 1;
64
+ if (code !== expected)
65
+ stale.push({ worker: reading.worker, serving: String(code) });
66
+ }
67
+ return { expected, stale, unreachable, answered };
68
+ }
69
+ /**
70
+ * Name the deploy route that can actually reach these workers.
71
+ *
72
+ * There are two, they share no mechanism, and the wrong one wastes real time. `worker:deploy` is
73
+ * `utmctl file push` plus a `utmctl` reboot: it takes a VM UUID and fails immediately off macOS, so it
74
+ * cannot touch a physical box. Bare-metal workers are git-cloned and deploy by PULLING, through Ansible.
75
+ *
76
+ * This printed the utmctl advice unconditionally, including to a fleet of four mini PCs where none of it
77
+ * applies — a tool confidently prescribing a remedy for a different kind of machine. Which kind a worker is
78
+ * is not a guess: `inventory.yml` is the single source of truth for the bare-metal fleet (ADR 0012).
79
+ *
80
+ * PURE, and the bare-metal list is a parameter rather than a read, because a remedy that only appears when
81
+ * something is already broken is one nobody sees until it matters. This repo has shipped an inert remedy
82
+ * before (`refreshBrowseBuffer`, whose trigger was never set) and confirmed it by results it had no part in
83
+ * producing. Returning lines makes both branches assertable with nothing stale and no fleet.
84
+ *
85
+ * @param {string[]} staleUrls
86
+ * @param {string[]} bareMetalUrls
87
+ * @returns {string[]}
88
+ */
89
+ export function remedyLines(staleUrls, bareMetalUrls) {
90
+ const bareMetal = new Set(bareMetalUrls);
91
+ const physical = staleUrls.filter((u) => bareMetal.has(u));
92
+ const vms = staleUrls.filter((u) => !bareMetal.has(u));
93
+ const lines = [`\n${staleUrls.length} stale worker(s).`];
94
+ if (physical.length) {
95
+ lines.push(`\n ${physical.length} in inventory.yml — bare metal, so they deploy by PULLING:`, " npm run fleet:deploy", " `npm run worker:deploy` cannot reach these: it is utmctl, keyed on a VM UUID.");
96
+ }
97
+ if (vms.length) {
98
+ lines.push(`\n ${vms.length} not in inventory.yml — local VM(s). A restart via \`utmctl exec\``, " silently does nothing on some guests; rebooting always picks up a pushed file:", " npm run worker:deploy");
99
+ }
100
+ return lines;
101
+ }
102
+ /**
103
+ * The refusal text, or `null` when the fleet is running this checkout. PURE.
104
+ *
105
+ * Every number it reports carries what it was computed from, which is this file's parent rule: the hash,
106
+ * which side is dirty, and how many boxes were silent rather than merely absent from the count.
107
+ *
108
+ * @param {{expected: string, stale: Array<{worker: string, serving: string}>, unreachable: string[],
109
+ * answered?: number}} drift
110
+ * @param {{when?: string, bareMetalUrls?: string[], sourceDirty?: string}} options
111
+ * @returns {string|null}
112
+ */
113
+ export function describeCodeDrift(drift, { when = "before the run", bareMetalUrls = [], sourceDirty = "" } = {}) {
114
+ // A FLEET NOBODY COULD REACH IS NOT A CLEAN FLEET. `describeEmptyPool` below makes exactly this
115
+ // argument for `workers.length === 0` -- "an affirmative claim about a fleet it had not looked at" --
116
+ // and the identical condition arrives by a second path when the pool is full and every box is silent:
117
+ // `stale` is empty because nothing was COMPARED, not because everything matched, so this returned null
118
+ // and the caller printed "Fleet runs this checkout (worker code …, 0 of 5 worker(s) checked)".
119
+ //
120
+ // A remedy that reaches one of several paths, in the module whose own header names that shape.
121
+ //
122
+ // Silence is NOT a per-worker finding -- a box asleep contributes no mismatch, and treating that as a
123
+ // fault is how a check earns a reputation for crying wolf. The judgement is about the WHOLE reading:
124
+ // some answered, so the ones that did not are context; none answered, so there is no reading at all.
125
+ if (drift?.answered === 0 && drift?.unreachable?.length) {
126
+ return [
127
+ "",
128
+ `REFUSING to vouch for the fleet ${when}: ${drift.unreachable.length} worker(s) were asked and NONE`,
129
+ `answered, so nothing was compared against this checkout (${drift.expected}).`,
130
+ ...drift.unreachable.map((w) => ` ${w}`),
131
+ "",
132
+ "This is not a clean fleet, it is an unexamined one. A capture is about to dispatch to these boxes,",
133
+ "so silence here is a broken invocation rather than a pass. Common causes: the fleet is powered",
134
+ "down (npm run fleet:status), or a deploy just rebooted it and nothing waited.",
135
+ "",
136
+ "Or pass --allow-stale-workers if you know something this check does not.",
137
+ "",
138
+ ].join("\n");
139
+ }
140
+ if (!drift?.stale?.length)
141
+ return null;
142
+ const lines = [
143
+ `\nFLEET IS NOT RUNNING THIS CHECKOUT ${when}.`,
144
+ `This checkout expects worker code ${drift.expected}; ${drift.stale.length} worker(s) serve something else:`,
145
+ ...drift.stale.map(({ worker, serving }) => ` ${worker} ${serving}`),
146
+ ];
147
+ if (drift.unreachable.length) {
148
+ // Counted and named, because "3 of 4 agreed" and "3 of 4 answered" are different sentences and only
149
+ // one of them is reassuring.
150
+ lines.push(` (${drift.unreachable.length} worker(s) did not answer and were not judged: `
151
+ + `${drift.unreachable.join(", ")})`);
152
+ }
153
+ lines.push("", "A capture stamps the commit the LAB is at, and nothing downstream keys on the worker's code —", "`workerCode` is outside the cache key on purpose — so evidence taken by a stale worker is", "indistinguishable from current evidence for ever after.");
154
+ if (sourceDirty) {
155
+ // The odd one out is HERE, so the remedy is the opposite one. Getting this backwards ships
156
+ // uncommitted work to twelve machines, and a CAPTURE_PROTOCOL_VERSION bump among it would invalidate
157
+ // every cached capture — the trap `worker:code` already warns about, arriving through this door.
158
+ lines.push("", "But the drift is on THIS side: the worker source in this checkout is modified against HEAD —", ` ${sourceDirty}`, "so the fleet may be perfectly current and deploying would ship uncommitted work. Commit or revert", "first, then re-check. Do not reach for the remedy below until this is clean.");
159
+ }
160
+ lines.push(...remedyLines(drift.stale.map((s) => s.worker), bareMetalUrls));
161
+ lines.push("", "Or pass --allow-stale-workers if you know something this check does not; it will say so in", "the output rather than passing quietly.");
162
+ return `${lines.join("\n")}\n`;
163
+ }
164
+ /** What a single worker is serving, or `null` when it did not answer. */
165
+ /** @param {string} url */
166
+ export async function readWorkerCode(url) {
167
+ try {
168
+ // `requestJson`, not `fetch` -- see worker-http.mjs's own header: undici silently caps the wait for
169
+ // response headers at ~300 s regardless of an AbortSignal, and this file exists to answer a question
170
+ // (does the fleet run this checkout) that a truncated response must never answer wrongly. `requestJson`
171
+ // imports only `node:http`/`node:https`, so pulling it in here does not add anything `packages/control`
172
+ // could not already resolve from a raw checkout -- verified against
173
+ // `control-has-no-dependencies.test.ts` rather than assumed, since this file's whole reason for existing
174
+ // is being the copy that constraint reaches.
175
+ const response = await requestJson(`${String(url).replace(/\/$/, "")}/health`, { timeoutMs: HEALTH_TIMEOUT_MS });
176
+ // `undefined` on unparseable JSON (requestJson's contract) is folded into the same "did not answer"
177
+ // path as a network failure below, via the throw -- matching fetch's `.json()`, which threw here too.
178
+ if (response.json === undefined)
179
+ throw new Error(`invalid JSON from ${url}`);
180
+ // "absent" rather than null: a worker that answers without a code field predates `/health.code`,
181
+ // which is a stale deploy and must not be filed under "did not answer".
182
+ return response.json.code ?? "absent";
183
+ }
184
+ catch {
185
+ return null;
186
+ }
187
+ }
188
+ /**
189
+ * Is the worker source in `sourceDir` modified against HEAD?
190
+ *
191
+ * The DIRECTORY IS THE CALLER'S, never a path guessed here: this file is reached from `control` (which asks
192
+ * `layer-checkouts.mjs`) and from `worker-fleet` (which asks the worker package by name), and a guessed
193
+ * monorepo path would read the wrong tree the day the layer lives in another repository (#3394).
194
+ *
195
+ * Guarded, exactly like `protocolBumpNote`: outside a git checkout there is simply nothing to add, and a
196
+ * precondition that throws because `git` is missing is worse than the drift it was checking for.
197
+ *
198
+ * @param {string} sourceDir
199
+ */
200
+ export function workerSourceDirty(sourceDir) {
201
+ // Outside the `try`: a missing directory is a caller's bug, and the catch below would turn it into "clean".
202
+ if (typeof sourceDir !== "string" || !sourceDir) {
203
+ throw new TypeError("workerSourceDirty needs the worker source directory to read; there is no default");
204
+ }
205
+ try {
206
+ return execFileSync("git", ["-C", sourceDir, "status", "--porcelain", "--", "."], { encoding: "utf8", env: sandboxGitEnv() }).trim().split("\n").filter(Boolean).join("; ");
207
+ }
208
+ catch {
209
+ return "";
210
+ }
211
+ }
212
+ /**
213
+ * AN EMPTY POOL IS NOT A CLEAN FLEET — the refusal, as a value, so it can be tested without exiting.
214
+ *
215
+ * `assertFleetRunsThisCheckout([])` used to print "Fleet runs this checkout (worker code …, 0 worker(s)
216
+ * checked)" and return: an affirmative claim about a fleet it had not looked at. The count sitting in the
217
+ * sentence is the only reason that was ever arguable, and "0 worker(s) checked" under a heading saying the
218
+ * fleet is fine is precisely how "verified" comes to mean "unexamined".
219
+ *
220
+ * REFUSED, not reported-and-continued. The pre-push hook's loud skip is right for `runs/`, whose absence is
221
+ * legitimate; an empty pool at a capture boundary never is. Both callers pass a pool a capture is about to
222
+ * dispatch to, so failing here with a named cause beats failing later as "0 captured".
223
+ *
224
+ * Returned rather than written, and that split is this file's own convention: the classification and the
225
+ * message are pure and driven directly by the tests, because a function that calls `process.exit` cannot be
226
+ * asserted on without stubbing the runtime out from under it.
227
+ *
228
+ * @param {string[] | undefined} workers
229
+ * @param {string} expected
230
+ * @returns {string | null} the refusal, or null when there is a pool to check
231
+ */
232
+ export function describeEmptyPool(workers, expected) {
233
+ if (workers?.length)
234
+ return null;
235
+ return "REFUSING to vouch for the fleet: no workers were given, so nothing was compared against this "
236
+ + `checkout (${expected}). A capture dispatches to workers, so an empty pool is a broken invocation `
237
+ + "rather than a clean fleet — check A11Y_WORKER(S), the local pool, or inventory.yml.\n";
238
+ }
239
+ /**
240
+ * Refuse to run against a fleet that is not serving `expected` — the shared body of
241
+ * `assertFleetRunsThisCheckout`, taking the hash as a parameter rather than computing it.
242
+ *
243
+ * That split is the whole reason this function exists rather than living only in `worker-code-check.mjs`:
244
+ * computing `expected` needs `codeVersion`/`workerSourceDir`, and reaching those from `packages/control`
245
+ * cannot go through a relative path (TS5055 in `worker-fleet`'s own build) or a package-name import (no
246
+ * `node_modules` on the control plane, ADR 0012) at once — see this file's header. So the CALLER computes
247
+ * `expected` however it can safely reach a hasher, and this function starts from the answer.
248
+ *
249
+ * Exits 3, matching `assertOneBrowserAcross`: a precondition the operator must act on, distinct from 1
250
+ * (captures failed) and 2 (the request was malformed).
251
+ *
252
+ * @param {string} expected
253
+ * @param {string[]} workers
254
+ * `sourceDir` is the worker source the `expected` hash was computed over, so a dirty tree is read from the
255
+ * same place the hash came from.
256
+ *
257
+ * @param {{when?: string, allow?: boolean, read?: (url: string) => Promise<string|null>, bareMetalUrls?: string[], sourceDir: string}} options
258
+ */
259
+ export async function assertWorkersServe(expected, workers, options) {
260
+ const { when = "before the run", allow = false, read = readWorkerCode, bareMetalUrls = [], sourceDir } = options;
261
+ if (allow) {
262
+ process.stdout.write("--allow-stale-workers: NOT checking that the fleet runs this checkout.\n");
263
+ return;
264
+ }
265
+ const empty = describeEmptyPool(workers, expected);
266
+ if (empty) {
267
+ process.stderr.write(empty);
268
+ process.exit(3);
269
+ }
270
+ const readings = await Promise.all(workers.map(async (worker) => ({ worker, code: await read(worker) })));
271
+ const drift = codeDrift(expected, readings);
272
+ const refusal = describeCodeDrift(drift, { when, bareMetalUrls, sourceDirty: workerSourceDirty(sourceDir) });
273
+ if (!refusal) {
274
+ // Says it CHECKED, not merely that nothing was wrong. A silent pass and a check that never ran look
275
+ // identical from the outside, which is the `refreshBrowseBuffer` lesson applied to a precondition.
276
+ process.stdout.write(`Fleet runs this checkout (worker code ${expected}, `
277
+ + `${drift.unreachable.length ? `${readings.length - drift.unreachable.length} of ` : ""}`
278
+ + `${readings.length} worker(s) checked).\n`);
279
+ return;
280
+ }
281
+ process.stderr.write(refusal);
282
+ process.exit(3);
283
+ }
284
+ //# sourceMappingURL=code-drift.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-drift.mjs","sourceRoot":"","sources":["../src/code-drift.mjs"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Every `.mjs` under the tree's known CLI roots, no predicate applied — the population itself, for a
3
+ * caller to classify by whatever question is theirs.
4
+ *
5
+ * @param {string} repoRoot absolute path to the repository root
6
+ * @returns {string[]} repo-relative paths, unsorted (discovery order)
7
+ */
8
+ export function allMjsFiles(repoRoot: string): string[];
9
+ /**
10
+ * Does this file read argv? Comments stripped first, and that is not a nicety: matching raw source
11
+ * classified a module as a CLI merely because a COMMENT mentioned `process.argv` — measured on
12
+ * `gates/dispatch.mjs`, a library whose comment said it deliberately does NOT read `process.argv`. This
13
+ * repo's own rule: a check must not derive its expectations from source TEXT, because text includes the
14
+ * prose about the code as well as the code.
15
+ *
16
+ * Excludes `cli-flags.mjs` itself, the guard's OWN definition, which mentions `process.argv` in its
17
+ * implementation without being a command line for anything to classify.
18
+ *
19
+ * @param {string} rel repo-relative path
20
+ * @param {string} repoRoot absolute path to the repository root
21
+ * @returns {boolean}
22
+ */
23
+ export function readsArgv(rel: string, repoRoot: string): boolean;
24
+ /**
25
+ * `allMjsFiles()` filtered to the ones that read argv — the population `cli-flags.test.ts`'s flag-guard
26
+ * census classifies. NOT the same question as "is this a runnable command with an entry-point guard";
27
+ * see this file's own header for why those stay two predicates over one walk rather than one.
28
+ *
29
+ * @param {string} repoRoot absolute path to the repository root
30
+ * @returns {string[]}
31
+ */
32
+ export function commandLineModules(repoRoot: string): string[];
33
+ //# sourceMappingURL=command-line-census.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-line-census.d.mts","sourceRoot":"","sources":["../src/command-line-census.mjs"],"names":[],"mappings":"AAmCA;;;;;;GAMG;AACH,sCAHW,MAAM,GACJ,MAAM,EAAE,CAkBpB;AAED;;;;;;;;;;;;;GAaG;AACH,+BAJW,MAAM,YACN,MAAM,GACJ,OAAO,CAMnB;AAED;;;;;;;GAOG;AACH,6CAHW,MAAM,GACJ,MAAM,EAAE,CAIpB"}
@@ -0,0 +1,96 @@
1
+ // @ts-check
2
+ /**
3
+ * Every `.mjs` under this repo's known CLI roots — DISCOVERED by walking the tree, never a hand-typed list.
4
+ *
5
+ * Extracted from `cli-flags.test.ts` (A2, #453) so a SECOND consumer with a DIFFERENT question about the
6
+ * same population reuses the walk rather than re-deriving it — the identical shape `preInstallEntries()`
7
+ * and `busy-worker-guard.test.ts` already follow, and the one this repo's own rule names: *"delete a copy,
8
+ * or derive one from the other"* ranks above a second independent `readdirSync` walk of the same tree that
9
+ * could silently narrow from this one's (a different root list, a missed `node_modules` exclusion).
10
+ *
11
+ * `allMjsFiles()` is the WALK ALONE, with no predicate applied — deliberately, because "reads argv" and
12
+ * "is a runnable command" are DIFFERENT questions over an overlapping but non-identical population.
13
+ * `cli-flags.test.ts` filters this walk with `readsArgv()` to classify flag-guard coverage; a consumer
14
+ * asking "does this file declare the `if (import.meta.url === ...) main()` entry-point guard" (docs
15
+ * generation, say) filters the SAME walk with its own predicate instead of importing `readsArgv` and
16
+ * getting the wrong population — a script can guard `main()` with no flags at all (nothing to read from
17
+ * argv), and in principle a pre-install script reads `process.argv` at import time with no guard at all.
18
+ * Sharing the walk and NOT the predicate is what keeps those two questions from being silently conflated.
19
+ *
20
+ * A CONSUMER OF THIS FILE ALREADY HAS `node_modules` AND A BUILD. `cli-flags.mjs` itself deliberately
21
+ * imports nothing but `node:path`/`node:fs`/`node:url`, because scripts like `auto-arm-sweep.mjs` are
22
+ * invoked by a workflow job with only `actions/checkout` -- no `npm ci`, no `dist` (#330/#331). This file
23
+ * is NOT that kind of dependency: `stripComments` is a real workspace import
24
+ * (`@a11ign/evidence/source-text`), so anything importing THIS module needs to run where that package's
25
+ * `dist` exists -- a test file, or tooling invoked after `npm run build`. Keeping `cli-flags.mjs` itself
26
+ * free of that import is why this is a sibling file rather than an addition to it.
27
+ */
28
+ import { readFileSync, readdirSync, statSync } from "node:fs";
29
+ import { join } from "node:path";
30
+ import { stripComments } from "@a11ign/evidence/source-text";
31
+ /** Every top-level directory a `.mjs` command line can live under, relative to the repo root. */
32
+ const ROOTS = ["scripts", ...["packages/lab", "packages/worker-fleet"]
33
+ .flatMap((pkg) => ["src", "scripts"].map((sub) => `${pkg}/${sub}`))];
34
+ /**
35
+ * Every `.mjs` under the tree's known CLI roots, no predicate applied — the population itself, for a
36
+ * caller to classify by whatever question is theirs.
37
+ *
38
+ * @param {string} repoRoot absolute path to the repository root
39
+ * @returns {string[]} repo-relative paths, unsorted (discovery order)
40
+ */
41
+ export function allMjsFiles(repoRoot) {
42
+ const found = /** @type {string[]} */ ([]);
43
+ /** @param {string} dir */
44
+ const walk = (dir) => {
45
+ for (const entry of readdirSync(join(repoRoot, dir), { withFileTypes: true })) {
46
+ const rel = `${dir}/${entry.name}`;
47
+ if (entry.isDirectory() && entry.name !== "node_modules")
48
+ walk(rel);
49
+ else if (!entry.isDirectory() && rel.endsWith(".mjs"))
50
+ found.push(rel);
51
+ }
52
+ };
53
+ for (const root of ROOTS) {
54
+ // A package without a `scripts/` directory is not a fault; anything else is, and must not be swallowed.
55
+ try {
56
+ statSync(join(repoRoot, root));
57
+ }
58
+ catch {
59
+ continue;
60
+ }
61
+ walk(root);
62
+ }
63
+ return found;
64
+ }
65
+ /**
66
+ * Does this file read argv? Comments stripped first, and that is not a nicety: matching raw source
67
+ * classified a module as a CLI merely because a COMMENT mentioned `process.argv` — measured on
68
+ * `gates/dispatch.mjs`, a library whose comment said it deliberately does NOT read `process.argv`. This
69
+ * repo's own rule: a check must not derive its expectations from source TEXT, because text includes the
70
+ * prose about the code as well as the code.
71
+ *
72
+ * Excludes `cli-flags.mjs` itself, the guard's OWN definition, which mentions `process.argv` in its
73
+ * implementation without being a command line for anything to classify.
74
+ *
75
+ * @param {string} rel repo-relative path
76
+ * @param {string} repoRoot absolute path to the repository root
77
+ * @returns {boolean}
78
+ */
79
+ export function readsArgv(rel, repoRoot) {
80
+ if (!rel.endsWith(".mjs"))
81
+ return false;
82
+ const source = stripComments(readFileSync(join(repoRoot, rel), "utf8"));
83
+ return source.includes("process.argv") && !source.includes("export function refuseUnknownFlags");
84
+ }
85
+ /**
86
+ * `allMjsFiles()` filtered to the ones that read argv — the population `cli-flags.test.ts`'s flag-guard
87
+ * census classifies. NOT the same question as "is this a runnable command with an entry-point guard";
88
+ * see this file's own header for why those stay two predicates over one walk rather than one.
89
+ *
90
+ * @param {string} repoRoot absolute path to the repository root
91
+ * @returns {string[]}
92
+ */
93
+ export function commandLineModules(repoRoot) {
94
+ return allMjsFiles(repoRoot).filter((rel) => readsArgv(rel, repoRoot));
95
+ }
96
+ //# sourceMappingURL=command-line-census.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-line-census.mjs","sourceRoot":"","sources":["../src/command-line-census.mjs"],"names":[],"mappings":"AAAA,YAAY;AACZ;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,iGAAiG;AACjG,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,cAAc,EAAE,uBAAuB,CAAC;SACnE,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,QAAQ;IAClC,MAAM,KAAK,GAAG,uBAAuB,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3C,0BAA0B;IAC1B,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,EAAE;QACnB,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YAC9E,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;gBAAE,IAAI,CAAC,GAAG,CAAC,CAAC;iBAC/D,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzE,CAAC;IACH,CAAC,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,wGAAwG;QACxG,IAAI,CAAC;YAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS,CAAC,GAAG,EAAE,QAAQ;IACrC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,MAAM,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACxE,OAAO,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,oCAAoC,CAAC,CAAC;AACnG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAQ;IACzC,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=compare-workers.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-workers.d.mts","sourceRoot":"","sources":["../src/compare-workers.mjs"],"names":[],"mappings":""}