@a11ign/screenreader-fleet 0.5.1 → 0.5.3
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/README.md +37 -0
- package/dist/doctor.mjs +2 -2
- package/dist/source-walk.d.mts +3 -3
- package/package.json +17 -8
- package/src/local-worker/build-vm.sh +2 -2
- package/src/local-worker/clone-worker.sh +1 -1
- package/src/local-worker/create-utm-vm.sh +1 -1
- package/src/local-worker/fetch-windows-iso.sh +5 -5
- package/src/local-worker/worker-ctl.sh +12 -12
- package/src/provisioning/apply-foreground-lock-timeout.ps1 +1 -1
- package/src/provisioning/bootstrap-windows-worker.ps1 +1 -1
- package/src/provisioning/diagnose-nvda-worker.ps1 +1 -1
- package/src/provisioning/stamp-provision-revision.ps1 +3 -3
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.
|
package/dist/doctor.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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";
|
|
@@ -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}:
|
|
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) {
|
package/dist/source-walk.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Every non-test source file under
|
|
2
|
+
* Every non-test source file under the repository root, as `[relativePath, source]`.
|
|
3
3
|
*
|
|
4
4
|
* @param {{ root?: string }} [options]
|
|
5
5
|
* @returns {Array<[string, string]>}
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
export function sourceFiles({ root }?: {
|
|
8
8
|
root?: string;
|
|
9
9
|
}): Array<[string, string]>;
|
|
10
|
-
/** The
|
|
11
|
-
export const
|
|
10
|
+
/** The repository root, resolved from this module rather than from the caller's cwd. */
|
|
11
|
+
export const REPOSITORY: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a11ign/screenreader-fleet",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Host-side lifecycle, health and capacity for a fleet of Windows NVDA capture workers: lease one, judge whether it is degrading, and know how many the host can afford.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -73,9 +73,16 @@
|
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@a11ign/evidence": "0.1.0",
|
|
76
|
-
"@a11ign/toolchain": "0.1.
|
|
76
|
+
"@a11ign/toolchain": "0.1.5",
|
|
77
|
+
"@changesets/cli": "3.0.3",
|
|
78
|
+
"@eslint/js": "^10.0.1",
|
|
77
79
|
"@rslib/core": "1.0.3",
|
|
80
|
+
"@rstest/core": "0.12.3",
|
|
81
|
+
"@types/node": "^26.6.4",
|
|
82
|
+
"eslint": "^10.12.0",
|
|
83
|
+
"globals": "^17.13.0",
|
|
78
84
|
"typescript": "^6.0.3",
|
|
85
|
+
"typescript-eslint": "^8.71.1",
|
|
79
86
|
"yaml": "^2.9.1"
|
|
80
87
|
},
|
|
81
88
|
"engines": {
|
|
@@ -84,11 +91,6 @@
|
|
|
84
91
|
"publishConfig": {
|
|
85
92
|
"access": "public"
|
|
86
93
|
},
|
|
87
|
-
"repository": {
|
|
88
|
-
"type": "git",
|
|
89
|
-
"url": "git+https://github.com/a11ign/screenreader-fleet.git",
|
|
90
|
-
"directory": "packages/worker-fleet"
|
|
91
|
-
},
|
|
92
94
|
"homepage": "https://github.com/a11ign/screenreader-fleet",
|
|
93
95
|
"keywords": [
|
|
94
96
|
"accessibility",
|
|
@@ -99,7 +101,14 @@
|
|
|
99
101
|
"utm",
|
|
100
102
|
"vm"
|
|
101
103
|
],
|
|
104
|
+
"repository": {
|
|
105
|
+
"type": "git",
|
|
106
|
+
"url": "git+https://github.com/a11ign/screenreader-fleet.git"
|
|
107
|
+
},
|
|
102
108
|
"scripts": {
|
|
103
|
-
"build": "rslib build"
|
|
109
|
+
"build": "rslib build",
|
|
110
|
+
"lint": "eslint .",
|
|
111
|
+
"typecheck": "tsc --noEmit",
|
|
112
|
+
"test": "rstest run"
|
|
104
113
|
}
|
|
105
114
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Build a local NVDA capture worker VM on Apple Silicon, unattended.
|
|
3
3
|
#
|
|
4
|
-
# ./
|
|
4
|
+
# ./src/local-worker/build-vm.sh /path/to/Win11_ARM64.iso
|
|
5
5
|
#
|
|
6
6
|
# Produces a self-contained VM directory (default ~/a11y-worker-vm) holding the disk
|
|
7
7
|
# image, UEFI vars and a run script. That directory IS the portable artifact: copy it
|
|
@@ -40,7 +40,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
40
40
|
die() { echo "error: $*" >&2; exit 1; }
|
|
41
41
|
info() { echo "==> $*"; }
|
|
42
42
|
|
|
43
|
-
[ -n "$WIN_ISO" ] || die "usage: $0 <windows-11-arm64.iso> (build one with CrystalFetch, or
|
|
43
|
+
[ -n "$WIN_ISO" ] || die "usage: $0 <windows-11-arm64.iso> (build one with CrystalFetch, or src/local-worker/fetch-windows-iso.sh)"
|
|
44
44
|
[ -f "$WIN_ISO" ] || die "not found: $WIN_ISO"
|
|
45
45
|
command -v qemu-system-aarch64 >/dev/null || die "qemu missing: brew install qemu"
|
|
46
46
|
command -v qemu-img >/dev/null || die "qemu-img missing: brew install qemu"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Clone the local NVDA worker VM into an additional, independent worker.
|
|
3
3
|
#
|
|
4
|
-
# ./
|
|
4
|
+
# ./src/local-worker/clone-worker.sh [new-name] # default: a11y-worker-2
|
|
5
5
|
#
|
|
6
6
|
# One worker serves one capture at a time by design (one desktop, one foreground window, one
|
|
7
7
|
# NVDA), so throughput scales by running more of them. On APFS the clone is copy-on-write, so
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Create the NVDA worker VM in UTM, fully from the CLI. No GUI clicking.
|
|
3
3
|
#
|
|
4
|
-
# ./
|
|
4
|
+
# ./src/local-worker/create-utm-vm.sh <windows-arm64.iso> [support.iso]
|
|
5
5
|
#
|
|
6
6
|
# Why UTM rather than plain QEMU: homebrew QEMU + HVF cannot boot Windows 11 ARM64 on
|
|
7
7
|
# Apple Silicon (open upstream bug, https://gitlab.com/qemu-project/qemu/-/issues/2893,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Build an official Windows 11 ARM64 ISO on macOS, from the CLI.
|
|
3
3
|
#
|
|
4
|
-
# ./
|
|
4
|
+
# ./src/local-worker/fetch-windows-iso.sh [outdir]
|
|
5
5
|
#
|
|
6
6
|
# Microsoft's ARM64 ISO download is a session-token web flow that does not script
|
|
7
7
|
# cleanly, so this uses UUP dump: it fetches the same Unified Update Platform packages
|
|
@@ -16,7 +16,7 @@ set -euo pipefail
|
|
|
16
16
|
|
|
17
17
|
# architecture-audit.md §8: builds an ISO for a local UTM worker VM, which is deprecated -- "The UTM is
|
|
18
18
|
# deprecated, that was a testing thing." (repository owner, 2026-09-05). Bare-metal boxes install via
|
|
19
|
-
# PXE/autounattend.xml instead — see
|
|
19
|
+
# PXE/autounattend.xml instead — see src/provisioning/bare-metal/.
|
|
20
20
|
echo "DEPRECATED: fetch-windows-iso.sh feeds a local UTM worker VM build. UTM was a testing path and is not the fleet." >&2
|
|
21
21
|
echo "Capture on the bare-metal fleet instead: npm run fleet:status, npm run fleet:deploy." >&2
|
|
22
22
|
|
|
@@ -100,7 +100,7 @@ cd "$OUT_DIR"
|
|
|
100
100
|
EXISTING="$(ls -t "$OUT_DIR"/*.ISO "$OUT_DIR"/*.iso 2>/dev/null | grep -vi support | head -1 || true)"
|
|
101
101
|
if [ -n "$EXISTING" ] && xorriso -indev "$EXISTING" -report_el_torito plain 2>/dev/null | grep -q UEFI; then
|
|
102
102
|
echo "ISO ready (already built): $EXISTING"
|
|
103
|
-
echo "Next: ./
|
|
103
|
+
echo "Next: ./src/local-worker/create-utm-vm.sh \"$EXISTING\""
|
|
104
104
|
exit 0
|
|
105
105
|
fi
|
|
106
106
|
if [ -n "$EXISTING" ]; then
|
|
@@ -234,5 +234,5 @@ fi
|
|
|
234
234
|
|
|
235
235
|
echo
|
|
236
236
|
echo "ISO ready: $ISO"
|
|
237
|
-
echo "Next: ./
|
|
238
|
-
echo " ./
|
|
237
|
+
echo "Next: ./src/local-worker/build-vm.sh \"$ISO\""
|
|
238
|
+
echo " ./src/local-worker/create-utm-vm.sh \"$ISO\""
|
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
# while you are not capturing.
|
|
4
4
|
#
|
|
5
5
|
# npm run worker:ctl -- up # make it ready (start or resume), wait for /health
|
|
6
|
-
# ./
|
|
7
|
-
# ./
|
|
8
|
-
# ./
|
|
9
|
-
# ./
|
|
10
|
-
# ./
|
|
11
|
-
# ./
|
|
12
|
-
# ./
|
|
13
|
-
# ./
|
|
6
|
+
# ./src/local-worker/worker-ctl.sh pause # freeze it: ~0.6% CPU, instant resume, RAM not guaranteed
|
|
7
|
+
# ./src/local-worker/worker-ctl.sh stop # shut it down: nothing held, ~15 s to come back
|
|
8
|
+
# ./src/local-worker/worker-ctl.sh status # state, resource use, health
|
|
9
|
+
# ./src/local-worker/worker-ctl.sh json # the same, machine-readable (used by the CLI)
|
|
10
|
+
# ./src/local-worker/worker-ctl.sh pool # every a11y-worker* VM, as JSON
|
|
11
|
+
# ./src/local-worker/worker-ctl.sh pool-up # start them all, wait for health
|
|
12
|
+
# ./src/local-worker/worker-ctl.sh pool-stop # shut the whole pool down
|
|
13
|
+
# ./src/local-worker/worker-ctl.sh pool-pause # freeze the whole pool
|
|
14
14
|
#
|
|
15
15
|
# One VM serves one capture at a time, so throughput comes from more VMs. `pool` reports the
|
|
16
16
|
# lot; add one with clone-worker.sh (which handles the duplicate-MAC trap).
|
|
17
17
|
# Operate on a single named VM with A11Y_VM_NAME=a11y-worker-2.
|
|
18
|
-
# ./
|
|
19
|
-
# ./
|
|
18
|
+
# ./src/local-worker/worker-ctl.sh idle-pause 15 # watch, then pause after 15 idle minutes
|
|
19
|
+
# ./src/local-worker/worker-ctl.sh idle-stop 30 # same but shut down instead
|
|
20
20
|
#
|
|
21
21
|
# Measured on an M4 Max, 4 vCPU / 8 GB guest. Every number here was observed on this
|
|
22
22
|
# machine; none is an estimate:
|
|
@@ -117,7 +117,7 @@ resolve_uuid() {
|
|
|
117
117
|
[ -n "${A11Y_VM_UUID:-}" ] && { echo "$A11Y_VM_UUID"; return; }
|
|
118
118
|
local matches
|
|
119
119
|
matches="$(utmctl list | awk -v n="$VM_NAME" '$3 == n { print $1 }')"
|
|
120
|
-
[ -n "$matches" ] || die "no VM named '$VM_NAME' (create one:
|
|
120
|
+
[ -n "$matches" ] || die "no VM named '$VM_NAME' (create one: src/local-worker/create-utm-vm.sh)"
|
|
121
121
|
if [ "$(echo "$matches" | wc -l | tr -d ' ')" -gt 1 ]; then
|
|
122
122
|
# Do NOT guess, and do NOT suggest deleting one. Duplicate registrations under the same
|
|
123
123
|
# name point at the SAME <name>.utm bundle, so `utmctl delete` on either removes that
|
|
@@ -317,7 +317,7 @@ case "$CMD" in
|
|
|
317
317
|
echo " it is down for 5-10s during a restart; if it persists:" >&2
|
|
318
318
|
echo " utmctl exec <uuid> --cmd powershell.exe -NoProfile -Command 'Start-ScheduledTask -TaskName a11ysrv'" >&2
|
|
319
319
|
else
|
|
320
|
-
# `$0` is this file's path, which after M6 is `
|
|
320
|
+
# `$0` is this file's path, which after M6 is `src/local-worker/worker-ctl.sh` — true,
|
|
321
321
|
# and not what anyone wants to type. The npm alias is the stable way to say it, and it is what the docs use.
|
|
322
322
|
echo " no guest IP either, so the VM itself is not ready. Try 'npm run worker:ctl -- up'." >&2
|
|
323
323
|
fi
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
# and run-server.cmd (so every worker start re-applies it for that session).
|
|
23
23
|
#
|
|
24
24
|
# Style note: `#` line comments and no param() block, matching the other scripts here --
|
|
25
|
-
# see
|
|
25
|
+
# see src/provisioning/diagnose-nvda-worker.ps1 for why.
|
|
26
26
|
|
|
27
27
|
$ErrorActionPreference = 'Stop'
|
|
28
28
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Run this ONCE, in the VM, in an elevated PowerShell, right after Windows setup:
|
|
4
4
|
#
|
|
5
5
|
# Set-ExecutionPolicy -Scope Process Bypass -Force
|
|
6
|
-
# irm https://raw.githubusercontent.com/a11ign/
|
|
6
|
+
# irm https://raw.githubusercontent.com/a11ign/screenreader-fleet/main/src/provisioning/bootstrap-windows-worker.ps1 | iex
|
|
7
7
|
#
|
|
8
8
|
# ...or, if you already have the repo, just run this file. It installs the
|
|
9
9
|
# prerequisites, makes the box reachable over SSH, clones the repo, and then hands
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# VERDICT per layer rather than raw dumps, so the first FAIL is the thing to fix.
|
|
5
5
|
# Exits non-zero if any check failed. Copy it over and run it with -File:
|
|
6
6
|
#
|
|
7
|
-
# scp
|
|
7
|
+
# scp src/provisioning/diagnose-nvda-worker.ps1 user@host:C:/Users/user/
|
|
8
8
|
# ssh user@host "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\user\diagnose-nvda-worker.ps1"
|
|
9
9
|
#
|
|
10
10
|
# Do NOT pipe this to `powershell -Command -`. That mode silently truncated this
|
|
@@ -47,8 +47,8 @@ param(
|
|
|
47
47
|
Set-StrictMode -Version Latest
|
|
48
48
|
$ErrorActionPreference = 'Stop'
|
|
49
49
|
|
|
50
|
-
#
|
|
51
|
-
# declared in `packages/control/layers.json`, and what its launchers reach outside it in the layer's own
|
|
50
|
+
# THREE OF THE FIVE PATHS ARE NOT WRITTEN HERE (ADR 0039 item 6d, #3397; this repository's own file since the flat layout, a11ign/a11ign#4216).
|
|
51
|
+
# Where the worker layer and this fleet layer live is declared in `packages/control/layers.json`, and what its launchers reach outside it in the layer's own
|
|
52
52
|
# `src/launcher-reach.cmd`, which `run-capture-check.cmd` `call`s. Both are READ, so a path cannot change in
|
|
53
53
|
# the launcher and stay behind in the stamp. A declaration that is absent or does not say THROWS: the stamp
|
|
54
54
|
# must not fall back to a literal, because a literal that was right yesterday is the stamp describing less
|
|
@@ -77,7 +77,7 @@ $FOREGROUND_LOCK = Get-DeclaredReach -Name 'FLT'
|
|
|
77
77
|
# The single definition. Explicit paths rather than a filename search, because `main.yml` is not unique
|
|
78
78
|
# in this repo and a search would silently pick the wrong one.
|
|
79
79
|
$ENVIRONMENT_FILES = @(
|
|
80
|
-
'
|
|
80
|
+
(Get-LayerFile -Layer 'screenreader-fleet' -Relative 'src/provisioning/provision-nvda-worker.ps1')
|
|
81
81
|
$RUN_SERVER
|
|
82
82
|
$FOREGROUND_LOCK
|
|
83
83
|
'packages/control/ansible/roles/worker/defaults/main.yml'
|