@a11ign/screenreader-fleet 0.5.2 → 0.6.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 (49) hide show
  1. package/README.md +37 -0
  2. package/dist/{capture-client.d.mts → capture-client.d.ts} +2 -2
  3. package/dist/{check-worker-code.d.mts → check-worker-code.d.ts} +2 -3
  4. package/dist/check-worker-code.mjs +1 -1
  5. package/dist/{cli-flags.d.mts → cli-flags.d.ts} +5 -5
  6. package/dist/{code-drift.d.mts → code-drift.d.ts} +7 -7
  7. package/dist/{command-line-census.d.mts → command-line-census.d.ts} +3 -3
  8. package/dist/{compare-workers.d.mts → compare-workers.d.ts} +1 -1
  9. package/dist/{control-plane-isolation.d.mts → control-plane-isolation.d.ts} +1 -1
  10. package/dist/{doctor.d.mts → doctor.d.ts} +57 -31
  11. package/dist/doctor.mjs +4 -4
  12. package/dist/{fleet-consistency.d.mts → fleet-consistency.d.ts} +50 -85
  13. package/dist/fleet-consistency.mjs +4 -0
  14. package/dist/{fleet-env.d.mts → fleet-env.d.ts} +45 -45
  15. package/dist/{fleet-scripts.d.mts → fleet-scripts.d.ts} +1 -1
  16. package/dist/{git-safe-env.d.mts → git-safe-env.d.ts} +3 -3
  17. package/dist/{guest-run.d.mts → guest-run.d.ts} +5 -5
  18. package/dist/{host-address.d.mts → host-address.d.ts} +4 -4
  19. package/dist/{host-capacity.d.mts → host-capacity.d.ts} +5 -5
  20. package/dist/{host-metrics.d.mts → host-metrics.d.ts} +23 -15
  21. package/dist/index.mjs +1 -1
  22. package/dist/{measure-guard.d.mts → measure-guard.d.ts} +14 -14
  23. package/dist/{npm-cli-executable.d.mts → npm-cli-executable.d.ts} +4 -4
  24. package/dist/{probe-outcome.d.mts → probe-outcome.d.ts} +43 -44
  25. package/dist/{protocol-guard.d.mts → protocol-guard.d.ts} +3 -3
  26. package/dist/source-walk.d.ts +11 -0
  27. package/dist/{transient-fault.d.mts → transient-fault.d.ts} +1 -1
  28. package/dist/tsx-import.d.ts +6 -0
  29. package/dist/{utm-deprecated.d.mts → utm-deprecated.d.ts} +1 -1
  30. package/dist/worker-code-check.d.ts +95 -0
  31. package/dist/worker-code-check.mjs +1 -1
  32. package/dist/{worker-health.d.mts → worker-health.d.ts} +2 -2
  33. package/dist/{worker-http.d.mts → worker-http.d.ts} +51 -51
  34. package/dist/{worker-stats.d.mts → worker-stats.d.ts} +40 -20
  35. package/package.json +30 -19
  36. package/src/local-worker/build-vm.sh +2 -2
  37. package/src/local-worker/clone-worker.sh +1 -1
  38. package/src/local-worker/create-utm-vm.sh +1 -1
  39. package/src/local-worker/fetch-windows-iso.sh +5 -5
  40. package/src/local-worker/worker-ctl.sh +12 -12
  41. package/src/provisioning/apply-foreground-lock-timeout.ps1 +1 -1
  42. package/src/provisioning/bootstrap-windows-worker.ps1 +1 -1
  43. package/src/provisioning/diagnose-nvda-worker.ps1 +1 -1
  44. package/src/provisioning/stamp-provision-revision.ps1 +3 -3
  45. package/dist/source-walk.d.mts +0 -11
  46. package/dist/worker-code-check.d.mts +0 -54
  47. /package/dist/{normalise-fleet.d.mts → normalise-fleet.d.ts} +0 -0
  48. /package/dist/{src_fleet-scripts_mjs.mjs → src_fleet-scripts_ts.mjs} +0 -0
  49. /package/dist/{src_git-safe-env_mjs.mjs → src_git-safe-env_ts.mjs} +0 -0
package/README.md CHANGED
@@ -93,3 +93,40 @@ the only real lifecycle — a cold boot to ready is 15–45 s, which is fine.
93
93
 
94
94
  Not exported: `host-metrics`, `worker-stats`, `fleet-consistency`. They are measurement internals whose shapes
95
95
  change every time something new gets measured.
96
+
97
+ ## Working here
98
+
99
+ This repository is the package: `package.json`, `src/` and this README sit at the root, and the README is the npm page. It moved here from
100
+ [`a11ign/a11ign`](https://github.com/a11ign/a11ign) with its history. It depends on
101
+ [`@a11ign/screenreader-worker`](https://github.com/a11ign/screenreader-worker) and `@a11ign/judge` **by name, from the registry**.
102
+
103
+ **The fleet drives workers that have no authentication.** Anyone who can reach a worker's port can drive the browser and the screen
104
+ reader on that machine (`SECURITY.md` in `a11ign/a11ign` says what else somebody must know first). Run it only on a network you control.
105
+
106
+ ```bash
107
+ pnpm install --frozen-lockfile
108
+ pnpm test # what the `gate` check runs on every pull request and merge-queue entry, with lint, typecheck and layout-check
109
+ ```
110
+
111
+ `main` takes pull requests only, each with one approving review, through the merge queue. `pnpm exec layout-check` is the repository-layout
112
+ check from `@a11ign/toolchain`: it fails a workspace of one package, a directory not named for its package, a second README and a leftover
113
+ `lerna.json`.
114
+
115
+ ### What did not come across: 27 tests
116
+
117
+ The package's test directory was written for the monorepo, and 27 of its 53 test files read something that is not in this repository:
118
+ `packages/control`'s playbooks and inventories, `packages/lab`'s capture clients, the private `guards` package, or the root's
119
+ `scripts/`. Run here, they fail on a file they cannot find, so they were **not** carried into this repository; they stay in
120
+ `a11ign/a11ign`, beside what they read, until its row #3504 relocates them. Their names are the `COUPLED` list in
121
+ `packages/lab/src/packaging/screenreader-fleet-extraction.test.ts` there. The other 26 run here and are what `gate` runs, with
122
+ `scripts/package-boundary.test.ts` holding the package to its own directory.
123
+
124
+ ### Releasing
125
+
126
+ This repository releases on its own, not with `a11ign/a11ign`. A change that should reach npm carries a changeset
127
+ (`pnpm exec changeset`), and **merging it to `main` is the release**: `.github/workflows/release.yml` calls the one
128
+ reusable per-merge workflow in `a11ign/toolchain`, which versions the merge on a detached commit, publishes, tags and
129
+ releases it. There is no version pull request and nothing is written to `main`, so its `package.json` version and
130
+ `CHANGELOG.md` lag the last tag (`.changeset/README.md`). The publish uses npm trusted publishing over OIDC with
131
+ provenance and no stored token. The first release, 0.1.0, was published before this workflow existed;
132
+ no git tag names it, so the first per-merge release bases on `main`'s 0.1.0.
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * @param {string} worker @param {string} captureId
15
15
  */
16
- export function recoverCapture(worker: string, captureId: string): Promise<{
16
+ export declare function recoverCapture(worker: string, captureId: string): Promise<{
17
17
  status: number;
18
18
  ok: boolean;
19
19
  text: string;
@@ -38,7 +38,7 @@ export function recoverCapture(worker: string, captureId: string): Promise<{
38
38
  * beforeRecovery?: (error: unknown) => Promise<void>,
39
39
  * onProgress?: (progress: object) => void, sync?: boolean }} request
40
40
  */
41
- export function captureTolerantly({ worker, body, timeoutMs, beforeRecovery, onProgress, sync }: {
41
+ export declare function captureTolerantly({ worker, body, timeoutMs, beforeRecovery, onProgress, sync }: {
42
42
  worker: string;
43
43
  body: object;
44
44
  timeoutMs?: number;
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { configuredWorkers, inventoryWorkerUrls } from "./fleet-env.js";
2
3
  /**
3
4
  * Which workers to ask, AND WHERE THAT LIST CAME FROM — the second half is not decoration.
4
5
  *
@@ -18,7 +19,7 @@
18
19
  * Reading it here is safe in a way it would not be for a capture run: this probes `/health` and starts
19
20
  * nothing, so the rule that naming workers means you are managing them does not apply.
20
21
  */
21
- export function workerUrls({ named, local, inventory, }?: {
22
+ export declare function workerUrls({ named, local, inventory, }?: {
22
23
  named?: typeof configuredWorkers | undefined;
23
24
  local?: typeof localPoolUrls | undefined;
24
25
  inventory?: typeof inventoryWorkerUrls | undefined;
@@ -26,8 +27,6 @@ export function workerUrls({ named, local, inventory, }?: {
26
27
  urls: string[];
27
28
  source: string;
28
29
  };
29
- import { configuredWorkers } from "./fleet-env.mjs";
30
30
  /** The local UTM pool, or none — `utmctl` is absent on a machine that never had one. */
31
31
  declare function localPoolUrls(): any;
32
- import { inventoryWorkerUrls } from "./fleet-env.mjs";
33
32
  export {};
@@ -3,7 +3,7 @@ import { realpathSync } from "node:fs";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { execFileSync } from "node:child_process";
5
5
  import { errorText } from "@a11ign/screenreader-worker/error-text";
6
- import { fleetScriptPaths } from "./src_fleet-scripts_mjs.mjs";
6
+ import { fleetScriptPaths } from "./src_fleet-scripts_ts.mjs";
7
7
  import { inventoryWorkerUrls, configuredWorkers, resolveWorkerPool } from "./fleet-env.mjs";
8
8
  import { remedyLines, codeDrift, resolveExpectedWorkerCode } from "./worker-code-check.mjs";
9
9
  import { refuseUnknownFlags } from "./cli-flags.mjs";
@@ -2,7 +2,7 @@
2
2
  * The flag name alone: `--shard=0/4` and `--shard` both name `--shard`.
3
3
  * @param {string} argument @returns {string}
4
4
  */
5
- export function nameOf(argument: string): string;
5
+ export declare function nameOf(argument: string): string;
6
6
  /**
7
7
  * `--name=value`'s value, or `undefined` if `--name=` was never passed. audit §9's "argv parsing" row:
8
8
  * VALIDATION is owned here (`refuseUnknownFlags`), but 15+ files each hand-rolled this exact three-line
@@ -21,7 +21,7 @@ export function nameOf(argument: string): string;
21
21
  * @param {readonly string[]} argv @param {string} name
22
22
  * @returns {string | undefined}
23
23
  */
24
- export function flagValue(argv: readonly string[], name: string): string | undefined;
24
+ export declare function flagValue(argv: readonly string[], name: string): string | undefined;
25
25
  /**
26
26
  * Which of `argv` are flags this command does not know. PURE, so it is testable without a process.
27
27
  *
@@ -45,12 +45,12 @@ export function flagValue(argv: readonly string[], name: string): string | undef
45
45
  /**
46
46
  * @param {string[]} argv @param {string[]} known @returns {string[]}
47
47
  */
48
- export function unknownFlags(argv: string[], known: string[]): string[];
48
+ export declare function unknownFlags(argv: string[], known: string[]): string[];
49
49
  /**
50
50
  * The closest known flag, when there is one close enough to be a likely typo rather than a guess.
51
51
  * @param {string} flag @param {string[]} known @returns {string | undefined}
52
52
  */
53
- export function didYouMean(flag: string, known: string[]): string | undefined;
53
+ export declare function didYouMean(flag: string, known: string[]): string | undefined;
54
54
  /**
55
55
  * Refuse, naming the flag and what this command does take. Exits 2; does not return on failure.
56
56
  *
@@ -63,7 +63,7 @@ export function didYouMean(flag: string, known: string[]): string | undefined;
63
63
  * `entry` is the caller's `import.meta.url`, and it is REQUIRED. `command` names the thing a HUMAN
64
64
  * typed — the npm script, not the file — because that is what they will retype.
65
65
  */
66
- export function refuseUnknownFlags(known: string[], { entry, argv, command }: {
66
+ export declare function refuseUnknownFlags(known: string[], { entry, argv, command }: {
67
67
  entry: string;
68
68
  argv?: string[];
69
69
  command?: string;
@@ -16,7 +16,7 @@
16
16
  * @returns {{expected: string, stale: Array<{worker: string, serving: string}>, unreachable: string[],
17
17
  * answered: number}}
18
18
  */
19
- export function codeDrift(expected: string, readings: Array<{
19
+ export declare function codeDrift(expected: string, readings: Array<{
20
20
  worker: string;
21
21
  code: string | null | undefined;
22
22
  }>): {
@@ -49,7 +49,7 @@ export function codeDrift(expected: string, readings: Array<{
49
49
  * @param {string[]} bareMetalUrls
50
50
  * @returns {string[]}
51
51
  */
52
- export function remedyLines(staleUrls: string[], bareMetalUrls: string[]): string[];
52
+ export declare function remedyLines(staleUrls: string[], bareMetalUrls: string[]): string[];
53
53
  /**
54
54
  * The refusal text, or `null` when the fleet is running this checkout. PURE.
55
55
  *
@@ -61,7 +61,7 @@ export function remedyLines(staleUrls: string[], bareMetalUrls: string[]): strin
61
61
  * @param {{when?: string, bareMetalUrls?: string[], sourceDirty?: string}} options
62
62
  * @returns {string|null}
63
63
  */
64
- export function describeCodeDrift(drift: {
64
+ export declare function describeCodeDrift(drift: {
65
65
  expected: string;
66
66
  stale: Array<{
67
67
  worker: string;
@@ -76,7 +76,7 @@ export function describeCodeDrift(drift: {
76
76
  }): string | null;
77
77
  /** What a single worker is serving, or `null` when it did not answer. */
78
78
  /** @param {string} url */
79
- export function readWorkerCode(url: string): Promise<any>;
79
+ export declare function readWorkerCode(url: string): Promise<any>;
80
80
  /**
81
81
  * Is the worker source in `sourceDir` modified against HEAD?
82
82
  *
@@ -89,7 +89,7 @@ export function readWorkerCode(url: string): Promise<any>;
89
89
  *
90
90
  * @param {string} sourceDir
91
91
  */
92
- export function workerSourceDirty(sourceDir: string): string;
92
+ export declare function workerSourceDirty(sourceDir: string): string;
93
93
  /**
94
94
  * AN EMPTY POOL IS NOT A CLEAN FLEET — the refusal, as a value, so it can be tested without exiting.
95
95
  *
@@ -110,7 +110,7 @@ export function workerSourceDirty(sourceDir: string): string;
110
110
  * @param {string} expected
111
111
  * @returns {string | null} the refusal, or null when there is a pool to check
112
112
  */
113
- export function describeEmptyPool(workers: string[] | undefined, expected: string): string | null;
113
+ export declare function describeEmptyPool(workers: string[] | undefined, expected: string): string | null;
114
114
  /**
115
115
  * Refuse to run against a fleet that is not serving `expected` — the shared body of
116
116
  * `assertFleetRunsThisCheckout`, taking the hash as a parameter rather than computing it.
@@ -131,7 +131,7 @@ export function describeEmptyPool(workers: string[] | undefined, expected: strin
131
131
  *
132
132
  * @param {{when?: string, allow?: boolean, read?: (url: string) => Promise<string|null>, bareMetalUrls?: string[], sourceDir: string}} options
133
133
  */
134
- export function assertWorkersServe(expected: string, workers: string[], options: {
134
+ export declare function assertWorkersServe(expected: string, workers: string[], options: {
135
135
  when?: string;
136
136
  allow?: boolean;
137
137
  read?: (url: string) => Promise<string | null>;
@@ -5,7 +5,7 @@
5
5
  * @param {string} repoRoot absolute path to the repository root
6
6
  * @returns {string[]} repo-relative paths, unsorted (discovery order)
7
7
  */
8
- export function allMjsFiles(repoRoot: string): string[];
8
+ export declare function allMjsFiles(repoRoot: string): string[];
9
9
  /**
10
10
  * Does this file read argv? Comments stripped first, and that is not a nicety: matching raw source
11
11
  * classified a module as a CLI merely because a COMMENT mentioned `process.argv` — measured on
@@ -20,7 +20,7 @@ export function allMjsFiles(repoRoot: string): string[];
20
20
  * @param {string} repoRoot absolute path to the repository root
21
21
  * @returns {boolean}
22
22
  */
23
- export function readsArgv(rel: string, repoRoot: string): boolean;
23
+ export declare function readsArgv(rel: string, repoRoot: string): boolean;
24
24
  /**
25
25
  * `allMjsFiles()` filtered to the ones that read argv — the population `cli-flags.test.ts`'s flag-guard
26
26
  * census classifies. NOT the same question as "is this a runnable command with an entry-point guard";
@@ -29,4 +29,4 @@ export function readsArgv(rel: string, repoRoot: string): boolean;
29
29
  * @param {string} repoRoot absolute path to the repository root
30
30
  * @returns {string[]}
31
31
  */
32
- export function commandLineModules(repoRoot: string): string[];
32
+ export declare function commandLineModules(repoRoot: string): string[];
@@ -12,7 +12,7 @@
12
12
  * @param {{ argv?: readonly string[], baseDir?: string }} [options]
13
13
  * @returns {string}
14
14
  */
15
- export function outDirFor({ argv, baseDir }?: {
15
+ export declare function outDirFor({ argv, baseDir }?: {
16
16
  argv?: readonly string[];
17
17
  baseDir?: string;
18
18
  }): string;
@@ -33,7 +33,7 @@
33
33
  * @param {{ hasNodeModules: boolean, hasFleetKey: boolean, packages?: number, isWorkspace?: boolean }} found
34
34
  * @returns {{ violated: boolean, why: string }}
35
35
  */
36
- export function controlPlaneIsolation({ hasNodeModules, hasFleetKey, packages, isWorkspace }: {
36
+ export declare function controlPlaneIsolation({ hasNodeModules, hasFleetKey, packages, isWorkspace }: {
37
37
  hasNodeModules: boolean;
38
38
  hasFleetKey: boolean;
39
39
  packages?: number;
@@ -12,10 +12,47 @@
12
12
  * @param {{ argv?: readonly string[], baseDir?: string }} [options]
13
13
  * @returns {string}
14
14
  */
15
- export function runsDirFor({ argv, baseDir }?: {
15
+ export declare function runsDirFor({ argv, baseDir }?: {
16
16
  argv?: readonly string[];
17
17
  baseDir?: string;
18
18
  }): string;
19
+ /** What the checks have recorded so far, for a test that drives one check through the real recorder. */
20
+ export declare const recordedChecks: () => {
21
+ name: string;
22
+ id: string;
23
+ ok: boolean;
24
+ detail: string;
25
+ fix: string | null;
26
+ note?: string | null;
27
+ advisory?: boolean;
28
+ }[];
29
+ /**
30
+ * One check's verdict, and its REMEDY. Every parameter is typed here rather than inferred, because
31
+ * `remedy` defaulting to `null` infers as exactly `null` -- so the argument that matters most, what a
32
+ * reader is to do about a failed check, was the one the compiler refused.
33
+ *
34
+ * `id` is always `name` -- #256 is the first check queried individually by `--json`
35
+ * (`.checks[] | select(.id=="dist-freshness")`), and every check already has a unique `name`, so a
36
+ * separate parameter here would be a second spelling of the same fact rather than a new one.
37
+ *
38
+ * #1059: `fix` IS A COMMAND OR IT IS `null`, and human advice goes in `note`.
39
+ *
40
+ * CLAUDE.md tells an agent to read `next_command` and do that, and `next_command` is `fix`. A sentence
41
+ * there ("unlock the Mac if it is locked, then re-run …") is not something anything can run, so an
42
+ * automated reader loops. The advice is not lost -- it moves to the field nothing executes.
43
+ * ONE `remedy` ARGUMENT, not two: a bare string stays a command (which is what every existing call site
44
+ * passes), and the object form carries the advice beside it. Four parameters is the house limit and
45
+ * bundling cohesive arguments is the house answer to it.
46
+ * @param {string} name
47
+ * @param {boolean} ok
48
+ * @param {string} detail
49
+ * @param {string|null|{fix: string|null, note?: string|null}} [remedy] a runnable command, `null` when
50
+ * none can be constructed, or `{ fix, note }` when there is advice a shell cannot run
51
+ */
52
+ export declare const addCheck: (name: string, ok: boolean, detail: string, remedy?: string | null | {
53
+ fix: string | null;
54
+ note?: string | null;
55
+ }) => void;
19
56
  /**
20
57
  * The remedy for a failed local-pool query, as a runnable command plus the advice that is not one.
21
58
  *
@@ -27,7 +64,7 @@ export function runsDirFor({ argv, baseDir }?: {
27
64
  * @param {string} observed
28
65
  * @returns {{ fix: string|null, note: string|null }}
29
66
  */
30
- export function workerControlFix(observed: string): {
67
+ export declare function workerControlFix(observed: string): {
31
68
  fix: string | null;
32
69
  note: string | null;
33
70
  };
@@ -64,10 +101,10 @@ export function workerControlFix(observed: string): {
64
101
  * and telling four of those five to mark themselves would be the #198 defect wearing an advisory's
65
102
  * clothes. It states what is true and names the command; the operator decides.
66
103
  */
67
- export function checkPrimaryCheckoutMark({ baseDir }?: {
104
+ export declare function checkPrimaryCheckoutMark({ baseDir }?: {
68
105
  baseDir?: string | undefined;
69
106
  }): void;
70
- export function checkControlPlaneIsolation({ baseDir }?: {
107
+ export declare function checkControlPlaneIsolation({ baseDir }?: {
71
108
  baseDir?: string | undefined;
72
109
  }): void;
73
110
  /**
@@ -80,7 +117,7 @@ export function checkControlPlaneIsolation({ baseDir }?: {
80
117
  * @param {string} thisCheckoutRootReal
81
118
  * @returns {boolean}
82
119
  */
83
- export function resolvesToThisCheckout(resolvedRealPath: string, thisCheckoutRootReal: string): boolean;
120
+ export declare function resolvesToThisCheckout(resolvedRealPath: string, thisCheckoutRootReal: string): boolean;
84
121
  /**
85
122
  * Pure: which checkout root does a resolved `packages/<name>/dist/...` path belong to? Everything before
86
123
  * the first `/packages/` -- this repo's own, fixed layout, not a guess. `null` when the path does not look
@@ -89,7 +126,7 @@ export function resolvesToThisCheckout(resolvedRealPath: string, thisCheckoutRoo
89
126
  * @param {string} resolvedRealPath
90
127
  * @returns {string | null}
91
128
  */
92
- export function checkoutRootFor(resolvedRealPath: string): string | null;
129
+ export declare function checkoutRootFor(resolvedRealPath: string): string | null;
93
130
  /**
94
131
  * The `exports` targets of `packageDir` that are NOT on disk, or `null` when the package's manifest could not be
95
132
  * read or declares none -- "could not tell" and "missing" need opposite responses (investigate vs. rebuild), and a
@@ -106,7 +143,7 @@ export function checkoutRootFor(resolvedRealPath: string): string | null;
106
143
  * @param {string} packageDir
107
144
  * @returns {string[] | null}
108
145
  */
109
- export function missingExportTargets(packageDir: string): string[] | null;
146
+ export declare function missingExportTargets(packageDir: string): string[] | null;
110
147
  /**
111
148
  * WHOSE dist a cross-package import actually resolves to, and is IT stale (#256) -- both computed from
112
149
  * the exact SPECIFIER a real import site in this repo uses, never the bare package name. CLAUDE.md's own
@@ -120,7 +157,7 @@ export function missingExportTargets(packageDir: string): string[] | null;
120
157
  * environmental fact would be ignored, which is how a guard gets switched off. It is reported every run
121
158
  * so a stale answer is a known condition, not a silent one.
122
159
  */
123
- export function checkCrossPackageDist({ baseDir }?: {
160
+ export declare function checkCrossPackageDist({ baseDir }?: {
124
161
  baseDir?: string | undefined;
125
162
  }): number | void;
126
163
  /**
@@ -135,10 +172,10 @@ export function checkCrossPackageDist({ baseDir }?: {
135
172
  * @param {{ from?: string }} [options] a path or file URL to resolve `@a11ign/judge` from
136
173
  * @returns {Promise<string>}
137
174
  */
138
- export function scorerWeightsFor({ from }?: {
175
+ export declare function scorerWeightsFor({ from }?: {
139
176
  from?: string;
140
177
  }): Promise<string>;
141
- export function checkJudge({ from }?: {
178
+ export declare function checkJudge({ from }?: {
142
179
  from?: string | undefined;
143
180
  }): Promise<void>;
144
181
  /**
@@ -184,7 +221,7 @@ export function checkJudge({ from }?: {
184
221
  * coverage?: { field: string, reported: number, asked: number }[] } }} input
185
222
  * @returns {string}
186
223
  */
187
- export function fleetAgreementLine({ agreeing, configured, fields }: {
224
+ export declare function fleetAgreementLine({ agreeing, configured, fields }: {
188
225
  agreeing: number;
189
226
  configured: number;
190
227
  fields: {
@@ -213,7 +250,7 @@ export function fleetAgreementLine({ agreeing, configured, fields }: {
213
250
  * @param {{ok: boolean, fix: string|null}[]} [checkList]
214
251
  * @returns {string|null}
215
252
  */
216
- export function nextCommand(checkList?: {
253
+ export declare function nextCommand(checkList?: {
217
254
  ok: boolean;
218
255
  fix: string | null;
219
256
  }[]): string | null;
@@ -227,7 +264,7 @@ export function nextCommand(checkList?: {
227
264
  * @param {string|null} line
228
265
  * @returns {boolean}
229
266
  */
230
- export function isRunnableCommand(line: string | null): boolean;
267
+ export declare function isRunnableCommand(line: string | null): boolean;
231
268
  /**
232
269
  * Only when RUN, never on import.
233
270
  *
@@ -246,10 +283,14 @@ export function isRunnableCommand(line: string | null): boolean;
246
283
  * @param {{name: string, ok: boolean}[]} checkList
247
284
  * @returns {boolean}
248
285
  */
249
- export function readyFrom(checkList: {
286
+ export declare function readyFrom(checkList: {
250
287
  name: string;
251
288
  ok: boolean;
252
289
  }[]): boolean;
290
+ /** Every DECLARED check name, gating or not -- so a test can compare the two sets without a literal. */
291
+ export declare const allChecks: () => string[];
292
+ /** Which checks decide `ready`, for a test that must not retype the list. */
293
+ export declare const gatingChecks: () => string[];
253
294
  /**
254
295
  * #1082: A `--json` RUN THAT CANNOT PRODUCE JSON STILL PRODUCES JSON.
255
296
  *
@@ -271,7 +312,7 @@ export function readyFrom(checkList: {
271
312
  * @param {unknown} error
272
313
  * @returns {{ready: false, error: string, checks: never[]}}
273
314
  */
274
- export function errorDocument(error: unknown): {
315
+ export declare function errorDocument(error: unknown): {
275
316
  ready: false;
276
317
  error: string;
277
318
  checks: never[];
@@ -283,25 +324,10 @@ export function errorDocument(error: unknown): {
283
324
  * err?: (line: string) => void, runsDir?: () => string }} [deps]
284
325
  * @returns {Promise<number>}
285
326
  */
286
- export function doctorRun(deps?: {
327
+ export declare function doctorRun(deps?: {
287
328
  steps?: (() => unknown)[];
288
329
  json?: boolean;
289
330
  out?: (line: string) => void;
290
331
  err?: (line: string) => void;
291
332
  runsDir?: () => string;
292
333
  }): Promise<number>;
293
- export function recordedChecks(): {
294
- name: string;
295
- id: string;
296
- ok: boolean;
297
- detail: string;
298
- fix: string | null;
299
- note?: string | null;
300
- advisory?: boolean;
301
- }[];
302
- export function addCheck(name: string, ok: boolean, detail: string, remedy?: string | null | {
303
- fix: string | null;
304
- note?: string | null;
305
- }): void;
306
- export function allChecks(): string[];
307
- export function gatingChecks(): string[];
package/dist/doctor.mjs CHANGED
@@ -2,17 +2,17 @@
2
2
  import { execFile, execFileSync } from "node:child_process";
3
3
  import { promisify } from "node:util";
4
4
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
5
- import { resolve } from "node:path";
5
+ import { join, resolve } from "node:path";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
7
  import { homedir } from "node:os";
8
8
  import { createRequire } from "node:module";
9
9
  import { availableHostMemoryMb, workersHostCanRun } from "./host-capacity.mjs";
10
10
  import { fleetConsistency, describeMismatches } from "./fleet-consistency.mjs";
11
- import { fleetScriptPaths } from "./src_fleet-scripts_mjs.mjs";
11
+ import { fleetScriptPaths } from "./src_fleet-scripts_ts.mjs";
12
12
  import { namedInventoryWorkers, configuredWorkers } from "./fleet-env.mjs";
13
13
  import { refuseUnknownFlags, flagValue } from "./cli-flags.mjs";
14
14
  import { requestJson } from "./worker-http.mjs";
15
- import { sandboxGitEnv } from "./src_git-safe-env_mjs.mjs";
15
+ import { sandboxGitEnv } from "./src_git-safe-env_ts.mjs";
16
16
  import { assessWorker } from "./worker-health.mjs";
17
17
  function controlPlaneIsolation({ hasNodeModules, hasFleetKey, packages, isWorkspace = false }) {
18
18
  if (!hasNodeModules || !hasFleetKey) return {
@@ -361,7 +361,7 @@ async function checkDegradedWorkers(probed) {
361
361
  for (const w of probed){
362
362
  if (!w.health) continue;
363
363
  const { degraded, reason } = assessWorker(w.health.vitals);
364
- if (degraded) add(`worker ${w.name}`, true, `DEGRADED — ${reason}`, `re-provision ${w.name}: packages/worker-fleet/src/provisioning/provision-nvda-worker.ps1, elevated, in the interactive session`);
364
+ if (degraded) add(`worker ${w.name}`, true, `DEGRADED — ${reason}`, `re-provision ${w.name}: ${join(fleetScriptPaths().provisioning, "provision-nvda-worker.ps1")}, elevated, in the interactive session`);
365
365
  }
366
366
  }
367
367
  function checkFleetConsistency(probed, configured) {
@@ -1,42 +1,3 @@
1
- /**
2
- * Compare guests field by field.
3
- *
4
- * @param {Guest[]} guests
5
- * @returns {{consistent: boolean, mismatches: Mismatch[], compared: number, fields: FieldCoverage,
6
- * reportedOnly: ReportedDrift[]}}
7
- * `compared` is how many guests the verdict is actually ABOUT -- #920. A guest with no `environment`
8
- * and no `policy` is dropped below before comparing, so it is not the length of what was passed in,
9
- * and a caller that reports `consistent` without it is stating agreement over a set it cannot name.
10
- * `fields` is that same question one axis over: WHICH fields the verdict is about -- #1997.
11
- * `reportedOnly` is the third channel (#2063): compared, named, and part of NEITHER of the two above,
12
- * which is what makes it something to report rather than something to refuse.
13
- */
14
- export function fleetConsistency(guests: Guest[]): {
15
- consistent: boolean;
16
- mismatches: Mismatch[];
17
- compared: number;
18
- fields: FieldCoverage;
19
- reportedOnly: ReportedDrift[];
20
- };
21
- /**
22
- * One line per reported-only field that has something to say, in `describeMismatches`'s own shape.
23
- *
24
- * SEPARATE FROM `describeMismatches` rather than a flag on it, because the two lines make different
25
- * claims and a reader has to be able to tell them apart at a glance: that one says the fleet is not
26
- * usable for a capture run, this one says the fleet is not identical and the run may proceed anyway.
27
- * Folding them would put the ruling's own distinction behind a boolean argument.
28
- *
29
- * @param {ReportedDrift[]} drifts
30
- * @returns {string[]}
31
- */
32
- export function describeReportedOnly(drifts: ReportedDrift[]): string[];
33
- /**
34
- * One line per mismatch, naming the guests, so the report is actionable rather than just alarming.
35
- *
36
- * @param {Mismatch[]} mismatches
37
- * @returns {string[]}
38
- */
39
- export function describeMismatches(mismatches: Mismatch[]): string[];
40
1
  /**
41
2
  * Are the guests actually interchangeable?
42
3
  *
@@ -65,7 +26,7 @@ export function describeMismatches(mismatches: Mismatch[]): string[];
65
26
  * `workerCode` is absent on purpose: it changes when a comment changes, and the deploy tooling already
66
27
  * verifies it against the checkout. Flagging it here would cry wolf on every reworded line.
67
28
  */
68
- export const MUST_MATCH: {
29
+ export declare const MUST_MATCH: {
69
30
  path: string;
70
31
  why: string;
71
32
  }[];
@@ -98,7 +59,7 @@ export const MUST_MATCH: {
98
59
  * until the worker carrying it was deployed on 2026-09-23, and the state any field added here starts in,
99
60
  * and it must be readable as such without refusing anything.
100
61
  */
101
- export const REPORTED_ONLY: {
62
+ export declare const REPORTED_ONLY: {
102
63
  path: string;
103
64
  why: string;
104
65
  }[];
@@ -110,11 +71,14 @@ export const REPORTED_ONLY: {
110
71
  * `Record<string, unknown>` on the other, which typecheck caught in the test that calls them in sequence.
111
72
  * Two spellings of one shape is the duplication this repo names as its most expensive recurring defect,
112
73
  * and a typedef is the cheapest form of "delete a copy".
113
- *
114
- * @typedef {{field: string, why: string, values: Record<string, unknown>}} Mismatch
115
74
  */
75
+ export type Mismatch = {
76
+ field: string;
77
+ why: string;
78
+ values: Record<string, unknown>;
79
+ };
116
80
  /** Edge policy values every guest must agree on, checked separately because they come from /diagnostics. */
117
- export const POLICY_MUST_MATCH: string[];
81
+ export declare const POLICY_MUST_MATCH: string[];
118
82
  /**
119
83
  * Which fields the verdict is actually ABOUT -- #1997.
120
84
  *
@@ -141,27 +105,6 @@ export type FieldReporters = {
141
105
  reported: number;
142
106
  asked: number;
143
107
  };
144
- /**
145
- * Which fields the verdict is actually ABOUT -- #1997.
146
- *
147
- * `compared` NAMES the fields at least one guest reported a value for; `unchecked` names the ones that
148
- * drew a value from NOBODY. The second list exists because `check()` below skips an absent value, which
149
- * is right (a rolling deploy must not flag the guest it has not reached yet) and has a cost: a field no
150
- * guest reports contributes no values, `new Set([]).size > 1` is false, and it reads as agreement.
151
- * A field compared on nobody and a field equal on everybody produced the IDENTICAL verdict.
152
- *
153
- * Measured 2026-09-22T20:09Z on the live fleet, at the merge of #1953: nine fields at 10/10 guests and
154
- * `displayMode` at 0/10, and `fleet:status` printed `fleet CONSISTENT across 10 of 10 -- these workers
155
- * are interchangeable for capture`. The display was still not compared. Naming the two lists is what
156
- * lets a reader tell "they agree" from "nobody was asked".
157
- *
158
- * `coverage` IS THE SAME QUESTION WITHOUT THE THRESHOLD -- #2019. The two lists above are a PARTITION on
159
- * "did anybody report it", which answers 0-of-10 and says nothing about 1-of-10; `fleet:status` read the
160
- * second as agreement about ten guests on the strength of one. So every asked field also carries how many
161
- * of the asked guests actually reported it, and the verdict draws its own line. `compared`/`unchecked`
162
- * stay because they are what a caller greps for the REMEDY -- a field at 0 sends a reader to the field,
163
- * a field at k sends them to the boxes -- and `coverage` is the measurement both are derived from.
164
- */
165
108
  export type FieldCoverage = {
166
109
  compared: string[];
167
110
  unchecked: string[];
@@ -195,22 +138,19 @@ export type ReportedDrift = {
195
138
  * that gates nothing. One reader means the two channels cannot drift apart in how they compare — a
196
139
  * second copy of this loop is how a "reported-only" field would end up counted differently from a gating
197
140
  * one and nobody would know which was right.
141
+ *
142
+ * @param {Guest[]} present
143
+ * @param {{field: string, why: string, source: (guest: Guest) => Record<string, unknown> | undefined,
144
+ * key: string}} ask `source` is the BLOCK this field lives in, not the value: coverage has to tell
145
+ * "the guest did not report this field" from "this caller never collected that block at all", and only
146
+ * the block answers the second
147
+ * @returns {Reading}
198
148
  */
199
149
  export type Guest = {
200
150
  worker: string;
201
151
  environment?: Record<string, unknown>;
202
152
  policy?: Record<string, unknown>;
203
153
  };
204
- /**
205
- * ONE FIELD READ ACROSS THE FLEET, before anybody decides what it means.
206
- *
207
- * Split out from `fleetConsistency` when the third channel arrived (#2063), because the READING and the
208
- * CONSEQUENCE are two things and only the second differs between the channels: `MUST_MATCH` turns a
209
- * reading into a mismatch and a coverage row, `REPORTED_ONLY` turns the same reading into a named drift
210
- * that gates nothing. One reader means the two channels cannot drift apart in how they compare — a
211
- * second copy of this loop is how a "reported-only" field would end up counted differently from a gating
212
- * one and nobody would know which was right.
213
- */
214
154
  export type Reading = {
215
155
  field: string;
216
156
  why: string;
@@ -219,16 +159,41 @@ export type Reading = {
219
159
  asked: number;
220
160
  };
221
161
  /**
222
- * One field the guests disagree about, and the guests' values for it.
162
+ * Compare guests field by field.
223
163
  *
224
- * Named once because it is produced by `fleetConsistency` and consumed by `describeMismatches`, and the
225
- * two had already drifted apart the moment either was annotated — `object` on one side against
226
- * `Record<string, unknown>` on the other, which typecheck caught in the test that calls them in sequence.
227
- * Two spellings of one shape is the duplication this repo names as its most expensive recurring defect,
228
- * and a typedef is the cheapest form of "delete a copy".
164
+ * @param {Guest[]} guests
165
+ * @returns {{consistent: boolean, mismatches: Mismatch[], compared: number, fields: FieldCoverage,
166
+ * reportedOnly: ReportedDrift[]}}
167
+ * `compared` is how many guests the verdict is actually ABOUT -- #920. A guest with no `environment`
168
+ * and no `policy` is dropped below before comparing, so it is not the length of what was passed in,
169
+ * and a caller that reports `consistent` without it is stating agreement over a set it cannot name.
170
+ * `fields` is that same question one axis over: WHICH fields the verdict is about -- #1997.
171
+ * `reportedOnly` is the third channel (#2063): compared, named, and part of NEITHER of the two above,
172
+ * which is what makes it something to report rather than something to refuse.
229
173
  */
230
- export type Mismatch = {
231
- field: string;
232
- why: string;
233
- values: Record<string, unknown>;
174
+ export declare function fleetConsistency(guests: Guest[]): {
175
+ consistent: boolean;
176
+ mismatches: Mismatch[];
177
+ compared: number;
178
+ fields: FieldCoverage;
179
+ reportedOnly: ReportedDrift[];
234
180
  };
181
+ /**
182
+ * One line per reported-only field that has something to say, in `describeMismatches`'s own shape.
183
+ *
184
+ * SEPARATE FROM `describeMismatches` rather than a flag on it, because the two lines make different
185
+ * claims and a reader has to be able to tell them apart at a glance: that one says the fleet is not
186
+ * usable for a capture run, this one says the fleet is not identical and the run may proceed anyway.
187
+ * Folding them would put the ruling's own distinction behind a boolean argument.
188
+ *
189
+ * @param {ReportedDrift[]} drifts
190
+ * @returns {string[]}
191
+ */
192
+ export declare function describeReportedOnly(drifts: ReportedDrift[]): string[];
193
+ /**
194
+ * One line per mismatch, naming the guests, so the report is actionable rather than just alarming.
195
+ *
196
+ * @param {Mismatch[]} mismatches
197
+ * @returns {string[]}
198
+ */
199
+ export declare function describeMismatches(mismatches: Mismatch[]): string[];