@vzn/vx 0.0.651 → 0.0.652

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 (78) hide show
  1. package/README.md +2 -3
  2. package/package.json +5 -5
  3. package/schemas/show.json +14 -20
  4. package/src/bin.ts +12 -0
  5. package/src/cache/archive.ts +1 -1
  6. package/src/cache/cache.ts +19 -5
  7. package/src/cache/history-stream.ts +134 -0
  8. package/src/cache/history-writer.ts +76 -0
  9. package/src/cache/index.ts +2 -0
  10. package/src/cache/inputs.ts +74 -14
  11. package/src/cache/layer.ts +6 -6
  12. package/src/cache/lockfile-memo.ts +2 -1
  13. package/src/cache/run-history.ts +46 -17
  14. package/src/cache/schema.ts +4 -1
  15. package/src/cli/cache.ts +2 -2
  16. package/src/cli/help.ts +2 -2
  17. package/src/cli/index.ts +24 -0
  18. package/src/cli/info.ts +1 -1
  19. package/src/cli/last.ts +34 -2
  20. package/src/cli/plugin-templates.ts +36 -45
  21. package/src/cli/show.ts +7 -10
  22. package/src/cli/watch-judge.ts +1 -1
  23. package/src/cli/watch-set.ts +4 -4
  24. package/src/cli/watch.ts +11 -8
  25. package/src/config.ts +47 -30
  26. package/src/exec/executor.ts +40 -1
  27. package/src/exec/index.ts +1 -0
  28. package/src/exec/sandbox-paths.ts +3 -1
  29. package/src/exec/sandbox-runtime.ts +314 -29
  30. package/src/exec/task-proxy.ts +93 -0
  31. package/src/graph/definition.ts +16 -3
  32. package/src/graph/scheduler.ts +3 -1
  33. package/src/graph/task-graph.ts +24 -23
  34. package/src/history/failure-mode.ts +24 -1
  35. package/src/history/index.ts +1 -0
  36. package/src/history/metrics.ts +5 -5
  37. package/src/index.ts +4 -2
  38. package/src/orchestrator/admission.ts +1 -1
  39. package/src/orchestrator/command-windows.ts +1 -1
  40. package/src/orchestrator/deferred-outputs.ts +1 -1
  41. package/src/orchestrator/download-policy.ts +5 -7
  42. package/src/orchestrator/excluded-keys.ts +1 -1
  43. package/src/orchestrator/execute-task.ts +51 -18
  44. package/src/orchestrator/hit-restore.ts +5 -6
  45. package/src/orchestrator/keyed-projects.ts +28 -0
  46. package/src/orchestrator/local-shortcircuit.ts +2 -2
  47. package/src/orchestrator/miss-save.ts +27 -8
  48. package/src/orchestrator/persistent-task.ts +2 -0
  49. package/src/orchestrator/placement.ts +1 -1
  50. package/src/orchestrator/plan.ts +2 -2
  51. package/src/orchestrator/plugin-host.ts +6 -2
  52. package/src/orchestrator/plugin.ts +6 -0
  53. package/src/orchestrator/remote-prefetch.ts +1 -1
  54. package/src/orchestrator/run-failures.ts +61 -12
  55. package/src/orchestrator/run-keys.ts +1 -1
  56. package/src/orchestrator/run-records.ts +86 -67
  57. package/src/orchestrator/run-report.ts +1 -1
  58. package/src/orchestrator/run.ts +92 -19
  59. package/src/orchestrator/sandbox-request.ts +50 -7
  60. package/src/orchestrator/shell-verdict.ts +9 -0
  61. package/src/orchestrator/stable-keys.ts +17 -18
  62. package/src/orchestrator/task-artifacts.ts +1 -1
  63. package/src/orchestrator/task-hash.ts +4 -4
  64. package/src/orchestrator/telemetry.ts +5 -1
  65. package/src/report/events.ts +3 -3
  66. package/src/report/framed-output.ts +16 -7
  67. package/src/report/logger.ts +1 -1
  68. package/src/report/plain-output.ts +2 -0
  69. package/src/report/summary.ts +5 -5
  70. package/src/settings.ts +8 -8
  71. package/src/util/config-frame.ts +128 -6
  72. package/src/util/paths.ts +1 -1
  73. package/src/util/settle.ts +2 -1
  74. package/src/util/tail.ts +4 -1
  75. package/src/workspace/bracket-literal.ts +2 -2
  76. package/src/workspace/config-schema.ts +66 -61
  77. package/src/workspace/filter.ts +8 -1
  78. package/src/workspace/migration.ts +12 -6
