@vzn/vx 0.0.632 → 0.0.634

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 (48) hide show
  1. package/README.md +14 -10
  2. package/docs/caching.md +2250 -0
  3. package/docs/cli.md +3283 -0
  4. package/docs/execution.md +708 -0
  5. package/docs/features.md +174 -0
  6. package/docs/patterns.md +219 -0
  7. package/docs/schema.md +2151 -0
  8. package/docs/security.md +209 -0
  9. package/package.json +12 -5
  10. package/schemas/docs.json +28 -0
  11. package/schemas/error.json +5 -1
  12. package/schemas/init.json +57 -0
  13. package/schemas/lock.json +21 -0
  14. package/schemas/plan.json +4 -0
  15. package/skills/vx/SKILL.md +13 -3
  16. package/src/cache/cache.ts +556 -122
  17. package/src/cache/chained-cache.ts +9 -0
  18. package/src/cache/inputs.ts +71 -30
  19. package/src/cache/layer.ts +8 -4
  20. package/src/cache/layered-cache.ts +4 -0
  21. package/src/cache/schema.ts +42 -16
  22. package/src/cli/docs-corpus.ts +21 -0
  23. package/src/cli/docs-text.d.ts +5 -0
  24. package/src/cli/docs.ts +173 -0
  25. package/src/cli/help.ts +14 -3
  26. package/src/cli/index.ts +2 -0
  27. package/src/cli/init.ts +44 -8
  28. package/src/cli/lock.ts +43 -15
  29. package/src/cli/plan-format.ts +3 -1
  30. package/src/cli/run.ts +8 -2
  31. package/src/cli/upgrade.ts +18 -6
  32. package/src/config.ts +9 -0
  33. package/src/graph/priorities.ts +72 -6
  34. package/src/graph/scheduler.ts +17 -4
  35. package/src/graph/task-graph.ts +56 -18
  36. package/src/orchestrator/metrics.ts +11 -4
  37. package/src/orchestrator/miss-reason.ts +3 -5
  38. package/src/orchestrator/miss-save.ts +6 -5
  39. package/src/orchestrator/projects.ts +2 -2
  40. package/src/orchestrator/remote-prefetch.ts +8 -2
  41. package/src/orchestrator/run.ts +13 -1
  42. package/src/orchestrator/task-hash.ts +4 -4
  43. package/src/util/errors.ts +9 -2
  44. package/src/util/paths.ts +32 -0
  45. package/src/util/verbs.ts +1 -0
  46. package/src/workspace/config-schema.ts +26 -8
  47. package/src/workspace/lockfile.ts +29 -2
  48. package/src/workspace/migration.ts +30 -3
