@codyswann/lisa 4.44.0 → 4.45.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 (63) hide show
  1. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +156 -14
  2. package/dist/configs/vitest/coverage-reports-directory.d.ts +70 -0
  3. package/dist/configs/vitest/coverage-reports-directory.d.ts.map +1 -1
  4. package/dist/configs/vitest/coverage-reports-directory.js +180 -2
  5. package/dist/configs/vitest/coverage-reports-directory.js.map +1 -1
  6. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  7. package/dist/core/lisa-owned-hash-ledger.js +4 -0
  8. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  9. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  10. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  11. package/dist/core/upstream-evidence-manifest.js +2 -1
  12. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  13. package/package.json +4 -4
  14. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa-agy/plugin.json +1 -1
  17. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo-agy/plugin.json +1 -1
  27. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  47. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails-agy/plugin.json +1 -1
  52. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  57. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  62. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
@@ -29,8 +29,8 @@
29
29
  * measurement when the suite that produced it finished.
30
30
  * @module lib/gate-failure-diagnosis
31
31
  */
32
- import { readFileSync } from "node:fs";
33
- import { availableParallelism, loadavg } from "node:os";
32
+ import { readdirSync, readFileSync, statSync } from "node:fs";
33
+ import { availableParallelism, loadavg, tmpdir } from "node:os";
34
34
 
35
35
  /** Default load-average source for {@link machineLoad}. */
36
36
  const osLoadavg = () => loadavg();
@@ -288,13 +288,10 @@ const TAIL_LINES = 3;
288
288
  * @param {string[]} items Every item found.
289
289
  * @returns {string[]} At most `MAX_EVIDENCE + 1` lines.
290
290
  */
291
- function capped(items) {
291
+ function capped(items, limit = MAX_EVIDENCE) {
292
292
  const unique = [...new Set(items)];
293
- if (unique.length <= MAX_EVIDENCE) return unique;
294
- return [
295
- ...unique.slice(0, MAX_EVIDENCE),
296
- `…and ${unique.length - MAX_EVIDENCE} more`,
297
- ];
293
+ if (unique.length <= limit) return unique;
294
+ return [...unique.slice(0, limit), `…and ${unique.length - limit} more`];
298
295
  }
299
296
 
300
297
  /**
@@ -349,18 +346,37 @@ function tailLines(output) {
349
346
 
350
347
  /**
351
348
  * A timeout verdict, worded so it can never be mistaken for a coverage miss.
349
+ *
350
+ * Carries the shared temp root's population, because a blown wall-clock budget
351
+ * is the one signature that gives no hint of its own cause. A saturated
352
+ * platform temp root makes every `mkdtemp` on the box slow, and the lane that
353
+ * pays is whichever one happened to create fixtures — so it presents as a
354
+ * single flaky-looking test in a suite that has nothing to do with the
355
+ * producer. The reading is attached here and nowhere else for that reason: on
356
+ * an assertion failure or a coverage miss it would be noise.
352
357
  * @param {{count: number, budgets: number[]}} timeouts What was found.
353
358
  * @param {string[]} suites Suites the output named as failing.
359
+ * @param {TempRootReading|null} [tempRoot] The temp root's population at
360
+ * diagnosis time. Omitted or null when it could not be read.
354
361
  * @returns {Diagnosis} The verdict.
355
362
  */
356
- function timeoutVerdict(timeouts, suites) {
363
+ function timeoutVerdict(timeouts, suites, tempRoot) {
357
364
  const budget = Math.max(...timeouts.budgets);
365
+ // Reserve the temp-root line's slot BEFORE capping the suites, so this
366
+ // verdict carries the same MAX_EVIDENCE + 1 ceiling as every other one.
367
+ // Appending after the cap made this the only verdict that could reach
368
+ // MAX_EVIDENCE + 2, and the cap test could not see it because that test
369
+ // suppresses the reading.
370
+ const tempEvidence = tempRootEvidence(tempRoot ?? null);
358
371
  return {
359
372
  kind: DIAGNOSIS.TIMEOUT,
360
373
  summary:
361
374
  `${timeouts.count} test(s)/hook(s) exceeded the ${budget}ms budget, ` +
362
375
  `so the suite did not finish — this is NOT a coverage shortfall`,
363
- evidence: capped(suites),
376
+ evidence: [
377
+ ...capped(suites, MAX_EVIDENCE - tempEvidence.length),
378
+ ...tempEvidence,
379
+ ],
364
380
  };
365
381
  }
366
382
 
@@ -627,6 +643,102 @@ function loadEvidence(load) {
627
643
  ];
628
644
  }
629
645
 
646
+ /**
647
+ * List the platform temp root's direct children.
648
+ *
649
+ * Deliberately NOT recursive. The cost being measured is the width of one
650
+ * directory — what `mkdtemp` and every `readdir` on that path must walk — and
651
+ * descending into tens of thousands of entries to diagnose a failure would
652
+ * itself be the expensive operation this line exists to warn about.
653
+ * @returns {string[]} Direct children of the platform temp root.
654
+ */
655
+ function defaultTempRootEntries() {
656
+ return readdirSync(tmpdir());
657
+ }
658
+
659
+ /**
660
+ * Read the platform temp root's own inode size.
661
+ * @returns {number} Size in bytes of the directory inode itself.
662
+ */
663
+ function defaultTempRootInodeBytes() {
664
+ return statSync(tmpdir()).size;
665
+ }
666
+
667
+ /**
668
+ * How crowded the shared platform temp root is, or `null` when unreadable.
669
+ * @typedef {object} TempRootReading
670
+ * @property {number} entries Direct children of the platform temp root.
671
+ * @property {number} inodeBytes Size of the directory's own inode.
672
+ */
673
+
674
+ /**
675
+ * Measure the shared platform temp root.
676
+ *
677
+ * Both numbers are taken because they answer different questions and only one
678
+ * of them is repairable by deleting things. The ENTRY COUNT is the current
679
+ * population. The INODE SIZE is what that population did to the directory, and
680
+ * a directory inode does not shrink when its entries are removed — so a root
681
+ * that once held tens of thousands of names stays expensive to walk after a
682
+ * prune, and a report that showed only the count would say "cleaned up" about
683
+ * a directory that is still slow.
684
+ *
685
+ * Injected for the same reason {@link machineLoad} is: a test must be able to
686
+ * state a population rather than inherit whatever the test machine's temp root
687
+ * happened to contain, which is shared with every other process on the box.
688
+ * @param {() => string[]} [readEntries] Lists the temp root's children.
689
+ * @param {() => number} [readInodeBytes] Reads the temp root's own inode size.
690
+ * @returns {TempRootReading|null} The reading, or null when either source fails.
691
+ */
692
+ export function tempRootPopulation(
693
+ readEntries = defaultTempRootEntries,
694
+ readInodeBytes = defaultTempRootInodeBytes
695
+ ) {
696
+ try {
697
+ const entries = readEntries().length;
698
+ const inodeBytes = readInodeBytes();
699
+ if (!Number.isFinite(entries) || !Number.isFinite(inodeBytes)) return null;
700
+ return { entries, inodeBytes };
701
+ } catch {
702
+ return null;
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Turn a temp-root reading into the line that lets an operator rule crowding
708
+ * in or out.
709
+ *
710
+ * REPORTS, NEVER JUDGES — and that is a measured decision rather than caution.
711
+ * A crowded platform temp root makes `mkdtemp` pathologically slow, which
712
+ * surfaces as one slow test in an unrelated lane and never as "the filesystem
713
+ * is the problem", so the number belongs beside a timeout. But the threshold
714
+ * at which it starts costing anything is NOT KNOWN: 16.5k entries measured at
715
+ * a 1.0x `mkdtemp` penalty against a nested directory — i.e. none at all — on
716
+ * the same platform where ~46k was reported harmful. Nothing measured in
717
+ * between.
718
+ *
719
+ * Guessing a boundary in that gap would produce a detector that fires on the
720
+ * ordinary state of a busy workstation, and a check that cries wolf on a
721
+ * healthy machine is worse than no check: it trains its own readers to skip
722
+ * the line. So this prints what it saw and names both calibration points,
723
+ * leaving the reader to decide — and accumulates the evidence a later change
724
+ * can set a real threshold from.
725
+ * @param {TempRootReading|null} tempRoot The reading, or null when unavailable.
726
+ * @returns {string[]} Zero or one evidence line.
727
+ */
728
+ function tempRootEvidence(tempRoot) {
729
+ if (tempRoot === null) return [];
730
+ const kb = Math.round(tempRoot.inodeBytes / 1024);
731
+ return [
732
+ `shared temp root at diagnosis: ${tempRoot.entries} entries, ` +
733
+ `${kb} KB directory inode. A crowded platform temp root slows every ` +
734
+ `mkdtemp on the box and shows up as one slow test in an unrelated ` +
735
+ `lane, so this is a candidate cause to rule in or out — not a verdict. ` +
736
+ `No threshold is asserted because none is known: ~16.5k entries ` +
737
+ `measured NO penalty and ~46k was reported harmful, with nothing ` +
738
+ `measured between. The inode size is the part a prune does not fix.`,
739
+ ];
740
+ }
741
+
630
742
  /**
631
743
  * Runnable work per core above which the box is treated as saturated.
632
744
  *
@@ -707,7 +819,7 @@ function emptyTranscriptVerdict() {
707
819
  * @param {LoadReading|null} load The machine's load at diagnosis time.
708
820
  * @returns {object} What the failure was, before it is attributed.
709
821
  */
710
- function classify(output, code, load, read) {
822
+ function classify(output, code, load, read, tempRoot) {
711
823
  if (wasKilled(code)) return killedVerdict(code ?? null, load);
712
824
 
713
825
  if (typeof output !== "string") return unavailableVerdict();
@@ -754,7 +866,16 @@ function classify(output, code, load, read) {
754
866
  const failures = findFailures(output);
755
867
  const misses = findThresholdMisses(output);
756
868
 
757
- if (timeouts.count > 0) return timeoutVerdict(timeouts, failures.suites);
869
+ if (timeouts.count > 0)
870
+ return timeoutVerdict(
871
+ timeouts,
872
+ failures.suites,
873
+ // Resolved HERE and nowhere else: this is the only consumer, and it is
874
+ // the last point at which `undefined` (measure it) and `null` (suppress
875
+ // it) are still distinguishable. Do not "simplify" this to `??` — that
876
+ // collapses the two and makes every suppressed test read the real box.
877
+ tempRoot === undefined ? tempRootPopulation() : tempRoot
878
+ );
758
879
 
759
880
  if ((failures.tally ?? 0) > 0 || failures.suites.length > 0) {
760
881
  const count = failures.tally ?? failures.suites.length;
@@ -1067,14 +1188,35 @@ function findTerminatedComments(output, read) {
1067
1188
  * @param {(path: string) => string|null} [read] Reads a source file the
1068
1189
  * transcript named, returning null when it cannot be read. Defaults to the
1069
1190
  * real filesystem; injected by tests so a fixture needs no files on disk.
1191
+ * @param {TempRootReading|null} [tempRoot] The shared temp root's population,
1192
+ * for a timeout's evidence line. OMIT to measure this machine — but the
1193
+ * measurement is taken lazily, only on the timeout path that consumes it.
1194
+ * Pass `null` to suppress the line, or a fixed reading to make output
1195
+ * deterministic.
1196
+ *
1197
+ * This is deliberately NOT a default parameter. A default is evaluated on
1198
+ * every call that omits the argument, so `tempRoot = tempRootPopulation()`
1199
+ * ran a `readdirSync` plus a `statSync` over the shared temp root for every
1200
+ * assertion failure, coverage miss and kill — none of which use the reading.
1201
+ * On a box whose temp root holds tens of thousands of entries that is not
1202
+ * free, and the irony is total: this was added by the change about temp-root
1203
+ * churn. Nothing in the suite could catch it, because cost is not asserted.
1204
+ *
1205
+ * Making the default lazier does NOT work, and that is the trap: a default
1206
+ * parameter cannot distinguish OMITTED from EXPLICITLY NULL, because both
1207
+ * arrive as `undefined`/`null` at different points and only `undefined`
1208
+ * triggers a default. `null` is the documented suppression that keeps test
1209
+ * output deterministic, so it must survive. Hence the resolution moved to
1210
+ * the single consuming call site, where the two are still distinguishable.
1070
1211
  * @returns {Diagnosis} What the failure was, and whose it was.
1071
1212
  */
1072
1213
  export function diagnoseFailure(
1073
1214
  output,
1074
1215
  code,
1075
1216
  load = machineLoad(),
1076
- read = readSourceFile
1217
+ read = readSourceFile,
1218
+ tempRoot
1077
1219
  ) {
1078
- const verdict = classify(output, code, load, read);
1220
+ const verdict = classify(output, code, load, read, tempRoot);
1079
1221
  return { ...verdict, proves: ATTRIBUTION[verdict.kind] ?? null };
1080
1222
  }
@@ -30,6 +30,76 @@ export declare const COVERAGE_REPORTS_DIR_ENV = "LISA_COVERAGE_REPORTS_DIR";
30
30
  * @returns Root-relative path, POSIX-separated as Vitest's own default is.
31
31
  */
32
32
  export declare function coverageRunDirectory(owner: number, startedAt: number, suffix: string): string;
33
+ /**
34
+ * Publish one finished run's artifacts to the stable directory, then drop it.
35
+ *
36
+ * ## Why isolation alone is not enough
37
+ *
38
+ * `reportsDirectory` is the only lever the provider offers, so isolating the
39
+ * scratch necessarily moves the REPORTS with it. An unpinned run therefore
40
+ * writes `coverage/.runs/<run>/lcov.info` and never writes `coverage/lcov.info`
41
+ * again.
42
+ *
43
+ * A project upgrading to that behaviour does not get a missing-file error. Its
44
+ * Sonar keeps reading **the file left there before the upgrade**, indefinitely,
45
+ * and reports a coverage number that has quietly stopped moving — a wrong value
46
+ * that looks like a real one, which is the same failure isolation removed from
47
+ * concurrent runs, relocated one layer out to consumers.
48
+ *
49
+ * ## Why this is safe where a mid-run shared directory is not
50
+ *
51
+ * Ordering. The provider runs `reportCoverage` -> `generateReports` ->
52
+ * `cleanAfterRun`, and this runs later still, at process exit. It therefore
53
+ * moves COMPLETED files, never files a writer is partway through — which is
54
+ * precisely the distinction that makes the shared-directory `rm` destructive
55
+ * and this move benign.
56
+ *
57
+ * Two concurrent runs publishing together is last-writer-wins per file, and can
58
+ * leave `lcov.info` from one beside `index.html` from another. That cost is
59
+ * accepted deliberately: each run still reported its own correct number, and
60
+ * nothing here can delete a directory another run is writing into.
61
+ * @param directory - Reports directory as {@link coverageReportsDirectory} returned it
62
+ * @param expectedName - The run-directory basename this process minted
63
+ * @returns True when this run's own directory was published and removed
64
+ */
65
+ export declare function publishCoverageRunDirectory(directory: string, expectedName: string): boolean;
66
+ /**
67
+ * Remove a run directory this process minted, and nothing else.
68
+ *
69
+ * The fallback when publishing could not happen, and the guard is the same one:
70
+ * the basename must be the name this process generated, so a mis-wired caller
71
+ * removes nothing rather than removing a directory another run is writing into.
72
+ * @param directory - Reports directory as {@link coverageReportsDirectory} returned it
73
+ * @param expectedName - The run-directory basename this process minted
74
+ * @returns True when the path was this run's own
75
+ */
76
+ export declare function removeOwnCoverageRunDirectory(directory: string, expectedName: string): boolean;
77
+ /**
78
+ * Remove run directories belonging to runs that are no longer alive.
79
+ *
80
+ * Publishing happens at process exit, so an ordinary run leaves nothing behind.
81
+ * A run that is `SIGKILL`ed does — and on a contended machine that is routine
82
+ * rather than exceptional, so without this `coverage/.runs/` accrues one
83
+ * directory per killed run with nothing ever removing them.
84
+ *
85
+ * ## Why liveness is legitimate here and not in a lock
86
+ *
87
+ * Reclaiming an abandoned directory is not adjudicating ownership of a
88
+ * contended resource, and the asymmetry is what makes it safe: a false KEEP is
89
+ * inert debris, and a false REMOVE is impossible, because a live run's own pid
90
+ * always probes alive. The lock this module refuses has the opposite asymmetry
91
+ * — there, a wrong answer releases a lock somebody is holding.
92
+ *
93
+ * It introduces no second authority. {@link parseRunRootName} and
94
+ * {@link isProcessAlive} are the scratch namespace's existing parser and
95
+ * liveness probe, and these directories are named by {@link runRootName}, so
96
+ * that parser reads them unchanged. Anything it does not recognise is left
97
+ * alone.
98
+ * @param runsRoot - Directory holding the run directories
99
+ * @param alive - Liveness probe; defaults to the shared one
100
+ * @returns Basenames of the directories removed
101
+ */
102
+ export declare function reclaimAbandonedCoverageRunDirectories(runsRoot?: string, alive?: (owner: number) => boolean): readonly string[];
33
103
  /**
34
104
  * Resolves the coverage reports directory for this run.
35
105
  * @param environment - Environment to read; defaults to this process's.
@@ -1 +1 @@
1
- {"version":3,"file":"coverage-reports-directory.d.ts","sourceRoot":"","sources":["../../../src/configs/vitest/coverage-reports-directory.ts"],"names":[],"mappings":"AAiEA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,8BAA8B,CAAC;AAKpE;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAYD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,GAAE,MAAM,CAAC,UAAgB,GACnC,MAAM,CAUR;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
1
+ {"version":3,"file":"coverage-reports-directory.d.ts","sourceRoot":"","sources":["../../../src/configs/vitest/coverage-reports-directory.ts"],"names":[],"mappings":"AAuEA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,8BAA8B,CAAC;AAkBpE;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAyBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAWT;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,sCAAsC,CACpD,QAAQ,GAAE,MAAoD,EAC9D,KAAK,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAwB,GACjD,SAAS,MAAM,EAAE,CAenB;AAYD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,GAAE,MAAM,CAAC,UAAgB,GACnC,MAAM,CAcR;AAkCD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAGpD"}
@@ -59,8 +59,11 @@
59
59
  * @module configs/vitest/coverage-reports-directory
60
60
  */
61
61
  import { randomBytes } from "node:crypto";
62
+ import * as fs from "node:fs";
63
+ import * as path from "node:path";
62
64
  import { env, pid } from "node:process";
63
- import { runRootName } from "./scratch-namespace-authority.js";
65
+ import { parseRunRootName, runRootName, } from "./scratch-namespace-authority.js";
66
+ import { isProcessAlive } from "./scratch-sweep.js";
64
67
  /**
65
68
  * Directory under `coverage/` holding one run's isolated output.
66
69
  *
@@ -84,6 +87,17 @@ export const COVERAGE_RUNS_DIR = ".runs";
84
87
  export const COVERAGE_REPORTS_DIR_ENV = "LISA_COVERAGE_REPORTS_DIR";
85
88
  /** Bytes of randomness disambiguating two runs started in the same millisecond. */
86
89
  const SUFFIX_BYTES = 4;
90
+ /** The stable directory a checkout's coverage artifacts are read from. */
91
+ const COVERAGE_BASE = "coverage";
92
+ /**
93
+ * The provider's own scratch subdirectory, named so it is never published.
94
+ *
95
+ * `cleanAfterRun` removes it before the process exits, so in practice it is
96
+ * already gone by the time anything publishes. Skipping it explicitly means a
97
+ * run killed between report generation and exit cannot copy a half-written
98
+ * `coverage-<n>.json` up to the stable path, where another run would read it.
99
+ */
100
+ const COVERAGE_SCRATCH_DIR = ".tmp";
87
101
  /**
88
102
  * Builds the relative reports directory for one run.
89
103
  *
@@ -97,6 +111,134 @@ const SUFFIX_BYTES = 4;
97
111
  export function coverageRunDirectory(owner, startedAt, suffix) {
98
112
  return `coverage/${COVERAGE_RUNS_DIR}/${runRootName(owner, startedAt, suffix)}`;
99
113
  }
114
+ /**
115
+ * Publish one finished run's artifacts to the stable directory, then drop it.
116
+ *
117
+ * ## Why isolation alone is not enough
118
+ *
119
+ * `reportsDirectory` is the only lever the provider offers, so isolating the
120
+ * scratch necessarily moves the REPORTS with it. An unpinned run therefore
121
+ * writes `coverage/.runs/<run>/lcov.info` and never writes `coverage/lcov.info`
122
+ * again.
123
+ *
124
+ * A project upgrading to that behaviour does not get a missing-file error. Its
125
+ * Sonar keeps reading **the file left there before the upgrade**, indefinitely,
126
+ * and reports a coverage number that has quietly stopped moving — a wrong value
127
+ * that looks like a real one, which is the same failure isolation removed from
128
+ * concurrent runs, relocated one layer out to consumers.
129
+ *
130
+ * ## Why this is safe where a mid-run shared directory is not
131
+ *
132
+ * Ordering. The provider runs `reportCoverage` -> `generateReports` ->
133
+ * `cleanAfterRun`, and this runs later still, at process exit. It therefore
134
+ * moves COMPLETED files, never files a writer is partway through — which is
135
+ * precisely the distinction that makes the shared-directory `rm` destructive
136
+ * and this move benign.
137
+ *
138
+ * Two concurrent runs publishing together is last-writer-wins per file, and can
139
+ * leave `lcov.info` from one beside `index.html` from another. That cost is
140
+ * accepted deliberately: each run still reported its own correct number, and
141
+ * nothing here can delete a directory another run is writing into.
142
+ * @param directory - Reports directory as {@link coverageReportsDirectory} returned it
143
+ * @param expectedName - The run-directory basename this process minted
144
+ * @returns True when this run's own directory was published and removed
145
+ */
146
+ export function publishCoverageRunDirectory(directory, expectedName) {
147
+ if (path.basename(directory) !== expectedName)
148
+ return false;
149
+ const from = path.resolve(process.cwd(), directory);
150
+ // Two levels up from `<base>/coverage/.runs/<run>` is `<base>/coverage`.
151
+ // Derived from the run directory rather than from `process.cwd()` so the
152
+ // pair travels together: a caller that states one has stated both, and a
153
+ // test can exercise this against a temporary tree without changing
154
+ // directory.
155
+ const stable = path.dirname(path.dirname(from));
156
+ try {
157
+ if (!fs.existsSync(from))
158
+ return false;
159
+ for (const entry of fs.readdirSync(from)) {
160
+ if (entry === COVERAGE_SCRATCH_DIR)
161
+ continue;
162
+ const target = path.join(stable, entry);
163
+ fs.rmSync(target, { recursive: true, force: true });
164
+ fs.renameSync(path.join(from, entry), target);
165
+ }
166
+ fs.rmSync(from, { recursive: true, force: true });
167
+ return true;
168
+ }
169
+ catch {
170
+ // Best effort. A run that cannot publish its artifacts has still measured
171
+ // and reported its coverage correctly, and failing over it would turn a
172
+ // tidiness problem into the red gate this module exists to prevent.
173
+ return false;
174
+ }
175
+ }
176
+ /**
177
+ * Remove a run directory this process minted, and nothing else.
178
+ *
179
+ * The fallback when publishing could not happen, and the guard is the same one:
180
+ * the basename must be the name this process generated, so a mis-wired caller
181
+ * removes nothing rather than removing a directory another run is writing into.
182
+ * @param directory - Reports directory as {@link coverageReportsDirectory} returned it
183
+ * @param expectedName - The run-directory basename this process minted
184
+ * @returns True when the path was this run's own
185
+ */
186
+ export function removeOwnCoverageRunDirectory(directory, expectedName) {
187
+ if (path.basename(directory) !== expectedName)
188
+ return false;
189
+ try {
190
+ fs.rmSync(path.resolve(process.cwd(), directory), {
191
+ recursive: true,
192
+ force: true,
193
+ });
194
+ return true;
195
+ }
196
+ catch {
197
+ return false;
198
+ }
199
+ }
200
+ /**
201
+ * Remove run directories belonging to runs that are no longer alive.
202
+ *
203
+ * Publishing happens at process exit, so an ordinary run leaves nothing behind.
204
+ * A run that is `SIGKILL`ed does — and on a contended machine that is routine
205
+ * rather than exceptional, so without this `coverage/.runs/` accrues one
206
+ * directory per killed run with nothing ever removing them.
207
+ *
208
+ * ## Why liveness is legitimate here and not in a lock
209
+ *
210
+ * Reclaiming an abandoned directory is not adjudicating ownership of a
211
+ * contended resource, and the asymmetry is what makes it safe: a false KEEP is
212
+ * inert debris, and a false REMOVE is impossible, because a live run's own pid
213
+ * always probes alive. The lock this module refuses has the opposite asymmetry
214
+ * — there, a wrong answer releases a lock somebody is holding.
215
+ *
216
+ * It introduces no second authority. {@link parseRunRootName} and
217
+ * {@link isProcessAlive} are the scratch namespace's existing parser and
218
+ * liveness probe, and these directories are named by {@link runRootName}, so
219
+ * that parser reads them unchanged. Anything it does not recognise is left
220
+ * alone.
221
+ * @param runsRoot - Directory holding the run directories
222
+ * @param alive - Liveness probe; defaults to the shared one
223
+ * @returns Basenames of the directories removed
224
+ */
225
+ export function reclaimAbandonedCoverageRunDirectories(runsRoot = path.join(COVERAGE_BASE, COVERAGE_RUNS_DIR), alive = isProcessAlive) {
226
+ const root = path.resolve(process.cwd(), runsRoot);
227
+ try {
228
+ const abandoned = fs.readdirSync(root).filter(name => {
229
+ const owner = parseRunRootName(name);
230
+ return owner !== undefined && !alive(owner.pid);
231
+ });
232
+ for (const name of abandoned)
233
+ fs.rmSync(path.join(root, name), { recursive: true, force: true });
234
+ return abandoned;
235
+ }
236
+ catch {
237
+ // An absent or unreadable runs directory is not an error: nothing to
238
+ // reclaim is the ordinary state on a checkout that has never run coverage.
239
+ return [];
240
+ }
241
+ }
100
242
  /**
101
243
  * The path this process's coverage output belongs in.
102
244
  *
@@ -115,9 +257,44 @@ export function coverageReportsDirectory(environment = env) {
115
257
  const override = environment[COVERAGE_REPORTS_DIR_ENV];
116
258
  if (override !== undefined && override.trim() !== "")
117
259
  return override;
118
- pinned.current ??= coverageRunDirectory(pid, Date.now(), randomBytes(SUFFIX_BYTES).toString("hex"));
260
+ if (pinned.current === undefined) {
261
+ const name = runRootName(pid, Date.now(), randomBytes(SUFFIX_BYTES).toString("hex"));
262
+ pinned.current = `${COVERAGE_BASE}/${COVERAGE_RUNS_DIR}/${name}`;
263
+ registerRunLifecycle(pinned.current, name);
264
+ }
119
265
  return pinned.current;
120
266
  }
267
+ /**
268
+ * Whether this process has already armed its reclaim and publish steps.
269
+ *
270
+ * Field named `current` to match {@link pinned}, and not by accident: the
271
+ * project's `functional/immutable-data` rule permits mutation only through
272
+ * `*.current`, `*.value` and `*.displayName`. Renaming it makes the file
273
+ * stop linting.
274
+ */
275
+ const lifecycle = { current: false };
276
+ /**
277
+ * Reclaim abandoned directories now, and publish this run's own at exit.
278
+ *
279
+ * Armed once per process, from the same place the directory is minted, so the
280
+ * two cannot disagree about which run they belong to. A pinned run never
281
+ * reaches here: the pinned directory belongs to whoever set the pin, and is
282
+ * left exactly as it was found.
283
+ * @param directory - This run's reports directory
284
+ * @param name - Its basename, which is the ownership proof both steps check
285
+ */
286
+ function registerRunLifecycle(directory, name) {
287
+ if (lifecycle.current)
288
+ return;
289
+ lifecycle.current = true;
290
+ // Before minting anything of our own: this run's directory does not exist
291
+ // yet and its pid is alive regardless, so it can never be its own victim.
292
+ reclaimAbandonedCoverageRunDirectories();
293
+ process.once("exit", () => {
294
+ if (!publishCoverageRunDirectory(directory, name))
295
+ removeOwnCoverageRunDirectory(directory, name);
296
+ });
297
+ }
121
298
  /**
122
299
  * Discards the memoized value.
123
300
  *
@@ -127,5 +304,6 @@ export function coverageReportsDirectory(environment = env) {
127
304
  */
128
305
  export function resetCoverageReportsDirectory() {
129
306
  pinned.current = undefined;
307
+ lifecycle.current = false;
130
308
  }
131
309
  //# sourceMappingURL=coverage-reports-directory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"coverage-reports-directory.js","sourceRoot":"","sources":["../../../src/configs/vitest/coverage-reports-directory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAEzC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,2BAA2B,CAAC;AAEpE,mFAAmF;AACnF,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAa,EACb,SAAiB,EACjB,MAAc;IAEd,OAAO,YAAY,iBAAiB,IAAI,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC;AAClF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,GAAoC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,cAAiC,GAAG;IAEpC,MAAM,QAAQ,GAAG,WAAW,CAAC,wBAAwB,CAAC,CAAC;IACvD,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,QAAQ,CAAC;IAEtE,MAAM,CAAC,OAAO,KAAK,oBAAoB,CACrC,GAAG,EACH,IAAI,CAAC,GAAG,EAAE,EACV,WAAW,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC1C,CAAC;IACF,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,6BAA6B;IAC3C,MAAM,CAAC,OAAO,GAAG,SAAS,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"coverage-reports-directory.js","sourceRoot":"","sources":["../../../src/configs/vitest/coverage-reports-directory.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"lisa-owned-hash-ledger.d.ts","sourceRoot":"","sources":["../../src/core/lisa-owned-hash-ledger.ts"],"names":[],"mappings":"AACA,6EAA6E;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CA+jCjC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CACpD,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAk+BjC,CAAC"}
1
+ {"version":3,"file":"lisa-owned-hash-ledger.d.ts","sourceRoot":"","sources":["../../src/core/lisa-owned-hash-ledger.ts"],"names":[],"mappings":"AACA,6EAA6E;AAE7E;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAC3C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAikCjC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CACpD,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAo+BjC,CAAC"}
@@ -358,8 +358,10 @@ export const LISA_OWNED_HASH_LEDGER = Object.freeze({
358
358
  "041094be579c3aefdf9f7b384e24ca23cd860c984c9094ffeb55b1542829bc39",
359
359
  "28600bd3ce643d80aefcc696c4e9607f83410ab926738d9baa7db694d78563ea",
360
360
  "3e9a5911c9b4b4f3c0dd6aef4ecc7accb762edf6e62eea66252248f7c02684f4",
361
+ "43dc203de647ebdcd0e3e1878240423e3895b588372002c79ffed3348e4f08a3",
361
362
  "4503ddbe7bd54cd553db2b5952972d5319d6b6eb39a258b7c58577b61c7e7ce7",
362
363
  "503b51e9239411d07ce6b51dbe2b3079bb12e94bf7c0310e76247af19ff667e6",
364
+ "5d0aee00b95dba43cae499f338358ad4b0286f869c993508c1146448e92bd777",
363
365
  "6742cc12ea14fc99b7bef31ff03df68d5c4610b56fad43107790372e6d777eb1",
364
366
  "6b72a6a71078c838fdb0531e74898a5e36d942cad424b20480e2cd370b5264eb",
365
367
  "7ffcd3f6a0595ae26c697bb0f77044b8a100200985774ad7059ebc3dc2e6bedd",
@@ -1440,8 +1442,10 @@ export const LISA_OWNED_HASH_HISTORY_DERIVED = Object.freeze({
1440
1442
  "041094be579c3aefdf9f7b384e24ca23cd860c984c9094ffeb55b1542829bc39",
1441
1443
  "28600bd3ce643d80aefcc696c4e9607f83410ab926738d9baa7db694d78563ea",
1442
1444
  "3e9a5911c9b4b4f3c0dd6aef4ecc7accb762edf6e62eea66252248f7c02684f4",
1445
+ "43dc203de647ebdcd0e3e1878240423e3895b588372002c79ffed3348e4f08a3",
1443
1446
  "4503ddbe7bd54cd553db2b5952972d5319d6b6eb39a258b7c58577b61c7e7ce7",
1444
1447
  "503b51e9239411d07ce6b51dbe2b3079bb12e94bf7c0310e76247af19ff667e6",
1448
+ "5d0aee00b95dba43cae499f338358ad4b0286f869c993508c1146448e92bd777",
1445
1449
  "6742cc12ea14fc99b7bef31ff03df68d5c4610b56fad43107790372e6d777eb1",
1446
1450
  "7ffcd3f6a0595ae26c697bb0f77044b8a100200985774ad7059ebc3dc2e6bedd",
1447
1451
  "9bce79d2cdcf709be40fee1bec469796bee2bb06a4cd38b11905677b97c12bc3",