@a11ign/screenreader-fleet 0.2.0 → 0.4.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 (130) hide show
  1. package/dist/capture-client.d.mts +0 -1
  2. package/dist/capture-client.mjs +144 -306
  3. package/dist/check-worker-code.d.mts +0 -1
  4. package/dist/check-worker-code.mjs +42 -110
  5. package/dist/cli-flags.d.mts +0 -1
  6. package/dist/cli-flags.mjs +33 -179
  7. package/dist/code-drift.d.mts +4 -4
  8. package/dist/command-line-census.d.mts +0 -1
  9. package/dist/compare-workers.d.mts +0 -1
  10. package/dist/compare-workers.mjs +383 -255
  11. package/dist/control-plane-isolation.d.mts +0 -1
  12. package/dist/doctor.d.mts +0 -1
  13. package/dist/doctor.mjs +361 -809
  14. package/dist/fleet-consistency.d.mts +0 -1
  15. package/dist/fleet-consistency.mjs +155 -379
  16. package/dist/fleet-env.d.mts +1 -2
  17. package/dist/fleet-env.mjs +148 -438
  18. package/dist/fleet-scripts.d.mts +0 -1
  19. package/dist/git-safe-env.d.mts +0 -1
  20. package/dist/guest-run.d.mts +0 -1
  21. package/dist/host-address.d.mts +0 -1
  22. package/dist/host-address.mjs +19 -90
  23. package/dist/host-capacity.d.mts +0 -1
  24. package/dist/host-capacity.mjs +22 -136
  25. package/dist/host-metrics.d.mts +0 -1
  26. package/dist/index.d.ts +0 -1
  27. package/dist/index.mjs +233 -0
  28. package/dist/local-vm.d.ts +0 -1
  29. package/dist/measure-guard.d.mts +0 -1
  30. package/dist/normalise-fleet.d.mts +0 -1
  31. package/dist/npm-cli-executable.d.mts +0 -1
  32. package/dist/probe-outcome.d.mts +0 -1
  33. package/dist/probe-outcome.mjs +50 -96
  34. package/dist/protocol-guard.d.mts +0 -1
  35. package/dist/source-walk.d.mts +0 -1
  36. package/dist/src_fleet-scripts_mjs.mjs +16 -0
  37. package/dist/src_git-safe-env_mjs.mjs +9 -0
  38. package/dist/transient-fault.d.mts +0 -1
  39. package/dist/transient-fault.mjs +21 -81
  40. package/dist/utm-deprecated.d.mts +0 -1
  41. package/dist/worker-code-check.d.mts +0 -1
  42. package/dist/worker-code-check.mjs +127 -83
  43. package/dist/worker-health.d.mts +0 -1
  44. package/dist/worker-health.mjs +16 -61
  45. package/dist/worker-http.d.mts +0 -1
  46. package/dist/worker-http.mjs +42 -234
  47. package/dist/worker-stats.d.mts +0 -1
  48. package/package.json +13 -7
  49. package/src/local-worker/worker-ctl.sh +1 -1
  50. package/src/provisioning/bootstrap-windows-worker.ps1 +1 -1
  51. package/dist/capture-client.d.mts.map +0 -1
  52. package/dist/capture-client.mjs.map +0 -1
  53. package/dist/check-worker-code.d.mts.map +0 -1
  54. package/dist/check-worker-code.mjs.map +0 -1
  55. package/dist/cli-flags.d.mts.map +0 -1
  56. package/dist/cli-flags.mjs.map +0 -1
  57. package/dist/code-drift.d.mts.map +0 -1
  58. package/dist/code-drift.mjs +0 -284
  59. package/dist/code-drift.mjs.map +0 -1
  60. package/dist/command-line-census.d.mts.map +0 -1
  61. package/dist/command-line-census.mjs +0 -96
  62. package/dist/command-line-census.mjs.map +0 -1
  63. package/dist/compare-workers.d.mts.map +0 -1
  64. package/dist/compare-workers.mjs.map +0 -1
  65. package/dist/control-plane-isolation.d.mts.map +0 -1
  66. package/dist/control-plane-isolation.mjs +0 -67
  67. package/dist/control-plane-isolation.mjs.map +0 -1
  68. package/dist/deploy-worker.d.mts +0 -3
  69. package/dist/deploy-worker.d.mts.map +0 -1
  70. package/dist/deploy-worker.mjs +0 -333
  71. package/dist/deploy-worker.mjs.map +0 -1
  72. package/dist/doctor.d.mts.map +0 -1
  73. package/dist/doctor.mjs.map +0 -1
  74. package/dist/fleet-consistency.d.mts.map +0 -1
  75. package/dist/fleet-consistency.mjs.map +0 -1
  76. package/dist/fleet-env.d.mts.map +0 -1
  77. package/dist/fleet-env.mjs.map +0 -1
  78. package/dist/fleet-scripts.d.mts.map +0 -1
  79. package/dist/fleet-scripts.mjs +0 -41
  80. package/dist/fleet-scripts.mjs.map +0 -1
  81. package/dist/git-safe-env.d.mts.map +0 -1
  82. package/dist/git-safe-env.mjs +0 -44
  83. package/dist/git-safe-env.mjs.map +0 -1
  84. package/dist/guest-run.d.mts.map +0 -1
  85. package/dist/guest-run.mjs +0 -164
  86. package/dist/guest-run.mjs.map +0 -1
  87. package/dist/host-address.d.mts.map +0 -1
  88. package/dist/host-address.mjs.map +0 -1
  89. package/dist/host-capacity.d.mts.map +0 -1
  90. package/dist/host-capacity.mjs.map +0 -1
  91. package/dist/host-metrics.d.mts.map +0 -1
  92. package/dist/host-metrics.mjs +0 -201
  93. package/dist/host-metrics.mjs.map +0 -1
  94. package/dist/index.d.ts.map +0 -1
  95. package/dist/index.js +0 -25
  96. package/dist/index.js.map +0 -1
  97. package/dist/local-vm.d.ts.map +0 -1
  98. package/dist/local-vm.js +0 -360
  99. package/dist/local-vm.js.map +0 -1
  100. package/dist/measure-guard.d.mts.map +0 -1
  101. package/dist/measure-guard.mjs +0 -73
  102. package/dist/measure-guard.mjs.map +0 -1
  103. package/dist/normalise-fleet.d.mts.map +0 -1
  104. package/dist/normalise-fleet.mjs +0 -76
  105. package/dist/normalise-fleet.mjs.map +0 -1
  106. package/dist/npm-cli-executable.d.mts.map +0 -1
  107. package/dist/npm-cli-executable.mjs +0 -159
  108. package/dist/npm-cli-executable.mjs.map +0 -1
  109. package/dist/probe-outcome.d.mts.map +0 -1
  110. package/dist/probe-outcome.mjs.map +0 -1
  111. package/dist/protocol-guard.d.mts.map +0 -1
  112. package/dist/protocol-guard.mjs +0 -121
  113. package/dist/protocol-guard.mjs.map +0 -1
  114. package/dist/source-walk.d.mts.map +0 -1
  115. package/dist/source-walk.mjs +0 -56
  116. package/dist/source-walk.mjs.map +0 -1
  117. package/dist/transient-fault.d.mts.map +0 -1
  118. package/dist/transient-fault.mjs.map +0 -1
  119. package/dist/utm-deprecated.d.mts.map +0 -1
  120. package/dist/utm-deprecated.mjs +0 -23
  121. package/dist/utm-deprecated.mjs.map +0 -1
  122. package/dist/worker-code-check.d.mts.map +0 -1
  123. package/dist/worker-code-check.mjs.map +0 -1
  124. package/dist/worker-health.d.mts.map +0 -1
  125. package/dist/worker-health.mjs.map +0 -1
  126. package/dist/worker-http.d.mts.map +0 -1
  127. package/dist/worker-http.mjs.map +0 -1
  128. package/dist/worker-stats.d.mts.map +0 -1
  129. package/dist/worker-stats.mjs +0 -143
  130. package/dist/worker-stats.mjs.map +0 -1
package/dist/local-vm.js DELETED
@@ -1,360 +0,0 @@
1
- /**
2
- * On-demand lifecycle for the local UTM worker VM.
3
- *
4
- * A Windows guest is never really idle -- Defender, Update and the search indexer keep it
5
- * busy -- so leaving one running between captures costs the host real CPU. Cold start is
6
- * cheap enough (12-15s to /health, measured) that starting it per run and shutting it down
7
- * afterwards is the better trade.
8
- *
9
- * The default is to LEAVE THE VM AS FOUND, which needs no configuration to do the right
10
- * thing: a VM that was stopped gets stopped again, a paused one gets re-paused, and one you
11
- * had already started yourself is left alone. That last case matters -- a run must never
12
- * shut down a VM somebody else is using.
13
- *
14
- * Everything UTM-specific lives in `local-worker/worker-ctl.sh` in this package (`json` emits the
15
- * state this module reads). Nothing here knows about utmctl, bundles or bookmarks.
16
- */
17
- import { execFile } from "node:child_process";
18
- import { promisify } from "node:util";
19
- import { networkInterfaces } from "node:os";
20
- import { availableHostMemoryMb, capacityReason, workersHostCanRun } from "./host-capacity.mjs";
21
- import { fleetScriptPaths } from "./fleet-scripts.mjs";
22
- import { inventoryWorkerUrls } from "./fleet-env.mjs";
23
- import { warnUtmDeprecated } from "./utm-deprecated.mjs";
24
- const execFileAsync = promisify(execFile);
25
- /** Where a hand-started worker on this machine has always lived. */
26
- export const DEFAULT_WORKER = "http://localhost:8765";
27
- // Relative to this module, not the process cwd: `npm run` happens to start in the repo
28
- // root, but a direct `tsx src/cli.ts` from elsewhere would not.
29
- const CTL = fleetScriptPaths().workerCtl;
30
- const STATUS_TIMEOUT_MS = 30_000;
31
- // Generous on purpose: `up` boots Windows, waits for auto-logon and then polls /health,
32
- // and worker-ctl gives that 180s of its own before giving up.
33
- const LIFECYCLE_TIMEOUT_MS = 300_000;
34
- const AFTER_RUN_VALUES = ["restore", "stop", "pause", "leave"];
35
- export function isAfterRun(v) {
36
- return AFTER_RUN_VALUES.includes(v);
37
- }
38
- const IPV4_OCTETS = 4;
39
- function ipv4ToInt(ip) {
40
- const parts = ip.split(".").map(Number);
41
- if (parts.length !== IPV4_OCTETS || parts.some((n) => !Number.isInteger(n) || n < 0 || n > 255))
42
- return null;
43
- return parts.reduce((acc, octet) => acc * 256 + octet, 0);
44
- }
45
- /**
46
- * This host's address on the same subnet as the guest -- the gateway end of UTM's shared
47
- * network (bridge100 on macOS). Derived by matching interfaces against the guest's address
48
- * rather than assuming the usual `x.y.z.1`, because guessing an address that silently does
49
- * not answer is worse than reporting that we could not find one.
50
- */
51
- function hostAddressFor(guestIp) {
52
- const guest = ipv4ToInt(guestIp);
53
- if (guest === null)
54
- return undefined;
55
- for (const addresses of Object.values(networkInterfaces())) {
56
- for (const a of addresses ?? []) {
57
- if (a.family !== "IPv4" || a.internal)
58
- continue;
59
- const host = ipv4ToInt(a.address);
60
- const mask = ipv4ToInt(a.netmask);
61
- if (host === null || mask === null)
62
- continue;
63
- // Both sides go through the same int32 coercion, so addresses above 2^31 comparing
64
- // as negative is harmless here.
65
- if ((host & mask) === (guest & mask))
66
- return a.address;
67
- }
68
- }
69
- return undefined;
70
- }
71
- async function ctl(args, timeoutMs, vmName) {
72
- const env = vmName ? { ...process.env, A11Y_VM_NAME: vmName } : process.env;
73
- const { stdout } = await execFileAsync(CTL, args, { timeout: timeoutMs, encoding: "utf8", env });
74
- return stdout;
75
- }
76
- /**
77
- * The registered local VM, or null when there is nothing to manage: not macOS, no UTM, no
78
- * VM registered under that name, or duplicate registrations (worker-ctl refuses to guess
79
- * between those rather than risk acting on the wrong one).
80
- */
81
- export async function findLocalVm() {
82
- if (process.platform !== "darwin")
83
- return null;
84
- try {
85
- return JSON.parse(await ctl(["json"], STATUS_TIMEOUT_MS));
86
- }
87
- catch (e) {
88
- // Absence is the normal case for anyone without a local VM, so this is a debug
89
- // breadcrumb rather than a warning -- but never a silent catch.
90
- if (process.env.A11Y_DEBUG_VM) {
91
- process.stderr.write(`local VM lookup failed (continuing without one): ${e.message}\n`);
92
- }
93
- return null;
94
- }
95
- }
96
- /** Which lifecycle action returns the VM to `stateBefore`. */
97
- function restoreAction(stateBefore) {
98
- if (stateBefore === "started")
99
- return "leave";
100
- if (stateBefore === "paused")
101
- return "pause";
102
- return "stop";
103
- }
104
- async function releaseVm(action, stateBefore) {
105
- const resolved = action === "restore" ? restoreAction(stateBefore) : action;
106
- if (resolved === "leave")
107
- return;
108
- // Another run may have started a capture while ours was finishing. Shutting the guest
109
- // down underneath it would look exactly like a worker crash, so stand down instead.
110
- const now = await findLocalVm();
111
- if (now?.busy) {
112
- process.stderr.write(`worker is busy with another capture; leaving the VM ${now.state}\n`);
113
- return;
114
- }
115
- process.stderr.write(`${resolved === "pause" ? "Pausing" : "Shutting down"} the local worker VM ...\n`);
116
- await ctl([resolved], LIFECYCLE_TIMEOUT_MS);
117
- }
118
- /**
119
- * Start (or resume) the local VM, returning its worker URL and the matching cleanup.
120
- * Throws if the VM never becomes healthy -- there is no point judging a capture that could
121
- * not happen.
122
- */
123
- export async function acquireLocalWorker(vm, after) {
124
- const stateBefore = vm.state;
125
- if (stateBefore === "started" && vm.healthy) {
126
- process.stderr.write(`Using the running local worker VM '${vm.name}' at ${vm.ip}\n`);
127
- }
128
- else {
129
- process.stderr.write(`Local worker VM '${vm.name}' is ${stateBefore}; bringing it up ...\n`);
130
- process.stderr.write(await ctl(["up"], LIFECYCLE_TIMEOUT_MS));
131
- }
132
- const ready = await findLocalVm();
133
- if (!ready?.healthy || !ready.ip) {
134
- throw new Error(`Local worker VM '${vm.name}' did not become healthy. Check it directly: ${CTL} status`);
135
- }
136
- return {
137
- worker: `http://${ready.ip}:${ready.port}`,
138
- source: "local-vm",
139
- hostAddress: hostAddressFor(ready.ip),
140
- release: () => releaseVm(after, stateBefore).catch((e) => {
141
- // Report and move on. The run's verdict is the product; a VM left running is a
142
- // resource leak the user can fix with one command, not a reason to fail the run.
143
- process.stderr.write(`WARNING: could not release the local worker VM: ${e.message}\n`);
144
- }),
145
- };
146
- }
147
- /**
148
- * Decide what to capture against, in priority order:
149
- *
150
- * 1. A worker the caller named is used as-is. Naming one is a statement that you are
151
- * managing it yourself, so the VM lifecycle is never touched.
152
- * 2. Otherwise, `inventory.yml`'s bare-metal fleet — always-on boxes, so there is no VM
153
- * lifecycle to run: the first declared worker is leased with a no-op release.
154
- * 3. Otherwise, a registered local UTM VM is started on demand and released afterwards.
155
- * 4. Otherwise, the historical default, so a hand-run worker on this machine still works.
156
- *
157
- * FOUND 2026-09-06 (`docs/backlog.md` §8): this used to go straight from (1) to (3), never
158
- * reading the inventory at all -- so a checkout WITH a bare-metal fleet declared and a UTM guest
159
- * still registered (the ordinary state of a Mac that used to run the deprecated pool) leased the
160
- * VM by default, exactly the wrong turn CLAUDE.md already records costing a capture-path change:
161
- * "a deprecated path that is still the first one documented is not deprecated". `resolveWorkerPool`
162
- * (`fleet-env.mjs`) already had the corrected order for the POOL case; this brings the single-worker
163
- * case into line, and `worker-precedence.test.ts` is what is supposed to keep them there.
164
- *
165
- * Step 2 costs nothing and throws nothing for anyone without an `inventory.yml` -- a public
166
- * consumer who installs this package -- because `inventoryWorkerUrls()` already treats an absent
167
- * or unparsable file as "no fleet declared here" and returns `[]`. So the fall-through to (3) and
168
- * (4) is byte-for-byte what it was before this change for that consumer; only a checkout that
169
- * DECLARES a fleet sees new behaviour, and it sees the behaviour `doctor` and `worker:code`
170
- * already assumed it had.
171
- *
172
- * A11Y_LOCAL_VM=0 skips step 3 for anyone who wants the pre-inventory local-VM behaviour back --
173
- * unchanged in what it does, moved because inventory now sits ahead of it.
174
- *
175
- * Shared by every entry point that needs a worker, so the priority order cannot drift
176
- * between them.
177
- *
178
- * `deps` is the injection seam: real callers get the real inventory reader and the real (shells
179
- * out to `utmctl`) VM lookup, and a test supplies fakes for both, so the precedence can be proven
180
- * without a filesystem `inventory.yml` or a UTM install. Called as `deps.inventory?.() ?? inventoryWorkerUrls()`
181
- * rather than resolved into a local first — deliberately, so `inventoryWorkerUrls(` and `findLocalVm(`
182
- * both appear as real CALLS at the exact decision point, which is what lets `worker-precedence.test.ts`
183
- * read this function's actual order rather than only the shape of its signature.
184
- */
185
- export async function leaseWorker({ worker, after }, deps = {}) {
186
- const release = async () => { };
187
- // Strip a trailing slash once, here, so no caller has to remember that `${worker}/capture`
188
- // would otherwise produce a double slash.
189
- if (worker)
190
- return { worker: worker.replace(/\/$/, ""), source: "explicit", release };
191
- const fleet = deps.inventory ? deps.inventory() : inventoryWorkerUrls();
192
- if (fleet.length)
193
- return { worker: fleet[0].replace(/\/$/, ""), source: "inventory.yml", release };
194
- if (process.env.A11Y_LOCAL_VM === "0")
195
- return { worker: DEFAULT_WORKER, source: "default", release };
196
- // Reached only with no explicit worker and no inventory.yml entry — architecture-audit.md §8's own
197
- // finding, named exactly: this fallback IS the deprecated path being the CLI's default. Warned, not
198
- // refused, because deleting it is a separate proposal and some machines still only have a UTM guest
199
- // to reach.
200
- const vm = deps.findLocalVm ? await deps.findLocalVm() : await findLocalVm();
201
- if (!vm)
202
- return { worker: DEFAULT_WORKER, source: "default", release };
203
- // Only once a VM exists: the notice says this run "manages a local UTM worker VM", which is false
204
- // on a machine with none, and the stranger running the published `npx a11ign` is that machine (#3219).
205
- warnUtmDeprecated("this run (no worker named, no fleet configured)");
206
- return acquireLocalWorker(vm, after);
207
- }
208
- /**
209
- * Rewrite a base URL the GUEST has to fetch so it points at this host rather than at
210
- * `localhost`, which from inside the guest means the guest.
211
- *
212
- * This is the trap in dataset capture: the pages are served by a plain HTTP server on the
213
- * Mac, and the default base URL is `http://localhost:5050`. Left alone, every capture
214
- * loads the guest's own empty port and the transcripts come back describing nothing --
215
- * with no error, because a connection refused inside Edge is not a worker failure.
216
- */
217
- /**
218
- * This host's address as seen from a worker at `workerUrl`, when the two share a subnet.
219
- * Returns undefined for a hostname, or for a worker somewhere we have no interface onto --
220
- * in which case the caller must be told where to reach us rather than have it guessed.
221
- */
222
- export function hostAddressForWorker(workerUrl) {
223
- try {
224
- const { hostname } = new URL(workerUrl);
225
- return ipv4ToInt(hostname) === null ? undefined : hostAddressFor(hostname);
226
- }
227
- catch {
228
- return undefined;
229
- }
230
- }
231
- export function guestReachableUrl(baseUrl, lease) {
232
- // Fall back to deriving it from the worker's own address.
233
- //
234
- // hostAddress used to be set only on the managed-VM path, so naming a worker explicitly --
235
- // A11Y_WORKER, or any pool -- silently skipped the rewrite and every capture fetched the
236
- // GUEST's localhost. Edge shows "localhost refused to connect", the title check rejects the
237
- // capture, and three attempts are burned per page before it gives up. The worker being
238
- // remote or explicit does not change the fact that it cannot reach our localhost.
239
- const hostAddress = lease.hostAddress ?? hostAddressForWorker(lease.worker);
240
- if (!hostAddress)
241
- return baseUrl;
242
- const parsed = new URL(baseUrl);
243
- if (parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1")
244
- return baseUrl;
245
- parsed.hostname = hostAddress;
246
- return parsed.toString().replace(/\/$/, "");
247
- }
248
- /** Every local worker VM, whatever state each is in. */
249
- async function findLocalPool() {
250
- if (process.platform !== "darwin")
251
- return [];
252
- try {
253
- return JSON.parse(await ctl(["pool"], STATUS_TIMEOUT_MS));
254
- }
255
- catch {
256
- return [];
257
- }
258
- }
259
- /** A11Y_MAX_WORKERS wins over the measurement: the operator may know something we cannot measure. */
260
- function configuredWorkerLimit() {
261
- const configured = Number(process.env.A11Y_MAX_WORKERS);
262
- return Number.isFinite(configured) && configured > 0 ? configured : null;
263
- }
264
- /**
265
- * Which registered VMs should actually be running for this run?
266
- *
267
- * The pool used to start all of them. Three guests on a 36 GB host made every capture 1.6x slower
268
- * than one, and caused mute-NVDA failures, because the guests were swapped out from under NVDA — so
269
- * "how many VMs exist" was the wrong question and "how much memory is there" is the right one.
270
- *
271
- * Already-running VMs are preferred over stopped ones: starting a guest in order to leave a running
272
- * one idle is pure churn. Note that a VM someone else has already started is never stopped here — it
273
- * is simply not used — because a run must not shut down a worker it did not start.
274
- */
275
- function chooseRunnableWorkers(pool) {
276
- const availableMb = availableHostMemoryMb();
277
- const running = pool.filter((vm) => vm.state === "started");
278
- const limit = configuredWorkerLimit()
279
- ?? workersHostCanRun({ availableMb, alreadyRunning: running.length });
280
- if (limit >= pool.length)
281
- return { chosen: pool, note: null };
282
- const chosen = [...running, ...pool.filter((vm) => vm.state !== "started")].slice(0, limit);
283
- return { chosen, note: capacityReason({ limit, wanted: pool.length, availableMb }) };
284
- }
285
- /**
286
- * Lease every local worker VM: start what is not running, and put each one back afterwards.
287
- *
288
- * This exists because the pool path used to hand back a no-op release, so a pooled run left
289
- * every VM running indefinitely — which is precisely the cost the single-worker lease was
290
- * written to avoid, reintroduced the moment pooling became the normal way to run.
291
- *
292
- * Per-VM restore, not a blanket stop: a VM you had already started stays started, exactly as
293
- * in the single-worker case. A long dataset run must not shut down a worker somebody else is
294
- * using, and with a pool that is likelier, not less.
295
- */
296
- export async function leaseWorkerPool(after) {
297
- warnUtmDeprecated("the local UTM worker pool");
298
- const pool = await findLocalPool();
299
- if (pool.length < 2)
300
- return null; // one VM is the single-worker path's job
301
- const { chosen, note } = chooseRunnableWorkers(pool);
302
- if (note)
303
- process.stderr.write(note + "\n");
304
- const chosenNames = new Set(chosen.map((vm) => vm.name));
305
- const before = new Map(pool.map((vm) => [vm.name, vm.state]));
306
- for (const vm of chosen) {
307
- if (vm.state === "started" && vm.healthy)
308
- continue;
309
- process.stderr.write(`Local worker '${vm.name}' is ${vm.state}; bringing it up ...\n`);
310
- try {
311
- await ctl(["up"], LIFECYCLE_TIMEOUT_MS, vm.name);
312
- }
313
- catch (error) {
314
- // One sick VM must not cancel the run. This threw and killed a resume outright while two other
315
- // workers sat ready: a guest wedged in "stopping" failed `up`, the exception escaped the lease,
316
- // and 1,000 cases went nowhere. Bringing up a pool is best-effort per member -- the readiness
317
- // filter below is what decides who actually takes work, and it already fails loudly if nobody
318
- // does.
319
- process.stderr.write(`Local worker '${vm.name}' would not come up (${error.message.split("\n")[0]}); ` +
320
- "continuing without it\n");
321
- }
322
- }
323
- // Only workers we CHOSE, even if a VM we declined is up and healthy: taking work to a guest the
324
- // capacity check excluded would defeat the check, and it may belong to somebody else's run.
325
- const ready = (await findLocalPool()).filter((vm) => vm.healthy && vm.ip && chosenNames.has(vm.name));
326
- if (!ready.length)
327
- throw new Error("no local worker became healthy; check: worker-ctl.sh pool");
328
- const missing = chosen.length - ready.length;
329
- if (missing > 0) {
330
- // Never silent: a run that quietly used two of three workers looks like an unexplained slowdown.
331
- process.stderr.write(`${missing} of ${chosen.length} local workers are unavailable; running on the rest\n`);
332
- }
333
- process.stderr.write(`Pool of ${ready.length}: ${ready.map((v) => v.name).join(", ")}\n`);
334
- return {
335
- workers: ready.map((vm) => `http://${vm.ip}:${vm.port}`),
336
- hostAddress: hostAddressFor(ready[0].ip),
337
- release: async () => {
338
- for (const vm of ready) {
339
- const action = after === "restore" ? restoreAction(before.get(vm.name) ?? "stopped") : after;
340
- if (action === "leave")
341
- continue;
342
- try {
343
- // Check per VM: another run may have picked this one up while ours was finishing.
344
- const now = (await findLocalPool()).find((v) => v.name === vm.name);
345
- if (now?.busy) {
346
- process.stderr.write(`'${vm.name}' is busy with another capture; leaving it ${now.state}\n`);
347
- continue;
348
- }
349
- process.stderr.write(`${action === "pause" ? "Pausing" : "Shutting down"} '${vm.name}' ...\n`);
350
- await ctl([action], LIFECYCLE_TIMEOUT_MS, vm.name);
351
- }
352
- catch (e) {
353
- // One VM failing to stop must not strand the others.
354
- process.stderr.write(`WARNING: could not ${action} '${vm.name}': ${e.message}\n`);
355
- }
356
- }
357
- },
358
- };
359
- }
360
- //# sourceMappingURL=local-vm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"local-vm.js","sourceRoot":"","sources":["../src/local-vm.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"measure-guard.d.mts","sourceRoot":"","sources":["../src/measure-guard.mjs"],"names":[],"mappings":"AAsBA;;;;;GAKG;AACH,sCALW,MAAM,EAAE,gCACR;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,YAAY,CAAA;CAAE,GAExF,OAAO,CAAC,IAAI,CAAC,CAmBzB;AAWD;;;;;;;;GAQG;AACH,qCAJW,MAAM,iCACN;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,qBAAqB,EAAE,YAAY,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACzG,OAAO,CAAC,GAAG,CAAC,CASxB;AAzBD;;;;;;GAMG;AACH,gCAAiC,KAAM,CAAC"}
@@ -1,73 +0,0 @@
1
- // @ts-check
2
- /**
3
- * REFUSE TO MEASURE A BOX THAT IS BUSY — because the rule alone does not work.
4
- *
5
- * "Do not measure while something else is running" is written in `capture-protocol-plan.md`, by the person
6
- * who then broke it FOUR times in two days:
7
- *
8
- * a page-status audit against :5050 while `capture:check` held it with a different root -> 4 false 404s
9
- * a transport probe against a box running a gate -> 12 consecutive 429s read as timings
10
- * an idle-reap measurement that timed `keepAliveTimeout` instead of the capture case
11
- * a capture-reap measurement against a busy fleet -> "responses" at 0.0-1.6 s, which are 429s
12
- *
13
- * Every one produced a NUMBER, which is what makes it dangerous: a measurement that fails loudly is
14
- * harmless, and one that returns a plausible wrong figure gets believed. Three of the four were caught only
15
- * because the number looked odd afterwards.
16
- *
17
- * So this is the repo's own housekeeping rule applied to measurement: anything a human has to remember is
18
- * something that does not happen. A measurement script asks the box whether it is free FIRST, and refuses
19
- * with the reason rather than sampling whatever a busy worker happens to say.
20
- */
21
- import { describeProbe, probeHealth, WORKER_PROBE_TIMEOUT_MS } from "./probe-outcome.mjs";
22
- /**
23
- * @param {string[]} workers
24
- * @param {{ what: string, timeoutMs?: number, request?: import("./probe-outcome.mjs").ProbeRequest }} about
25
- * `what` names the measurement, so a refusal says what was NOT measured; `timeoutMs` and `request` are injectable
26
- * @returns {Promise<void>} resolves when every worker is free; throws naming the busy ones
27
- */
28
- export async function refuseIfBusy(workers, { what, timeoutMs = WORKER_PROBE_TIMEOUT_MS, request }) {
29
- const states = await Promise.all(workers.map(async (worker) => {
30
- const probe = await probeHealth(worker, { timeoutMs, request });
31
- // `ready` is the right field, not `ok`: `ok` only ever meant "the HTTP server is answering", and a
32
- // worker answered it while NVDA could not start. `ready` is false while a capture holds the box.
33
- if (probe.outcome === "ready")
34
- return { worker, free: true, why: "" };
35
- // NOT ANSWERING IS NOT FREE, AND IT IS NOT "DOWN" EITHER. Measuring against a box that cannot answer /health
36
- // produces exactly the kind of plausible-looking nonsense this guard exists to prevent, so it refuses; but
37
- // what it says is the observed thing (#2683), one sentence per outcome from `describeProbe`.
38
- return { worker, free: false, why: describeProbe(probe, { worker, timeoutMs }) };
39
- }));
40
- const busy = states.filter((s) => !s.free);
41
- if (!busy.length)
42
- return;
43
- throw new Error(`REFUSING to measure ${what}: ${busy.length} of ${workers.length} worker(s) are not free `
44
- + `— ${busy.map((b) => b.why).join(" ")} A measurement taken against a busy box `
45
- + "samples its 429s, not its behaviour, and returns a number that looks real. Wait, or pass a worker "
46
- + "that is free.");
47
- }
48
- /**
49
- * THE VITALS PROBE'S TIMEOUT, WITH ITS READING (#2683). `worker:compare` reads `vitals` before the rounds and again
50
- * straight AFTER the last capture, so the second read is the LOADED case: a box that has just finished one can take
51
- * up to about 10 s to answer (#2671, the reading beside `WORKER_PROBE_TIMEOUT_MS`), against 3.09 s for the slowest
52
- * healthy first-after-idle answer. 20 s is that ceiling twice over, kept from before #2683 because it already clears
53
- * both. A vitals sample that times out costs the run one column, so there is no reason to give it less.
54
- */
55
- export const VITALS_TIMEOUT_MS = 20_000;
56
- /**
57
- * A worker's `/health` `vitals`, or `null` when there are none to be had, and SAYS WHY through `warn`: a box that did
58
- * not answer, one that refused, and one that answered without vitals are three different reasons for the same empty
59
- * column in the report, and the run used to print none of them.
60
- *
61
- * @param {string} worker
62
- * @param {{ timeoutMs?: number, request?: import("./probe-outcome.mjs").ProbeRequest, warn?: (line: string) => void }} [options]
63
- * @returns {Promise<any>}
64
- */
65
- export async function sampleVitals(worker, { timeoutMs = VITALS_TIMEOUT_MS, request, warn = () => { } } = {}) {
66
- const probe = await probeHealth(worker, { timeoutMs, request });
67
- if (probe.outcome === "refused" || probe.outcome === "no-answer") {
68
- warn(` no vitals sampled: ${describeProbe(probe, { worker, timeoutMs })}\n`);
69
- return null;
70
- }
71
- return probe.health?.vitals ?? null;
72
- }
73
- //# sourceMappingURL=measure-guard.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"measure-guard.mjs","sourceRoot":"","sources":["../src/measure-guard.mjs"],"names":[],"mappings":"AAAA,YAAY;AACZ;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAE1F;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,GAAG,uBAAuB,EAAE,OAAO,EAAE;IAChG,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;QAC5D,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QAChE,mGAAmG;QACnG,iGAAiG;QACjG,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO;YAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;QACtE,6GAA6G;QAC7G,2GAA2G;QAC3G,6FAA6F;QAC7F,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,aAAa,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IACnF,CAAC,CAAC,CAAC,CAAC;IACJ,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO;IACzB,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,KAAK,IAAI,CAAC,MAAM,OAAO,OAAO,CAAC,MAAM,0BAA0B;UACtG,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,0CAA0C;UAC/E,oGAAoG;UACpG,eAAe,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAM,EAAE,EAAE,SAAS,GAAG,iBAAiB,EAAE,OAAO,EAAE,IAAI,GAAG,GAAG,EAAE,GAAE,CAAC,EAAE,GAAG,EAAE;IACzG,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;IAChE,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;QACjE,IAAI,CAAC,wBAAwB,aAAa,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,IAAI,CAAC;AACtC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalise-fleet.d.mts","sourceRoot":"","sources":["../src/normalise-fleet.mjs"],"names":[],"mappings":""}
@@ -1,76 +0,0 @@
1
- // @ts-check
2
- // Bring every local guest to the same baseline, elevated, and prove it took.
3
- //
4
- // npm run fleet:normalise
5
- //
6
- // Fleet CONSISTENCY is the point, more than any individual setting. Measured today:
7
- // StartupBoostEnabled was 1 on two guests and 0 on a third, and Edge had auto-updated from 150 to 151
8
- // on one before the others. Two guests with different browser behaviour producing into one corpus is
9
- // the same class of problem as a mixed OS image -- the cache key cannot see it, and the evidence is
10
- // quietly heterogeneous.
11
- //
12
- // Runs scripts/guest/normalise-fleet.cmd on each guest through guest-run.mjs, which is the only
13
- // channel that can do elevated work on these VMs (see that file for why the obvious ones cannot).
14
- import { fileURLToPath, pathToFileURL } from "node:url";
15
- import { execFile } from "node:child_process";
16
- import { promisify } from "node:util";
17
- import { refuseUnknownFlags } from "./cli-flags.mjs";
18
- import { warnUtmDeprecated } from "./utm-deprecated.mjs";
19
- /**
20
- * takes no flags: it brings every local guest to one baseline.
21
- *
22
- * An unrecognised flag is otherwise IGNORED, so it runs the default and reports success.
23
- */
24
- refuseUnknownFlags([], { entry: import.meta.url, command: "npm run fleet:normalise" });
25
- // A SIBLING in this package, resolved from this module. It was the cwd-relative path `scripts/guest-run.mjs`,
26
- // which stopped existing when M8 moved the fleet tooling — and was only ever right when the cwd happened to be
27
- // the repo root.
28
- const GUEST_RUN = fileURLToPath(new URL("./guest-run.mjs", import.meta.url));
29
- const run = promisify(execFile);
30
- const UTMCTL = "/Applications/UTM.app/Contents/MacOS/utmctl";
31
- // Only when RUN, never on import. Unguarded, importing this file SHELLED OUT to `utmctl list` and then
32
- // drove DISM across every registered guest -- so `node -e "import('./normalise-fleet.mjs')"`, which
33
- // CLAUDE.md makes the only real check that this file still loads, normalised the fleet as a side effect.
34
- //
35
- // This file has already been broken once by a path that nothing exercised: it read
36
- // `scripts/guest/normalise-fleet.cmd`, which the packages/ restructure moved, and `npm run fleet:normalise`
37
- // stayed broken while `doctor` printed it as the FIX for a failed consistency check. The import check is
38
- // what catches that class, so it has to be safe to run.
39
- async function main() {
40
- warnUtmDeprecated("npm run fleet:normalise");
41
- const { stdout } = await run(UTMCTL, ["list"]);
42
- const vms = stdout.split("\n").slice(1)
43
- .map((l) => l.trim().split(/\s+/))
44
- .filter((c) => c.length >= 3 && c[2].startsWith("a11y-worker"))
45
- .map((c) => ({ uuid: c[0], state: c[1], name: c[2] }));
46
- if (!vms.length) {
47
- process.stderr.write("no a11y-worker VMs registered\n");
48
- process.exit(2);
49
- }
50
- // Resolved from THIS module, never the cwd. It read `scripts/guest/normalise-fleet.cmd` -- a path the
51
- // packages/ restructure moved and nothing noticed, so `npm run fleet:normalise` has been broken, and
52
- // doctor printed it as the FIX for a failed consistency check. A path spelled relative to the working
53
- // directory is a path that breaks the moment anyone runs the command from somewhere else.
54
- const script = fileURLToPath(new URL("./guest/normalise-fleet.cmd", import.meta.url));
55
- const failures = [];
56
- for (const vm of vms) {
57
- // One at a time. Three guests doing DISM and service work simultaneously is exactly the disk
58
- // contention this project spent a day diagnosing.
59
- try {
60
- const { stdout: out } = await run("node", [GUEST_RUN, vm.name, script, "--timeout=900"], { maxBuffer: 1 << 24 });
61
- process.stdout.write(out);
62
- }
63
- catch (error) {
64
- failures.push(vm.name);
65
- process.stdout.write(`==> ${vm.name}: FAILED — ${(error instanceof Error ? error.message : String(error)).split("\n")[0]}\n`);
66
- }
67
- }
68
- process.stdout.write(`\n${vms.length - failures.length}/${vms.length} guest(s) normalised` +
69
- (failures.length ? `; failed: ${failures.join(", ")}\n` : "\n"));
70
- process.stdout.write("Verify with: npm run doctor\n");
71
- if (failures.length)
72
- process.exit(1);
73
- }
74
- if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href)
75
- await main();
76
- //# sourceMappingURL=normalise-fleet.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalise-fleet.mjs","sourceRoot":"","sources":["../src/normalise-fleet.mjs"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"npm-cli-executable.d.mts","sourceRoot":"","sources":["../src/npm-cli-executable.mjs"],"names":[],"mappings":"AAkCA;;;GAGG;AACH,6CAHW,KAAK,GAAG,KAAK,GACX,MAAM,EAAE,CAWpB;AAwBD;;;GAGG;AACH,0CAHW,KAAK,GAAG,KAAK,GACX,MAAM,CAUlB;AAED;;;;GAIG;AACH,uCAJW,KAAK,GAAG,KAAK,QACb,MAAM,EAAE,GACN;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAI/C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wCAHW,MAAM,EAAE,GACN;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAgB/C"}