@@ -0,0 +1,209 @@
1
+ # Security model
2
+
3
+ What vx trusts, what it checks, what the sandbox stops and what it does
4
+ not, and how to report a problem. Report a vulnerability privately
5
+ through GitHub's private vulnerability reporting on
6
+ [vznjs/vx](https://github.com/vznjs/vx/security) (Security → Report a
7
+ vulnerability), never in a public issue.
8
+
9
+ ## What vx trusts
10
+
11
+ - **Your configs.** `vx.config.ts` and `vx.workspace.ts` are programs vx
12
+ runs in its own process. A config can do anything you can: vx
13
+ validates what it returns, not what it does. What a config imports
14
+ must already be installed: vx runs Bun with `--no-install`, so an
15
+ import no `node_modules` provides is refused, never fetched from the
16
+ registry (L-22).
17
+ - **Your plugins.** A plugin is code you imported. It runs in vx's
18
+ process with a config's reach; vx checks the shape of what it hands
19
+ back and isolates a telemetry plugin's failures, nothing more.
20
+ - **Your lockfile and your repo.** vx parses a lockfile (and
21
+ `turbo.json` or an Nx graph when migrating) into keys and refuses one
22
+ it cannot read, naming the file. A lockfile that names another
23
+ install is a different key, never a code path.
24
+ - **Whoever can write your remote cache.** A cache key names the
25
+ inputs, not the bytes, so a store that answers a key with bytes of its
26
+ choosing is trusted as a writer, as it is in every action cache. Use a
27
+ store only your builds write to, or `turboCache()`'s signature key,
28
+ which refuses an artifact a keyholder did not sign. A job you do not
29
+ trust (a fork's pull request) reads at most: `--cache=local:rw,remote:r`,
30
+ or the tools' own `TURBO_REMOTE_CACHE_READ_ONLY`, `TURBO_CACHE` and
31
+ `NX_SKIP_REMOTE_CACHE`, which the cache plugins honour (L-34).
32
+
33
+ ## Cache poisoning
34
+
35
+ A pull request that can write the cache the default branch reads can
36
+ plant bytes main later replays (CREEP, CVE-2025-36852). The boundary is
37
+ the credential, enforced by the cache server: the default branch's job
38
+ holds a token that writes the trusted keys, and a pull request's job
39
+ holds one that cannot (read-only, or confined by the server to its own
40
+ namespace), or none.
41
+
42
+ `cacheScope` is the client half. `'pr-123'` reads its own keys,
43
+ then the trusted ones, and writes only its own, which `github()` sets on
44
+ Actions (`pr-<n>`, `ref-<name>`; the default branch stays trusted)
45
+ and `VX_CACHE_SCOPE` sets on any CI.
46
+ It keeps honest runs apart and stops a non-hermetic PR build from
47
+ landing where main reads; it is not a boundary, since the PR's own
48
+ config decides it and any run holding a write credential can write any
49
+ key.
50
+
51
+ A boundary the server holds, with bazel-remote and `@vzn/vx-reapi`: reads
52
+ are open, writes need a password, and only main's push job has it.
53
+
54
+ ```sh
55
+ htpasswd -cB /etc/bazel-remote/htpasswd ci # the write credential
56
+ bazel-remote --dir /data --max_size 50 --grpc_address 0.0.0.0:9092 \
57
+ --htpasswd_file /etc/bazel-remote/htpasswd --allow_unauthenticated_reads
58
+ ```
59
+
60
+ ```ts
61
+ // vx.workspace.ts: the header only where the secret is set
62
+ reapi({
63
+ endpoint: 'grpcs://cache.example.com:9092',
64
+ headers: process.env.CACHE_AUTH ? { authorization: `Basic ${process.env.CACHE_AUTH}` } : {},
65
+ })
66
+ ```
67
+
68
+ ```yaml
69
+ # .github/workflows/ci.yml: CACHE_AUTH (base64 of ci:<password>) is a
70
+ # secret of the `cache-write` environment, whose only deployment branch
71
+ # is main, so no other branch's workflow can read it
72
+ jobs:
73
+ build:
74
+ if: github.event_name != 'push' || github.ref != 'refs/heads/main'
75
+ steps:
76
+ - run: vx run build --all
77
+ build-main:
78
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
79
+ environment: cache-write
80
+ env:
81
+ CACHE_AUTH: ${{ secrets.CACHE_AUTH }}
82
+ steps:
83
+ - run: vx run build --all
84
+ ```
85
+
86
+ A pull request's job has no `CACHE_AUTH`, so the server refuses its
87
+ writes whatever its config says. Never give the write secret to a
88
+ `pull_request_target`, `issue_comment` or `workflow_run` job that checks
89
+ out a pull request's code: it runs with main's ref and secrets, and
90
+ `github()` scopes it `pr-<n>` only on the client side.
91
+
92
+ ## What vx checks on bytes it did not write
93
+
94
+ Every artifact that arrives from a remote, and every one read back from
95
+ the local store:
96
+
97
+ - ends in a CRC-32 of its entries: a byte changed in transit or at rest
98
+ makes the hit a miss, never a replay of the damaged bytes;
99
+ - on arrival, records the cache key it was packed under, and is
100
+ refused under another (a local read-back is not re-checked);
101
+ - on arrival, carries only the files the task declares as outputs;
102
+ - lands each file under the task's project or declared workspace path:
103
+ traversal (`..`, `.`, absolute names) is refused before anything
104
+ lands, a refused archive leaves nothing behind, and links and devices
105
+ are never written; a seeded fuzz of hostile archives holds this;
106
+ - is bounded: a decompression bomb, an oversized header or body, and a
107
+ remote answer larger than asked for are refused as they are read.
108
+
109
+ Remote execution (`@vzn/vx-reapi`) holds each blob to its digest, and
110
+ writes only the server's outputs a declared glob names, inside the
111
+ workspace, through no link that leads out of it.
112
+
113
+ ## What the sandbox stops
114
+
115
+ A task with `exec.sandbox` runs where only what it declares exists:
116
+
117
+ - reads of the workspace outside the task's project and grants fail;
118
+ - writes outside its `allow.write` grants fail (declared outputs grant
119
+ none);
120
+ - the network is closed except to the domains granted, one union per
121
+ run: a task granted any domain reaches every domain the run grants;
122
+ with none, it reaches no listener on the host's loopback and no unix
123
+ socket outside the workspace (L-46);
124
+ - its temp directory, port-bridge socket and trace log are its own
125
+ (mode 0700), unreachable from another task and another local user;
126
+ - the host's credential stores (`~/.ssh`, `~/.gnupg`, `~/.aws`,
127
+ `~/.npmrc`, `~/.netrc` and the like; the list is in the schema's
128
+ sandbox section) are unreadable unless `allow.read` names one (L-41);
129
+ - vx's own cache directory, wherever `cacheDir` puts it in the
130
+ workspace, is a wall like `.vx`: a broad grant stops at it, and a write
131
+ grant that would bind it is refused (L-24);
132
+ - a symlinked output must resolve inside its project: vx packs outputs
133
+ outside the sandbox, and a link to another project's file would have
134
+ carried that file into the cache (L-23). An output directory linked out
135
+ of the project refuses the task by name (X-88).
136
+
137
+ An undeclared touch of the task's own files fails the task, and a failed
138
+ task is never cached. See [Sandboxing tasks](https://vznjs.github.io/vx/guides/sandboxing/).
139
+
140
+ ## What it does not stop
141
+
142
+ - **A task with no `exec.sandbox`.** It runs with your permissions.
143
+ - **Reads outside the workspace root.** `~/.cache`, `/etc` and the like
144
+ stay readable (tools need them) and fold into no key. The credential
145
+ stores under home (`~/.ssh`, `~/.aws`, `~/.npmrc` and the like) are
146
+ not: a task reads one only when its `allow.read` names it (L-41).
147
+ - **Resource use.** CPU, memory and disk are bounded by timeouts and the
148
+ artifact ceiling, not by the sandbox.
149
+ - **A weaker sandbox where the host cannot nest one**:
150
+ `weakerWhenNested` in a container, a task that itself sandboxes on
151
+ macOS. `vx info` says what the host supports.
152
+
153
+ ## Agents (`vx mcp`)
154
+
155
+ An agent connected to `vx mcp` can do what `vx run` can do, nothing more.
156
+ Every tool but `runTasks` only reads. `runTasks` runs `vx run --format json`
157
+ as a child process: the agent names tasks and sets `all`, `force`,
158
+ `filter` and `affected`, and nothing else. A task name that starts with
159
+ `-` is refused, a filter goes as one `--filter=` value, and a ref reaches
160
+ git after `--end-of-options`, so no argument becomes a flag. The commands
161
+ that run are the ones your configs declare, from the workspace root, with
162
+ the environment `vx mcp` started with, filtered by each task's `exec.env`
163
+ and held by its `exec.sandbox`. `force` costs a rerun, not a wrong entry:
164
+ the key stays the same. What reaches the agent (summaries, refusals,
165
+ `getFailures` output) is masked as described under Secrets.
166
+ `mcp({ run: false })` takes `runTasks` away, and `mcp({ run: ['test'] })`
167
+ limits it to those task names. An agent that
168
+ can edit `vx.config.ts` can run anything, as you can, because configs are
169
+ code vx trusts.
170
+
171
+ ## Secrets
172
+
173
+ The value, of 6 characters or more, of a variable whose name holds
174
+ `TOKEN`, `SECRET`, `KEY`, `PASSWORD`, `PASSWD` or `CREDENTIAL` (not one
175
+ ending `_FILE`, `_PATH` or `_DIR`, nor `GIT_CONFIG_KEY_<n>`), or that a
176
+ task lists in `exec.env.secret`, and the password of a URL
177
+ (`scheme://user:<password>@`) in any variable's value, is printed as `***` wherever vx shows, stores or
178
+ exports it: task output, the stdout a hit replays, commands, an
179
+ executor's error, telemetry,
180
+ `vx show` and `vx mcp`'s `listTasks` (L-26). Values in the run history are digests under a per-store
181
+ salt, and what follows `--` on the command line reaches telemetry as a
182
+ count, not a quote, a secret before it masked (L-44). A remote executor (`@vzn/vx-reapi`) receives a
183
+ task's `exec.env.define` values and the `cache.inputs.env` values its
184
+ local child gets, never `passThrough`; those values sit unmasked in the
185
+ action's Command, which the remote stores in its CAS, as Bazel's
186
+ `--action_env` does, so a secret a remote task needs is trusted to that
187
+ remote. `vx lock` refuses to write a lock holding a secret value: the lock is
188
+ committed (L-42).
189
+
190
+ Masking covers the text vx shows and stores, not a task's outputs: they
191
+ are cached and uploaded as the task wrote them, so a build that bakes a
192
+ secret into a file (a server bundle reading `process.env.API_KEY`) hands
193
+ it to every reader of the remote cache. Keep secrets out of cached
194
+ outputs, or give that task no `cache`. vx does not scan outputs for
195
+ secret values: a name-based match would refuse public `*_KEY` values too.
196
+
197
+ ## Releases
198
+
199
+ Release binaries carry a build-provenance attestation
200
+ (`gh attestation verify vx-<target> --repo vznjs/vx`); `vx upgrade`
201
+ checks each download's SHA-256 before it replaces anything and stages it in a fresh directory, never a name another user could plant a link at, which proves
202
+ the bytes arrived intact, not who built them: the digest comes from the
203
+ release that serves the asset, and no signature is checked (owner,
204
+ 2026-10-03: no release signing key for now); npm packages
205
+ publish with provenance; every third-party action in CI runs from a
206
+ full commit SHA; and a workflow hands an event's or a dispatcher's value
207
+ to a script through `env`, never pasted in (a release tag is checked as a
208
+ version first); every workflow names its token's permissions, and CI's
209
+ is read-only. A test holds each rule.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.632",
3
+ "version": "0.0.634",
4
4
  "description": "The fastest task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,10 +21,10 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "optionalDependencies": {
24
- "@vzn/vx-linux-x64": "0.0.632",
25
- "@vzn/vx-linux-arm64": "0.0.632",
26
- "@vzn/vx-darwin-x64": "0.0.632",
27
- "@vzn/vx-darwin-arm64": "0.0.632"
24
+ "@vzn/vx-linux-x64": "0.0.634",
25
+ "@vzn/vx-linux-arm64": "0.0.634",
26
+ "@vzn/vx-darwin-x64": "0.0.634",
27
+ "@vzn/vx-darwin-arm64": "0.0.634"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -35,6 +35,13 @@
35
35
  "config",
36
36
  "schemas",
37
37
  "skills",
38
+ "docs/cli.md",
39
+ "docs/schema.md",
40
+ "docs/caching.md",
41
+ "docs/execution.md",
42
+ "docs/patterns.md",
43
+ "docs/security.md",
44
+ "docs/features.md",
38
45
  "launcher.cjs",
39
46
  "README.md",
40
47
  "LICENSE"
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "vx docs <query> --format json",
4
+ "description": "The reference sections holding every word of the query, best first.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["query", "hits"],
8
+ "properties": {
9
+ "query": { "type": "string", "description": "the query's words, lowercased" },
10
+ "hits": {
11
+ "type": "array",
12
+ "items": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["page", "heading", "url", "text"],
16
+ "properties": {
17
+ "page": { "type": "string", "description": "the page: cli, schema, caching …" },
18
+ "heading": {
19
+ "type": "string",
20
+ "description": "the section's heading; empty for a page's opening"
21
+ },
22
+ "url": { "type": "string", "description": "the section on the site" },
23
+ "text": { "type": "string", "description": "the section's markdown, heading included" }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
@@ -16,7 +16,11 @@
16
16
  "type": "string",
17
17
  "description": "Stable; branch on it. VX_E_USAGE, VX_E_UNKNOWN_COMMAND, VX_E_UNKNOWN_TASK, VX_E_UNKNOWN_PROJECT, VX_E_NO_HISTORY, VX_E_AMBIGUOUS, VX_E_CONFIG, VX_E_CYCLE, VX_E_NO_WORKSPACE, VX_E_CACHE_VERSION, VX_E_REFUSED, VX_E_FS, VX_E_FDS, VX_E_INTERNAL."
18
18
  },
19
- "message": { "type": "string", "description": "The refusal in prose; may change." }
19
+ "message": { "type": "string", "description": "The refusal in prose; may change." },
20
+ "docs": {
21
+ "type": "string",
22
+ "description": "For a core code (VX_E_…): its section of the CLI reference, which says what to do. A plugin's code has none."
23
+ }
20
24
  }
21
25
  }
22
26
  }
