@codyswann/lisa 4.48.0 → 4.49.0

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 (72) hide show
  1. package/dist/configs/vitest/base.d.ts +71 -1
  2. package/dist/configs/vitest/base.d.ts.map +1 -1
  3. package/dist/configs/vitest/base.js +141 -23
  4. package/dist/configs/vitest/base.js.map +1 -1
  5. package/dist/configs/vitest/fleet-admission-global-setup.d.ts +34 -0
  6. package/dist/configs/vitest/fleet-admission-global-setup.d.ts.map +1 -0
  7. package/dist/configs/vitest/fleet-admission-global-setup.js +123 -0
  8. package/dist/configs/vitest/fleet-admission-global-setup.js.map +1 -0
  9. package/dist/configs/vitest/fleet-admission.d.ts +129 -0
  10. package/dist/configs/vitest/fleet-admission.d.ts.map +1 -0
  11. package/dist/configs/vitest/fleet-admission.js +239 -0
  12. package/dist/configs/vitest/fleet-admission.js.map +1 -0
  13. package/dist/configs/vitest/synthesis-scratch-reclaim.d.ts +86 -0
  14. package/dist/configs/vitest/synthesis-scratch-reclaim.d.ts.map +1 -0
  15. package/dist/configs/vitest/synthesis-scratch-reclaim.js +226 -0
  16. package/dist/configs/vitest/synthesis-scratch-reclaim.js.map +1 -0
  17. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  18. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  19. package/dist/core/upstream-evidence-manifest.js +9 -1
  20. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  21. package/package.json +4 -4
  22. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa-agy/plugin.json +1 -1
  25. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  27. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  28. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo-agy/plugin.json +1 -1
  35. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  45. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  55. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails-agy/plugin.json +1 -1
  60. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  65. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  70. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  72. package/scripts/cleanup-worktrees.sh +69 -16
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Environment variable that switches admission off entirely.
3
+ *
4
+ * The value `off` disables the wait; anything else leaves it armed. This
5
+ * matches the escape hatch `LISA_VITEST_MAX_WORKERS` already provides and the
6
+ * reason given for it — a cap nobody can lift is a cap that gets worked around
7
+ * by deleting it. This is a load control, not a safety guard, and the two want
8
+ * opposite answers about overrides.
9
+ */
10
+ export declare const FLEET_ADMISSION_VAR = "LISA_FLEET_ADMISSION";
11
+ /** Value of {@link FLEET_ADMISSION_VAR} that disables the wait. */
12
+ export declare const FLEET_ADMISSION_OFF = "off";
13
+ /** Environment variable overriding how long a run may wait for a slot. */
14
+ export declare const FLEET_ADMISSION_DEADLINE_VAR = "LISA_FLEET_ADMISSION_DEADLINE_MS";
15
+ /**
16
+ * How long a run waits for a slot before starting anyway.
17
+ *
18
+ * Two minutes is long enough for a typical unit suite ahead of it to finish and
19
+ * short enough that a stuck older run cannot look like a hang. It is not a
20
+ * measured optimum and is overridable; what matters is that it is finite.
21
+ */
22
+ export declare const DEFAULT_ADMISSION_DEADLINE_MS = 120000;
23
+ /** How often a waiting run re-reads the namespace. */
24
+ export declare const ADMISSION_POLL_MS = 2000;
25
+ /** One live run root, as the namespace names it. */
26
+ export interface FleetRunRoot {
27
+ /** The directory basename, which is the run's identity. */
28
+ readonly basename: string;
29
+ /** Owning process id. */
30
+ readonly pid: number;
31
+ /** Epoch milliseconds at which the root was created. */
32
+ readonly startedAt: number;
33
+ }
34
+ /** What admission decided, and why. */
35
+ export type AdmissionDecision = "admit-alone" | "admit-within-capacity" | "admit-unranked" | "admit-disabled" | "admit-deadline-expired" | "wait";
36
+ /** One admission verdict, with the sentence an operator reads. */
37
+ export interface AdmissionVerdict {
38
+ /** What was decided. */
39
+ readonly decision: AdmissionDecision;
40
+ /** Whether the run may execute now. */
41
+ readonly admitted: boolean;
42
+ /** Operator-readable single line, or empty when there is nothing to say. */
43
+ readonly notice: string;
44
+ }
45
+ /**
46
+ * How many runs may execute workers at once before demand exceeds the machine.
47
+ *
48
+ * The bound is the same one the divisor layer starts from — half the cores —
49
+ * divided by the smallest pool a run may be given. Below that, admitting
50
+ * another run necessarily pushes total demand past the machine, because every
51
+ * admitted run is entitled to {@link MIN_FLEET_WORKERS}.
52
+ *
53
+ * Never zero. A machine too small to host one run at the minimum still has to
54
+ * run its tests, and a capacity of zero would refuse every run on every
55
+ * two-core CI runner.
56
+ * @param cores - Logical cores available.
57
+ * @returns The number of runs that may execute concurrently, at least one.
58
+ */
59
+ export declare function fleetCapacity(cores: number): number;
60
+ /**
61
+ * Total workers the fleet demands once admission is enforced.
62
+ *
63
+ * The property #3941 asks for, expressed as a number a test can check at any
64
+ * fleet size. Pool sizing alone cannot satisfy it: `fleetShare(cores, fleet) *
65
+ * fleet` grows without bound in `fleet`, because the share stops falling at the
66
+ * floor. Capping the count of runs that execute is what makes the product stop
67
+ * growing.
68
+ * @param fleet - How many runs want to execute.
69
+ * @param cores - Logical cores available.
70
+ * @returns Workers demanded by the runs actually executing.
71
+ */
72
+ export declare function admittedWorkerDemand(fleet: number, cores: number): number;
73
+ /**
74
+ * Where a run stands in the queue, oldest first.
75
+ *
76
+ * Ordered by creation time, then by pid, then by basename — a total order over
77
+ * facts every run reads identically, so two runs never both believe they hold
78
+ * the same slot. Ties on the millisecond are real: several lanes launched
79
+ * together routinely share a timestamp.
80
+ * @param roots - Live run roots read from the namespace.
81
+ * @param selfBasename - This run's own root basename, if it has one.
82
+ * @returns The zero-based position, or undefined when this run is not among them.
83
+ */
84
+ export declare function admissionRank(roots: readonly FleetRunRoot[], selfBasename: string | undefined): number | undefined;
85
+ /**
86
+ * Parse the run roots out of one namespace listing.
87
+ * @param names - Directory basenames, already bounded by the caller.
88
+ * @returns The entries that are run roots, in the order given.
89
+ */
90
+ export declare function readFleetRunRoots(names: readonly string[]): readonly FleetRunRoot[];
91
+ /**
92
+ * Read the admission deadline an operator stated, or the default.
93
+ * @param environment - Environment to read.
94
+ * @returns Milliseconds a run may wait before starting anyway.
95
+ */
96
+ export declare function admissionDeadlineMs(environment?: NodeJS.ProcessEnv): number;
97
+ /**
98
+ * Whether admission is armed at all.
99
+ * @param environment - Environment to read.
100
+ * @returns Whether the wait should be attempted.
101
+ */
102
+ export declare function admissionEnabled(environment?: NodeJS.ProcessEnv): boolean;
103
+ /** Everything one admission decision is made from. */
104
+ export interface AdmissionInput {
105
+ /** This run's queue position, or undefined when it has none. */
106
+ readonly rank: number | undefined;
107
+ /** How many runs may execute at once. */
108
+ readonly capacity: number;
109
+ /** How many live runs were observed, including this one. */
110
+ readonly liveRuns: number;
111
+ /** Milliseconds spent waiting so far. */
112
+ readonly elapsedMs: number;
113
+ /** Milliseconds this run may wait in total. */
114
+ readonly deadlineMs: number;
115
+ /** Whether admission is armed. */
116
+ readonly enabled: boolean;
117
+ }
118
+ /**
119
+ * Decide whether this run may execute now, and say so in one line.
120
+ *
121
+ * The order of the branches is the order of confidence. Anything this cannot
122
+ * measure admits before anything it can measure is consulted, so a defect in
123
+ * the ranking can only make the control weaker, never make it refuse a run it
124
+ * should have allowed.
125
+ * @param input - The facts the decision is made from.
126
+ * @returns The verdict and its operator-readable notice.
127
+ */
128
+ export declare function admissionVerdict(input: AdmissionInput): AdmissionVerdict;
129
+ //# sourceMappingURL=fleet-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet-admission.d.ts","sourceRoot":"","sources":["../../../src/configs/vitest/fleet-admission.ts"],"names":[],"mappings":"AAmEA;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAEzC,0EAA0E;AAC1E,eAAO,MAAM,4BAA4B,qCAAqC,CAAC;AAE/E;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,SAAU,CAAC;AAErD,sDAAsD;AACtD,eAAO,MAAM,iBAAiB,OAAQ,CAAC;AAEvC,oDAAoD;AACpD,MAAM,WAAW,YAAY;IAC3B,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yBAAyB;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,uCAAuC;AACvC,MAAM,MAAM,iBAAiB,GACzB,aAAa,GACb,uBAAuB,GACvB,gBAAgB,GAChB,gBAAgB,GAChB,wBAAwB,GACxB,MAAM,CAAC;AAEX,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,wBAAwB;IACxB,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,MAAM,GAAG,SAAS,CAcpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,SAAS,YAAY,EAAE,CAOzB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,GAAE,MAAM,CAAC,UAAgB,GACnC,MAAM,CAOR;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,GAAE,MAAM,CAAC,UAAgB,GACnC,OAAO,CAET;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,kCAAkC;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB,CAsBxE"}
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Admission control for the fleet's test runs.
3
+ *
4
+ * ## The gap this fills
5
+ *
6
+ * `resolveMaxWorkers` has three layers and all three size a *pool*. None of
7
+ * them decides whether a run may start. Past the point where the divisor
8
+ * saturates that distinction becomes the whole problem: on eighteen cores
9
+ * `floor(cores / 2 / fleet)` clamps to {@link MIN_FLEET_WORKERS} from a fleet of
10
+ * five upward, so fleets of 5, 13, 20 and 50 all resolve to two workers each and
11
+ * the fleet's TOTAL demand is `2 x fleet` — 26 workers at thirteen runs, 100 at
12
+ * fifty, on a machine with eighteen cores. The layer's own docstring says it
13
+ * bounds the fleet "rather than multiplied by its own size"; past four runs it
14
+ * multiplies (CodySwannGT/lisa#3941).
15
+ *
16
+ * The arithmetic is not a bug in the divisor. **With a per-run floor that may
17
+ * not be lowered, no pool-sizing rule can bound a sum over an unbounded number
18
+ * of runs.** Bounding it requires deciding that some run does not execute yet.
19
+ * That decision is what this module is.
20
+ *
21
+ * ## Why the floor is not the lever
22
+ *
23
+ * {@link MIN_FLEET_WORKERS} is measured and deliberately out of scope. One
24
+ * worker serialises a suite, so every file waits behind every other file and
25
+ * per-test budgets expire even as machine load falls — the recorded 124-timeouts
26
+ * -against-54 result. Lowering it trades a visible failure for a subtler one,
27
+ * which is the move this module exists to avoid rather than to make.
28
+ *
29
+ * ## The rule, and why it cannot deadlock
30
+ *
31
+ * Every supervised run registers a `run-<pid>-<startedAt>-<suffix>` root before
32
+ * its payload starts. A run admits itself when its own root is among the
33
+ * {@link fleetCapacity} OLDEST live roots. That is a total order on facts each
34
+ * run can read for itself: no lock, no leader, no coordinator, and no message
35
+ * anybody has to receive. At least `capacity` runs always satisfy it, so the
36
+ * fleet always makes progress, and a waiting run's rank improves for free as
37
+ * older runs finish and their roots disappear.
38
+ *
39
+ * Ranking by age rather than by, say, pid also makes the queue fair in the one
40
+ * direction that matters: the run that has been waiting longest goes first, so
41
+ * nothing starves.
42
+ *
43
+ * ## Every unknown admits
44
+ *
45
+ * This runs before a single test does, so a mistake here does not degrade a run,
46
+ * it prevents one. Unranked (the run registered no root, or is unsupervised), an
47
+ * unreadable namespace, a capacity of zero, a disabled switch, an expired
48
+ * deadline — all admit. The control bounds demand when it can see the fleet and
49
+ * gets out of the way when it cannot. It is deliberately NOT fail-closed:
50
+ * refusing to run on missing information would turn a load control into a
51
+ * correctness signal, and a red gate reads exactly like a regression.
52
+ *
53
+ * ## Why the deadline exists
54
+ *
55
+ * A run that waits forever is worse than an overloaded machine. The agent
56
+ * watching it sees a hang, kills it, and retries — which is the cascade the cap
57
+ * exists to stop, arriving through the control rather than around it. So the
58
+ * wait is bounded, and expiry admits with a notice rather than refusing. Past
59
+ * the deadline the behaviour degrades exactly to what shipped before this
60
+ * module, loudly.
61
+ * @module configs/vitest/fleet-admission
62
+ */
63
+ import { env } from "node:process";
64
+ import { MIN_FLEET_WORKERS, fleetShare } from "./base.js";
65
+ import { parseScratchRunRootName } from "./scratch-owner.js";
66
+ /**
67
+ * Environment variable that switches admission off entirely.
68
+ *
69
+ * The value `off` disables the wait; anything else leaves it armed. This
70
+ * matches the escape hatch `LISA_VITEST_MAX_WORKERS` already provides and the
71
+ * reason given for it — a cap nobody can lift is a cap that gets worked around
72
+ * by deleting it. This is a load control, not a safety guard, and the two want
73
+ * opposite answers about overrides.
74
+ */
75
+ export const FLEET_ADMISSION_VAR = "LISA_FLEET_ADMISSION";
76
+ /** Value of {@link FLEET_ADMISSION_VAR} that disables the wait. */
77
+ export const FLEET_ADMISSION_OFF = "off";
78
+ /** Environment variable overriding how long a run may wait for a slot. */
79
+ export const FLEET_ADMISSION_DEADLINE_VAR = "LISA_FLEET_ADMISSION_DEADLINE_MS";
80
+ /**
81
+ * How long a run waits for a slot before starting anyway.
82
+ *
83
+ * Two minutes is long enough for a typical unit suite ahead of it to finish and
84
+ * short enough that a stuck older run cannot look like a hang. It is not a
85
+ * measured optimum and is overridable; what matters is that it is finite.
86
+ */
87
+ export const DEFAULT_ADMISSION_DEADLINE_MS = 120_000;
88
+ /** How often a waiting run re-reads the namespace. */
89
+ export const ADMISSION_POLL_MS = 2_000;
90
+ /**
91
+ * How many runs may execute workers at once before demand exceeds the machine.
92
+ *
93
+ * The bound is the same one the divisor layer starts from — half the cores —
94
+ * divided by the smallest pool a run may be given. Below that, admitting
95
+ * another run necessarily pushes total demand past the machine, because every
96
+ * admitted run is entitled to {@link MIN_FLEET_WORKERS}.
97
+ *
98
+ * Never zero. A machine too small to host one run at the minimum still has to
99
+ * run its tests, and a capacity of zero would refuse every run on every
100
+ * two-core CI runner.
101
+ * @param cores - Logical cores available.
102
+ * @returns The number of runs that may execute concurrently, at least one.
103
+ */
104
+ export function fleetCapacity(cores) {
105
+ return Math.max(1, Math.floor(cores / 2 / MIN_FLEET_WORKERS));
106
+ }
107
+ /**
108
+ * Total workers the fleet demands once admission is enforced.
109
+ *
110
+ * The property #3941 asks for, expressed as a number a test can check at any
111
+ * fleet size. Pool sizing alone cannot satisfy it: `fleetShare(cores, fleet) *
112
+ * fleet` grows without bound in `fleet`, because the share stops falling at the
113
+ * floor. Capping the count of runs that execute is what makes the product stop
114
+ * growing.
115
+ * @param fleet - How many runs want to execute.
116
+ * @param cores - Logical cores available.
117
+ * @returns Workers demanded by the runs actually executing.
118
+ */
119
+ export function admittedWorkerDemand(fleet, cores) {
120
+ const wanted = Math.max(1, Math.floor(fleet));
121
+ const admitted = Math.min(wanted, fleetCapacity(cores));
122
+ return admitted * fleetShare(cores, wanted);
123
+ }
124
+ /**
125
+ * Where a run stands in the queue, oldest first.
126
+ *
127
+ * Ordered by creation time, then by pid, then by basename — a total order over
128
+ * facts every run reads identically, so two runs never both believe they hold
129
+ * the same slot. Ties on the millisecond are real: several lanes launched
130
+ * together routinely share a timestamp.
131
+ * @param roots - Live run roots read from the namespace.
132
+ * @param selfBasename - This run's own root basename, if it has one.
133
+ * @returns The zero-based position, or undefined when this run is not among them.
134
+ */
135
+ export function admissionRank(roots, selfBasename) {
136
+ if (selfBasename === undefined)
137
+ return undefined;
138
+ const ordered = [...roots].sort((left, right) => left.startedAt - right.startedAt ||
139
+ left.pid - right.pid ||
140
+ (left.basename < right.basename
141
+ ? -1
142
+ : left.basename > right.basename
143
+ ? 1
144
+ : 0));
145
+ const position = ordered.findIndex(root => root.basename === selfBasename);
146
+ return position === -1 ? undefined : position;
147
+ }
148
+ /**
149
+ * Parse the run roots out of one namespace listing.
150
+ * @param names - Directory basenames, already bounded by the caller.
151
+ * @returns The entries that are run roots, in the order given.
152
+ */
153
+ export function readFleetRunRoots(names) {
154
+ return names.flatMap(basename => {
155
+ const owner = parseScratchRunRootName(basename);
156
+ return owner === undefined
157
+ ? []
158
+ : [{ basename, pid: owner.pid, startedAt: owner.startedAt }];
159
+ });
160
+ }
161
+ /**
162
+ * Read the admission deadline an operator stated, or the default.
163
+ * @param environment - Environment to read.
164
+ * @returns Milliseconds a run may wait before starting anyway.
165
+ */
166
+ export function admissionDeadlineMs(environment = env) {
167
+ const raw = environment[FLEET_ADMISSION_DEADLINE_VAR]?.trim();
168
+ if (raw === undefined || !/^\d+$/u.test(raw)) {
169
+ return DEFAULT_ADMISSION_DEADLINE_MS;
170
+ }
171
+ const value = Number(raw);
172
+ return value > 0 ? value : DEFAULT_ADMISSION_DEADLINE_MS;
173
+ }
174
+ /**
175
+ * Whether admission is armed at all.
176
+ * @param environment - Environment to read.
177
+ * @returns Whether the wait should be attempted.
178
+ */
179
+ export function admissionEnabled(environment = env) {
180
+ return environment[FLEET_ADMISSION_VAR]?.trim() !== FLEET_ADMISSION_OFF;
181
+ }
182
+ /**
183
+ * Decide whether this run may execute now, and say so in one line.
184
+ *
185
+ * The order of the branches is the order of confidence. Anything this cannot
186
+ * measure admits before anything it can measure is consulted, so a defect in
187
+ * the ranking can only make the control weaker, never make it refuse a run it
188
+ * should have allowed.
189
+ * @param input - The facts the decision is made from.
190
+ * @returns The verdict and its operator-readable notice.
191
+ */
192
+ export function admissionVerdict(input) {
193
+ if (!input.enabled) {
194
+ return { decision: "admit-disabled", admitted: true, notice: "" };
195
+ }
196
+ if (input.rank === undefined) {
197
+ return { decision: "admit-unranked", admitted: true, notice: "" };
198
+ }
199
+ if (input.rank < input.capacity) {
200
+ return {
201
+ decision: input.liveRuns <= 1 ? "admit-alone" : "admit-within-capacity",
202
+ admitted: true,
203
+ notice: "",
204
+ };
205
+ }
206
+ if (input.elapsedMs >= input.deadlineMs) {
207
+ return {
208
+ decision: "admit-deadline-expired",
209
+ admitted: true,
210
+ notice: overCapacityNotice(input),
211
+ };
212
+ }
213
+ return { decision: "wait", admitted: false, notice: waitingNotice(input) };
214
+ }
215
+ /**
216
+ * The line a run prints while it is waiting for a slot.
217
+ * @param input - The facts the decision was made from.
218
+ * @returns One operator-readable line.
219
+ */
220
+ function waitingNotice(input) {
221
+ return (`lisa fleet admission: waiting for a worker slot — ` +
222
+ `${String(input.liveRuns)} run(s) live, this run is #${String((input.rank ?? 0) + 1)}, ` +
223
+ `${String(input.capacity)} may execute at once. ` +
224
+ `Starting anyway in ${String(Math.max(0, Math.ceil((input.deadlineMs - input.elapsedMs) / 1000)))}s. ` +
225
+ `Set ${FLEET_ADMISSION_VAR}=${FLEET_ADMISSION_OFF} to disable this wait.`);
226
+ }
227
+ /**
228
+ * The line a run prints when it starts despite being over capacity.
229
+ * @param input - The facts the decision was made from.
230
+ * @returns One operator-readable line.
231
+ */
232
+ function overCapacityNotice(input) {
233
+ return (`lisa fleet admission: starting OVER CAPACITY after ` +
234
+ `${String(Math.round(input.elapsedMs / 1000))}s — ` +
235
+ `${String(input.liveRuns)} run(s) live, this run is #${String((input.rank ?? 0) + 1)}, ` +
236
+ `${String(input.capacity)} may execute at once. ` +
237
+ `The machine is oversubscribed; expect timeouts rather than failures.`);
238
+ }
239
+ //# sourceMappingURL=fleet-admission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet-admission.js","sourceRoot":"","sources":["../../../src/configs/vitest/fleet-admission.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /** Prefix CDK uses for a `mkdtemp` cloud-assembly directory. */
2
+ export declare const SYNTHESIS_PREFIX = "cdk.out";
3
+ /** File CDK writes when an assembly completes. */
4
+ export declare const COMPLETION_MANIFEST = "manifest.json";
5
+ /** How long an assembly must be untouched before completion authorizes removal. */
6
+ export declare const DEFAULT_QUIESCENCE_MS: number;
7
+ /** Why one entry was placed in its bucket. */
8
+ export type ReclaimDisposition = "reclaimable" | "live" | "undetermined";
9
+ /** Facts read from one candidate, separated from the judgement about them. */
10
+ export interface SynthesisEntryFacts {
11
+ /** Direct child basename. */
12
+ readonly name: string;
13
+ /** Whether the candidate is a real directory rather than a link or file. */
14
+ readonly directory: boolean;
15
+ /** Whether the candidate's children could be listed. */
16
+ readonly readable: boolean;
17
+ /** Whether the completion manifest is present. */
18
+ readonly complete: boolean;
19
+ /** Milliseconds since the candidate was last modified. */
20
+ readonly ageMs: number;
21
+ }
22
+ /** One entry's bucket, with the reason stated for an operator. */
23
+ export interface SynthesisEntryVerdict {
24
+ /** Direct child basename. */
25
+ readonly name: string;
26
+ /** Bucket this entry falls in. */
27
+ readonly disposition: ReclaimDisposition;
28
+ /** Operator-readable reason, always populated. */
29
+ readonly reason: string;
30
+ }
31
+ /**
32
+ * Judge one candidate from its facts alone.
33
+ *
34
+ * Ordered so the conservative arms win: anything that is not a plain directory,
35
+ * or cannot be read, or was touched recently, is kept before completion is even
36
+ * consulted. Completion is necessary but never sufficient.
37
+ * @param facts - Facts read from the candidate
38
+ * @param quiescenceMs - Required untouched interval
39
+ * @returns The bucket and the reason for it
40
+ */
41
+ export declare const classifySynthesisEntry: (facts: SynthesisEntryFacts, quiescenceMs?: number) => SynthesisEntryVerdict;
42
+ /**
43
+ * Read the facts for one candidate without ever following a symlink.
44
+ * @param root - Directory holding the candidate
45
+ * @param name - Direct child basename
46
+ * @param now - Clock reading for the age computation
47
+ * @returns Facts describing the candidate
48
+ */
49
+ export declare const readSynthesisEntryFacts: (root: string, name: string, now: number) => SynthesisEntryFacts;
50
+ /** Inputs for one reclaim pass. */
51
+ export interface ReclaimSynthesisScratchOptions {
52
+ /** Temp root to inspect. */
53
+ readonly root: string;
54
+ /** Clock reading used for every age computation. */
55
+ readonly now?: number;
56
+ /** Required untouched interval before completion authorizes removal. */
57
+ readonly quiescenceMs?: number;
58
+ /** Whether to remove. False reports without touching anything. */
59
+ readonly apply?: boolean;
60
+ /** Adversarial hook invoked after quarantine and before unlink, for tests. */
61
+ readonly afterQuarantine?: (quarantine: string) => void;
62
+ }
63
+ /** What one reclaim pass found and did. */
64
+ export interface ReclaimSynthesisScratchResult {
65
+ /** Whether any candidate matched the expected naming convention. */
66
+ readonly recognised: boolean;
67
+ /** Entries removed, or that would be removed when not applying. */
68
+ readonly reclaimed: readonly SynthesisEntryVerdict[];
69
+ /** Entries kept because they are inside the quiescence window. */
70
+ readonly live: readonly SynthesisEntryVerdict[];
71
+ /** Entries kept because their status could not be established. */
72
+ readonly undetermined: readonly SynthesisEntryVerdict[];
73
+ /** Entries whose removal was attempted and refused by the fence. */
74
+ readonly refused: readonly SynthesisEntryVerdict[];
75
+ }
76
+ /**
77
+ * Reclaim completed, quiescent synthesis assemblies from one temp root.
78
+ *
79
+ * Removes nothing when no candidate matches the convention: a naming change in
80
+ * the producing tool must read as "recognised nothing" rather than as a clean
81
+ * sweep, because the two are opposite facts and only one of them is good news.
82
+ * @param options - Root, clock, thresholds and hooks
83
+ * @returns What was found in each bucket, and what was removed
84
+ */
85
+ export declare function reclaimSynthesisScratch(options: ReclaimSynthesisScratchOptions): ReclaimSynthesisScratchResult;
86
+ //# sourceMappingURL=synthesis-scratch-reclaim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synthesis-scratch-reclaim.d.ts","sourceRoot":"","sources":["../../../src/configs/vitest/synthesis-scratch-reclaim.ts"],"names":[],"mappings":"AA2BA,gEAAgE;AAChE,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C,kDAAkD;AAClD,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AAEnD,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,QAAsB,CAAC;AAEzD,8CAA8C;AAC9C,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,MAAM,GAAG,cAAc,CAAC;AAEzE,8EAA8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,kEAAkE;AAClE,MAAM,WAAW,qBAAqB;IACpC,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC;IACzC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO,mBAAmB,EAC1B,eAAc,MAA8B,KAC3C,qBAkCF,CAAC;AA8BF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAClC,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,KAAK,MAAM,KACV,mBAmBF,CAAC;AAEF,mCAAmC;AACnC,MAAM,WAAW,8BAA8B;IAC7C,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACzD;AAED,2CAA2C;AAC3C,MAAM,WAAW,6BAA6B;IAC5C,oEAAoE;IACpE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACrD,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAChD,kEAAkE;IAClE,QAAQ,CAAC,YAAY,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACxD,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACpD;AA2FD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,8BAA8B,GACtC,6BAA6B,CA8B/B"}