@vzn/vx 0.0.638 → 0.0.639

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 (103) hide show
  1. package/docs/caching.md +44 -15
  2. package/docs/cli.md +168 -345
  3. package/docs/execution.md +18 -43
  4. package/docs/features.md +7 -10
  5. package/docs/patterns.md +5 -7
  6. package/docs/schema.md +47 -49
  7. package/package.json +5 -5
  8. package/schemas/last.json +7 -4
  9. package/skills/vx/SKILL.md +6 -4
  10. package/src/cache/cache.ts +40 -10
  11. package/src/cache/git-inputs.ts +6 -0
  12. package/src/cache/index.ts +2 -2
  13. package/src/cache/layer.ts +25 -11
  14. package/src/cache/layered-cache.ts +108 -315
  15. package/src/cache/plugin-cache.ts +2 -10
  16. package/src/cache/remote-lane.ts +383 -0
  17. package/src/cache/run-history.ts +24 -8
  18. package/src/cache/run-store.ts +4 -4
  19. package/src/cache/schema.ts +6 -4
  20. package/src/cache/scoped-cache.ts +7 -0
  21. package/src/cli/completions.ts +2 -2
  22. package/src/cli/foreign-flags.ts +10 -22
  23. package/src/cli/help.ts +6 -31
  24. package/src/cli/info.ts +2 -1
  25. package/src/cli/last.ts +6 -3
  26. package/src/cli/plan-format.ts +5 -58
  27. package/src/cli/plugin-commands.ts +2 -2
  28. package/src/cli/run.ts +23 -185
  29. package/src/cli/show.ts +6 -0
  30. package/src/cli/upgrade.ts +123 -39
  31. package/src/cli/watch-cycle.ts +2 -1
  32. package/src/cli/watch.ts +29 -14
  33. package/src/cli/why.ts +4 -4
  34. package/src/config.ts +0 -6
  35. package/src/exec/executor.ts +1 -1
  36. package/src/exec/sandbox-runtime.ts +1 -1
  37. package/src/exec/sandbox-violations.ts +3 -1
  38. package/src/graph/index.ts +9 -1
  39. package/src/graph/priorities.ts +1 -1
  40. package/src/graph/scheduler.ts +4 -16
  41. package/src/graph/task-graph.ts +87 -17
  42. package/src/graph/task-status.ts +95 -0
  43. package/src/{orchestrator → history}/failure-mode.ts +1 -1
  44. package/src/{orchestrator → history}/history.ts +1 -1
  45. package/src/history/index.ts +48 -0
  46. package/src/{orchestrator → history}/metrics.ts +7 -20
  47. package/src/{orchestrator → history}/miss-reason.ts +1 -1
  48. package/src/index.ts +10 -28
  49. package/src/orchestrator/doctor.ts +1 -1
  50. package/src/orchestrator/execute-args.ts +80 -0
  51. package/src/orchestrator/execute-task.ts +21 -386
  52. package/src/orchestrator/fingerprint-watch.ts +2 -2
  53. package/src/orchestrator/index.ts +1 -73
  54. package/src/orchestrator/miss-save.ts +33 -3
  55. package/src/orchestrator/options.ts +1 -36
  56. package/src/orchestrator/output-host.ts +4 -3
  57. package/src/orchestrator/persistent-task.ts +321 -0
  58. package/src/orchestrator/persistent.ts +130 -3
  59. package/src/orchestrator/placement.ts +3 -3
  60. package/src/orchestrator/plan.ts +18 -5
  61. package/src/orchestrator/plugin-host.ts +67 -47
  62. package/src/orchestrator/plugin.ts +6 -6
  63. package/src/orchestrator/prepare.ts +28 -29
  64. package/src/orchestrator/remote-prefetch.ts +14 -1
  65. package/src/orchestrator/run-artifacts.ts +5 -177
  66. package/src/orchestrator/run-context.ts +55 -0
  67. package/src/orchestrator/run-failures.ts +15 -3
  68. package/src/orchestrator/run-records.ts +35 -3
  69. package/src/orchestrator/run-report.ts +81 -179
  70. package/src/orchestrator/run-settings.ts +1 -16
  71. package/src/orchestrator/run-stop.ts +153 -0
  72. package/src/orchestrator/run.ts +260 -601
  73. package/src/orchestrator/sandbox-fix.ts +0 -16
  74. package/src/orchestrator/signals.ts +14 -6
  75. package/src/orchestrator/stable-keys.ts +107 -3
  76. package/src/orchestrator/steps.ts +50 -0
  77. package/src/orchestrator/task-hash.ts +3 -16
  78. package/src/orchestrator/task-log-buffer.ts +1 -1
  79. package/src/orchestrator/task-process.ts +94 -0
  80. package/src/orchestrator/telemetry-host.ts +1 -1
  81. package/src/orchestrator/telemetry.ts +5 -82
  82. package/src/{orchestrator → report}/events.ts +41 -8
  83. package/src/{orchestrator → report}/forecast.ts +35 -2
  84. package/src/{orchestrator → report}/framed-output.ts +39 -33
  85. package/src/report/index.ts +36 -0
  86. package/src/{orchestrator → report}/logger.ts +158 -20
  87. package/src/{orchestrator → report}/status-line.ts +84 -7
  88. package/src/{orchestrator → report}/summary.ts +126 -35
  89. package/src/report/tally.ts +83 -0
  90. package/src/settings.ts +1 -22
  91. package/src/util/colors.ts +44 -4
  92. package/src/util/git-ignored.ts +143 -0
  93. package/src/util/index.ts +19 -4
  94. package/src/util/report.ts +13 -5
  95. package/src/util/settle.ts +14 -39
  96. package/src/util/tail.ts +1 -1
  97. package/src/util/ui.ts +17 -6
  98. package/schemas/summary.json +0 -133
  99. package/src/cache/chained-cache.ts +0 -280
  100. package/src/orchestrator/tally.ts +0 -112
  101. /package/src/{orchestrator → report}/path-links.ts +0 -0
  102. /package/src/{orchestrator → report}/plain-output.ts +0 -0
  103. /package/src/{orchestrator → report}/terminal-signals.ts +0 -0
@@ -96,6 +96,7 @@ import {
96
96
  type PruneResult,
97
97
  type RunRecord,
98
98
  type SaveArgs,
99
+ type TaskInputComponent,
99
100
  type TaskInputRow,
100
101
  WORKSPACE_OUTPUT_PREFIX,
101
102
  } from './layer.js'
@@ -309,17 +310,19 @@ function outOfFdsAtOpen(dbFile: string, err: unknown): UserError | undefined {
309
310
  // v38: blob_sizes moved to the store, and the store's task_outcomes
310
311
  // holds the flaky record, so worktrees share both. The cache KEY is
311
312
  // unchanged.
312
- export const SCHEMA_VERSION = 'v38'
313
+ // v39: invocations drops `tags` (`--tag` is gone). The cache KEY is
314
+ // unchanged.
315
+ // v40: runs.remote_error — each remote read or write of a task's key that
316
+ // failed, for `vx last`. The cache KEY is unchanged.
317
+ export const SCHEMA_VERSION = 'v40'
313
318
 
314
319
  /**
315
320
  * An entry's key components as its one `entry_inputs` row (v34): JSON
316
321
  * `[kind, name, hash]` triples in (kind, name) code-unit order, the first
317
322
  * of a repeated (kind, name) kept, as the per-component primary key did.
318
323
  */
319
- function encodeEntryInputs(
320
- components: readonly Pick<TaskInputRow, 'kind' | 'name' | 'hash'>[],
321
- ): string {
322
- const byId = new Map<string, Pick<TaskInputRow, 'kind' | 'name' | 'hash'>>()
324
+ function encodeEntryInputs(components: readonly TaskInputComponent[]): string {
325
+ const byId = new Map<string, TaskInputComponent>()
323
326
  for (const c of components) {
324
327
  const id = `${c.kind}\0${c.name}`
325
328
  if (!byId.has(id)) byId.set(id, c)
@@ -788,6 +791,23 @@ export interface UploadTally {
788
791
  failed: number
789
792
  }
790
793
 
794
+ /** One task's traffic with the remotes this run, by its key. */
795
+ export interface RemoteKeyTraffic {
796
+ /** Its uploads queued or running. */
797
+ uploading: number
798
+ /** An upload of it landed on a remote. */
799
+ landed: boolean
800
+ /** Each distinct remote read or write of it that failed: `upload: <message>`. */
801
+ failures: string[]
802
+ }
803
+
804
+ /** The run's remote traffic per key: what the footer's remote part counts, task by task. */
805
+ export interface RemoteTraffic {
806
+ /** Uploads queued or running across every remote. */
807
+ inFlight: number
808
+ readonly keys: Map<string, RemoteKeyTraffic>
809
+ }
810
+
791
811
  export class Cache implements CacheLayer {
792
812
  /** This IS the local layer — there is nothing slower behind it. */
793
813
  readonly hasRemote = false
@@ -797,6 +817,8 @@ export class Cache implements CacheLayer {
797
817
  * so the run can report them without a method on the layer seam.
798
818
  */
799
819
  readonly uploads: UploadTally = { count: 0, bytes: 0, ms: 0, failed: 0 }
820
+ /** The same remotes' traffic per key, filled by the same `LayeredCache`. */
821
+ readonly traffic: RemoteTraffic = { inFlight: 0, keys: new Map() }
800
822
 
801
823
  private readonly db: Database
802
824
  private readonly insertEntry: ReturnType<Database['prepare']>
@@ -1758,9 +1780,14 @@ export class Cache implements CacheLayer {
1758
1780
 
1759
1781
  private async hasEntry(hash: string): Promise<'local' | 'remote' | null> {
1760
1782
  if (!this.read) return null
1783
+ return this.storedHere(hash) ? 'local' : null
1784
+ }
1785
+
1786
+ /** The index row and its artifact are both here, whatever the run may read. */
1787
+ private storedHere(hash: string): boolean {
1761
1788
  const row = this.entryExists.get(hash) as { inline_at: number | null } | null
1762
- if (row === null) return null
1763
- return row.inline_at !== null || existsSync(this.tarPath(hash)) ? 'local' : null
1789
+ if (row === null) return false
1790
+ return row.inline_at !== null || existsSync(this.tarPath(hash))
1764
1791
  }
1765
1792
 
1766
1793
  // Local cache has no slower layer to warm from — prefetch is a no-op.
@@ -2054,8 +2081,6 @@ export class Cache implements CacheLayer {
2054
2081
  entry: Omit<CacheEntry, 'hash' | 'storedAt' | 'outputFiles' | 'exitCode'>
2055
2082
  projectDir: string
2056
2083
  outputFiles: string[]
2057
- /** See `CacheLayer.save`. */
2058
- skipLocalWrite?: boolean
2059
2084
  workspaceOutputFiles?: string[]
2060
2085
  workspaceRoot?: string
2061
2086
  inputComponents?: readonly TaskInputRow[]
@@ -2080,7 +2105,9 @@ export class Cache implements CacheLayer {
2080
2105
  // still uploads to remote — it calls `packArtifact` itself to get
2081
2106
  // the bytes, since there's no local artifact to read off disk.
2082
2107
  if (!this.write) return
2083
- if (args.skipLocalWrite === true) return
2108
+ // A cache that reads saves only on a miss. One that does not (`--force`)
2109
+ // re-executes a key it may hold: that entry is kept as it is.
2110
+ if (!this.read && this.guard(() => this.storedHere(args.hash))) return
2084
2111
  // The cache directory is the constructor's (`mkdirSync`, with a
2085
2112
  // refusal recorded for `assertWritable`); re-creating it here and
2086
2113
  // again in `writeArtifactAndIndex` cost every save two thread-pool
@@ -2631,6 +2658,9 @@ export class Cache implements CacheLayer {
2631
2658
  recordRunBundle(bundle: { runs: readonly RunRecord[]; invocation: InvocationRecord }): void {
2632
2659
  return this.guard(() => this.history.recordRunBundle(bundle))
2633
2660
  }
2661
+ recordRemoteErrors(runId: string, errors: readonly (readonly [string, string])[]): void {
2662
+ return this.guard(() => this.history.recordRemoteErrors(runId, errors))
2663
+ }
2634
2664
  stats(opts: CacheStatsOptions = {}): CacheStats {
2635
2665
  return this.guard(() => this.statsOf(opts))
2636
2666
  }
@@ -289,6 +289,12 @@ export class GitFilesCache extends Map<string, readonly string[]> {
289
289
  * a cache no enumeration filled; its OIDs are then re-checked by content.
290
290
  */
291
291
  enumeratedAtMs: number | undefined
292
+ /**
293
+ * The declared outputs (root-relative globs) git ignores whole, asked
294
+ * once per run beside the enumeration: no key reads them, so a reader
295
+ * beside their producer keys up front (`dependsOnSiblingOutputs`).
296
+ */
297
+ ignoredOutputs: ReadonlySet<string> = new Set()
292
298
 
293
299
  get undecodableNames(): ReadonlySet<string> {
294
300
  return this.undecodable
@@ -21,6 +21,7 @@ export {
21
21
  KEYED_RUNS_SQL,
22
22
  parseCachePolicy,
23
23
  type RunRecord,
24
+ type TaskInputComponent,
24
25
  type TaskInputRow,
25
26
  WORKSPACE_OUTPUT_PREFIX,
26
27
  type OutputDirRow,
@@ -69,14 +70,13 @@ export {
69
70
  type BlobSizeMemo,
70
71
  } from './git-inputs.js'
71
72
  export { LayeredCache, type RemoteCacheLayer } from './layered-cache.js'
73
+ export type { ResolvedRemote } from './remote-lane.js'
72
74
  export { ScopedCache } from './scoped-cache.js'
73
75
  export {
74
76
  absentPluginCache,
75
77
  pluginCache,
76
78
  PLUGIN_ENTRY_MAX_BYTES,
77
79
  type PluginCache,
78
- type PluginCacheRemote,
79
80
  } from './plugin-cache.js'
80
81
  export { packRunOutputs, putRunEntry, runEntryName, writesRunStore } from './run-store.js'
81
- export { ChainedCache } from './chained-cache.js'
82
82
  export { branchSource } from './run-history.js'
@@ -1,5 +1,5 @@
1
1
  // The cache layer CONTRACT and its records — what every cache implementation
2
- // honours (`Cache`, `LayeredCache`, `ChainedCache`, a plugin's remote layer) and
2
+ // honours (`Cache`, `LayeredCache`, a plugin's remote layer) and
3
3
  // the shapes that cross it: the key input, entries, run-history rows, output
4
4
  // fingerprints, stats, prune options. No implementation lives here; the one
5
5
  // reference to `Cache` below is a type (the local handle a layer may wrap).
@@ -208,6 +208,8 @@ export interface RunRecord {
208
208
  timedOut?: true
209
209
  sandboxViolations?: number
210
210
  notReady?: 'timeout' | 'exited' | 'spawn'
211
+ /** Each remote read or write of its key that failed, `upload: <message>`, `; `-joined (v40). */
212
+ remoteError?: string
211
213
  }
212
214
 
213
215
  /**
@@ -247,8 +249,18 @@ export interface InvocationRecord {
247
249
  os: string | null
248
250
  arch: string | null
249
251
  vxVersion: string
250
- /** JSON object `{k:v}` of `--tag` pairs. */
251
- tags: string
252
+ }
253
+
254
+ /**
255
+ * One cache-key component as `key()` folded it, the `captureInto` row.
256
+ * Persisted on a miss (`TaskInputRow`) so a later run can diff its inputs
257
+ * against this one (the Tier-3 "why did this re-run?" moat); a hit saves
258
+ * nothing, so the warm path is free.
259
+ */
260
+ export interface TaskInputComponent {
261
+ kind: string
262
+ name: string
263
+ hash: string
252
264
  }
253
265
 
254
266
  /**
@@ -556,6 +568,13 @@ export interface CacheLayer {
556
568
  * no round-trip. Only meaningful with `hasRemote`.
557
569
  */
558
570
  markRemoteAbsent?(hashes: Iterable<string>): void
571
+ /**
572
+ * The run's up-front keys, for each remote the run writes but does not
573
+ * read (`--force`): one batch probe in the background, and an upload of
574
+ * a key the remote already holds is skipped. Only meaningful with
575
+ * `hasRemote`.
576
+ */
577
+ probeBeforeUpload?(hashes: Promise<readonly string[]>): void
559
578
  /**
560
579
  * Resolve once every background write-through upload settles. The
561
580
  * orchestrator awaits it before `close()` — an upload reading layer state
@@ -563,6 +582,8 @@ export interface CacheLayer {
563
582
  * uploads synchronously (or not at all) omits it.
564
583
  */
565
584
  drainUploads?(): Promise<void>
585
+ /** The run was stopped: start no more uploads, and let `drainUploads` resolve now. */
586
+ stopUploads?(): void
566
587
  key(input: CacheKeyInput): Promise<string>
567
588
  get(hash: string, ctx?: CacheGetContext): Promise<CacheEntry | null>
568
589
  /**
@@ -580,7 +601,7 @@ export interface CacheLayer {
580
601
  * artifact read, no remote download, no local ingest, no accessed_at
581
602
  * bump (on the remote side the layer decides the call: an HTTP HEAD, or
582
603
  * REAPI's `getActionResult` + `findMissingBlobs`). Planning
583
- * (`--dry` / `--graph`) predicts hits with this instead of `get` so a
604
+ * (`--dry`) predicts hits with this instead of `get` so a
584
605
  * dry run can't pull N artifacts over the network. Remote errors
585
606
  * degrade to `null` — an existence probe never fails anything.
586
607
  */
@@ -670,13 +691,6 @@ export interface CacheLayer {
670
691
  entry: Omit<CacheEntry, 'hash' | 'storedAt' | 'outputFiles' | 'exitCode'>
671
692
  projectDir: string
672
693
  outputFiles: string[]
673
- /**
674
- * Set only by ChainedCache when an EARLIER layer in the chain already
675
- * wrote this artifact to the same local handle: the local pack + write
676
- * is skipped and only the layer's remote side acts. A layer with no
677
- * remote side treats it as a full no-op.
678
- */
679
- skipLocalWrite?: boolean
680
694
  /**
681
695
  * Resolved `outputs.workspaceFiles` (absolute paths) + the root
682
696
  * they're relative to. Packed under `workspace-outputs/<rel>`.