@@ -0,0 +1,57 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "vx init --dry --format json",
4
+ "description": "What vx init (or vx-migrate) would write, nothing written. In a Turbo or Nx repo the plan is vx-migrate's.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["dry", "source", "files", "kept", "replaced", "todos", "notes", "next"],
8
+ "properties": {
9
+ "dry": { "const": true },
10
+ "source": {
11
+ "type": "string",
12
+ "description": "what the plan maps: package.json scripts, turbo.json, nx graph, lerna.json …"
13
+ },
14
+ "files": {
15
+ "type": "array",
16
+ "description": "each file the run would write, root-relative, with its whole text",
17
+ "items": {
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "required": ["path", "contents"],
21
+ "properties": { "path": { "type": "string" }, "contents": { "type": "string" } }
22
+ }
23
+ },
24
+ "kept": {
25
+ "type": "array",
26
+ "items": { "type": "string" },
27
+ "description": "configs already here that the run leaves as they are"
28
+ },
29
+ "replaced": {
30
+ "type": "array",
31
+ "items": { "type": "string" },
32
+ "description": "files of another extension the run would delete for the new ones"
33
+ },
34
+ "todos": {
35
+ "type": "array",
36
+ "description": "what the mapping could not settle, one reason with the tasks it names",
37
+ "items": {
38
+ "type": "object",
39
+ "additionalProperties": false,
40
+ "required": ["reason", "tasks"],
41
+ "properties": {
42
+ "reason": { "type": "string" },
43
+ "tasks": { "type": "array", "items": { "type": "string" }, "description": "project#task" }
44
+ }
45
+ }
46
+ },
47
+ "notes": {
48
+ "type": "array",
49
+ "items": { "type": "string" },
50
+ "description": "the report's notes"
51
+ },
52
+ "next": {
53
+ "type": ["string", "null"],
54
+ "description": "the command to type after the real run; null where the plan names none"
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "vx lock --check --format json",
4
+ "description": "The lock audit: exit 0 when upToDate, else 1 with each drift in problems.",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["upToDate", "audited", "notAudited", "problems"],
8
+ "properties": {
9
+ "upToDate": { "type": "boolean" },
10
+ "audited": { "type": "integer", "description": "projects with a vx.config, each audited" },
11
+ "notAudited": {
12
+ "type": "integer",
13
+ "description": "projects without a vx.config: nothing to freeze, never audited"
14
+ },
15
+ "problems": {
16
+ "type": "array",
17
+ "items": { "type": "string" },
18
+ "description": "one line per drift, as the plain audit prints it on stderr; a missing lock is one"
19
+ }
20
+ }
21
+ }
package/schemas/plan.json CHANGED
@@ -6,6 +6,10 @@
6
6
  "additionalProperties": false,
7
7
  "required": ["tasks"],
8
8
  "properties": {
9
+ "affectedBase": {
10
+ "type": "string",
11
+ "description": "under --affected: the ref the diff was taken against (a bare --affected guesses it: origin/HEAD, a trunk branch, else HEAD~1)"
12
+ },
9
13
  "tasks": {
10
14
  "type": "array",
11
15
  "items": {
@@ -21,6 +21,9 @@ vx run build --all --format json # the result as JSON on stdout; logs go to s
21
21
  The exit code is the verdict: `0` every task passed or hit the cache, `1` a
22
22
  task failed or was skipped, or the command was refused. `--format json`
23
23
  prints `schemas/summary.json` (`runId`, `ok`, `exitCode`, a row per task).
24
+ A refusal under `--format json` is one line on stdout,
25
+ `{"ok":false,"error":{"code","message","docs"}}`: branch on `code`;
26
+ `docs` (or `vx docs <code>` offline) says what to do.
24
27
  Without a TTY a missing task name never prompts: vx exits 1 and lists the
25
28
  tasks it found.
26
29
 
@@ -31,7 +34,10 @@ vx show --format json # projects, their tasks and depen
31
34
  vx run test --affected=origin/main --dry=json # what would run, cache hit or miss, and why
32
35
  ```
33
36
 
34
- Under `--affected`, each kept task in `--dry=json` carries `affected`:
37
+ Under `--affected`, `--dry=json` states `affectedBase` (the ref it diffed
38
+ against; name one, `--affected=origin/main`, rather than trust the guess)
39
+ and each task you asked for carries `affected` (a dependency it pulls in
40
+ does not):
35
41
  `{ kind, file?, project?, via? }`, where `kind` is `input` (a changed file
36
42
  is one of its inputs), `project`, `package`, `named` or `selected`, and
37
43
  `via` is the `dependsOn` chain that carried the change.
@@ -64,10 +70,11 @@ vx info --format json # vx and Bun versions, projects, config errors, plugins,
64
70
  When `@vzn/vx-mcp` is a plugin in `vx.workspace.ts`, `vx mcp` serves the
65
71
  same facts and runs tasks. Adoption does not add it: install
66
72
  `@vzn/vx-mcp` and put `mcp()` (from `'@vzn/vx-mcp'`) in `plugins`. Its tools: `listTasks`, `getCacheStats`,
67
- `getRunHistory`, `explainCacheKey`, `whyDidThisRerun`, `getFailures`, `getTaskLog`, `getConfig`,
73
+ `getRunHistory`, `explainCacheKey`, `whyDidThisRerun`, `getFailures`, `getTaskLog`, `getConfig`, `checkLock`, `pruneCache`, `planInit`, `searchDocs`,
68
74
  `getWorkspaceInfo`, `runTasks`, `planTasks`. `runTasks` answers what
69
75
  `vx run <tasks> --format json` prints, `planTasks` what
70
- `vx run <tasks> --dry=json` prints; the others only read.
76
+ `vx run <tasks> --dry=json` prints, `pruneCache` evicts only with
77
+ `dryRun: false`; the others only read.
71
78
 
72
79
  ## Rules
73
80
 
@@ -78,6 +85,9 @@ same facts and runs tasks. Adoption does not add it: install
78
85
  does not re-run a task, the file is not one of its inputs. Fix the
79
86
  config, do not force.
80
87
  - Every JSON shape is a JSON Schema in `node_modules/@vzn/vx/schemas/`.
88
+ - Offline reference: `vx docs <words> --format json` searches the CLI, config
89
+ and cache pages that ship with vx; the sections holding every word print
90
+ whole.
81
91
  - Docs as markdown: https://vznjs.github.io/vx/llms.txt (its index).
82
92
  When that host is unreachable, the same pages are
83
93
  `https://raw.githubusercontent.com/vznjs/vx/main/packages/vx-docs/src/content/docs/<page>.md`: