@a11ign/screenreader-fleet 0.0.0-reserved.0 → 0.1.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/LICENSE +661 -0
- package/README.md +94 -2
- package/dist/capture-client.d.mts +49 -0
- package/dist/capture-client.d.mts.map +1 -0
- package/dist/capture-client.mjs +352 -0
- package/dist/capture-client.mjs.map +1 -0
- package/dist/check-worker-code.d.mts +34 -0
- package/dist/check-worker-code.d.mts.map +1 -0
- package/dist/check-worker-code.mjs +173 -0
- package/dist/check-worker-code.mjs.map +1 -0
- package/dist/cli-flags.d.mts +71 -0
- package/dist/cli-flags.d.mts.map +1 -0
- package/dist/cli-flags.mjs +207 -0
- package/dist/cli-flags.mjs.map +1 -0
- package/dist/code-drift.d.mts +140 -0
- package/dist/code-drift.d.mts.map +1 -0
- package/dist/code-drift.mjs +284 -0
- package/dist/code-drift.mjs.map +1 -0
- package/dist/command-line-census.d.mts +33 -0
- package/dist/command-line-census.d.mts.map +1 -0
- package/dist/command-line-census.mjs +96 -0
- package/dist/command-line-census.mjs.map +1 -0
- package/dist/compare-workers.d.mts +3 -0
- package/dist/compare-workers.d.mts.map +1 -0
- package/dist/compare-workers.mjs +332 -0
- package/dist/compare-workers.mjs.map +1 -0
- package/dist/control-plane-isolation.d.mts +45 -0
- package/dist/control-plane-isolation.d.mts.map +1 -0
- package/dist/control-plane-isolation.mjs +67 -0
- package/dist/control-plane-isolation.mjs.map +1 -0
- package/dist/deploy-worker.d.mts +3 -0
- package/dist/deploy-worker.d.mts.map +1 -0
- package/dist/deploy-worker.mjs +333 -0
- package/dist/deploy-worker.mjs.map +1 -0
- package/dist/doctor.d.mts +216 -0
- package/dist/doctor.d.mts.map +1 -0
- package/dist/doctor.mjs +962 -0
- package/dist/doctor.mjs.map +1 -0
- package/dist/fleet-consistency.d.mts +235 -0
- package/dist/fleet-consistency.d.mts.map +1 -0
- package/dist/fleet-consistency.mjs +436 -0
- package/dist/fleet-consistency.mjs.map +1 -0
- package/dist/fleet-env.d.mts +228 -0
- package/dist/fleet-env.d.mts.map +1 -0
- package/dist/fleet-env.mjs +509 -0
- package/dist/fleet-env.mjs.map +1 -0
- package/dist/fleet-scripts.d.mts +11 -0
- package/dist/fleet-scripts.d.mts.map +1 -0
- package/dist/fleet-scripts.mjs +41 -0
- package/dist/fleet-scripts.mjs.map +1 -0
- package/dist/git-safe-env.d.mts +10 -0
- package/dist/git-safe-env.d.mts.map +1 -0
- package/dist/git-safe-env.mjs +44 -0
- package/dist/git-safe-env.mjs.map +1 -0
- package/dist/guest-run.d.mts +26 -0
- package/dist/guest-run.d.mts.map +1 -0
- package/dist/guest-run.mjs +164 -0
- package/dist/guest-run.mjs.map +1 -0
- package/dist/host-address.d.mts +33 -0
- package/dist/host-address.d.mts.map +1 -0
- package/dist/host-address.mjs +105 -0
- package/dist/host-address.mjs.map +1 -0
- package/dist/host-capacity.d.mts +64 -0
- package/dist/host-capacity.d.mts.map +1 -0
- package/dist/host-capacity.mjs +152 -0
- package/dist/host-capacity.mjs.map +1 -0
- package/dist/host-metrics.d.mts +116 -0
- package/dist/host-metrics.d.mts.map +1 -0
- package/dist/host-metrics.mjs +201 -0
- package/dist/host-metrics.mjs.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/local-vm.d.ts +125 -0
- package/dist/local-vm.d.ts.map +1 -0
- package/dist/local-vm.js +360 -0
- package/dist/local-vm.js.map +1 -0
- package/dist/measure-guard.d.mts +34 -0
- package/dist/measure-guard.d.mts.map +1 -0
- package/dist/measure-guard.mjs +73 -0
- package/dist/measure-guard.mjs.map +1 -0
- package/dist/normalise-fleet.d.mts +2 -0
- package/dist/normalise-fleet.d.mts.map +1 -0
- package/dist/normalise-fleet.mjs +76 -0
- package/dist/normalise-fleet.mjs.map +1 -0
- package/dist/npm-cli-executable.d.mts +42 -0
- package/dist/npm-cli-executable.d.mts.map +1 -0
- package/dist/npm-cli-executable.mjs +159 -0
- package/dist/npm-cli-executable.mjs.map +1 -0
- package/dist/probe-outcome.d.mts +89 -0
- package/dist/probe-outcome.d.mts.map +1 -0
- package/dist/probe-outcome.mjs +104 -0
- package/dist/probe-outcome.mjs.map +1 -0
- package/dist/protocol-guard.d.mts +34 -0
- package/dist/protocol-guard.d.mts.map +1 -0
- package/dist/protocol-guard.mjs +121 -0
- package/dist/protocol-guard.mjs.map +1 -0
- package/dist/source-walk.d.mts +12 -0
- package/dist/source-walk.d.mts.map +1 -0
- package/dist/source-walk.mjs +56 -0
- package/dist/source-walk.mjs.map +1 -0
- package/dist/transient-fault.d.mts +6 -0
- package/dist/transient-fault.d.mts.map +1 -0
- package/dist/transient-fault.mjs +86 -0
- package/dist/transient-fault.mjs.map +1 -0
- package/dist/utm-deprecated.d.mts +6 -0
- package/dist/utm-deprecated.d.mts.map +1 -0
- package/dist/utm-deprecated.mjs +23 -0
- package/dist/utm-deprecated.mjs.map +1 -0
- package/dist/worker-code-check.d.mts +29 -0
- package/dist/worker-code-check.d.mts.map +1 -0
- package/dist/worker-code-check.mjs +78 -0
- package/dist/worker-code-check.mjs.map +1 -0
- package/dist/worker-health.d.mts +56 -0
- package/dist/worker-health.d.mts.map +1 -0
- package/dist/worker-health.mjs +73 -0
- package/dist/worker-health.mjs.map +1 -0
- package/dist/worker-http.d.mts +103 -0
- package/dist/worker-http.d.mts.map +1 -0
- package/dist/worker-http.mjs +277 -0
- package/dist/worker-http.mjs.map +1 -0
- package/dist/worker-stats.d.mts +66 -0
- package/dist/worker-stats.d.mts.map +1 -0
- package/dist/worker-stats.mjs +143 -0
- package/dist/worker-stats.mjs.map +1 -0
- package/package.json +96 -4
- package/src/local-worker/autounattend.xml +280 -0
- package/src/local-worker/build-vm.sh +218 -0
- package/src/local-worker/clone-worker.sh +141 -0
- package/src/local-worker/create-utm-vm.sh +202 -0
- package/src/local-worker/fetch-windows-iso.sh +238 -0
- package/src/local-worker/first-boot.cmd +58 -0
- package/src/local-worker/worker-ctl.sh +442 -0
- package/src/provisioning/README.md +28 -0
- package/src/provisioning/apply-foreground-lock-timeout.ps1 +71 -0
- package/src/provisioning/bare-metal/README.md +213 -0
- package/src/provisioning/bare-metal/a11y-bootstrap.service +58 -0
- package/src/provisioning/bare-metal/autounattend.xml +428 -0
- package/src/provisioning/bare-metal/serve-bootstrap.sh +86 -0
- package/src/provisioning/bootstrap-control-plane.sh +463 -0
- package/src/provisioning/bootstrap-windows-worker.ps1 +649 -0
- package/src/provisioning/build-lean-worker-image.ps1 +275 -0
- package/src/provisioning/diagnose-nvda-worker.ps1 +174 -0
- package/src/provisioning/provision-nvda-worker.ps1 +827 -0
- package/src/provisioning/set-display-mode.ps1 +411 -0
- package/src/provisioning/stamp-provision-revision.ps1 +184 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// A DELIBERATE, DISCLOSED DUPLICATE of `scripts/npm-cli-executable.mjs` at the repo root.
|
|
2
|
+
//
|
|
3
|
+
// #492: never spawn `npx`/`npm` at all -- resolve npm's own CLI script and run it through
|
|
4
|
+
// `process.execPath`, since Node's CVE-2024-27980 fix permanently made `spawn`/`spawnSync`/`execFileSync`
|
|
5
|
+
// refuse to launch a `.bat`/`.cmd` file directly without `shell` (and `ceo`'s ruling is no `shell: true`,
|
|
6
|
+
// anywhere). See `scripts/npm-cli-executable.mjs`'s own header for the full incident and the two-layout
|
|
7
|
+
// resolution this file duplicates behaviourally.
|
|
8
|
+
//
|
|
9
|
+
// This package publishes `check-worker-code.mjs` and `deploy-worker.mjs` as `bin` entries
|
|
10
|
+
// (`package.json`), so every file they import -- `doctor.mjs` among them -- ships in the published
|
|
11
|
+
// tarball and can only import from INSIDE `@a11ign/screenreader-fleet`. The repo-root `scripts/` directory
|
|
12
|
+
// does not exist once this package is installed from npm, so importing it here would work in this
|
|
13
|
+
// monorepo and break for every real consumer. That is the entire reason this file exists rather than a
|
|
14
|
+
// relative import to the root: not stylistic preference, a publish-boundary constraint (see ADR 0004,
|
|
15
|
+
// and `git-safe-env.mjs`'s own header beside this file for the identical shape).
|
|
16
|
+
//
|
|
17
|
+
// #2890: `pnpmCliInvocation` (#2301) and the two helpers it uses are copied verbatim, because `doctor.mjs`
|
|
18
|
+
// spawns pnpm now, not npm; the npm half stays so the pin below keeps comparing whole files.
|
|
19
|
+
//
|
|
20
|
+
// Kept behaviourally identical to `scripts/npm-cli-executable.mjs` and pinned equal to it by
|
|
21
|
+
// `npm-cli-executable.test.ts`, which is this repo's own remedy #3 ("pin them equal with a test") for a
|
|
22
|
+
// fact that CANNOT be stated once because the two copies cross a package-publishing boundary neither can
|
|
23
|
+
// import through.
|
|
24
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
25
|
+
import { join, dirname, delimiter } from "node:path";
|
|
26
|
+
/**
|
|
27
|
+
* @param {"npx" | "npm"} name
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function cliScriptName(name) {
|
|
31
|
+
return name === "npx" ? "npx-cli.js" : "npm-cli.js";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @param {"npx" | "npm"} name
|
|
35
|
+
* @returns {string[]}
|
|
36
|
+
*/
|
|
37
|
+
export function npmCliScriptCandidates(name) {
|
|
38
|
+
const script = cliScriptName(name);
|
|
39
|
+
const nodeDir = dirname(process.execPath);
|
|
40
|
+
const fixed = [
|
|
41
|
+
join(nodeDir, "node_modules", "npm", "bin", script),
|
|
42
|
+
join(nodeDir, "..", "lib", "node_modules", "npm", "bin", script),
|
|
43
|
+
];
|
|
44
|
+
const fromPath = pathDerivedCandidate(name, script);
|
|
45
|
+
return fromPath === null ? fixed : [...fixed, fromPath];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* #1268: THE THIRD LAYOUT. Debian and Ubuntu package npm at `/usr/share/nodejs/npm/bin/`, nowhere near
|
|
49
|
+
* `node`, and put `/usr/bin/npm` and `/usr/bin/npx` on PATH as symlinks straight to the CLI scripts. So
|
|
50
|
+
* the executable on PATH, symlinks resolved, IS the script (Debian) or sits in the script's directory
|
|
51
|
+
* (the upstream tarball's `bin/npx` wrapper). Tried LAST so the two fixed layouts keep their order on
|
|
52
|
+
* the platforms they were measured on; `null` when nothing named `name` is on PATH, so the candidate
|
|
53
|
+
* list never carries a path that cannot exist. Found by the first `npm ci` on the agents host.
|
|
54
|
+
* @param {"npx" | "npm"} name
|
|
55
|
+
* @param {string} script
|
|
56
|
+
* @returns {string | null}
|
|
57
|
+
*/
|
|
58
|
+
function pathDerivedCandidate(name, script) {
|
|
59
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
60
|
+
if (dir === "")
|
|
61
|
+
continue;
|
|
62
|
+
const executable = join(dir, name);
|
|
63
|
+
if (!existsSync(executable))
|
|
64
|
+
continue;
|
|
65
|
+
const real = realpathSync(executable);
|
|
66
|
+
return real.endsWith(script) ? real : join(dirname(real), script);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @param {"npx" | "npm"} name
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
export function resolveNpmCliScript(name) {
|
|
75
|
+
const candidates = npmCliScriptCandidates(name);
|
|
76
|
+
const found = candidates.find((path) => existsSync(path));
|
|
77
|
+
if (!found) {
|
|
78
|
+
throw new Error(`could not find npm's own ${cliScriptName(name)} beside this Node install -- tried:\n`
|
|
79
|
+
+ candidates.map((path) => ` ${path}`).join("\n"));
|
|
80
|
+
}
|
|
81
|
+
return found;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @param {"npx" | "npm"} name
|
|
85
|
+
* @param {string[]} args
|
|
86
|
+
* @returns {{ command: string, args: string[] }}
|
|
87
|
+
*/
|
|
88
|
+
export function npmCliInvocation(name, args) {
|
|
89
|
+
return { command: process.execPath, args: [resolveNpmCliScript(name), ...args] };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* #2301: `pnpm <args>` WITHOUT SPAWNING A `.cmd`, for the same reason `npmCliInvocation` exists, and with the
|
|
93
|
+
* added problem that pnpm is not always ON `PATH` at all: this host and the Windows workers run it as
|
|
94
|
+
* `corepack pnpm` (`packageManager` in the root manifest pins the version), while a CI runner has the
|
|
95
|
+
* shim `pnpm/action-setup` puts there. Three ways to reach it, tried in this order, each ending in an argv
|
|
96
|
+
* that `execFileSync` can run with no shell:
|
|
97
|
+
*
|
|
98
|
+
* 1. `npm_execpath`, when this process was itself started BY pnpm (`pnpm run ...`, `pnpm exec ...`): pnpm
|
|
99
|
+
* says which script it is, so nothing is searched for and no other pnpm can be picked up by mistake.
|
|
100
|
+
* 2. a `pnpm` on `PATH`: the executable itself on POSIX; on Windows the shim is `pnpm.cmd`, so the
|
|
101
|
+
* `pnpm.cjs` beside it is run through `process.execPath` instead.
|
|
102
|
+
* 3. `corepack` on `PATH`, the same way: `corepack pnpm` on POSIX, `corepack.js` beside `node.exe`
|
|
103
|
+
* through `process.execPath` on Windows.
|
|
104
|
+
*
|
|
105
|
+
* Throws NAMING WHAT WAS TRIED, for the reason `resolveNpmCliScript` does.
|
|
106
|
+
* @param {string[]} args
|
|
107
|
+
* @returns {{ command: string, args: string[] }}
|
|
108
|
+
*/
|
|
109
|
+
export function pnpmCliInvocation(args) {
|
|
110
|
+
const fromParent = process.env.npm_execpath ?? "";
|
|
111
|
+
if (/pnpm\.c?js$/.test(fromParent) && existsSync(fromParent)) {
|
|
112
|
+
return { command: process.execPath, args: [fromParent, ...args] };
|
|
113
|
+
}
|
|
114
|
+
const shim = onPath("pnpm");
|
|
115
|
+
if (shim !== null)
|
|
116
|
+
return shimInvocation(shim, join("node_modules", "pnpm", "bin", "pnpm.cjs"), args);
|
|
117
|
+
const corepack = onPath("corepack");
|
|
118
|
+
if (corepack !== null) {
|
|
119
|
+
return shimInvocation(corepack, join("node_modules", "corepack", "dist", "corepack.js"), ["pnpm", ...args]);
|
|
120
|
+
}
|
|
121
|
+
throw new Error("could not find pnpm: `npm_execpath` is not a pnpm script, and neither `pnpm` nor `corepack` "
|
|
122
|
+
+ "is on PATH. `packageManager` in the root package.json names the version -- `corepack enable` or "
|
|
123
|
+
+ "`pnpm/action-setup` provides it.");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The first executable called `name` on PATH, or `null`. On Windows the executable is `name.cmd`, which
|
|
127
|
+
* `existsSync` finds only when the extension is tried, so both spellings are.
|
|
128
|
+
* @param {string} name
|
|
129
|
+
* @returns {string | null}
|
|
130
|
+
*/
|
|
131
|
+
function onPath(name) {
|
|
132
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
133
|
+
if (dir === "")
|
|
134
|
+
continue;
|
|
135
|
+
for (const candidate of [join(dir, name), join(dir, `${name}.cmd`)]) {
|
|
136
|
+
if (existsSync(candidate))
|
|
137
|
+
return candidate;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* A POSIX shim is spawned as it is; a `.cmd` shim is never spawned (CVE-2024-27980, see the header), so the
|
|
144
|
+
* package script it wraps is run through `process.execPath`, found beside the shim (the layout `pnpm add -g`
|
|
145
|
+
* and `pnpm/action-setup` share) or beside `node` (corepack's).
|
|
146
|
+
* @param {string} shim
|
|
147
|
+
* @param {string} script the wrapped script, relative to the shim's directory or to `node`'s
|
|
148
|
+
* @param {string[]} args
|
|
149
|
+
* @returns {{ command: string, args: string[] }}
|
|
150
|
+
*/
|
|
151
|
+
function shimInvocation(shim, script, args) {
|
|
152
|
+
if (!shim.endsWith(".cmd"))
|
|
153
|
+
return { command: shim, args };
|
|
154
|
+
const found = [join(dirname(shim), script), join(dirname(process.execPath), script)].find((path) => existsSync(path));
|
|
155
|
+
if (found === undefined)
|
|
156
|
+
throw new Error(`${shim} is a .cmd shim and its script ${script} is not beside it or beside node`);
|
|
157
|
+
return { command: process.execPath, args: [found, ...args] };
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=npm-cli-executable.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-cli-executable.mjs","sourceRoot":"","sources":["../src/npm-cli-executable.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} worker the worker's base URL
|
|
3
|
+
* @param {{ timeoutMs?: number, request?: ProbeRequest }} [options]
|
|
4
|
+
* @returns {Promise<Probe>}
|
|
5
|
+
*/
|
|
6
|
+
export function probeHealth(worker: string, { timeoutMs, request }?: {
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
request?: ProbeRequest;
|
|
9
|
+
}): Promise<Probe>;
|
|
10
|
+
/**
|
|
11
|
+
* One sentence per outcome, in the words of what was OBSERVED. Only `refused`, `not-ready` and `busy` say the box is
|
|
12
|
+
* up, because only those are things the box itself said; a silent probe says "did not answer" and never "down".
|
|
13
|
+
*
|
|
14
|
+
* @param {Probe} probe
|
|
15
|
+
* @param {{ worker: string, timeoutMs?: number }} about
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function describeProbe(probe: Probe, { worker, timeoutMs }: {
|
|
19
|
+
worker: string;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
}): string;
|
|
22
|
+
/** @typedef {typeof requestJson} ProbeRequest */
|
|
23
|
+
/**
|
|
24
|
+
* THE PER-PROBE TIMEOUT, WITH ITS READING. A probe that outlives it is UNKNOWN, never "down", so this number
|
|
25
|
+
* decides how much slowness a healthy box is allowed.
|
|
26
|
+
*
|
|
27
|
+
* All of it READ by others on the real fleet (`orchestrator`, #2671) and none measured by this row, whose
|
|
28
|
+
* engineer is barred from probing it:
|
|
29
|
+
* - the slowest HEALTHY box: 2.80 to 3.09 s on a11y-worker-13, -14 and -16, twelve others 0.53 to 0.76 s;
|
|
30
|
+
* - that is the FIRST answer after the box has been quiet more than 5 s, because the worker rebuilds its
|
|
31
|
+
* environment block with two synchronous `powershell.exe` calls when it is older than that, so a probe made
|
|
32
|
+
* by hand is nearly always the slow case;
|
|
33
|
+
* - a LOADED box (one that has just stopped a capture) can take up to about 10 s: each of the two calls is
|
|
34
|
+
* bounded at 5 s and they stop the worker's event loop for the whole time.
|
|
35
|
+
* 12 s is that loaded ceiling plus 2 s, the number `fleet-wake.mjs` `HEALTH_TIMEOUT_MS` states for the same
|
|
36
|
+
* reading. Before #2683 these entries used 5 s (`witness`, 1.91 s over the 3.09 s box and none over a loaded
|
|
37
|
+
* one), 8 s (`auth:leak-check`) and 10 s (`worker:compare`'s busy guard), read at `d119fb0f2`. Cost of the
|
|
38
|
+
* generosity: a box that really is off costs one 12 s wait before the message. A refusal costs nothing, it comes
|
|
39
|
+
* back at once.
|
|
40
|
+
*/
|
|
41
|
+
export const WORKER_PROBE_TIMEOUT_MS: 12000;
|
|
42
|
+
/** How to wake a box from a checkout of this repo. The published entries do not do it themselves. */
|
|
43
|
+
export const WAKE_HINT: string;
|
|
44
|
+
/**
|
|
45
|
+
* WHAT ONE `/health` PROBE CAN SAY, for the entries that reach a worker from the PUBLISHED side and by hand
|
|
46
|
+
* (`witness`, `worker:compare`, `auth:leak-check`, #2683 of #2655).
|
|
47
|
+
*
|
|
48
|
+
* Imports `worker-http` and nothing else: no `control` (this package is published and `@a11ign/control` never
|
|
49
|
+
* is, `worker-fleet-does-not-read-control.test.ts`) and no corpus reader, so a test may import it without
|
|
50
|
+
* pulling the corpus closure in. These entries do NOT wake a box (ADR 0012, product-manager 2026-09-26): they
|
|
51
|
+
* say it did not answer, and name the command that wakes one. `packages/control/src/fleet-wake.mjs` keeps its
|
|
52
|
+
* own `probeWorker` with the same five outcomes because it is the unpublished side of that line.
|
|
53
|
+
*
|
|
54
|
+
* The outcomes, and the one distinction that matters (an unanswered probe is UNKNOWN, never "down"):
|
|
55
|
+
*
|
|
56
|
+
* ready the box's own report, `ready: true`
|
|
57
|
+
* busy the box's own report, `busy: true`: a capture is running. Up, and not free
|
|
58
|
+
* not-ready it answered and its own `ready:false` says why, or it answered a non-2xx status. UP
|
|
59
|
+
* refused the connection was refused, so something answered the TCP handshake with a reset: the BOX IS UP
|
|
60
|
+
* and the worker is not listening. UP
|
|
61
|
+
* no-answer nothing came back inside the timeout (`timedOut`), or the transport failed some other way
|
|
62
|
+
* (unreachable, reset). One silent probe cannot separate "off" from "slow" from "the path dropped
|
|
63
|
+
* it", so it is never called down
|
|
64
|
+
*
|
|
65
|
+
* `health` is the body the box sent, on the three outcomes where one arrived, because "usable" is a different
|
|
66
|
+
* question in different entries (`workerIsUsable` counts a worker predating the `ready` field as usable; the
|
|
67
|
+
* measurement guard wants `ready: true`) and this module does not choose for them.
|
|
68
|
+
*/
|
|
69
|
+
export type Probe = {
|
|
70
|
+
outcome: "ready";
|
|
71
|
+
health: any;
|
|
72
|
+
} | {
|
|
73
|
+
outcome: "busy";
|
|
74
|
+
health: any;
|
|
75
|
+
} | {
|
|
76
|
+
outcome: "not-ready";
|
|
77
|
+
reason: string;
|
|
78
|
+
health: any;
|
|
79
|
+
} | {
|
|
80
|
+
outcome: "refused";
|
|
81
|
+
message: string;
|
|
82
|
+
} | {
|
|
83
|
+
outcome: "no-answer";
|
|
84
|
+
message: string;
|
|
85
|
+
timedOut: boolean;
|
|
86
|
+
};
|
|
87
|
+
export type ProbeRequest = typeof requestJson;
|
|
88
|
+
import { requestJson } from "./worker-http.mjs";
|
|
89
|
+
//# sourceMappingURL=probe-outcome.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe-outcome.d.mts","sourceRoot":"","sources":["../src/probe-outcome.mjs"],"names":[],"mappings":"AAuDA;;;;GAIG;AACH,oCAJW,MAAM,2BACN;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,YAAY,CAAA;CAAE,GAC5C,OAAO,CAAC,KAAK,CAAC,CAmB1B;AAMD;;;;;;;GAOG;AACH,qCAJW,KAAK,yBACL;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC,MAAM,CAalB;AArED,iDAAiD;AAEjD;;;;;;;;;;;;;;;;;GAiBG;AACH,sCAAuC,KAAM,CAAC;AA0B9C,qGAAqG;AACrG,+BAC6E;;;;;;;;;;;;;;;;;;;;;;;;;;oBAvDhE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GAC1E;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GACrD;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE;2BAIrD,OAAO,WAAW;4BAFJ,mBAAmB"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* WHAT ONE `/health` PROBE CAN SAY, for the entries that reach a worker from the PUBLISHED side and by hand
|
|
4
|
+
* (`witness`, `worker:compare`, `auth:leak-check`, #2683 of #2655).
|
|
5
|
+
*
|
|
6
|
+
* Imports `worker-http` and nothing else: no `control` (this package is published and `@a11ign/control` never
|
|
7
|
+
* is, `worker-fleet-does-not-read-control.test.ts`) and no corpus reader, so a test may import it without
|
|
8
|
+
* pulling the corpus closure in. These entries do NOT wake a box (ADR 0012, product-manager 2026-09-26): they
|
|
9
|
+
* say it did not answer, and name the command that wakes one. `packages/control/src/fleet-wake.mjs` keeps its
|
|
10
|
+
* own `probeWorker` with the same five outcomes because it is the unpublished side of that line.
|
|
11
|
+
*
|
|
12
|
+
* The outcomes, and the one distinction that matters (an unanswered probe is UNKNOWN, never "down"):
|
|
13
|
+
*
|
|
14
|
+
* ready the box's own report, `ready: true`
|
|
15
|
+
* busy the box's own report, `busy: true`: a capture is running. Up, and not free
|
|
16
|
+
* not-ready it answered and its own `ready:false` says why, or it answered a non-2xx status. UP
|
|
17
|
+
* refused the connection was refused, so something answered the TCP handshake with a reset: the BOX IS UP
|
|
18
|
+
* and the worker is not listening. UP
|
|
19
|
+
* no-answer nothing came back inside the timeout (`timedOut`), or the transport failed some other way
|
|
20
|
+
* (unreachable, reset). One silent probe cannot separate "off" from "slow" from "the path dropped
|
|
21
|
+
* it", so it is never called down
|
|
22
|
+
*
|
|
23
|
+
* `health` is the body the box sent, on the three outcomes where one arrived, because "usable" is a different
|
|
24
|
+
* question in different entries (`workerIsUsable` counts a worker predating the `ready` field as usable; the
|
|
25
|
+
* measurement guard wants `ready: true`) and this module does not choose for them.
|
|
26
|
+
*
|
|
27
|
+
* @typedef {{ outcome: "ready", health: any } | { outcome: "busy", health: any }
|
|
28
|
+
* | { outcome: "not-ready", reason: string, health: any }
|
|
29
|
+
* | { outcome: "refused", message: string }
|
|
30
|
+
* | { outcome: "no-answer", message: string, timedOut: boolean }} Probe
|
|
31
|
+
*/
|
|
32
|
+
import { requestJson } from "./worker-http.mjs";
|
|
33
|
+
/** @typedef {typeof requestJson} ProbeRequest */
|
|
34
|
+
/**
|
|
35
|
+
* THE PER-PROBE TIMEOUT, WITH ITS READING. A probe that outlives it is UNKNOWN, never "down", so this number
|
|
36
|
+
* decides how much slowness a healthy box is allowed.
|
|
37
|
+
*
|
|
38
|
+
* All of it READ by others on the real fleet (`orchestrator`, #2671) and none measured by this row, whose
|
|
39
|
+
* engineer is barred from probing it:
|
|
40
|
+
* - the slowest HEALTHY box: 2.80 to 3.09 s on a11y-worker-13, -14 and -16, twelve others 0.53 to 0.76 s;
|
|
41
|
+
* - that is the FIRST answer after the box has been quiet more than 5 s, because the worker rebuilds its
|
|
42
|
+
* environment block with two synchronous `powershell.exe` calls when it is older than that, so a probe made
|
|
43
|
+
* by hand is nearly always the slow case;
|
|
44
|
+
* - a LOADED box (one that has just stopped a capture) can take up to about 10 s: each of the two calls is
|
|
45
|
+
* bounded at 5 s and they stop the worker's event loop for the whole time.
|
|
46
|
+
* 12 s is that loaded ceiling plus 2 s, the number `fleet-wake.mjs` `HEALTH_TIMEOUT_MS` states for the same
|
|
47
|
+
* reading. Before #2683 these entries used 5 s (`witness`, 1.91 s over the 3.09 s box and none over a loaded
|
|
48
|
+
* one), 8 s (`auth:leak-check`) and 10 s (`worker:compare`'s busy guard), read at `d119fb0f2`. Cost of the
|
|
49
|
+
* generosity: a box that really is off costs one 12 s wait before the message. A refusal costs nothing, it comes
|
|
50
|
+
* back at once.
|
|
51
|
+
*/
|
|
52
|
+
export const WORKER_PROBE_TIMEOUT_MS = 12_000;
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} worker the worker's base URL
|
|
55
|
+
* @param {{ timeoutMs?: number, request?: ProbeRequest }} [options]
|
|
56
|
+
* @returns {Promise<Probe>}
|
|
57
|
+
*/
|
|
58
|
+
export async function probeHealth(worker, { timeoutMs = WORKER_PROBE_TIMEOUT_MS, request = requestJson } = {}) {
|
|
59
|
+
let response;
|
|
60
|
+
try {
|
|
61
|
+
response = await request(`${worker.replace(/\/$/, "")}/health`, { timeoutMs });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const { code, message } = /** @type {NodeJS.ErrnoException} */ (error);
|
|
65
|
+
// `message` is EMPTY for a raw ECONNREFUSED on this Node version, so it falls back to the code.
|
|
66
|
+
if (code === "ECONNREFUSED")
|
|
67
|
+
return { outcome: "refused", message: message || code };
|
|
68
|
+
return { outcome: "no-answer", timedOut: code === "ETIMEDOUT",
|
|
69
|
+
message: `${code ? `${code}: ` : ""}${message}` };
|
|
70
|
+
}
|
|
71
|
+
const health = response.json;
|
|
72
|
+
if (!response.ok)
|
|
73
|
+
return { outcome: "not-ready", reason: `/health answered HTTP ${response.status}`, health };
|
|
74
|
+
if (health?.busy === true)
|
|
75
|
+
return { outcome: "busy", health };
|
|
76
|
+
if (health?.ready === true)
|
|
77
|
+
return { outcome: "ready", health };
|
|
78
|
+
return { outcome: "not-ready", health,
|
|
79
|
+
reason: response.json?.reason ?? "/health answered without `ready: true` and without a reason" };
|
|
80
|
+
}
|
|
81
|
+
/** How to wake a box from a checkout of this repo. The published entries do not do it themselves. */
|
|
82
|
+
export const WAKE_HINT = "If it is a fleet box that has gone to sleep, wake it from a checkout of the a11ign repo with "
|
|
83
|
+
+ "`npm run fleet:wake -- <name>` (<name> is its entry in inventory.yml).";
|
|
84
|
+
/**
|
|
85
|
+
* One sentence per outcome, in the words of what was OBSERVED. Only `refused`, `not-ready` and `busy` say the box is
|
|
86
|
+
* up, because only those are things the box itself said; a silent probe says "did not answer" and never "down".
|
|
87
|
+
*
|
|
88
|
+
* @param {Probe} probe
|
|
89
|
+
* @param {{ worker: string, timeoutMs?: number }} about
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
export function describeProbe(probe, { worker, timeoutMs = WORKER_PROBE_TIMEOUT_MS }) {
|
|
93
|
+
switch (probe.outcome) {
|
|
94
|
+
case "ready": return `${worker} answered and is ready.`;
|
|
95
|
+
case "busy": return `${worker} is up and busy with a capture.`;
|
|
96
|
+
case "not-ready": return `${worker} is up and answered, but it says it is not ready: ${probe.reason}.`;
|
|
97
|
+
case "refused": return `${worker} refused the connection (${probe.message}): the machine is up and the worker `
|
|
98
|
+
+ "is not listening. Waking it will not help; start the worker on it.";
|
|
99
|
+
case "no-answer": return `${worker} did not answer${probe.timedOut ? ` within ${timeoutMs / 1000} s` : ""} `
|
|
100
|
+
+ `(${probe.message}). That does not say it is off: it may be asleep, slow, or not reachable from here. ${WAKE_HINT}`;
|
|
101
|
+
default: throw new Error(`unknown probe outcome: ${JSON.stringify(probe)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=probe-outcome.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe-outcome.mjs","sourceRoot":"","sources":["../src/probe-outcome.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether a deploy may proceed.
|
|
3
|
+
*
|
|
4
|
+
* @param {{ local: number|string|null, served: {worker: string, protocol: number|string|null}[],
|
|
5
|
+
* allowed: boolean, source?: string }} input
|
|
6
|
+
* @returns {{ refuse: boolean, message: string }}
|
|
7
|
+
*/
|
|
8
|
+
export function protocolVerdict({ local, served, allowed, source }: {
|
|
9
|
+
local: number | string | null;
|
|
10
|
+
served: {
|
|
11
|
+
worker: string;
|
|
12
|
+
protocol: number | string | null;
|
|
13
|
+
}[];
|
|
14
|
+
allowed: boolean;
|
|
15
|
+
source?: string;
|
|
16
|
+
}): {
|
|
17
|
+
refuse: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Ask each worker which protocol it serves.
|
|
22
|
+
*
|
|
23
|
+
* A worker that cannot be reached reports `null` rather than being dropped, so the caller can tell "the
|
|
24
|
+
* fleet agrees" from "we could not ask" — the distinction the verdict above turns on.
|
|
25
|
+
*
|
|
26
|
+
* @param {string[]} urls
|
|
27
|
+
* @returns {Promise<{worker: string, protocol: number|string|null}[]>}
|
|
28
|
+
*/
|
|
29
|
+
export function servedProtocols(urls: string[]): Promise<{
|
|
30
|
+
worker: string;
|
|
31
|
+
protocol: number | string | null;
|
|
32
|
+
}[]>;
|
|
33
|
+
export const RECAPTURE_COST: string;
|
|
34
|
+
//# sourceMappingURL=protocol-guard.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-guard.d.mts","sourceRoot":"","sources":["../src/protocol-guard.mjs"],"names":[],"mappings":"AAqDA;;;;;;GAMG;AACH,oEAJW;IAAE,KAAK,EAAE,MAAM,GAAC,MAAM,GAAC,IAAI,CAAC;IAAC,MAAM,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAC,MAAM,GAAC,IAAI,CAAA;KAAC,EAAE,CAAC;IACpF,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAmChD;AAED;;;;;;;;GAQG;AACH,sCAHW,MAAM,EAAE,GACN,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAC,MAAM,GAAC,IAAI,CAAA;CAAC,EAAE,CAAC,CAqBrE;AA9ED,oCAG6G"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REFUSE A DEPLOY THAT WOULD SILENTLY INVALIDATE THE CORPUS.
|
|
3
|
+
*
|
|
4
|
+
* `CAPTURE_PROTOCOL_VERSION` is a capture-cache key. Shipping a change to it invalidates every cached
|
|
5
|
+
* capture — `RECAPTURE_COST` below says how many a full re-run produces — and that is sometimes exactly
|
|
6
|
+
* what you want. What must never happen is it going out without somebody deciding.
|
|
7
|
+
*
|
|
8
|
+
* **The guard existed and reached only the DEPRECATED path.** `deploy-worker.mjs` refuses without
|
|
9
|
+
* `--allow-protocol-change`, and that script is `utmctl file push` against a VM UUID: it cannot reach a
|
|
10
|
+
* bare-metal worker and fails immediately off macOS. Every box in `inventory.yml` is bare metal and
|
|
11
|
+
* deploys through `fleet:deploy`, which had no such check. So the only live deploy path was the unguarded
|
|
12
|
+
* one — this repo's signature defect (a remedy that reaches one call site when the behaviour reaches
|
|
13
|
+
* several) with the remedy on the path nobody uses.
|
|
14
|
+
*
|
|
15
|
+
* **This asks the FLEET, not git, and that is the stronger question.** The UTM guard compares the working
|
|
16
|
+
* tree against HEAD, which answers "did I commit the bump". Useful, and not the thing at risk: what
|
|
17
|
+
* decides whether the cache survives is whether the version about to ship differs from the one the boxes
|
|
18
|
+
* are *already serving*, because that is the version every cached capture was stamped under. Reading it
|
|
19
|
+
* from `/health` also means the check shares no failure mode with the action — the deploy goes over SSH,
|
|
20
|
+
* the verification over HTTP — which is this repo's rule after a hash-check that returned EMPTY whenever
|
|
21
|
+
* the channel it used was broken, and read as a flaky tool rather than a failed deploy.
|
|
22
|
+
*/
|
|
23
|
+
import { requestJson } from "./worker-http.mjs";
|
|
24
|
+
/**
|
|
25
|
+
* WHAT A FULL RECAPTURE COSTS, in the words the two deploy refusals print -- derived here and shown, not
|
|
26
|
+
* retyped (#2244). The refusals printed 2,122, a count from 2026-07-26; on 2026-09-23 the cache held 4,500
|
|
27
|
+
* captures across 1,715 cases.
|
|
28
|
+
*
|
|
29
|
+
* **The population is what a full RE-RUN produces, not what the cache holds.** A run captures each case
|
|
30
|
+
* `manifest.json` lists, twice; the cache holds 4,500 captures because it also holds cases the manifest no
|
|
31
|
+
* longer lists, and a protocol bump does not make those cost anything, since nothing will ask for them
|
|
32
|
+
* again. So 1,715 x 2 = 3,430 is the price an operator is deciding on, and 4,500 is not.
|
|
33
|
+
*
|
|
34
|
+
* The inputs are `orchestrator`'s reading on the corpus host, 2026-09-23T14:26Z (#1561 comment
|
|
35
|
+
* 5796655391), and are not re-measured here. This file cannot read the manifest -- the control host that
|
|
36
|
+
* runs the guard does not have the corpus -- so the reading is dated in the message itself, where a stale
|
|
37
|
+
* one is visible to the person who acts on it.
|
|
38
|
+
*
|
|
39
|
+
* **NO WALL-CLOCK.** This used to say "about four hours of fleet time". That was a rate times the 1,061-case
|
|
40
|
+
* corpus of 2026-07-26 on a fleet of a size nothing in this file can read, and #2155 dropped its own estimates for the same
|
|
41
|
+
* reason. The rate is what `npm run fleet:status` can show; the product is somebody's to time.
|
|
42
|
+
*/
|
|
43
|
+
const MANIFEST_CASES = 1715;
|
|
44
|
+
const CAPTURES_PER_CASE = 2;
|
|
45
|
+
export const RECAPTURE_COST = `${(MANIFEST_CASES * CAPTURES_PER_CASE).toLocaleString("en-US")} captures `
|
|
46
|
+
+ `(${MANIFEST_CASES.toLocaleString("en-US")} cases in manifest.json x ${CAPTURES_PER_CASE}, read `
|
|
47
|
+
+ "2026-09-23T14:26Z; how long that takes depends on the fleet, so time a run rather than trust a figure)";
|
|
48
|
+
/** How long a worker gets to answer `/health` before it counts as unreachable. */
|
|
49
|
+
const HEALTH_TIMEOUT_MS = 5_000;
|
|
50
|
+
/**
|
|
51
|
+
* Decide whether a deploy may proceed.
|
|
52
|
+
*
|
|
53
|
+
* @param {{ local: number|string|null, served: {worker: string, protocol: number|string|null}[],
|
|
54
|
+
* allowed: boolean, source?: string }} input
|
|
55
|
+
* @returns {{ refuse: boolean, message: string }}
|
|
56
|
+
*/
|
|
57
|
+
export function protocolVerdict({ local, served, allowed, source }) {
|
|
58
|
+
if (local === null || local === undefined) {
|
|
59
|
+
// NAMES THE FILE IT WAS GIVEN, never a hardcoded one. This message said "from capture-core.mjs" while
|
|
60
|
+
// the caller had long since been pointed elsewhere, so the one sentence a broken guard prints sent the
|
|
61
|
+
// reader to a file that was not the problem.
|
|
62
|
+
return { refuse: true, message: `cannot read CAPTURE_PROTOCOL_VERSION from ${source ?? "the worker "
|
|
63
|
+
+ "source"}, so this deploy cannot say whether it would invalidate the corpus. That is a broken `
|
|
64
|
+
+ "guard, not a clean one." };
|
|
65
|
+
}
|
|
66
|
+
const reachable = served.filter((s) => s.protocol !== null && s.protocol !== undefined);
|
|
67
|
+
// EXAMINED NOTHING IS NOT AGREEMENT. If no worker answered, the guard has no opinion — and a guard with
|
|
68
|
+
// no opinion that reports success is the `evidence:check` defect: it exited 0 saying "evidence unchanged"
|
|
69
|
+
// having compared 2 of 48, because its own guard covered only `compared === 0` and called that "the
|
|
70
|
+
// extreme case rather than a different one". Nothing here is the extreme case of that; it IS that.
|
|
71
|
+
if (reachable.length === 0) {
|
|
72
|
+
return { refuse: !allowed, message: "no worker answered /health, so nothing could say which "
|
|
73
|
+
+ `CAPTURE_PROTOCOL_VERSION the fleet is serving. Local is ${local}. Deploying blind could invalidate `
|
|
74
|
+
+ "every cached capture. Check `npm run fleet:status`, or pass --allow-protocol-change if you mean it." };
|
|
75
|
+
}
|
|
76
|
+
const differing = reachable.filter((s) => String(s.protocol) !== String(local));
|
|
77
|
+
if (differing.length === 0)
|
|
78
|
+
return { refuse: false, message: "" };
|
|
79
|
+
const detail = differing.map((s) => ` ${s.worker} serves ${s.protocol}`).join("\n");
|
|
80
|
+
if (allowed) {
|
|
81
|
+
return { refuse: false, message: `\n Deploying CAPTURE_PROTOCOL_VERSION ${local} as requested.\n${detail}\n`
|
|
82
|
+
+ " Every cached capture becomes invalid and the next run recaptures all of them.\n" };
|
|
83
|
+
}
|
|
84
|
+
return { refuse: true, message: `\nREFUSING TO DEPLOY: this checkout has CAPTURE_PROTOCOL_VERSION = ${local}, and the fleet does not.\n\n`
|
|
85
|
+
+ `${detail}\n\n`
|
|
86
|
+
+ "That value is a capture-cache key, so deploying it invalidates every cached capture and forces a\n"
|
|
87
|
+
+ `full recapture: ${RECAPTURE_COST}.\n\n`
|
|
88
|
+
+ " If that is what you want: npm run fleet:deploy -- --allow-protocol-change\n"
|
|
89
|
+
+ " If it is not: check what changed in packages/nvda-worker/src/capture-core.mjs\n" };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Ask each worker which protocol it serves.
|
|
93
|
+
*
|
|
94
|
+
* A worker that cannot be reached reports `null` rather than being dropped, so the caller can tell "the
|
|
95
|
+
* fleet agrees" from "we could not ask" — the distinction the verdict above turns on.
|
|
96
|
+
*
|
|
97
|
+
* @param {string[]} urls
|
|
98
|
+
* @returns {Promise<{worker: string, protocol: number|string|null}[]>}
|
|
99
|
+
*/
|
|
100
|
+
export async function servedProtocols(urls) {
|
|
101
|
+
return Promise.all(urls.map(async (url) => {
|
|
102
|
+
try {
|
|
103
|
+
// `requestJson`, not `fetch` -- the same worker JSON API this repo's own audit found duplicated with
|
|
104
|
+
// its own timeout mechanism at several other call sites (worker-http.mjs's own header), and this one
|
|
105
|
+
// is on the deploy-guard path: a swallowed truncation here would misreport a fleet mid-protocol-bump
|
|
106
|
+
// as unreachable rather than as differing, silently weakening the one check standing between a
|
|
107
|
+
// deploy and an unannounced cache wipe.
|
|
108
|
+
const response = await requestJson(`${url.replace(/\/$/, "")}/health`, { timeoutMs: HEALTH_TIMEOUT_MS });
|
|
109
|
+
// `?? null` and never `?? 0`: a worker predating the field has no opinion, and reading absence as a
|
|
110
|
+
// number is the defect this project pays for most often. `response.json` is `undefined` rather than
|
|
111
|
+
// a throw on unparseable JSON (requestJson's own contract), which `?.` already treats as "no opinion".
|
|
112
|
+
return { worker: url, protocol: response.json?.environment?.captureProtocol ?? null };
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Deliberately not rethrown: unreachable is a VERDICT here, handled by `protocolVerdict`, not an
|
|
116
|
+
// error. Swallowing it would be wrong only if nothing downstream distinguished it, and something does.
|
|
117
|
+
return { worker: url, protocol: null };
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=protocol-guard.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-guard.mjs","sourceRoot":"","sources":["../src/protocol-guard.mjs"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every non-test source file under `packages/`, as `[relativePath, source]`.
|
|
3
|
+
*
|
|
4
|
+
* @param {{ root?: string }} [options]
|
|
5
|
+
* @returns {Array<[string, string]>}
|
|
6
|
+
*/
|
|
7
|
+
export function sourceFiles({ root }?: {
|
|
8
|
+
root?: string;
|
|
9
|
+
}): Array<[string, string]>;
|
|
10
|
+
/** The `packages/` directory, resolved from this module rather than from the caller's cwd. */
|
|
11
|
+
export const PACKAGES: string;
|
|
12
|
+
//# sourceMappingURL=source-walk.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-walk.d.mts","sourceRoot":"","sources":["../src/source-walk.mjs"],"names":[],"mappings":"AAiCA;;;;;GAKG;AACH,uCAHW;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACf,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkBnC;AA3BD,8FAA8F;AAC9F,8BAAkF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* Every source file under `packages/`, so a guard can be written over what EXISTS rather than over a list.
|
|
4
|
+
*
|
|
5
|
+
* This repo's most expensive recurring shape is a check that only examines the places somebody already
|
|
6
|
+
* thought of. Two instances are recorded in `budget-ladder.test.ts` alone: the worker-file list that let a
|
|
7
|
+
* file deploy invisibly, and the ladder guard that read ONE hardcoded path and therefore could not see
|
|
8
|
+
* `capture-real-pages.mjs` declaring 300 s against a 520 s hard timeout — inverted, on the client that
|
|
9
|
+
* needed it most.
|
|
10
|
+
*
|
|
11
|
+
* Extracted here because there were about to be TWO copies of the walk: one for capture budgets and one for
|
|
12
|
+
* `--worker` validation. A duplicated discovery is a discovery that drifts, which is the same defect one
|
|
13
|
+
* level up.
|
|
14
|
+
*
|
|
15
|
+
* `dist` is excluded because it is build output of the very files being checked, so including it
|
|
16
|
+
* double-counts and reports a stale copy as a violation after the source has been fixed. Test files are
|
|
17
|
+
* excluded because a guard asserting on other guards' source is noise.
|
|
18
|
+
*
|
|
19
|
+
* Dot-directories are skipped, as `.gitignore` would, because this walk reads the FILESYSTEM and a test may
|
|
20
|
+
* plant a transient one inside the repository (`corpus-backup.test.ts` does, as `.corpus-backup-mutation-*`,
|
|
21
|
+
* so a copied script can resolve `node_modules`). Listing it and reading the file after the other test has
|
|
22
|
+
* removed it failed `pnpm run verify` with ENOENT (#3337, after #2876 and #1919). No tracked source lives in
|
|
23
|
+
* one, and a dot-directory is not a place a guard's population should come from.
|
|
24
|
+
*/
|
|
25
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
26
|
+
import { join, dirname } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
/** The `packages/` directory, resolved from this module rather than from the caller's cwd. */
|
|
29
|
+
export const PACKAGES = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
30
|
+
const SKIPPED_DIRECTORY = /^(node_modules|dist|\..*)$/;
|
|
31
|
+
/**
|
|
32
|
+
* Every non-test source file under `packages/`, as `[relativePath, source]`.
|
|
33
|
+
*
|
|
34
|
+
* @param {{ root?: string }} [options]
|
|
35
|
+
* @returns {Array<[string, string]>}
|
|
36
|
+
*/
|
|
37
|
+
export function sourceFiles({ root = PACKAGES } = {}) {
|
|
38
|
+
/** @type {Array<[string, string]>} */
|
|
39
|
+
const found = [];
|
|
40
|
+
/** @param {string} dir */
|
|
41
|
+
const walk = (dir) => {
|
|
42
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
43
|
+
const path = join(dir, entry.name);
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
if (!SKIPPED_DIRECTORY.test(entry.name))
|
|
46
|
+
walk(path);
|
|
47
|
+
}
|
|
48
|
+
else if (/\.(mjs|ts)$/.test(entry.name) && !entry.name.includes(".test.")) {
|
|
49
|
+
found.push([path.slice(root.length + 1), readFileSync(path, "utf8")]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
walk(root);
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=source-walk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-walk.mjs","sourceRoot":"","sources":["../src/source-walk.mjs"],"names":[],"mappings":"AAAA,YAAY;AACZ;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,8FAA8F;AAC9F,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAElF,MAAM,iBAAiB,GAAG,4BAA4B,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,IAAI,GAAG,QAAQ,EAAE,GAAG,EAAE;IAClD,sCAAsC;IACtC,MAAM,KAAK,GAAG,EAAE,CAAC;IACjB,0BAA0B;IAC1B,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,EAAE;QACnB,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,CAAC;iBAAM,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5E,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,CAAC;IACX,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transient-fault.d.mts","sourceRoot":"","sources":["../src/transient-fault.mjs"],"names":[],"mappings":"AAsEA;;;GAGG;AACH,mCAHW,OAAO,GACL,OAAO,CAanB"}
|