package/src/config.ts CHANGED
@@ -28,6 +28,10 @@ export interface WorkspaceConfig {
28
28
  baseBranch?: string
29
29
  run?: RunSettings
30
30
  cache?: CacheSettings
31
+ /** Input defaults for every task. */
32
+ inputs?: WorkspaceInputs
33
+ /** Output defaults for every task. */
34
+ outputs?: WorkspaceOutputs
31
35
  lock?: LockSettings
32
36
  watch?: WatchSettings
33
37
  /**
@@ -76,6 +80,18 @@ export interface RunSettings {
76
80
  rules?: WorkspaceRules
77
81
  }
78
82
 
83
+ /** `WorkspaceConfig.inputs`. */
84
+ export interface WorkspaceInputs {
85
+ /** The `inputs.stability` of every task that does not set its own. */
86
+ stability?: InputStability
87
+ }
88
+
89
+ /** `WorkspaceConfig.outputs`. */
90
+ export interface WorkspaceOutputs {
91
+ /** The `outputs.clean` of every task that does not set its own. */
92
+ clean?: boolean
93
+ }
94
+
79
95
  /** `WorkspaceConfig.cache`: where the cache lives and what a run does with it. */
80
96
  export interface CacheSettings {
81
97
  /**
@@ -93,10 +109,6 @@ export interface CacheSettings {
93
109
  policy?: string
94
110
  /** Which remote hits land on disk: `all` (the default), only the requested tasks' (`toplevel`), or `none`. */
95
111
  download?: 'all' | 'toplevel' | 'none'
96
- /** The `cache.inputStability` of every task that does not set its own. */
97
- inputStability?: InputStability
98
- /** The `cache.outputs.clean` of every task that does not set its own. */
99
- outputs?: { clean?: boolean }
100
112
  /**
101
113
  * Which runs keep a failed task's log and the files its outputs left in
102
114
  * the remote: CI runs (`'ci'`, the default), runs off CI (`'local'`), or
@@ -305,13 +317,15 @@ export interface TaskConfig {
305
317
  * keyed group: its inputs are a key its dependents fold, with no process.
306
318
  */
307
319
  inputs?: InputsConfig
320
+ /** What the task produces: saved on a miss, restored on a hit. Needs `cache: true`. */
321
+ outputs?: TaskOutputs
308
322
  /**
309
- * Caching configuration. **Caching is opt-in.** If this field is omitted,
310
- * the task always runs and nothing is read from or written to the cache.
311
- * Requires `exec` and `inputs`: the key is the task's inputs, and a group
312
- * runs nothing to cache.
323
+ * `true` caches the task. **Caching is opt-in.** Omitted or `false`, the
324
+ * task always runs and nothing is read from or written to the cache.
325
+ * Requires `exec`, `inputs` and `outputs`: the key is the task's inputs,
326
+ * and a group runs nothing to cache.
313
327
  */
314
- cache?: CacheConfig
328
+ cache?: boolean
315
329
  }
316
330
 
317
331
  /**
@@ -335,7 +349,7 @@ export interface TaskConfig {
335
349
  * from the other coupled them both ways — a declaration added for
336
350
  * caching silently widened the sandbox, and a path the task needed had
337
351
  * to be laundered through the cache key to get it (2026-09-05).
338
- * A declared `cache.outputs` is NOT a write grant; the request
352
+ * A declared `outputs` is NOT a write grant; the request
339
353
  * `sandbox-request.ts` builds carries no write of its own and binds
340
354
  * `allow.write` alone. This comment claimed the opposite — here and on
341
355
  * both grant fields — while the file beside it and `schema.md` both said
@@ -391,6 +405,13 @@ export interface SandboxConfig {
391
405
  weakerWhenNested?: boolean
392
406
  /** Accept weaker network isolation (host proxy) for lower overhead. */
393
407
  weakerNetworkIsolation?: boolean
408
+ /**
409
+ * Refuse every read outside the workspace too (`/usr`, `~/.cache`, the
410
+ * system toolchain), not only the workspace's other projects. A path
411
+ * outside it is readable only when `allow.read` names it; the shell a
412
+ * task runs in stays readable, so a task can start.
413
+ */
414
+ hermetic?: boolean
394
415
  }
395
416
 
396
417
  /** The denials `sandbox.ignore` can leave out of the report: patterns per class. */
@@ -410,9 +431,9 @@ export interface SandboxGrants {
410
431
  read?: readonly string[]
411
432
  /**
412
433
  * Writable paths or globs. The task can write NOTHING without these:
413
- * declaring `cache.outputs.files` grants no write, and a task whose
434
+ * declaring `outputs.files` grants no write, and a task whose
414
435
  * only declaration is its outputs writes into a tmpfs that evaporates
415
- * (vx then warns that `cache.outputs` matched no files).
436
+ * (vx then warns that `outputs` matched no files).
416
437
  */
417
438
  write?: readonly string[]
418
439
  /**
@@ -585,17 +606,6 @@ export interface ExecEnv {
585
606
  secret?: readonly string[]
586
607
  }
587
608
 
588
- export interface CacheConfig {
589
- /** What this task produces. Captured for restore on a cache hit. */
590
- outputs: CacheOutputs
591
- /**
592
- * What a write to an input while the command runs means. Each field,
593
- * highest first: its env var, this, the workspace's `inputStability`,
594
- * the default (`'immutable'`, `'warn'`).
595
- */
596
- inputStability?: InputStability
597
- }
598
-
599
609
  export interface InputStability {
600
610
  /**
601
611
  * `'immutable'` (the default): any write to an input during the command
@@ -615,7 +625,7 @@ export interface InputStability {
615
625
  severity?: 'warn' | 'error'
616
626
  }
617
627
 
618
- export interface CacheOutputs {
628
+ export interface TaskOutputs {
619
629
  /**
620
630
  * Files produced by this task, as project-relative globs. Pass `[]`
621
631
  * when the task has no files to capture (e.g. `lint`, `typecheck`).
@@ -640,7 +650,7 @@ export interface CacheOutputs {
640
650
  * (tsc's `.tsbuildinfo`) instead of removing them first. A run that
641
651
  * leaves a file it found untouched saves nothing, so an entry holds only
642
652
  * what a clean run would leave. A retry still starts clean. Omitted →
643
- * the workspace's `cache.outputs.clean`, else `true`.
653
+ * the workspace's `outputs.clean`, else `true`.
644
654
  */
645
655
  clean?: boolean
646
656
  }
@@ -716,6 +726,13 @@ export interface InputsConfig {
716
726
  * tool versions, OS info, etc.
717
727
  */
718
728
  workspaceRuntime?: readonly string[]
729
+ /**
730
+ * What a write to an input while the command runs means for the save.
731
+ * Each field, highest first: its env var, this, the workspace's
732
+ * `inputs.stability`, the default (`'immutable'`, `'warn'`). Needs
733
+ * `cache: true`.
734
+ */
735
+ stability?: InputStability
719
736
  }
720
737
 
721
738
  /**
@@ -785,9 +802,7 @@ export function defineProject<const T extends ProjectConfig>(
785
802
  dependsOn?: readonly DependsOnEntry<Extract<keyof NonNullable<T['tasks']>, string>>[]
786
803
  exec?: Known<At<NonNullable<T['tasks']>[K], 'exec'>, ExecConfig>
787
804
  inputs?: Known<At<NonNullable<T['tasks']>[K], 'inputs'>, InputsConfig>
788
- cache?: Known<At<NonNullable<T['tasks']>[K], 'cache'>, CacheConfig> & {
789
- outputs?: Known<At<At<NonNullable<T['tasks']>[K], 'cache'>, 'outputs'>, CacheOutputs>
790
- }
805
+ outputs?: Known<At<NonNullable<T['tasks']>[K], 'outputs'>, TaskOutputs>
791
806
  }
792
807
  }
793
808
  },
@@ -801,9 +816,11 @@ export function defineWorkspace<T extends WorkspaceConfig>(
801
816
  run?: Known<At<T, 'run'>, RunSettings> & {
802
817
  rules?: Known<At<At<T, 'run'>, 'rules'>, WorkspaceRules>
803
818
  }
804
- cache?: Known<At<T, 'cache'>, CacheSettings> & {
805
- inputStability?: Known<At<At<T, 'cache'>, 'inputStability'>, InputStability>
819
+ cache?: Known<At<T, 'cache'>, CacheSettings>
820
+ inputs?: Known<At<T, 'inputs'>, WorkspaceInputs> & {
821
+ stability?: Known<At<At<T, 'inputs'>, 'stability'>, InputStability>
806
822
  }
823
+ outputs?: Known<At<T, 'outputs'>, WorkspaceOutputs>
807
824
  lock?: Known<At<T, 'lock'>, LockSettings>
808
825
  watch?: Known<At<T, 'watch'>, WatchSettings>
809
826
  },
@@ -43,6 +43,11 @@ export interface ExecuteSandbox {
43
43
  * machine's paths and platform, hands `sandboxRequestFor`.
44
44
  */
45
45
  readonly keyed?: readonly string[]
46
+ /**
47
+ * The dependencies' outputs it may read (absolute), which an executor
48
+ * that builds the request again hands `sandboxRequestFor` too.
49
+ */
50
+ readonly dependencyReads?: readonly string[]
46
51
  }
47
52
 
48
53
  /** One declared input file, as the cache key saw it. */
@@ -108,7 +113,7 @@ export interface ExecuteRequest {
108
113
  /** `${project}#${task}` — for executors that route or log by task. */
109
114
  readonly taskId: string
110
115
  readonly workspaceRoot: string
111
- /** Present for cacheable tasks (the miss path); absent when the task declares no `cache`. */
116
+ /** Present for cacheable tasks (the miss path); absent for a task without `cache: true`. */
112
117
  readonly inputs?: TaskInputs
113
118
  /**
114
119
  * The task's cache key (present when the task is cacheable). An executor
@@ -145,6 +150,12 @@ export interface ExecuteRequest {
145
150
  * executor's `platforms`: its key includes it. Absent when the task names none.
146
151
  */
147
152
  readonly platform?: string
153
+ /**
154
+ * Which attempt of the task this is, from 2 when it retries; absent on
155
+ * the first. An executor that can, runs a retry somewhere other than
156
+ * the attempt before it, so the machine is not the flake.
157
+ */
158
+ readonly attempt?: number
148
159
  /**
149
160
  * Whether this task's outputs must land on the submitter's disk now.
150
161
  * 'eager' (or absent) = materialise as today. 'deferred' = leave them
@@ -377,6 +388,13 @@ export interface TaskExecutor {
377
388
  * anything but a finite number, reports none.
378
389
  */
379
390
  idealMs?(): number | undefined
391
+ /**
392
+ * The workers that would have run the run best, judged after it from
393
+ * what each task took: for the run summary, so its reader can change
394
+ * how many workers the executor may start and of what size. Asked once
395
+ * at the end, only when a sink is on. A throw reports none.
396
+ */
397
+ advice?(): readonly WorkerAdvice[]
380
398
  /**
381
399
  * Its own Chrome-trace events for `vx run --profile` (the machines it ran
382
400
  * tasks on, in time), asked once at the end, only when the flag is set.
@@ -423,6 +441,27 @@ export interface MachineReport {
423
441
  startMs?: number
424
442
  }
425
443
 
444
+ /**
445
+ * Workers that would have run the run best (`TaskExecutor.advice`): `of`
446
+ * says on what. `declared`: as many of each size the executor may start
447
+ * as end it soonest; `more`: past each size's limit, when that ends it
448
+ * sooner; `any`: one shape a platform, of any cores, ending as soon for
449
+ * the least core time.
450
+ */
451
+ export interface WorkerAdvice {
452
+ of: 'declared' | 'more' | 'any'
453
+ /** The run's end on them, from its start. */
454
+ endMs: number
455
+ workers: readonly {
456
+ count: number
457
+ cores: number
458
+ memoryBytes: number
459
+ platform?: string
460
+ /** The size it is, for `declared` and `more`. */
461
+ size?: string
462
+ }[]
463
+ }
464
+
426
465
  /**
427
466
  * What a remote executor rejects with when it gives a task back: core runs
428
467
  * the same request on the local floor and says `reason` once (a remote that
package/src/exec/index.ts CHANGED
@@ -64,6 +64,7 @@ export {
64
64
  type ExecuteSandbox,
65
65
  type InputFile,
66
66
  type MachineReport,
67
+ type WorkerAdvice,
67
68
  type ServerSpec,
68
69
  type TaskExecutor,
69
70
  type TaskInputs,
@@ -102,6 +102,8 @@ export function sandboxReads(
102
102
  readonly config: {
103
103
  readonly allowRead: readonly string[]
104
104
  readonly allowWrite: readonly string[]
105
+ /** Denies the whole host, not only `baseDenyRead`: only a grant reads. */
106
+ readonly hermetic?: boolean
105
107
  }
106
108
  },
107
109
  file: string,
@@ -109,7 +111,7 @@ export function sandboxReads(
109
111
  const real = toRealPath(file)
110
112
  const under = (g: string): boolean => atOrUnder(real, toRealPath(g))
111
113
  return (
112
- !sandbox.baseDenyRead.some(under) ||
114
+ (sandbox.config.hermetic !== true && !sandbox.baseDenyRead.some(under)) ||
113
115
  [...sandbox.baseAllowRead, ...sandbox.config.allowRead, ...sandbox.config.allowWrite].some(
114
116
  under,
115
117
  )