@vzn/vx 0.0.638 → 0.0.640
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 +1 -1
- package/docs/caching.md +152 -95
- package/docs/cli.md +225 -372
- package/docs/execution.md +22 -47
- package/docs/features.md +14 -16
- package/docs/patterns.md +6 -8
- package/docs/schema.md +268 -215
- package/docs/security.md +1 -1
- package/package.json +5 -5
- package/schemas/last.json +7 -4
- package/schemas/show.json +14 -14
- package/skills/vx/SKILL.md +10 -7
- package/src/cache/cache.ts +44 -11
- package/src/cache/file-hashes.ts +7 -12
- package/src/cache/fs-clock.ts +62 -0
- package/src/cache/git-inputs.ts +8 -2
- package/src/cache/index.ts +5 -2
- package/src/cache/inputs.ts +154 -83
- package/src/cache/key-fold.ts +23 -3
- package/src/cache/layer.ts +33 -18
- package/src/cache/layered-cache.ts +108 -315
- package/src/cache/linked-inputs.ts +87 -0
- package/src/cache/plugin-cache.ts +2 -10
- package/src/cache/remote-lane.ts +383 -0
- package/src/cache/run-history.ts +24 -8
- package/src/cache/run-store.ts +4 -4
- package/src/cache/schema.ts +6 -4
- package/src/cache/scoped-cache.ts +7 -0
- package/src/cli/completions.ts +2 -2
- package/src/cli/foreign-flags.ts +11 -23
- package/src/cli/help.ts +6 -31
- package/src/cli/info.ts +2 -1
- package/src/cli/init.ts +23 -11
- package/src/cli/last.ts +30 -4
- package/src/cli/plan-format.ts +5 -58
- package/src/cli/plugin-commands.ts +2 -2
- package/src/cli/plugin-templates.ts +2 -2
- package/src/cli/run.ts +23 -185
- package/src/cli/select.ts +16 -6
- package/src/cli/show.ts +15 -6
- package/src/cli/upgrade.ts +215 -55
- package/src/cli/watch-cycle.ts +2 -1
- package/src/cli/watch-filter.ts +22 -6
- package/src/cli/watch-judge.ts +49 -5
- package/src/cli/watch-set.ts +18 -8
- package/src/cli/watch.ts +103 -16
- package/src/cli/why.ts +9 -7
- package/src/config.ts +19 -21
- package/src/exec/executor.ts +5 -5
- package/src/exec/sandbox-runtime.ts +1 -1
- package/src/exec/sandbox-violations.ts +3 -1
- package/src/graph/dependency-spec.ts +2 -2
- package/src/graph/folds.ts +236 -0
- package/src/graph/index.ts +17 -2
- package/src/graph/priorities.ts +1 -1
- package/src/graph/scheduler.ts +45 -31
- package/src/graph/task-graph.ts +190 -58
- package/src/graph/task-status.ts +95 -0
- package/src/{orchestrator → history}/failure-mode.ts +1 -1
- package/src/{orchestrator → history}/history.ts +1 -1
- package/src/history/index.ts +48 -0
- package/src/{orchestrator → history}/metrics.ts +7 -20
- package/src/{orchestrator → history}/miss-reason.ts +1 -1
- package/src/index.ts +11 -29
- package/src/orchestrator/affected-tasks.ts +20 -19
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/download-policy.ts +16 -22
- package/src/orchestrator/excluded-keys.ts +4 -5
- package/src/orchestrator/execute-args.ts +80 -0
- package/src/orchestrator/execute-task.ts +50 -411
- package/src/orchestrator/fingerprint-watch.ts +2 -2
- package/src/orchestrator/index.ts +2 -73
- package/src/orchestrator/keyed-projects.ts +12 -51
- package/src/orchestrator/local-shortcircuit.ts +1 -1
- package/src/orchestrator/miss-save.ts +33 -3
- package/src/orchestrator/options.ts +1 -36
- package/src/orchestrator/output-host.ts +4 -3
- package/src/orchestrator/persistent-task.ts +321 -0
- package/src/orchestrator/persistent.ts +130 -3
- package/src/orchestrator/placement.ts +5 -5
- package/src/orchestrator/plan.ts +19 -6
- package/src/orchestrator/plugin-host.ts +129 -69
- package/src/orchestrator/plugin.ts +18 -8
- package/src/orchestrator/prepare.ts +40 -34
- package/src/orchestrator/projects.ts +7 -10
- package/src/orchestrator/remote-prefetch.ts +14 -1
- package/src/orchestrator/run-artifacts.ts +5 -177
- package/src/orchestrator/run-context.ts +55 -0
- package/src/orchestrator/run-failures.ts +15 -3
- package/src/orchestrator/run-records.ts +35 -3
- package/src/orchestrator/run-report.ts +151 -171
- package/src/orchestrator/run-settings.ts +1 -16
- package/src/orchestrator/run-steps.ts +57 -0
- package/src/orchestrator/run-stop.ts +153 -0
- package/src/orchestrator/run.ts +293 -604
- package/src/orchestrator/sandbox-fix.ts +1 -17
- package/src/orchestrator/sandbox-request.ts +2 -2
- package/src/orchestrator/signals.ts +14 -6
- package/src/orchestrator/stable-keys.ts +164 -27
- package/src/orchestrator/steps.ts +70 -0
- package/src/orchestrator/task-hash.ts +34 -25
- package/src/orchestrator/task-log-buffer.ts +1 -1
- package/src/orchestrator/task-process.ts +94 -0
- package/src/orchestrator/telemetry-host.ts +4 -4
- package/src/orchestrator/telemetry.ts +14 -86
- package/src/orchestrator/upstream.ts +4 -181
- package/src/{orchestrator → report}/events.ts +41 -8
- package/src/{orchestrator → report}/forecast.ts +35 -2
- package/src/{orchestrator → report}/framed-output.ts +40 -34
- package/src/report/index.ts +36 -0
- package/src/{orchestrator → report}/logger.ts +158 -20
- package/src/{orchestrator → report}/status-line.ts +88 -7
- package/src/{orchestrator → report}/summary.ts +126 -35
- package/src/report/tally.ts +83 -0
- package/src/settings.ts +12 -39
- package/src/util/colors.ts +44 -4
- package/src/util/git-ignored.ts +143 -0
- package/src/util/index.ts +19 -4
- package/src/util/paths.ts +1 -1
- package/src/util/report.ts +13 -5
- package/src/util/settle.ts +14 -39
- package/src/util/tail.ts +1 -1
- package/src/util/ui.ts +17 -6
- package/src/workspace/affected.ts +81 -2
- package/src/workspace/config-imports.ts +1 -1
- package/src/workspace/config-schema.ts +142 -143
- package/src/workspace/lockfile.ts +1 -1
- package/src/workspace/migration.ts +19 -3
- package/schemas/summary.json +0 -133
- package/src/cache/chained-cache.ts +0 -280
- package/src/orchestrator/tally.ts +0 -112
- /package/src/{orchestrator → report}/path-links.ts +0 -0
- /package/src/{orchestrator → report}/plain-output.ts +0 -0
- /package/src/{orchestrator → report}/terminal-signals.ts +0 -0
package/README.md
CHANGED
|
@@ -65,8 +65,8 @@ export default defineProject({
|
|
|
65
65
|
build: {
|
|
66
66
|
exec: { command: 'tsc -b' },
|
|
67
67
|
dependsOn: ['^build'], // dependencies build first
|
|
68
|
+
inputs: { files: ['src/**', 'tsconfig.json'] },
|
|
68
69
|
cache: {
|
|
69
|
-
inputs: { files: ['src/**', 'tsconfig.json'] },
|
|
70
70
|
outputs: { files: ['dist/**'] },
|
|
71
71
|
},
|
|
72
72
|
},
|
package/docs/caching.md
CHANGED
|
@@ -8,8 +8,10 @@ invalidation_, _what's actually stored_, and _why_.
|
|
|
8
8
|
## Why caching is opt-in
|
|
9
9
|
|
|
10
10
|
A task is cached iff its `TaskConfig` provides a `cache` block, with
|
|
11
|
-
|
|
12
|
-
task always runs; no read, no write.
|
|
11
|
+
`cache.outputs.files` and the task's `inputs.files`. Omit `cache` and
|
|
12
|
+
the task always runs; no read, no write. `inputs` stands alone: a task
|
|
13
|
+
is keyed on it whether it caches or not, and its dependents fold that
|
|
14
|
+
key.
|
|
13
15
|
|
|
14
16
|
The reasoning:
|
|
15
17
|
|
|
@@ -55,9 +57,9 @@ over (in order):
|
|
|
55
57
|
`exec.remote` is dropped, because it says WHERE a task runs and not
|
|
56
58
|
what it does (§ What's NOT in the key). Captures:
|
|
57
59
|
- `exec` block (command, env declarations, timeout, persistent).
|
|
58
|
-
- `dependsOn` and `
|
|
59
|
-
- `cache.outputs.files`, `
|
|
60
|
-
`
|
|
60
|
+
- `dependsOn` and `inputs.tasks` declarations.
|
|
61
|
+
- `cache.outputs.files`, `inputs.files`, `inputs.env`,
|
|
62
|
+
`inputs.runtime` / `workspaceRuntime` declarations (the
|
|
61
63
|
strings themselves; their resolved file content / env values /
|
|
62
64
|
command output contribute separately).
|
|
63
65
|
- `description` (because it's part of the resolved object — even
|
|
@@ -73,14 +75,14 @@ over (in order):
|
|
|
73
75
|
pulled deps don't see them (their cache identity stays clean) — and
|
|
74
76
|
to a command: a requested default `build` runs none and folds none
|
|
75
77
|
(X-119).
|
|
76
|
-
5. **`
|
|
78
|
+
5. **`inputs.env` resolved values** — `[name, value]` pairs
|
|
77
79
|
read from host `process.env` at hash time (delimited `name\0value`
|
|
78
80
|
so boundaries are unambiguous). Listed names get their current
|
|
79
81
|
values; an unset name folds its bare name, with no `\0`, so unset
|
|
80
82
|
and set-to-empty are two keys (the child tells them apart:
|
|
81
83
|
`passThrough` leaves an unset name out). A name holding a NUL is
|
|
82
84
|
refused at load.
|
|
83
|
-
6. **`
|
|
85
|
+
6. **`inputs.runtime` resolved output** — `[command, output]`
|
|
84
86
|
pairs, where `output` is the trimmed stdout of each command run via
|
|
85
87
|
`sh -c` in the **project dir** at hash time; a command with stderr
|
|
86
88
|
(or a NUL in stdout) folds `\0<stdout length>\0<stdout><stderr>`,
|
|
@@ -88,12 +90,12 @@ over (in order):
|
|
|
88
90
|
The runtime-output analog of step 5: the command _strings_ are in
|
|
89
91
|
the resolved config (step 3), their _output_ is resolved live every
|
|
90
92
|
run. Folded with the command count + each `command\0output` pair.
|
|
91
|
-
7. **`
|
|
93
|
+
7. **`inputs.workspaceRuntime` resolved output** — same as step
|
|
92
94
|
8 but commands run at the **workspace root**, and the pairs fold
|
|
93
95
|
into a **distinct namespace** (`ws-runtime-values:`) so an identical
|
|
94
96
|
`(command, output)` never aliases the project-cwd `runtime` values.
|
|
95
97
|
8. **Filtered upstream task cache hashes** — every upstream task's
|
|
96
|
-
own cache key, filtered by `
|
|
98
|
+
own cache key, filtered by `inputs.tasks` (default: all of
|
|
97
99
|
them). Sorted by hash before folding so the ordering of `dependsOn`
|
|
98
100
|
doesn't change the key. This is the cascade mechanism: if anything
|
|
99
101
|
beneath you changes, your hash changes too. The set is `dependsOn`'s,
|
|
@@ -103,7 +105,7 @@ over (in order):
|
|
|
103
105
|
whatever is on disk, which nothing proves current, so a task whose
|
|
104
106
|
key folds one, and everything built on it, may hit but does not
|
|
105
107
|
save (`orchestrator/excluded-keys.ts`); a task with
|
|
106
|
-
`
|
|
108
|
+
`inputs.tasks: []` folds none and saves as usual.
|
|
107
109
|
9. **Plugin key material** — the `{ name: value }` pairs a plugin's
|
|
108
110
|
`key(task, ctx)` stage returned for this task, stored on the node as
|
|
109
111
|
sorted `plugin/name` pairs and folded after the upstream keys and BEFORE the input files,
|
|
@@ -147,7 +149,7 @@ over (in order):
|
|
|
147
149
|
[`modules/fingerprint-watch.md`](./modules/fingerprint-watch.md)).
|
|
148
150
|
With nothing claimed, no root file is read or watched.
|
|
149
151
|
|
|
150
|
-
10. **Input files' content hashes** — `
|
|
152
|
+
10. **Input files' content hashes** — `inputs.files` resolved to
|
|
151
153
|
a concrete list of project-relative paths (gitignore-aware,
|
|
152
154
|
declared-outputs-excluded, nested-projects-excluded), each file
|
|
153
155
|
contributing its **git blob OID** (v20). On a clean tree the OID
|
|
@@ -216,19 +218,23 @@ add` under a clean filter (`core.autocrlf=true`, a `text` rule)
|
|
|
216
218
|
same shape — git reports the nested repository as one changed path,
|
|
217
219
|
and every project under it is selected.
|
|
218
220
|
|
|
219
|
-
A task with **no `cache` block** derives a key too
|
|
220
|
-
fold it (step 8)
|
|
221
|
-
its
|
|
222
|
-
and
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
221
|
+
A task with **no `cache` block** derives a key too, from its
|
|
222
|
+
`inputs` — its dependents fold it (step 8), as Turbo and Nx hash
|
|
223
|
+
every task. That key is for its dependents only: the task always
|
|
224
|
+
runs, and no cache, local or remote, is asked about or handed
|
|
225
|
+
anything under it; its log goes to the run's own store
|
|
226
|
+
(`run/<id>/<task>/log`). A lookup follows `cache`, never a key
|
|
227
|
+
(`tests/uncached-no-lookup.test.ts`). One that also declares no
|
|
228
|
+
`inputs` is **keyless**: no
|
|
229
|
+
key can stand for what it read, so a key that folds it is this
|
|
230
|
+
run's alone, and every task behind it misses and saves nothing.
|
|
231
|
+
A task reading `**` with no `cache` folds its own outputs too,
|
|
232
|
+
since it declared none: its key moves whenever it writes a file
|
|
233
|
+
git does not ignore, so a cached dependent misses once more after
|
|
234
|
+
the upstream's first run. Ignore the outputs in git, narrow its
|
|
235
|
+
`inputs`, or declare the block (`tests/uncached-upstream-key.test.ts`).
|
|
236
|
+
|
|
237
|
+
Declaring no outputs, such a task may also have **written** anywhere
|
|
232
238
|
in its project, so what the run learned about that project at its
|
|
233
239
|
start — the git listing and the index OIDs — is dropped once its command exits, pass or fail, and a later key
|
|
234
240
|
re-lists and hashes by content. For the same reason a task after it
|
|
@@ -260,20 +266,19 @@ add` under a clean filter (`core.autocrlf=true`, a `text` rule)
|
|
|
260
266
|
dropping out of the key or the artifact without a word
|
|
261
267
|
(turborepo#9345). Rename it, or exclude it with a negated glob.
|
|
262
268
|
|
|
263
|
-
Your globs are a **filter over the set git reports**, so a
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
a
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
under it, folds nothing, and is refused (item 576).
|
|
269
|
+
Your globs are a **filter over the set git reports**, so a broad
|
|
270
|
+
glob never reaches a gitignored file. An entry that names one
|
|
271
|
+
outright does: a literal (`.env.local`, `gen`), or a glob whose fixed
|
|
272
|
+
prefix git ignores (`dist/**`), walked on disk under that prefix
|
|
273
|
+
only (owner, 2026-10-16). Such a file is hashed from disk with the
|
|
274
|
+
blob formula an untracked file gets, and is never stored or
|
|
275
|
+
restored. `--affected` reflects only what git sees, so an edit to
|
|
276
|
+
one never selects a task on its own (as in Nx); `vx watch` re-runs
|
|
277
|
+
it on an edit there.
|
|
278
|
+
A **glob** matching nothing stays silent, and so does a literal
|
|
279
|
+
naming a path that does not exist, until it does. An ignored
|
|
280
|
+
directory named outright folds every file under it; an empty one
|
|
281
|
+
folds nothing.
|
|
277
282
|
|
|
278
283
|
A **bracket is literal** in a task glob, and there are no character
|
|
279
284
|
classes (item 667): `app/[id]/**` folds the route directory
|
|
@@ -315,7 +320,15 @@ add` under a clean filter (`core.autocrlf=true`, a `text` rule)
|
|
|
315
320
|
second by two, since FAT32 keeps even seconds; a rewrite later in
|
|
316
321
|
the same stamp keeps it (`racyWindowMs`; until 2026-09-27, A-2 and
|
|
317
322
|
A-38, such a rewrite of the same size was a hit on the first bytes'
|
|
318
|
-
output).
|
|
323
|
+
output). Every such window compares a file's stamp, on its file
|
|
324
|
+
system's clock, with a moment vx read from its own, so a run first
|
|
325
|
+
measures how far the file system runs behind (one file written and
|
|
326
|
+
removed in the cache dir) and widens every window by a lag past the
|
|
327
|
+
kernel's tick (20 ms): a WSL2 VM drifted from the Windows host that
|
|
328
|
+
stamps `/mnt/c`, a Docker Desktop bind mount or an NFS server dated an
|
|
329
|
+
edit made while a command ran before the command, and the entry
|
|
330
|
+
filed the edit's output under the old bytes' key (until 2026-10-16).
|
|
331
|
+
That is the same value the index holds whenever no filter applies,
|
|
319
332
|
so a file's contribution doesn't flip across dirty↔clean
|
|
320
333
|
transitions. Folded as `(relPath, identity)` pairs, sorted for
|
|
321
334
|
stability across OSes and walk orders. The identity is the OID
|
|
@@ -330,19 +343,21 @@ add` under a clean filter (`core.autocrlf=true`, a `text` rule)
|
|
|
330
343
|
there a clean file's mode comes from an lstat too, and its OID
|
|
331
344
|
from the index (item 1076).
|
|
332
345
|
|
|
333
|
-
A **symlink** folds as git folds it
|
|
334
|
-
_string_ (its mode-120000 index OID),
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
A link
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
A **symlink** folds as git folds it, the blob of its target
|
|
347
|
+
_string_ (its mode-120000 index OID), and then the content behind
|
|
348
|
+
it, as Nx reads a linked file where it points: a file's identity, or
|
|
349
|
+
for a directory every file beneath it at any depth, tracked or not
|
|
350
|
+
(`.git`, `node_modules`, `.vx/cache` and `.yarn/cache` aside, as Nx's walker skips them), a link
|
|
351
|
+
among them followed in turn. The target may be anywhere: in the
|
|
352
|
+
project, elsewhere in the workspace, outside it. Retargeting a link
|
|
353
|
+
changes the key, to equal bytes too; an edit behind it changes the
|
|
354
|
+
key. A dangling link, or a loop of links, keys its target string
|
|
355
|
+
alone. `--affected` selects a link whose target changed (a file
|
|
356
|
+
the diff names, or one under a linked directory) as a changed path of
|
|
357
|
+
its project, and a target git cannot diff (outside the workspace,
|
|
358
|
+
git-ignored) on every run; `vx watch` arms each link's target and
|
|
359
|
+
names the link when it changes (`linked-inputs.ts`). Nx does not
|
|
360
|
+
enter a linked directory; vx does, since the task reads it.
|
|
346
361
|
|
|
347
362
|
The composition is seed-chained (`xxh3(part, prevDigest)`) with a
|
|
348
363
|
label prefix per field, so two different field layouts can't collide.
|
|
@@ -452,9 +467,20 @@ root-anchored output can land in any project's directory — and every
|
|
|
452
467
|
transitive dependant of one with it (their up-front keys fold a
|
|
453
468
|
preliminary key); a glob with no literal prefix reaches every project.
|
|
454
469
|
Before item 584 one such declaration emptied the tier graph-wide.
|
|
455
|
-
`
|
|
456
|
-
|
|
457
|
-
|
|
470
|
+
`vx lock` refuses a task whose input globs can match another task's
|
|
471
|
+
declared outputs (X-54, no opt-out), unless git ignores those outputs
|
|
472
|
+
whole (and the entry does not start inside them) or the producer is a
|
|
473
|
+
dependency its key does not fold, whose bytes it then hashes after the
|
|
474
|
+
producer ran, so the clauses on declared outputs fire only for such a
|
|
475
|
+
dependency read or a pair the check cannot decide. An output git ignores whole is no reach either:
|
|
476
|
+
a key reads only what git lists, so a `**/*` reader beside a gitignored
|
|
477
|
+
`**/dist/**` producer keys up front, the same key before and after the
|
|
478
|
+
build. The run asks git once, beside its walk and only when a reader
|
|
479
|
+
reaches a same-project output: one `git check-ignore` over each such
|
|
480
|
+
output's directory (`dist/`, and a fresh `__vx_probe__/dist/` for a
|
|
481
|
+
glob that starts with `**`), and the walk's listing must hold no file
|
|
482
|
+
the glob matches (a tracked output, a directory a `!` rule takes back);
|
|
483
|
+
anything git cannot answer keeps the key late. Undeclared writes, an
|
|
458
484
|
`outputs.workspaceFiles` producer upstream, a rewriter the key does not
|
|
459
485
|
fold, and runtime probes after a writer still leave a key preliminary.
|
|
460
486
|
The
|
|
@@ -541,9 +567,8 @@ Hard invariants of the remote prefetch:
|
|
|
541
567
|
stat pass.
|
|
542
568
|
- **Stable keys only.** A task with a _preliminary_ key (§ Local
|
|
543
569
|
restore tier's stability gate: undeclared writes, an
|
|
544
|
-
`outputs.workspaceFiles` producer upstream, or
|
|
545
|
-
|
|
546
|
-
declared outputs) is not prefetched: its key would name the wrong
|
|
570
|
+
`outputs.workspaceFiles` producer upstream, or input globs that match
|
|
571
|
+
the outputs of a dependency the key does not fold) is not prefetched: its key would name the wrong
|
|
547
572
|
artifact, and it resolves via the lazy read-through in `execute-task`. Instability propagates: a task that folds an
|
|
548
573
|
unstable upstream is itself unstable. When in doubt, skip.
|
|
549
574
|
- **At most once.** The `LayeredCache` keeps an in-flight map keyed by
|
|
@@ -553,7 +578,24 @@ Hard invariants of the remote prefetch:
|
|
|
553
578
|
later `get` finds it as a now-local hit — still reports
|
|
554
579
|
`source: 'remote'`, so the outcome is `cache-hit-remote`.
|
|
555
580
|
- **A remote-read-off policy** (`--no-cache`, `--force`, or
|
|
556
|
-
`--cache-policy=remote:`) fires no prefetch.
|
|
581
|
+
`--cache-policy=remote:`) fires no prefetch. With remote writes on
|
|
582
|
+
(`--force`), the same up-front keys go to one `hasMany` per remote
|
|
583
|
+
the run writes but does not read and that can batch, in the
|
|
584
|
+
background: no task waits for it, only each upload does. An upload
|
|
585
|
+
of a key that remote holds is skipped (an entry is immutable) and is
|
|
586
|
+
counted nowhere. A key the probe did not cover, or a remote with no
|
|
587
|
+
`hasMany`, asks `has` for that key inside its upload job; a probe
|
|
588
|
+
that fails uploads, as before.
|
|
589
|
+
- **No `has` before a `put`.** An entry is immutable: a remote's `put`
|
|
590
|
+
of a key it holds keeps the stored bytes, and a wire that would
|
|
591
|
+
overwrite checks inside (`turboCache()` asks HEAD unless a 404 or a
|
|
592
|
+
batch answer this run said the key is absent; `reapi()` looks the
|
|
593
|
+
action up beside its upload and keeps a stored result; Nx servers
|
|
594
|
+
answer 409). `ctx.cache.put` and a run's own entries send the `put`
|
|
595
|
+
alone.
|
|
596
|
+
- **`--dry` batches too.** The plan derives every key first, then asks
|
|
597
|
+
each remote with `hasMany` once; a remote without it is asked `has`
|
|
598
|
+
per key.
|
|
557
599
|
- **Never fail.** Every remote path (get / put / ingest / prefetch)
|
|
558
600
|
catches all errors and degrades to a cache miss. A remote 500, a
|
|
559
601
|
network drop, a corrupt artifact, or a failed integrity check can
|
|
@@ -573,16 +615,16 @@ fire-and-forget background upload**. The task's outcome (and its
|
|
|
573
615
|
dependents) never wait on upload latency — the uploads race alongside
|
|
574
616
|
the rest of the run and are drained before `cache.close()`, so a
|
|
575
617
|
short run still ships every artifact before the process exits. Upload
|
|
576
|
-
failures
|
|
618
|
+
failures are warned once per failure class and are otherwise ignored (the task
|
|
577
619
|
already succeeded; the only loss is the remote entry).
|
|
578
620
|
|
|
579
621
|
An upload is the outputs as the task wrote them; secret masking does
|
|
580
622
|
not reach file contents, so a task whose outputs embed a secret is one
|
|
581
623
|
to leave uncached ([security](./security.md)).
|
|
582
624
|
|
|
583
|
-
### Planning probes (`--dry`
|
|
625
|
+
### Planning probes (`--dry`)
|
|
584
626
|
|
|
585
|
-
The planning
|
|
627
|
+
The planning path (`vx run --dry`) predicts hits without
|
|
586
628
|
side effects: against a remote cache they use a **lightweight
|
|
587
629
|
existence probe** — no artifact download, no local ingest. A predicted
|
|
588
630
|
`hit-remote` means the artifact exists remotely; the bytes move only
|
|
@@ -602,12 +644,14 @@ get/save and the file-hash memo's writes (never `recordRun` / `stats` / `prune`
|
|
|
602
644
|
read-through (`remoteRead`), upload (`remoteWrite`), and prefetch
|
|
603
645
|
(`remoteRead`). The orchestrator derives two booleans per task:
|
|
604
646
|
|
|
605
|
-
- `willRead = task has a cache block AND it is not remote-only AND (localRead || remoteRead)`
|
|
647
|
+
- `willRead = task has a cache block AND it is not remote-only AND it folds no keyless task AND (localRead || remoteRead)`
|
|
606
648
|
- `willWrite = task has a cache block AND it is not remote-only AND (localWrite || remoteWrite)`
|
|
607
649
|
|
|
608
650
|
A task reads the cache only when `willRead`, saves only when
|
|
609
|
-
`willWrite
|
|
610
|
-
|
|
651
|
+
`willWrite` and it folds no keyless task, and cleans its declared outputs
|
|
652
|
+
before exec only when `willWrite`. A keyless task runs a command and
|
|
653
|
+
declares no `inputs`: its key is unknown, so a task that folds it
|
|
654
|
+
(through `dependsOn`, directly or through a group) always misses. Remote-only is an `exec.remote: 'only'` task placed on a
|
|
611
655
|
remote executor: it never touches this machine's disk (no probe, no
|
|
612
656
|
restore, no output clean, no local save), and its result lives in the
|
|
613
657
|
remote executor's own store.
|
|
@@ -615,8 +659,8 @@ remote executor's own store.
|
|
|
615
659
|
The CLI maps three flags to a policy (precedence: start all-on → apply
|
|
616
660
|
`--cache-policy` → `--no-cache` forces all off → `--force` forces both reads
|
|
617
661
|
off): `--no-cache` = everything off (no read, no write, no output
|
|
618
|
-
clean); `--force` = reads off / writes on (re-execute
|
|
619
|
-
|
|
662
|
+
clean); `--force` = reads off / writes on (re-execute, outputs cleaned;
|
|
663
|
+
a key already stored, locally or in a remote, is kept as it is); `--cache-policy=<spec>` = explicit per-layer control.
|
|
620
664
|
Off CI, unless a spec names the remote, `remoteWrite` starts off: a key
|
|
621
665
|
holds only declared inputs, so a laptop's undeclared tool or file could
|
|
622
666
|
replay on CI. See `docs/cli.md` § Cache control.
|
|
@@ -665,7 +709,10 @@ is on):
|
|
|
665
709
|
Until 2026-09-27 (A-3) the rename came first, and a commit refused
|
|
666
710
|
past the busy timeout left the new bytes beside the old rows: every
|
|
667
711
|
later hit on the key failed the task as a corrupt artifact.
|
|
668
|
-
A
|
|
712
|
+
A `--force` save of a key the index already holds is skipped, so
|
|
713
|
+
`--force` rewrites nothing. A re-save (a key dropped as corrupt, or
|
|
714
|
+
two writers that both found it absent) first moves the previous
|
|
715
|
+
artifact aside under
|
|
669
716
|
a temp name and unlinks it after the commit: ext4 flushes the
|
|
670
717
|
incoming file when a rename replaces one, 0.55 ms a save on the main
|
|
671
718
|
thread against 0.04 (a forced 1,000-task run 4.01 s → 3.46 s,
|
|
@@ -755,7 +802,7 @@ from the snapshot's index OID for an output (or for an input the task
|
|
|
755
802
|
rewrote) restored the bytes from before the command.
|
|
756
803
|
|
|
757
804
|
A declared set that resolves to **nothing** is said on the run's
|
|
758
|
-
status line. `
|
|
805
|
+
status line. `inputs matched no files (lib/**)` — the key would
|
|
759
806
|
not change when the source does — is said on every miss of a cacheable
|
|
760
807
|
task, right after `describeTaskInputs` and before the command, whether
|
|
761
808
|
or not it saves. `cache.outputs matched no files (build/**)` — an empty
|
|
@@ -919,13 +966,13 @@ A task's cache becomes invalid when any of these change:
|
|
|
919
966
|
| Any package manager updates a lockfile (`pnpm`, `npm`, `yarn`, `bun`) | step 9 — the `key` material of the plugin that claims it (`@vzn/vx-lockfile`: only the projects whose dependency closure moved); **NOT a trigger** with no such plugin, since core keys no package file |
|
|
920
967
|
| Edit an install setting (`.npmrc`, `bunfig.toml`, `pnpm-workspace.yaml`, `.yarnrc.yml`) | step 9 — the claiming lockfile plugin folds it whole into every task; **NOT a trigger** without one |
|
|
921
968
|
| Edit `package.json`'s `workspaces` field | membership: a project that joins or leaves changes which projects exist and which nested dirs its parent's globs exclude (step 10) |
|
|
922
|
-
| Edit the project's `package.json` (dep / version / scripts change) | step 10 — an input file of every cached task when `@vzn/vx-lockfile` is declared (it adds `package.json` to `
|
|
969
|
+
| Edit the project's `package.json` (dep / version / scripts change) | step 10 — an input file of every cached task when `@vzn/vx-lockfile` is declared (it adds `package.json` to `inputs.files`), or when a task lists it; **NOT a trigger** otherwise |
|
|
923
970
|
| Edit the task's `vx.config.ts` | step 3 (task config hash) |
|
|
924
971
|
| Edit a config file that the task config imports | step 3 (configHash sees the resolved object after Bun evaluates imports) |
|
|
925
972
|
| Change CLI `forwardArgs` (after `--`) | step 4 |
|
|
926
|
-
| Change a `
|
|
927
|
-
| Change the combined stdout+stderr of a `
|
|
928
|
-
| Change the combined stdout+stderr of a `
|
|
973
|
+
| Change a `inputs.env` host value | step 5 |
|
|
974
|
+
| Change the combined stdout+stderr of a `inputs.runtime` command (resolved at hash time) | step 6 |
|
|
975
|
+
| Change the combined stdout+stderr of a `inputs.workspaceRuntime` command (resolved at hash time) | step 7 |
|
|
929
976
|
| Upstream task's cache key changes (because its inputs changed) | step 8 |
|
|
930
977
|
| Bump `CACHE_VERSION` | step 1 — orphans every entry |
|
|
931
978
|
| Change `exec.env.passThrough` _values_ alone | **NOT a trigger** by design — passThrough values are host-specific |
|
|
@@ -939,7 +986,7 @@ re-runs automatically.
|
|
|
939
986
|
|
|
940
987
|
## Cross-project boundaries
|
|
941
988
|
|
|
942
|
-
A project's `
|
|
989
|
+
A project's `inputs.files` globs **never** reach into another
|
|
943
990
|
project's directory, even if a `**/*` pattern would otherwise match.
|
|
944
991
|
|
|
945
992
|
`workspace/nested-dirs.ts` computes the set of nested project
|
|
@@ -956,21 +1003,22 @@ globs is `dependsOn` + upstream-hash propagation (step 8).
|
|
|
956
1003
|
For a task with `exec.sandbox` and `cache`, the sandbox enforces that
|
|
957
1004
|
"only". A workspace package reached through a `node_modules` link is
|
|
958
1005
|
granted only when the task's key folds a task of that package, walking
|
|
959
|
-
the same selection `
|
|
960
|
-
(`orchestrator/keyed-projects.ts`, over `
|
|
1006
|
+
the same selection `inputs.tasks` applies at hash time
|
|
1007
|
+
(`orchestrator/keyed-projects.ts`, over `graph/folds.ts`'s
|
|
961
1008
|
`selectFoldedDeps`); every other link target inside the root is
|
|
962
1009
|
withheld, so an import of a sibling the key never sees fails and is
|
|
963
1010
|
reported instead of caching a result an edit there would not re-run.
|
|
964
|
-
Any exec task counts, cached or not:
|
|
965
|
-
`inputs
|
|
966
|
-
|
|
967
|
-
e2e behind a dev server re-runs when
|
|
1011
|
+
Any exec task counts, cached or not: its key folds its project's files
|
|
1012
|
+
through its `inputs`, and one with no `inputs` is keyless, so a key that
|
|
1013
|
+
folds it is never stored or read. A persistent task counts the same way,
|
|
1014
|
+
so a cached e2e behind a dev server keyed on its sources re-runs when
|
|
1015
|
+
they change.
|
|
968
1016
|
The coverage is per package, not per file: an edge to `ui#source`
|
|
969
1017
|
(inputs `src/**`) also admits a read of `ui/README.md`, whose edit moves
|
|
970
1018
|
no key — the same limit a grant wider than a task's own inputs already
|
|
971
1019
|
has. A task with no `cache` keeps every link, having no key to be stale.
|
|
972
1020
|
|
|
973
|
-
**Exception:** `
|
|
1021
|
+
**Exception:** `inputs.workspaceFiles` /
|
|
974
1022
|
`cache.outputs.workspaceFiles` are workspace-root-anchored and apply
|
|
975
1023
|
NO boundary rule — a deliberate escape hatch (owner call: "they don't
|
|
976
1024
|
care about boundaries; it is bad practice but is there"). Prefer
|
|
@@ -1032,8 +1080,8 @@ the gate exists to avoid paying needlessly.
|
|
|
1032
1080
|
|
|
1033
1081
|
## Runtime inputs and the lock (the env parallel)
|
|
1034
1082
|
|
|
1035
|
-
`
|
|
1036
|
-
exactly on `
|
|
1083
|
+
`inputs.runtime` / `inputs.workspaceRuntime` are modeled
|
|
1084
|
+
exactly on `inputs.env`, and they share its asymmetry with the
|
|
1037
1085
|
lockfile:
|
|
1038
1086
|
|
|
1039
1087
|
- **The command _strings_ live in the resolved config**, so `vx lock`
|
|
@@ -1185,7 +1233,7 @@ by this user, or point cacheDir / --cache-dir at one that is.` (A-40).
|
|
|
1185
1233
|
An input the key must hash that this user cannot read fails its task
|
|
1186
1234
|
naming the read: `<path> is not readable by this user (EACCES), and vx
|
|
1187
1235
|
reads it to derive a cache key. Make it readable, or, for a task input,
|
|
1188
|
-
take it out of
|
|
1236
|
+
take it out of inputs.files.` (A-50).
|
|
1189
1237
|
|
|
1190
1238
|
By default the entries and their artifacts live in a **shared store**
|
|
1191
1239
|
in `~/.vx/<id>/cache`, and each workspace keeps only its own index in
|
|
@@ -1470,7 +1518,7 @@ keep). The artifacts stay: each is indexed again from its own bytes
|
|
|
1470
1518
|
when its task next asks for its key (below).
|
|
1471
1519
|
|
|
1472
1520
|
```sql
|
|
1473
|
-
-- src/cache/schema.ts (SCHEMA_VERSION = '
|
|
1521
|
+
-- src/cache/schema.ts (SCHEMA_VERSION = 'v40', in cache.ts)
|
|
1474
1522
|
-- With a shared store, entries, entry_stdout, output_files, entry_inputs,
|
|
1475
1523
|
-- store_meta, task_durations, task_outcomes, blob_sizes and plugin_blobs live in its store.db, attached as `store`; the rest is
|
|
1476
1524
|
-- the workspace's cache.db. A named cache dir holds all of them.
|
|
@@ -1527,7 +1575,8 @@ CREATE TABLE runs (
|
|
|
1527
1575
|
timed_out INTEGER, -- 1 when vx's own timeout killed it
|
|
1528
1576
|
sandbox_violations INTEGER, -- the sandbox's violation count
|
|
1529
1577
|
not_ready TEXT, -- 'timeout' | 'exited' | 'spawn' (persistent task)
|
|
1530
|
-
restored INTEGER
|
|
1578
|
+
restored INTEGER, -- v31, on a hit: 1 = outputs restored, 0 = up to date
|
|
1579
|
+
remote_error TEXT -- v40: each remote read or write of the key that failed
|
|
1531
1580
|
);
|
|
1532
1581
|
|
|
1533
1582
|
-- Two indexes, both append-only under a run's inserts: every row of a run
|
|
@@ -1628,7 +1677,7 @@ CREATE TABLE output_dirs (
|
|
|
1628
1677
|
|
|
1629
1678
|
-- v22 (Tier 3): one header row per `vx run` invocation. The `runs`
|
|
1630
1679
|
-- table is per-task; this is the per-invocation record carrying the
|
|
1631
|
-
-- command, git/CI/host context,
|
|
1680
|
+
-- command, git/CI/host context, and run-level counts. Recorded
|
|
1632
1681
|
-- atomically alongside `runs` via recordRunBundle (one transaction).
|
|
1633
1682
|
CREATE TABLE invocations (
|
|
1634
1683
|
run_id TEXT PRIMARY KEY, -- UUIDv7, == runs.run_id
|
|
@@ -1657,8 +1706,7 @@ CREATE TABLE invocations (
|
|
|
1657
1706
|
host TEXT, -- os.hostname()
|
|
1658
1707
|
os TEXT, -- process.platform
|
|
1659
1708
|
arch TEXT, -- process.arch
|
|
1660
|
-
vx_version TEXT NOT NULL
|
|
1661
|
-
tags TEXT NOT NULL DEFAULT '{}' -- JSON object {k:v} from --tag
|
|
1709
|
+
vx_version TEXT NOT NULL
|
|
1662
1710
|
);
|
|
1663
1711
|
CREATE INDEX invocations_started ON invocations(started_at);
|
|
1664
1712
|
CREATE INDEX invocations_branch ON invocations(branch);
|
|
@@ -1821,10 +1869,19 @@ moat.
|
|
|
1821
1869
|
passthrough still bumps the key for affected tasks.
|
|
1822
1870
|
- **Files outside the project directory that aren't declared.**
|
|
1823
1871
|
Workspace-root configs (`tsconfig.base.json`, etc.) are not
|
|
1824
|
-
auto-included — declare them via `
|
|
1872
|
+
auto-included — declare them via `inputs.workspaceFiles`
|
|
1825
1873
|
(root-anchored globs).
|
|
1874
|
+
- **Gitignored files no entry names.** Vite and Next load `.env.local`
|
|
1875
|
+
on their own; a broad glob skips it, so editing it replays the old
|
|
1876
|
+
build. Name it: `files: ['src/**', '.env.local']`. Its bytes are
|
|
1877
|
+
hashed, never stored, and a missing file is simply not an input.
|
|
1878
|
+
Machines with different files compute different keys, so they miss
|
|
1879
|
+
each other's shared entries. `--affected` ignores it (only what git
|
|
1880
|
+
sees selects a task), and `vx watch` re-runs on its edit. A probe
|
|
1881
|
+
(`runtime: ['cat .env.local 2>/dev/null | cksum']`) still works where
|
|
1882
|
+
a command, not a path, decides what to read.
|
|
1826
1883
|
- **Node / Bun / OS / build-tool versions — unless you declare them.**
|
|
1827
|
-
The canonical mechanism is `
|
|
1884
|
+
The canonical mechanism is `inputs.runtime` /
|
|
1828
1885
|
`workspaceRuntime` (e.g. `workspaceRuntime: ['node -v']`): the
|
|
1829
1886
|
command output is resolved live at hash time, so it stays correct
|
|
1830
1887
|
under the lock. Avoid baking versions via
|
|
@@ -2141,7 +2198,7 @@ breaking footer).
|
|
|
2141
2198
|
migration), so this costs one cold rebuild.
|
|
2142
2199
|
- **SCHEMA v21 → v22 (no `CACHE_VERSION` bump)**: Tier-3 dashboard
|
|
2143
2200
|
tables — `invocations` (one header row per `vx run` with command,
|
|
2144
|
-
git/CI/host context,
|
|
2201
|
+
git/CI/host context, and run-level counts, recorded with `runs`
|
|
2145
2202
|
via `Cache.recordRunBundle`) and `entry_inputs` (one row per cache-key
|
|
2146
2203
|
component, keyed by the cache-ENTRY hash — the input-fingerprint
|
|
2147
2204
|
moat). `entry_inputs` is written **inside the entry-save transaction,
|
|
@@ -2158,11 +2215,11 @@ breaking footer).
|
|
|
2158
2215
|
committed, so git enumerates it, but it's vx's own frozen-config
|
|
2159
2216
|
metadata — never a task input. Without this, a `vx lock` re-write
|
|
2160
2217
|
busts every key on a project that globs the root lockfile (a broad
|
|
2161
|
-
`**/*` on the root project). Tasks whose `
|
|
2218
|
+
`**/*` on the root project). Tasks whose `inputs.files` never
|
|
2162
2219
|
matched it derive byte-identical keys. No SCHEMA bump — only the
|
|
2163
2220
|
hashed file set changed, not the key layout or on-disk format.
|
|
2164
2221
|
|
|
2165
|
-
- **v22 → v23**: fold `
|
|
2222
|
+
- **v22 → v23**: fold `inputs.runtime` / `workspaceRuntime`
|
|
2166
2223
|
command output into the key (two namespaced sections after
|
|
2167
2224
|
env-values). The command _strings_ stay in the config hash (step 3);
|
|
2168
2225
|
their combined trimmed stdout+stderr is resolved live at hash time
|
|
@@ -2207,7 +2264,7 @@ breaking footer).
|
|
|
2207
2264
|
- **v11 → v12** (PR #42): project's `package.json` bytes folded into
|
|
2208
2265
|
every task's cache key implicitly. Matches Turbo / Nx "implicit
|
|
2209
2266
|
dependencies" behavior — a `package.json` dep change invalidates
|
|
2210
|
-
the project's tasks even when `
|
|
2267
|
+
the project's tasks even when `inputs.files` is narrow and
|
|
2211
2268
|
doesn't cover the file.
|
|
2212
2269
|
- **v12 → v13** (PR #65): per-entry on-disk layout unified. Outputs
|
|
2213
2270
|
moved from `<hash>/<rel>` (mixed with metadata) to
|