@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
@@ -0,0 +1,95 @@
1
+ // A task's terminal status and what each one means: a pass, a hit, where
2
+ // its result came from. One vocabulary every reader (the run verdict, the
3
+ // logger, the history queries, telemetry, plugins) asks, so none re-lists
4
+ // the statuses.
5
+
6
+ export type TaskStatus =
7
+ | 'success'
8
+ | 'cache-hit'
9
+ | 'cache-hit-remote'
10
+ | 'failed'
11
+ | 'skipped'
12
+ // A child killed by a shutdown signal (SIGINT/SIGTERM — e.g. Ctrl-C):
13
+ // the task never finished on its own terms, so it reverts to aborted —
14
+ // excluded from the tally and the run history. It still PROPAGATES like
15
+ // a failure (see `willSkip`): the child died mid-write, so its outputs
16
+ // are partial.
17
+ | 'aborted'
18
+
19
+ /** Where a task's result came from, derived ONCE in core from the status. */
20
+ export type CacheSource = 'miss' | 'local' | 'remote' | 'none'
21
+
22
+ /**
23
+ * Map a task outcome status to its cache source. `success`/`failed` ran
24
+ * (a miss as far as the cache read is concerned); the two cache-hit
25
+ * statuses restored from local/remote; `skipped`/`aborted` never engaged
26
+ * the cache. Pure function — the single place this mapping is decided;
27
+ * `isCacheHit` below is derived from it rather than re-listing the two
28
+ * hit statuses, so there is one decision, not two that can disagree.
29
+ */
30
+ export function deriveCacheSource(status: TaskStatus): CacheSource {
31
+ switch (status) {
32
+ case 'cache-hit':
33
+ return 'local'
34
+ case 'cache-hit-remote':
35
+ return 'remote'
36
+ case 'success':
37
+ case 'failed':
38
+ return 'miss'
39
+ case 'skipped':
40
+ case 'aborted':
41
+ return 'none'
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Whether each status counts as a PASS. Written as a `Record<TaskStatus, …>`
47
+ * rather than a `new Set([...])` on purpose: a Record must name every member,
48
+ * so adding a status to the union is a COMPILE error HERE and the omission
49
+ * cannot ship. A Set of string literals has no such tripwire — it silently
50
+ * answers `false` for the new member, which is how ten hand-rolled copies of
51
+ * this vocabulary accumulated across core, cloud and the dashboard.
52
+ */
53
+ const PASSES: Record<TaskStatus, boolean> = {
54
+ success: true,
55
+ 'cache-hit': true,
56
+ 'cache-hit-remote': true,
57
+ failed: false,
58
+ skipped: false,
59
+ aborted: false,
60
+ }
61
+
62
+ /**
63
+ * Every `TaskStatus`, at runtime. Read off `PASSES`'s keys, which the
64
+ * `Record<TaskStatus, …>` type guarantees is exactly the union — so this is
65
+ * derived, not a second list that can fall behind. Exported so a consumer that
66
+ * cannot import the type (a test asserting a copy of this vocabulary in
67
+ * another package agrees) can still enumerate the real set.
68
+ */
69
+ export const TASK_STATUSES: readonly TaskStatus[] = Object.keys(PASSES) as TaskStatus[]
70
+
71
+ /**
72
+ * Did the task pass? A cache hit counts — it produced the same result without
73
+ * spending the time, which is the whole point. `skipped` and `aborted` do NOT:
74
+ * neither finished on its own terms, so neither can vouch for anything.
75
+ *
76
+ * Takes `string`, not `TaskStatus`, because most callers hold a status that
77
+ * arrived over a wire or out of a database column. An unrecognised string
78
+ * reads as NOT passing — the safe direction, since the alternative is calling
79
+ * a run green on a status this build has never heard of.
80
+ */
81
+ export function isPassStatus(status: string): boolean {
82
+ return PASSES[status as TaskStatus] === true
83
+ }
84
+
85
+ /**
86
+ * Did the task's result come out of the cache (either layer)? Derived from
87
+ * `deriveCacheSource` rather than re-listing the two hit statuses, so the two
88
+ * cannot disagree about what a hit is. Unknown strings read as not-a-hit.
89
+ */
90
+ export function isCacheHit(status: string): boolean {
91
+ // No known-status guard: an unknown string falls through the switch to
92
+ // undefined, which is neither hit source (the guard survived item 654).
93
+ const source = deriveCacheSource(status as TaskStatus)
94
+ return source === 'local' || source === 'remote'
95
+ }
@@ -10,7 +10,7 @@
10
10
 
11
11
  import type { Database } from 'bun:sqlite'
12
12
  import { KEYED_RUNS_SQL } from '../cache/index.js'
13
- import { isCacheHit, TASK_STATUSES } from './telemetry.js'
13
+ import { isCacheHit, TASK_STATUSES } from '../graph/index.js'
14
14
 
15
15
  // SQL hit set derived from the predicate — never a hand-typed list and never
16
16
  // a prefix LIKE (a prefix counts any status merely NAMED cache-hit-*). The
@@ -16,7 +16,7 @@ import { branchSource, EXECUTED_RUNS_SQL } from '../cache/index.js'
16
16
  import { splitTaskId } from '../util/index.js'
17
17
  import { failureModeOf, mixedOutcomeKeysSql } from './failure-mode.js'
18
18
  import type { FailureMode } from './failure-mode.js'
19
- import { isPassStatus, TASK_STATUSES } from './telemetry.js'
19
+ import { isPassStatus, TASK_STATUSES } from '../graph/index.js'
20
20
 
21
21
  const DEFAULT_RECENT = 50
22
22
 
@@ -0,0 +1,48 @@
1
+ // What runs leave behind, read back: the duration table a schedule learns
2
+ // from, the flakiness verdict, the queries behind `vx last` / `vx why`, and
3
+ // what a miss's key changed. Read-only SQL over the cache's database; it
4
+ // never runs, renders or writes a run.
5
+ export {
6
+ EmptyHistoryProvider,
7
+ type HistoryProvider,
8
+ type HistoryTable,
9
+ LocalHistoryProvider,
10
+ PASS_STATUSES,
11
+ type TaskHistory,
12
+ type TaskResources,
13
+ } from './history.js'
14
+ export {
15
+ detectFlaky,
16
+ type FailureMode,
17
+ type FlakyCandidate,
18
+ type FlakyFinding,
19
+ type FlakyTask,
20
+ flakyTasks,
21
+ } from './failure-mode.js'
22
+ export {
23
+ cacheKeyDiff,
24
+ explainCacheKey,
25
+ getInvocation,
26
+ getRun,
27
+ inputWriters,
28
+ latestRunId,
29
+ listInvocations,
30
+ listRuns,
31
+ rootCauses,
32
+ whyDidThisRerun,
33
+ } from './metrics.js'
34
+ export type {
35
+ CacheEntryRow,
36
+ CacheKeyDiff,
37
+ CacheKeyExplanation,
38
+ InputDiffEntry,
39
+ InputWriter,
40
+ InvocationDetail,
41
+ ListInvocationsArgs,
42
+ ListRunsArgs,
43
+ RootCause,
44
+ RunDetail,
45
+ RunSummaryRow,
46
+ WhyDidThisRerun,
47
+ } from './metrics.js'
48
+ export { createMissExplainer, type MissExplainer } from './miss-reason.js'
@@ -15,11 +15,9 @@ import { statSync } from 'node:fs'
15
15
  import path from 'node:path'
16
16
  // The keyed-run predicate lives beside the schema in cache/cache.ts, so a
17
17
  // rule written once cannot drift.
18
- import { KEYED_RUNS_SQL } from '../cache/index.js'
19
- import { splitTaskId } from '../graph/index.js'
18
+ import { KEYED_RUNS_SQL, type TaskInputComponent } from '../cache/index.js'
19
+ import { isPassStatus, splitTaskId } from '../graph/index.js'
20
20
  import { clampInt, MASKED, secretNamed } from '../util/index.js'
21
- import type { TaskInputComponent } from './task-hash.js'
22
- import { isPassStatus } from './telemetry.js'
23
21
 
24
22
  // A recorded `cache_policy` is the `--cache-policy` spec (`local:rw,remote:r`).
25
23
  const readsCache = (spec: string): boolean => /(?:^|,)(?:local|remote):w?r/.test(spec)
@@ -57,6 +55,8 @@ export interface RunSummaryRow {
57
55
  notReady: 'timeout' | 'exited' | 'spawn' | null
58
56
  /** The task ran with the run's forwarded args (`-- …`): a requested one under a run that had some. */
59
57
  forwarded: boolean
58
+ /** Each remote read or write of its key that failed, `upload: <message>`, `; `-joined (v40). */
59
+ remoteError: string | null
60
60
  }
61
61
 
62
62
  export interface ListRunsArgs {
@@ -117,7 +117,7 @@ export function listRuns(db: Database, args: ListRunsArgs = {}): RunSummaryRow[]
117
117
  wallclock_start_ns AS wallclockStartNs, wallclock_end_ns AS wallclockEndNs,
118
118
  blocked_by AS blockedBy, timed_out AS timedOut,
119
119
  sandbox_violations AS sandboxViolations, not_ready AS notReady,
120
- forward_args IS NOT NULL AS forwarded
120
+ forward_args IS NOT NULL AS forwarded, remote_error AS remoteError
121
121
  FROM runs ${clause} ORDER BY id DESC LIMIT ?`,
122
122
  )
123
123
  .all(...params, limit) as RawRow[]
@@ -135,8 +135,7 @@ export function listRuns(db: Database, args: ListRunsArgs = {}): RunSummaryRow[]
135
135
 
136
136
  /**
137
137
  * One `vx run` invocation header — every `invocations` column, camelCased,
138
- * `tags` parsed to a `Record<string,string>` and `dirty`/`ci`/`exitOk`
139
- * surfaced as booleans. The SPA's
138
+ * `dirty`/`ci`/`exitOk` surfaced as booleans. The SPA's
140
139
  * existing fields (runId / startedAt / endedAt / taskCount / failedCount /
141
140
  * hitCount / totalDurationMs) are all present, so existing views keep working.
142
141
  */
@@ -170,7 +169,6 @@ export interface InvocationDetail {
170
169
  os: string | null
171
170
  arch: string | null
172
171
  vxVersion: string
173
- tags: Record<string, string>
174
172
  }
175
173
 
176
174
  interface InvocationRawRow {
@@ -201,7 +199,6 @@ interface InvocationRawRow {
201
199
  os: string | null
202
200
  arch: string | null
203
201
  vxVersion: string
204
- tags: string
205
202
  }
206
203
 
207
204
  const INVOCATION_COLUMNS = `
@@ -214,7 +211,7 @@ const INVOCATION_COLUMNS = `
214
211
  restored_remote_count AS restoredRemoteCount,
215
212
  exit_ok AS exitOk,
216
213
  commit_sha AS commitSha, branch, dirty, ci, ci_provider AS ciProvider,
217
- host, os, arch, vx_version AS vxVersion, tags`
214
+ host, os, arch, vx_version AS vxVersion`
218
215
 
219
216
  function mapInvocation(r: InvocationRawRow): InvocationDetail {
220
217
  let requestedTasks: string[] = []
@@ -222,15 +219,6 @@ function mapInvocation(r: InvocationRawRow): InvocationDetail {
222
219
  const parsed = JSON.parse(r.requestedTasks) as unknown
223
220
  if (Array.isArray(parsed)) requestedTasks = parsed.map(String)
224
221
  } catch {}
225
- let tags: Record<string, string> = {}
226
- try {
227
- const parsed = JSON.parse(r.tags) as unknown
228
- if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
229
- tags = Object.fromEntries(
230
- Object.entries(parsed as Record<string, unknown>).map(([k, v]) => [k, String(v)]),
231
- )
232
- }
233
- } catch {}
234
222
  return {
235
223
  runId: r.runId,
236
224
  command: r.command,
@@ -259,7 +247,6 @@ function mapInvocation(r: InvocationRawRow): InvocationDetail {
259
247
  os: r.os,
260
248
  arch: r.arch,
261
249
  vxVersion: r.vxVersion,
262
- tags,
263
250
  }
264
251
  }
265
252
 
@@ -9,7 +9,7 @@
9
9
  import type { Database } from 'bun:sqlite'
10
10
  import type { InputChanges } from '../graph/index.js'
11
11
  import { diffKeyComponents, loadEntryInputs } from './metrics.js'
12
- import type { TaskInputComponent } from './task-hash.js'
12
+ import type { TaskInputComponent } from '../cache/index.js'
13
13
 
14
14
  /** How many changed components a miss names; `count` says how many there were. */
15
15
  const NAMED = 10
package/src/index.ts CHANGED
@@ -56,13 +56,9 @@ export { run, planRun, prepareRun } from './orchestrator/index.js'
56
56
  // The per-task duration history a `schedule` plugin learns from
57
57
  // (`@vzn/vx-schedule-history` does; a plugin package reaches core only
58
58
  // through this façade, which is what put these here).
59
- export { currentBranch, LocalHistoryProvider } from './orchestrator/index.js'
60
- export type {
61
- HistoryProvider,
62
- HistoryTable,
63
- TaskHistory,
64
- TaskResources,
65
- } from './orchestrator/index.js'
59
+ export { currentBranch } from './orchestrator/index.js'
60
+ export { LocalHistoryProvider } from './history/index.js'
61
+ export type { HistoryProvider, HistoryTable, TaskHistory, TaskResources } from './history/index.js'
66
62
  export type { PreparedRun } from './orchestrator/index.js'
67
63
  // `RunOptions.affected`: what `--affected`'s diff changed, per project.
68
64
  export type { AffectedChanges } from './workspace/index.js'
@@ -72,14 +68,8 @@ export type { AffectedChanges } from './workspace/index.js'
72
68
  // docs/cli.md § Programmatic API listed all three (item 387, 2026-09-19).
73
69
  export type { RunPlan, PlannedTask } from './orchestrator/index.js'
74
70
  export type { CiContext, GitContext, HostContext, WorkspaceIdentity } from './orchestrator/index.js'
75
- export type {
76
- CacheFlags,
77
- CachePolicy,
78
- Logger,
79
- OutputView,
80
- RunOptions,
81
- RunSummary,
82
- } from './orchestrator/index.js'
71
+ export type { CacheFlags, CachePolicy, RunOptions, RunSummary } from './orchestrator/index.js'
72
+ export type { Logger, OutputView } from './report/index.js'
83
73
  // `splitTaskId` is on the façade because the alternative is what happened:
84
74
  // with only `taskId()` to JOIN an id and nothing exported to SPLIT one,
85
75
  // consumers roll their own `split('#', 2)` and drift from the graph, which
@@ -178,8 +168,8 @@ export type {
178
168
  RunReportTask,
179
169
  PluginHookName,
180
170
  PluginHookHandlers,
181
- RunStartInfo,
182
171
  } from './orchestrator/index.js'
172
+ export type { RunStartInfo } from './report/index.js'
183
173
  // The per-task execution contract a plugin's `executor` capability returns.
184
174
  // (`runCommand` / `runSandboxed`, the local executor's own primitives, left
185
175
  // the façade 2026-09-10: no executor plugin built on them — `@vzn/vx-reapi`
@@ -211,15 +201,8 @@ export type {
211
201
  // consumer rolls its own Set of status literals — and a Set has no
212
202
  // compile-time tripwire when the union gains a member, so it silently answers
213
203
  // "no" for the new one.
214
- // `escapeMarkdownCell` is on the façade for the same demonstrated need: a
215
- // plugin that renders a run as a markdown table takes the same unvalidated
216
- // task names core does, and the cloud job summary shipped without the escape.
217
- export {
218
- escapeMarkdownCell,
219
- TELEMETRY_SCHEMA_VERSION,
220
- isCacheHit,
221
- isPassStatus,
222
- } from './orchestrator/index.js'
204
+ export { type CacheSource, isCacheHit, isPassStatus } from './graph/index.js'
205
+ export { TELEMETRY_SCHEMA_VERSION } from './orchestrator/index.js'
223
206
  // `TaskLogBuffer` is on the façade on the same demonstrated need: EVERY
224
207
  // telemetry sink that ships build output has to bound it, and the retention
225
208
  // rules (per-task tail, per-run budget, failures never evicted by successes,
@@ -231,7 +214,6 @@ export { LOG_WIRE_VERSION, TaskLogBuffer } from './orchestrator/index.js'
231
214
  // say `128 + SIGKILL`; the decode is the runner's convention, one copy.
232
215
  export { exitSignal } from './orchestrator/index.js'
233
216
  export type {
234
- CacheSource,
235
217
  RunContextRecord,
236
218
  RunSummaryRecord,
237
219
  TaskLogBundle,
@@ -242,5 +224,5 @@ export type {
242
224
  TelemetrySink,
243
225
  } from './orchestrator/index.js'
244
226
 
245
- // The serializable run result and its per-task views (what `run()` returns).
246
- export type { RunResult, TaskView, OutcomeView } from './orchestrator/index.js'
227
+ // The serializable per-task views.
228
+ export type { TaskView, OutcomeView } from './report/index.js'
@@ -30,7 +30,7 @@ import {
30
30
  resolveCacheDir,
31
31
  resolveStoreRoot,
32
32
  } from '../workspace/index.js'
33
- import { flakyTasks, type FlakyTask } from './failure-mode.js'
33
+ import { flakyTasks, type FlakyTask } from '../history/index.js'
34
34
  import type { VxPlugin } from './plugin.js'
35
35
  import { discoverProjects, loadProjects, loadWorkspacePlugins, lockReader } from './projects.js'
36
36
  import { atPath, envName, resolveSetting, SETTINGS, type SettingSource } from '../settings.js'
@@ -0,0 +1,80 @@
1
+ // What a dispatch hands executeTask: the run's half, built once, and the
2
+ // task's half, looked up per dispatch. The run's half is one fixed-shape
3
+ // object every task spreads; the task's optional fields are assigned only
4
+ // when present: twelve conditional spreads per task cost ~18 ms of object
5
+ // copying on a 1,090-package warm run (X-149).
6
+
7
+ import type { TaskNode, TaskOutcome } from '../graph/index.js'
8
+ import type { Logger } from '../report/index.js'
9
+ import type { DownloadMode } from './download-policy.js'
10
+ import type { ExecuteArgs } from './execute-task.js'
11
+ import type { ShortCircuit } from './local-shortcircuit.js'
12
+ import { UNPLACED_EXECUTOR, type Placements } from './placement.js'
13
+
14
+ type TaskField =
15
+ | 'node'
16
+ | 'upstream'
17
+ | 'log'
18
+ | 'executor'
19
+ | 'nestedProjectDirs'
20
+ | 'remoteOnlyNoop'
21
+ | 'remoteOnly'
22
+ | 'download'
23
+ | 'preProbed'
24
+ | 'upfrontKey'
25
+ | 'upfrontGroupKey'
26
+ | 'taintedUpstream'
27
+ | 'noDependants'
28
+ | 'terminal'
29
+
30
+ /** What every task of a run shares; an absent optional stays absent. */
31
+ export type RunScope = Omit<ExecuteArgs, TaskField>
32
+
33
+ /** Where the task's half is read from, per task id. */
34
+ export interface TaskFacts {
35
+ log: (node: TaskNode) => Logger
36
+ placements: Placements
37
+ nestedDirsByProject: ReadonlyMap<string, string[]>
38
+ downloadModeOf: ReadonlyMap<string, DownloadMode>
39
+ shortCircuit: Pick<ShortCircuit, 'preProbed' | 'uncachedKeys' | 'groupKeys'>
40
+ /** Tasks some other task depends on. */
41
+ dependedOn: ReadonlySet<string>
42
+ /** Tasks that hold the terminal (`terminalHolders`). */
43
+ holders: ReadonlySet<string>
44
+ }
45
+
46
+ /**
47
+ * The dispatch builder. `reuseProbe: false` takes the task's key again
48
+ * instead of the up-front probe (admission's retry after a dropped probe);
49
+ * `tainted` is the run's taint verdict for this dispatch.
50
+ */
51
+ export function executeArgsOf(
52
+ scope: RunScope,
53
+ facts: TaskFacts,
54
+ ): (node: TaskNode, upstream: TaskOutcome[], reuseProbe: boolean, tainted: boolean) => ExecuteArgs {
55
+ const { placements, shortCircuit } = facts
56
+ return (node, upstream, reuseProbe, tainted) => {
57
+ const id = node.id
58
+ const a: ExecuteArgs = {
59
+ ...scope,
60
+ node,
61
+ upstream,
62
+ log: facts.log(node),
63
+ executor: placements.executors.get(id) ?? UNPLACED_EXECUTOR,
64
+ nestedProjectDirs: facts.nestedDirsByProject.get(node.projectName) ?? [],
65
+ }
66
+ const probe = reuseProbe ? shortCircuit.preProbed.get(id) : undefined
67
+ const upfrontKey = shortCircuit.uncachedKeys.get(id)
68
+ const upfrontGroupKey = shortCircuit.groupKeys.get(id)
69
+ if (facts.downloadModeOf.get(id) === 'deferred') a.download = 'deferred'
70
+ if (placements.remoteOnlyNoop.has(id)) a.remoteOnlyNoop = true
71
+ if (placements.remoteOnly.has(id)) a.remoteOnly = true
72
+ if (probe !== undefined) a.preProbed = probe
73
+ if (upfrontKey !== undefined) a.upfrontKey = upfrontKey
74
+ if (upfrontGroupKey !== undefined) a.upfrontGroupKey = upfrontGroupKey
75
+ if (tainted) a.taintedUpstream = true
76
+ if (!facts.dependedOn.has(id)) a.noDependants = true
77
+ if (facts.holders.has(id)) a.terminal = true
78
+ return a
79
+ }
80
+ }