@vibe-agent-toolkit/cli 0.2.0-rc.3 â 0.2.0-rc.5
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.
- package/dist/bin/vat +2 -1
- package/dist/bin/vat.js +2 -1
- package/dist/bin/vat.js.map +1 -1
- package/dist/bin.js +37 -3
- package/dist/bin.js.map +1 -1
- package/dist/commands/audit/distributed-tree.d.ts +1 -1
- package/dist/commands/audit/distributed-tree.d.ts.map +1 -1
- package/dist/commands/audit/distributed-tree.js +2 -1
- package/dist/commands/audit/distributed-tree.js.map +1 -1
- package/dist/commands/audit/git-url-clone.d.ts +1 -1
- package/dist/commands/audit/git-url-clone.d.ts.map +1 -1
- package/dist/commands/audit/git-url-clone.js.map +1 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +25 -6
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +14 -11
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/cache/cache-control.d.ts +16 -7
- package/dist/commands/cache/cache-control.d.ts.map +1 -1
- package/dist/commands/cache/cache-control.js +17 -8
- package/dist/commands/cache/cache-control.js.map +1 -1
- package/dist/commands/claude/budget.js +1 -1
- package/dist/commands/claude/budget.js.map +1 -1
- package/dist/commands/claude/context.d.ts +138 -9
- package/dist/commands/claude/context.d.ts.map +1 -1
- package/dist/commands/claude/context.js +450 -80
- package/dist/commands/claude/context.js.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/git-publish.js +2 -1
- package/dist/commands/claude/marketplace/git-publish.js.map +1 -1
- package/dist/commands/claude/marketplace/validate.d.ts +18 -0
- package/dist/commands/claude/marketplace/validate.d.ts.map +1 -1
- package/dist/commands/claude/marketplace/validate.js +40 -20
- package/dist/commands/claude/marketplace/validate.js.map +1 -1
- package/dist/commands/claude/plugin/build.d.ts +17 -1
- package/dist/commands/claude/plugin/build.d.ts.map +1 -1
- package/dist/commands/claude/plugin/build.js +45 -16
- package/dist/commands/claude/plugin/build.js.map +1 -1
- package/dist/commands/claude/plugin/helpers.d.ts.map +1 -1
- package/dist/commands/claude/plugin/helpers.js +2 -1
- package/dist/commands/claude/plugin/helpers.js.map +1 -1
- package/dist/commands/claude/plugin/install.d.ts.map +1 -1
- package/dist/commands/claude/plugin/install.js +2 -2
- package/dist/commands/claude/plugin/install.js.map +1 -1
- package/dist/commands/claude/plugin/tree-copy.d.ts.map +1 -1
- package/dist/commands/claude/plugin/tree-copy.js +2 -1
- package/dist/commands/claude/plugin/tree-copy.js.map +1 -1
- package/dist/commands/consistency-check.d.ts.map +1 -1
- package/dist/commands/consistency-check.js +2 -1
- package/dist/commands/consistency-check.js.map +1 -1
- package/dist/commands/corpus/runner.d.ts.map +1 -1
- package/dist/commands/corpus/runner.js +3 -20
- package/dist/commands/corpus/runner.js.map +1 -1
- package/dist/commands/corpus/scan.d.ts.map +1 -1
- package/dist/commands/corpus/scan.js +2 -1
- package/dist/commands/corpus/scan.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +3 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/inventory.js +2 -2
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/phase-utils.d.ts +154 -103
- package/dist/commands/phase-utils.d.ts.map +1 -1
- package/dist/commands/phase-utils.js +109 -155
- package/dist/commands/phase-utils.js.map +1 -1
- package/dist/commands/resources/check-progress.d.ts +145 -0
- package/dist/commands/resources/check-progress.d.ts.map +1 -0
- package/dist/commands/resources/check-progress.js +205 -0
- package/dist/commands/resources/check-progress.js.map +1 -0
- package/dist/commands/resources/check-supervisor.d.ts +465 -0
- package/dist/commands/resources/check-supervisor.d.ts.map +1 -0
- package/dist/commands/resources/check-supervisor.js +566 -0
- package/dist/commands/resources/check-supervisor.js.map +1 -0
- package/dist/commands/resources/check.d.ts +454 -0
- package/dist/commands/resources/check.d.ts.map +1 -0
- package/dist/commands/resources/check.js +1237 -0
- package/dist/commands/resources/check.js.map +1 -0
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +277 -6
- package/dist/commands/resources/index.js.map +1 -1
- package/dist/commands/resources/query.d.ts +89 -0
- package/dist/commands/resources/query.d.ts.map +1 -0
- package/dist/commands/resources/query.js +146 -0
- package/dist/commands/resources/query.js.map +1 -0
- package/dist/commands/resources/scan.d.ts.map +1 -1
- package/dist/commands/resources/scan.js +2 -2
- package/dist/commands/resources/scan.js.map +1 -1
- package/dist/commands/resources/validate.d.ts +11 -0
- package/dist/commands/resources/validate.d.ts.map +1 -1
- package/dist/commands/resources/validate.js +96 -48
- package/dist/commands/resources/validate.js.map +1 -1
- package/dist/commands/skill/test/run.d.ts.map +1 -1
- package/dist/commands/skill/test/run.js +11 -2
- package/dist/commands/skill/test/run.js.map +1 -1
- package/dist/commands/skills/build.d.ts +10 -0
- package/dist/commands/skills/build.d.ts.map +1 -1
- package/dist/commands/skills/build.js +83 -25
- package/dist/commands/skills/build.js.map +1 -1
- package/dist/commands/skills/install.js +0 -1
- package/dist/commands/skills/install.js.map +1 -1
- package/dist/commands/skills/skill-discovery.d.ts.map +1 -1
- package/dist/commands/skills/skill-discovery.js +6 -3
- package/dist/commands/skills/skill-discovery.js.map +1 -1
- package/dist/commands/skills/validate.d.ts +78 -1
- package/dist/commands/skills/validate.d.ts.map +1 -1
- package/dist/commands/skills/validate.js +67 -32
- package/dist/commands/skills/validate.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +26 -16
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.d.ts +9 -8
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +46 -35
- package/dist/commands/verify.js.map +1 -1
- package/dist/utils/agent-runner.d.ts.map +1 -1
- package/dist/utils/agent-runner.js +7 -3
- package/dist/utils/agent-runner.js.map +1 -1
- package/dist/utils/command-error.d.ts +88 -2
- package/dist/utils/command-error.d.ts.map +1 -1
- package/dist/utils/command-error.js +98 -11
- package/dist/utils/command-error.js.map +1 -1
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +10 -3
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/issue-severity.d.ts +12 -5
- package/dist/utils/issue-severity.d.ts.map +1 -1
- package/dist/utils/issue-severity.js +1 -1
- package/dist/utils/issue-severity.js.map +1 -1
- package/dist/utils/population-wiring.d.ts +47 -6
- package/dist/utils/population-wiring.d.ts.map +1 -1
- package/dist/utils/population-wiring.js +45 -2
- package/dist/utils/population-wiring.js.map +1 -1
- package/dist/utils/projection-query.d.ts +263 -0
- package/dist/utils/projection-query.d.ts.map +1 -0
- package/dist/utils/projection-query.js +262 -0
- package/dist/utils/projection-query.js.map +1 -0
- package/dist/utils/projection-store.d.ts +110 -6
- package/dist/utils/projection-store.d.ts.map +1 -1
- package/dist/utils/projection-store.js +133 -9
- package/dist/utils/projection-store.js.map +1 -1
- package/dist/utils/resource-loader.d.ts +1 -1
- package/dist/utils/resource-loader.d.ts.map +1 -1
- package/dist/utils/resource-loader.js +11 -1
- package/dist/utils/resource-loader.js.map +1 -1
- package/dist/utils/vat-bin-path.d.ts +36 -0
- package/dist/utils/vat-bin-path.d.ts.map +1 -0
- package/dist/utils/vat-bin-path.js +49 -0
- package/dist/utils/vat-bin-path.js.map +1 -0
- package/package.json +25 -14
- package/dist/commands/resources/command-helpers.d.ts +0 -5
- package/dist/commands/resources/command-helpers.d.ts.map +0 -1
- package/dist/commands/resources/command-helpers.js +0 -6
- package/dist/commands/resources/command-helpers.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-supervisor.d.ts","sourceRoot":"","sources":["../../../src/commands/resources/check-supervisor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AA+CH,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAK3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CA+BlE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3D,OAAO,CAET;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAWP;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACrF;;;;;;;;;;;;;GAaG;GACD;IACA,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GACC;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE;AAChC;;;;;;;;;;;;;;;;;;GAkBG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,qFAAqF;AACrF,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAC5D,WAAW,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC;CACvE,GAAG,aAAa,CAchB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,aAAa,EACzB,MAAM,EAAE,MAAM,GACb,aAAa,CAGf;AAED,kCAAkC;AAClC,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjF;IAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAChF;IACA,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,aAAa,CAAC,CA2EzB;AAiDD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAO1F"}
|
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The time bound on `vat resources check`, enforced from OUTSIDE the process
|
|
3
|
+
* doing the work.
|
|
4
|
+
*
|
|
5
|
+
* ## ðĻ Why an external process, and not any of the cheaper things
|
|
6
|
+
*
|
|
7
|
+
* This verb runs adopter-authored SQL as an unattended CI gate. An accidental
|
|
8
|
+
* cross join, or a `WITH RECURSIVE` with no termination, runs forever â and
|
|
9
|
+
* every in-process lever was tried and MEASURED on Node 24.13.1 before this
|
|
10
|
+
* shape was chosen. Do not re-derive them:
|
|
11
|
+
*
|
|
12
|
+
* - **A worker thread does not work.** `worker.terminate()` never resolves
|
|
13
|
+
* against a thread blocked in native SQLite, and the parent's own
|
|
14
|
+
* `process.exit()` does not exit either. V8 can unwind a worker spinning in
|
|
15
|
+
* JS â which is why the parse pool's `terminate()` genuinely works â but a
|
|
16
|
+
* thread inside a synchronous native call is not spinning in JS. Do NOT
|
|
17
|
+
* "align" this design with the parse pool's.
|
|
18
|
+
* - **A signal handler is a REGRESSION.** A process blocked in synchronous
|
|
19
|
+
* `node:sqlite` dies INSTANTLY on SIGINT while no handler is installed, and
|
|
20
|
+
* SURVIVES both SIGINT and SIGTERM once one is â the handler is a JS callback,
|
|
21
|
+
* and the event loop that would schedule it is the resource that is blocked.
|
|
22
|
+
* Installing one removes the operator's Ctrl-C. There is no
|
|
23
|
+
* `process.on('SIGINT')` here and there must never be.
|
|
24
|
+
* - **The kill is `SIGKILL` because SIGKILL cannot be REGRESSED.** No code the
|
|
25
|
+
* child runs, now or after any future edit, can handle, ignore or block it.
|
|
26
|
+
*
|
|
27
|
+
* â ïļ This header used to claim, as measured fact, that "SIGTERM does not kill
|
|
28
|
+
* the child either, for the same reason". **That is false and was measured to
|
|
29
|
+
* be false**: `kill -TERM` on a child blocked inside synchronous `node:sqlite`
|
|
30
|
+
* `.all()` killed it instantly. The measurement above is about a process that
|
|
31
|
+
* HAS a JS handler installed â a handler is a JS callback and the blocked
|
|
32
|
+
* event loop is what would schedule it â and with no handler installed
|
|
33
|
+
* SIGTERM's default disposition is kernel-delivered termination that needs no
|
|
34
|
+
* cooperation at all. So SIGTERM works today only because nothing here
|
|
35
|
+
* installs a handler, which is a property one future edit could silently
|
|
36
|
+
* remove; SIGKILL is not a property anything can remove. The design did not
|
|
37
|
+
* change, only the reason for it â recorded because a false claim is at its
|
|
38
|
+
* worst in a section that tells the next reader not to re-derive it.
|
|
39
|
+
* - **The database cannot be handed over.** `DatabaseSync` has no `serialize()`
|
|
40
|
+
* and `:memory:` is per-CONNECTION, so whoever runs the SQL populates its own
|
|
41
|
+
* projection. That is why the child is a whole `vat resources check` run and
|
|
42
|
+
* not a query executor.
|
|
43
|
+
*
|
|
44
|
+
* ## ð The budget is ABSENCE OF PROGRESS, not total runtime
|
|
45
|
+
*
|
|
46
|
+
* A total-runtime bound has to be set above the slowest legitimate run on the
|
|
47
|
+
* largest adopter tree, which makes it useless as a hang detector, and it kills
|
|
48
|
+
* healthy runs that are merely large. So the parent watches the child's progress
|
|
49
|
+
* log and resets its clock on every new line â INCLUDING the population line,
|
|
50
|
+
* which is what bounds the population itself without charging it the first
|
|
51
|
+
* check's budget. A gate that can hang forever is the thing being fixed, and
|
|
52
|
+
* that includes hanging before the first statement.
|
|
53
|
+
*
|
|
54
|
+
* â ïļ **That property holds for the CHECKS. It does NOT hold for the population,
|
|
55
|
+
* and the difference is not a detail.** The population emits exactly ONE line,
|
|
56
|
+
* at its end, so for the single longest unit in a cold run the budget degrades
|
|
57
|
+
* to precisely the total-runtime bound this section argues against: a population
|
|
58
|
+
* that takes longer than the budget is killed however healthily it is working.
|
|
59
|
+
*
|
|
60
|
+
* That is not cheap to fix and a half-fix would be worse than saying so.
|
|
61
|
+
* `onContributorTiming` does fire per contributor, but ~88% of a cold population
|
|
62
|
+
* is the BLOB stage â one call, remark-parsing every document, 33-35 s of the
|
|
63
|
+
* 35 s â and it emits nothing. Per-contributor lines would therefore bound the
|
|
64
|
+
* cheap part, leave the expensive part exactly as unbounded as it is now, and
|
|
65
|
+
* make the whole thing LOOK instrumented. So the population stays one
|
|
66
|
+
* un-instrumented unit, the default budget is set well above a cold one, and the
|
|
67
|
+
* per-unit property is claimed only where it is true.
|
|
68
|
+
*
|
|
69
|
+
* ## Why the child is the CLI itself
|
|
70
|
+
*
|
|
71
|
+
* The child is `vat resources check` with a hidden `--cost-log` flag, running
|
|
72
|
+
* the ordinary in-process path. Forking the logic would give the supervised lane
|
|
73
|
+
* and the unsupervised one two implementations of the same gate â and the one
|
|
74
|
+
* nobody runs interactively is the one that would rot.
|
|
75
|
+
*/
|
|
76
|
+
import { spawn } from 'node:child_process';
|
|
77
|
+
import { mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
78
|
+
import { safePath } from '@vibe-agent-toolkit/utils';
|
|
79
|
+
import { normalizedTmpdir } from '@vibe-agent-toolkit/utils/fs';
|
|
80
|
+
import { resolveVatBinPath } from '../../utils/vat-bin-path.js';
|
|
81
|
+
/**
|
|
82
|
+
* The bound applied when `--budget` is not passed, in seconds.
|
|
83
|
+
*
|
|
84
|
+
* Not a round number chosen for looks, and no longer sized against this
|
|
85
|
+
* repository alone. Population is ~1.2 s warm here and 33-35 s with a cold parse
|
|
86
|
+
* cache; on a real adopter tree of 9,992 tracked files it is ~5 s warm and
|
|
87
|
+
* **16.5 s cold**, which is the first time the default was checked outside VAT.
|
|
88
|
+
* So 300 s is ~18Ã the worst thing anyone has actually measured â deliberately
|
|
89
|
+
* loose, because a FALSE kill is far worse than a slow honest failure: it
|
|
90
|
+
* teaches the operator to pass `--budget 0` and lose the bound entirely. Five
|
|
91
|
+
* minutes still turns an infinite hang into a clean bounded failure, which is
|
|
92
|
+
* the whole objective. A tree ten times that adopter's lands near 165 s, still
|
|
93
|
+
* inside the default but no longer comfortably.
|
|
94
|
+
*/
|
|
95
|
+
const DEFAULT_BUDGET_SECONDS = 300;
|
|
96
|
+
/** How many times per budget the parent looks at the log. */
|
|
97
|
+
const POLLS_PER_BUDGET = 10;
|
|
98
|
+
/** Poll bounds, so a huge budget still notices promptly and a tiny one is cheap. */
|
|
99
|
+
const MIN_POLL_MS = 50;
|
|
100
|
+
const MAX_POLL_MS = 250;
|
|
101
|
+
/**
|
|
102
|
+
* How many refused SIGKILLs the watchdog sends before it stops trying and
|
|
103
|
+
* REPORTS instead.
|
|
104
|
+
*
|
|
105
|
+
* A retry count, not a bound on anything stored. It exists because the two ways
|
|
106
|
+
* a kill can fail want opposite responses: `ESRCH` is a race that resolves
|
|
107
|
+
* itself the instant `close` arrives, while `EPERM` is a permission fact that
|
|
108
|
+
* will be just as true on the thousandth attempt. Retrying a handful of times
|
|
109
|
+
* covers the first at negligible cost; continuing past that would turn the
|
|
110
|
+
* supervisor into a second unbounded hang, which is the thing this module
|
|
111
|
+
* exists to abolish. So it escalates to an honest report naming the pid.
|
|
112
|
+
*/
|
|
113
|
+
const KILL_ATTEMPTS_BEFORE_REPORTING = 5;
|
|
114
|
+
/**
|
|
115
|
+
* Decide whether the child has gone quiet for longer than the budget.
|
|
116
|
+
*
|
|
117
|
+
* Pure, and separated from the timer for exactly that reason: the interesting
|
|
118
|
+
* behaviour is "a run that keeps making progress is never killed, however long
|
|
119
|
+
* it takes", and a test of that against a real timer would be a twenty-second
|
|
120
|
+
* test that flakes. Here it is twenty function calls.
|
|
121
|
+
*
|
|
122
|
+
* ðŠĪ Growth is measured in BYTES rather than parsed lines. A partially written
|
|
123
|
+
* line also grows the file, and treating that as progress is the safe direction:
|
|
124
|
+
* the child is demonstrably alive and writing. Counting lines instead would mean
|
|
125
|
+
* a very slow write could look like silence.
|
|
126
|
+
*
|
|
127
|
+
* @param state - What the previous poll saw
|
|
128
|
+
* @param observation - The run
|
|
129
|
+
* @param observation.bytes - The progress log's current size
|
|
130
|
+
* @param observation.now - The clock, in milliseconds
|
|
131
|
+
* @param observation.budgetMs - How long silence is allowed to last
|
|
132
|
+
* @returns The state to carry forward, and whether the budget was blown
|
|
133
|
+
*/
|
|
134
|
+
export function pollWatchdog(state, observation) {
|
|
135
|
+
if (observation.bytes !== state.bytesSeen) {
|
|
136
|
+
return { state: { bytesSeen: observation.bytes, quietSince: observation.now }, breach: false };
|
|
137
|
+
}
|
|
138
|
+
return { state, breach: observation.now - state.quietSince >= observation.budgetMs };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Read `--budget`, or supply the default.
|
|
142
|
+
*
|
|
143
|
+
* ðŠĪ A typo is REFUSED rather than defaulted. `--budget 2O` (letter O) would
|
|
144
|
+
* otherwise run with a five-minute bound while the operator believed they had
|
|
145
|
+
* set two seconds â and they would only find out from a CI job that hung for
|
|
146
|
+
* five minutes instead of two. Thrown, so it exits 2 as an operator error, in
|
|
147
|
+
* line with this command's existing contract for an unknown `--check` name.
|
|
148
|
+
*
|
|
149
|
+
* ðĻ **A BLANK value is refused before `Number` ever sees it, and that is the
|
|
150
|
+
* more important half.** `Number('')`, `Number(' ')` and `Number('\n')` are all
|
|
151
|
+
* `0` â finite, non-negative, and therefore accepted by the range guard below â
|
|
152
|
+
* and `0` is this flag's documented escape hatch meaning *no bound at all*. So
|
|
153
|
+
* `--budget "$CHECK_BUDGET"` with the variable unset did not shorten the bound
|
|
154
|
+
* and did not complain: it silently REMOVED it, on the one flag whose entire
|
|
155
|
+
* purpose is to stop an unattended job hanging forever. An unset shell variable
|
|
156
|
+
* is a far more common CI accident than the typo the guard was written for.
|
|
157
|
+
*
|
|
158
|
+
* ðĻ **The blank string was only the visible half of the hazard, and the guard
|
|
159
|
+
* below it is the rest.** What actually removes the bound is `Number(raw) === 0`
|
|
160
|
+
* for a raw string nobody would recognise as a zero â `--budget 1e-400`
|
|
161
|
+
* underflows to `0`, and `--budget -0` is negative zero, which sails past the
|
|
162
|
+
* `seconds < 0` test because `-0 < 0` is false. Both were MEASURED to run
|
|
163
|
+
* completely unbounded, in process, exiting 0 with no supervision and no word
|
|
164
|
+
* said. So a value that PARSES to zero is refused unless the operator literally
|
|
165
|
+
* wrote a zero: `raw.trim() !== '0'` and it is an operator error. The escape
|
|
166
|
+
* hatch is still there, it just has to be spelled the one unambiguous way.
|
|
167
|
+
*
|
|
168
|
+
* â ïļ That deliberately refuses `0.0` and `0x0` as well. They are not accidents
|
|
169
|
+
* an operator can fall into, they cost one character to rewrite, and a guard
|
|
170
|
+
* with an exception list is a guard whose next exception nobody argues about.
|
|
171
|
+
*
|
|
172
|
+
* ð **Non-zero hex is deliberately KEPT.** `Number('0x10')` is 16 and
|
|
173
|
+
* `--budget 0x10` runs with a sixteen-second bound. That is unambiguous, it
|
|
174
|
+
* cannot arise by accident the way an empty variable can, and refusing it would
|
|
175
|
+
* buy nothing â recorded here so the next reader does not "fix" it into a
|
|
176
|
+
* refusal.
|
|
177
|
+
*
|
|
178
|
+
* @param raw - Whatever Commander parsed, or undefined
|
|
179
|
+
* @returns The budget in seconds; 0 means no bound
|
|
180
|
+
* @throws When the value is blank, means zero without saying so, or is not a
|
|
181
|
+
* non-negative number
|
|
182
|
+
*/
|
|
183
|
+
export function parseBudgetSeconds(raw) {
|
|
184
|
+
if (raw === undefined)
|
|
185
|
+
return DEFAULT_BUDGET_SECONDS;
|
|
186
|
+
if (raw.trim() === '') {
|
|
187
|
+
throw new Error('--budget was given an empty value. That is almost always an unset shell'
|
|
188
|
+
+ ' variable (`--budget "$CHECK_BUDGET"`), and it is refused rather than read'
|
|
189
|
+
+ ' as `--budget 0` â which would remove the bound entirely and let the run'
|
|
190
|
+
+ ' hang forever. Pass a number of seconds, or `--budget 0` explicitly if that'
|
|
191
|
+
+ ' is really what you mean.');
|
|
192
|
+
}
|
|
193
|
+
const seconds = Number(raw);
|
|
194
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
195
|
+
throw new Error(`--budget must be a non-negative number of seconds, not "${raw}".`
|
|
196
|
+
+ ` It bounds how long the run may go without completing a unit of work;`
|
|
197
|
+
+ ` --budget 0 removes the bound entirely (and can then hang forever).`);
|
|
198
|
+
}
|
|
199
|
+
if (seconds === 0 && raw.trim() !== '0') {
|
|
200
|
+
throw new Error(`--budget "${raw}" is a number that means ZERO â and 0 is this flag's escape`
|
|
201
|
+
+ ' hatch for "no bound at all", so the run would have gone entirely'
|
|
202
|
+
+ ' unsupervised and could hang forever. `1e-400` underflows to zero and `-0`'
|
|
203
|
+
+ ' is negative zero, neither of which reads as a request to remove the bound.'
|
|
204
|
+
+ ' Pass a positive number of seconds, or exactly `--budget 0` if removing it'
|
|
205
|
+
+ ' is really what you mean.');
|
|
206
|
+
}
|
|
207
|
+
return seconds;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Does this process do the work, or supervise a child that does?
|
|
211
|
+
*
|
|
212
|
+
* ðĻ **The recursion guard, and the reason it is a named function.** `--cost-log`
|
|
213
|
+
* is what a supervising parent hands its child, and it must WIN against the
|
|
214
|
+
* budget â a child that consulted the budget first would spawn a child of its
|
|
215
|
+
* own, which would spawn a child of its own. A condition inline in the command
|
|
216
|
+
* would be a fork bomb one refactor away with nothing asserting on it.
|
|
217
|
+
*
|
|
218
|
+
* `--budget 0` also runs here: there is nothing to supervise, and a spawn would
|
|
219
|
+
* cost a second process startup to buy no bound at all.
|
|
220
|
+
*
|
|
221
|
+
* @param options - The run
|
|
222
|
+
* @param options.costLog - The hidden `--cost-log` value, or undefined
|
|
223
|
+
* @param options.budgetSecs - The bound in seconds; 0 means none
|
|
224
|
+
* @returns True when the checks run in this process
|
|
225
|
+
*/
|
|
226
|
+
export function runsInThisProcess(options) {
|
|
227
|
+
return options.costLog !== undefined || options.budgetSecs === 0;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Refuse a combination where one flag the operator passed would do nothing.
|
|
231
|
+
*
|
|
232
|
+
* ðĻ **The defect: `--cost-log` wins the fork above unconditionally**, so
|
|
233
|
+
* `vat resources check --budget 60 --cost-log /tmp/x` ran entirely UNBOUNDED and
|
|
234
|
+
* said nothing about it. That is harmless to the recursion guard â a child must
|
|
235
|
+
* keep winning â but an explicitly-passed, documented flag that is silently
|
|
236
|
+
* inert is exactly the shape an operator never discovers until the hang it was
|
|
237
|
+
* meant to prevent.
|
|
238
|
+
*
|
|
239
|
+
* â ïļ **The real spawn path must survive this.** {@link runsInThisProcess}'s
|
|
240
|
+
* caller builds the child's argv with `--cost-log` and does NOT forward
|
|
241
|
+
* `--budget` â which is why the RAW flag is the discriminator here rather than
|
|
242
|
+
* the parsed seconds: the child's `budgetSecs` is the 300 s default, not
|
|
243
|
+
* anything the operator typed.
|
|
244
|
+
*
|
|
245
|
+
* `--budget 0` alongside `--cost-log` is allowed: both say "nobody is
|
|
246
|
+
* supervising this", so there is no contradiction to report.
|
|
247
|
+
*
|
|
248
|
+
* @param options - The run
|
|
249
|
+
* @param options.costLog - The hidden `--cost-log` value, or undefined
|
|
250
|
+
* @param options.budgetRaw - What the operator actually typed for `--budget`
|
|
251
|
+
* @param options.budgetSecs - The parsed bound; 0 means none
|
|
252
|
+
* @throws When both flags were passed and the budget could not be honoured
|
|
253
|
+
*/
|
|
254
|
+
export function requireSupervisableFlags(options) {
|
|
255
|
+
if (options.costLog === undefined)
|
|
256
|
+
return;
|
|
257
|
+
if (options.budgetRaw === undefined || options.budgetSecs === 0)
|
|
258
|
+
return;
|
|
259
|
+
throw new Error('--budget cannot be combined with --cost-log. --cost-log means "you are the'
|
|
260
|
+
+ ' child a supervisor already spawned", so this process does the work rather'
|
|
261
|
+
+ ' than bounding it, and the budget would be silently ignored. Drop one:'
|
|
262
|
+
+ ' --budget alone gets you the bound, --cost-log alone gets you the progress'
|
|
263
|
+
+ ' log with no bound at all.');
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Read `close`'s two arguments, and the watchdog's belief, into one answer.
|
|
267
|
+
*
|
|
268
|
+
* ## ðĻ The defect this closes: a signal death READ AS SUCCESS
|
|
269
|
+
*
|
|
270
|
+
* `close` fires with `(code, signal)`, and a process that dies from a signal has
|
|
271
|
+
* `code === null` with `signal` set. This handler used to read only the code and
|
|
272
|
+
* coerce it â `resolve(exitCode ?? 0)` â so every signal death became a
|
|
273
|
+
* COMPLETED run with exit code 0. The parent then forwarded an empty stdout and
|
|
274
|
+
* exited 0, and a CI gate reads that as a clean pass.
|
|
275
|
+
*
|
|
276
|
+
* It needed no external actor. A check whose statement materialises an unbounded
|
|
277
|
+
* result set â `SELECT i FROM <infinite recursive CTE>`, the row-returning twin
|
|
278
|
+
* of the aggregate this whole module exists for â makes `.all()` fill the heap
|
|
279
|
+
* until V8 gives up and Node ABORTS. Measured: `PARENT_EXIT=0`, zero bytes of
|
|
280
|
+
* stdout, on precisely the input the bound exists to catch. Through the
|
|
281
|
+
* unsupervised lane the identical input exits 134.
|
|
282
|
+
*
|
|
283
|
+
* Every memory death arrives here: Node's own heap abort, a kernel OOM killer on
|
|
284
|
+
* a memory-capped runner (which picks the large CHILD, never the idle parent),
|
|
285
|
+
* and a container limit. `?? 0` turned all of them into a pass.
|
|
286
|
+
*
|
|
287
|
+
* ## ðŠĪ A kill only counts if it LANDED
|
|
288
|
+
*
|
|
289
|
+
* `killed` says the watchdog fired, not that the child died of it. libuv runs
|
|
290
|
+
* timers BEFORE the poll phase that reaps the child, so the timer can fire in
|
|
291
|
+
* the same loop turn as a child that has already exited cleanly. A NORMAL ending
|
|
292
|
+
* â a non-null code with no signal â is therefore honoured whatever `killed`
|
|
293
|
+
* says: that run finished on its own terms and its answer is real. Reporting it
|
|
294
|
+
* as killed fails closed, but it discards a correct result, which is its own
|
|
295
|
+
* kind of wrong.
|
|
296
|
+
*
|
|
297
|
+
* ## ðĻ The ORDER of the tests below is load-bearing
|
|
298
|
+
*
|
|
299
|
+
* `spawnError` used to be tested FIRST, so `killed` was never consulted on that
|
|
300
|
+
* branch â and `child.on('error')` is not only a spawn-failure listener. Node
|
|
301
|
+
* emits `error` on the ChildProcess when `subprocess.kill()` itself fails, so a
|
|
302
|
+
* refused SIGKILL landed on the spawn-failure branch and the report said the
|
|
303
|
+
* child "could not be started at all", that "nothing ran at all, so this is an
|
|
304
|
+
* installation or PATH problem", and that "the watchdog never fired". Three
|
|
305
|
+
* statements, all false, about a runaway that was still executing.
|
|
306
|
+
*
|
|
307
|
+
* So: a real ending first, then what the SUPERVISOR did (a refused kill, then a
|
|
308
|
+
* kill), and only then a failure to start. A run whose kill was refused must
|
|
309
|
+
* never claim the watchdog was uninvolved.
|
|
310
|
+
*
|
|
311
|
+
* @param ending - What `close` reported, plus what the supervisor did and saw
|
|
312
|
+
* @param ending.code - `close`'s first argument
|
|
313
|
+
* @param ending.signal - `close`'s second argument
|
|
314
|
+
* @param ending.killed - Whether the watchdog sent SIGKILL
|
|
315
|
+
* @param ending.spawnError - Set when the child never started at all
|
|
316
|
+
* @param ending.killFailure - Set when the watchdog's SIGKILL was REFUSED
|
|
317
|
+
* @returns What actually happened
|
|
318
|
+
*/
|
|
319
|
+
export function resolveChildEnding(ending) {
|
|
320
|
+
if (ending.code !== null && ending.signal === null) {
|
|
321
|
+
return { kind: 'completed', code: ending.code };
|
|
322
|
+
}
|
|
323
|
+
if (ending.killFailure !== undefined) {
|
|
324
|
+
return { kind: 'abnormal', death: { kind: 'kill-failed', ...ending.killFailure } };
|
|
325
|
+
}
|
|
326
|
+
if (ending.killed)
|
|
327
|
+
return { kind: 'killed' };
|
|
328
|
+
if (ending.spawnError !== undefined) {
|
|
329
|
+
return { kind: 'abnormal', death: { kind: 'spawn-failed', ...ending.spawnError } };
|
|
330
|
+
}
|
|
331
|
+
return ending.signal === null
|
|
332
|
+
? { kind: 'abnormal', death: { kind: 'no-status' } }
|
|
333
|
+
: { kind: 'abnormal', death: { kind: 'signal', signal: ending.signal } };
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Reconcile "how the process ended" with "what it left behind".
|
|
337
|
+
*
|
|
338
|
+
* ## ðĻ A completion that PUBLISHED NOTHING did not complete
|
|
339
|
+
*
|
|
340
|
+
* {@link resolveChildEnding} answers a question about the process â what `close`
|
|
341
|
+
* reported, reconciled with what the supervisor did. This answers a DIFFERENT
|
|
342
|
+
* one, about the artifact, and the two disagree in exactly one place: a child
|
|
343
|
+
* that exited with a code and wrote no document.
|
|
344
|
+
*
|
|
345
|
+
* Every in-process path through `checkCommand` ends at `emitCheckDocument` or at
|
|
346
|
+
* `handleCommandError`, and both write to stdout â a clean pass, a violation, a
|
|
347
|
+
* bricked config, a mistyped `--check`. There is no exit this command can take
|
|
348
|
+
* that legitimately publishes nothing. So an empty stdout is not a quiet run, it
|
|
349
|
+
* is a run that was cut off before it could say anything, and forwarding its
|
|
350
|
+
* exit code verbatim tells the operator a number and nothing else.
|
|
351
|
+
*
|
|
352
|
+
* ## ðŠĪ Why this is not folded into {@link resolveChildEnding}
|
|
353
|
+
*
|
|
354
|
+
* That function's branch ORDER is load-bearing and reasoned entirely about
|
|
355
|
+
* process endings and supervisor beliefs. The emptiness of stdout belongs to
|
|
356
|
+
* neither, and threading it through would put an artifact test inside an
|
|
357
|
+
* ordering argument that has already been got wrong twice. It runs AFTER, on the
|
|
358
|
+
* one branch it can change, and it changes no other.
|
|
359
|
+
*
|
|
360
|
+
* â ïļ **Blank, not unparseable.** The parent forwards the child's document
|
|
361
|
+
* verbatim precisely so it stays `--format`-agnostic, so it cannot ask whether
|
|
362
|
+
* the bytes parse without learning the format. A truncated document â an abort
|
|
363
|
+
* that landed mid-write â therefore still forwards, and that is the honest
|
|
364
|
+
* limit of this test rather than an oversight.
|
|
365
|
+
*
|
|
366
|
+
* @param resolution - How the process ended
|
|
367
|
+
* @param stdout - Everything the child wrote to stdout
|
|
368
|
+
* @returns The resolution, with a silent completion re-read as abnormal
|
|
369
|
+
*/
|
|
370
|
+
export function resolveSilentCompletion(resolution, stdout) {
|
|
371
|
+
if (resolution.kind !== 'completed' || stdout.trim() !== '')
|
|
372
|
+
return resolution;
|
|
373
|
+
return { kind: 'abnormal', death: { kind: 'no-output', code: resolution.code } };
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Run `vat resources check` as a child, bounded by the budget.
|
|
377
|
+
*
|
|
378
|
+
* `stdio` is `['ignore', 'pipe', 'inherit']`: the child's stdout is accumulated
|
|
379
|
+
* so the parent can forward the document verbatim, and its stderr is INHERITED
|
|
380
|
+
* so warnings and blob-stage refusals stream live exactly as they do today.
|
|
381
|
+
*
|
|
382
|
+
* â ïļ Asynchronous `spawn`, never `spawnSync`. The parent's event loop is the
|
|
383
|
+
* thing holding the watchdog timer; a synchronous spawn would block it for
|
|
384
|
+
* precisely as long as the run it is supposed to be bounding.
|
|
385
|
+
*
|
|
386
|
+
* ðĻ There is an `error` listener as well as a `close` one, and it is not
|
|
387
|
+
* optional politeness: without it an unspawnable binary is an unhandled `error`
|
|
388
|
+
* event, which Node turns into a stack dump instead of a report. It fails
|
|
389
|
+
* closed, so this is legibility â but a stack dump is not a report.
|
|
390
|
+
*
|
|
391
|
+
* ## ðĻ `error` is NOT only a spawn-failure listener, and reading it as one
|
|
392
|
+
* ABANDONED A LIVE CHILD
|
|
393
|
+
*
|
|
394
|
+
* Node emits `error` on the ChildProcess when `subprocess.kill()` itself fails.
|
|
395
|
+
* The shipped function ends `else { /* Other error, almost certainly EPERM. *\/
|
|
396
|
+
* this.emit('error', new ErrnoException(err, 'kill')); }`. So: the watchdog
|
|
397
|
+
* breached, `killed` was set, the SIGKILL was refused, `error` fired, the
|
|
398
|
+
* handler recorded a SPAWN failure and resolved immediately â which cleared the
|
|
399
|
+
* watchdog timer, so no further kill could ever be attempted â and the parent
|
|
400
|
+
* published a report saying the child "could not be started at all" and "the
|
|
401
|
+
* watchdog never fired" while the runaway was still executing. The bound this
|
|
402
|
+
* module exists to enforce became an orphaned unbounded hang, described as its
|
|
403
|
+
* own opposite.
|
|
404
|
+
*
|
|
405
|
+
* The handler therefore forks on `killed`, and the two arms are deliberately
|
|
406
|
+
* asymmetric:
|
|
407
|
+
*
|
|
408
|
+
* - **Before the watchdog fired**, an `error` means the child never started.
|
|
409
|
+
* Resolve at once: there is nothing alive to wait for.
|
|
410
|
+
* - **After it fired**, an `error` means the KILL failed and the child is,
|
|
411
|
+
* as far as this process can tell, still running. Do NOT resolve. Resolving
|
|
412
|
+
* is what clears the timer, and clearing the timer is what makes the failure
|
|
413
|
+
* permanent. The poll keeps breaching (the log is not growing and
|
|
414
|
+
* `quietSince` is not reset), so it keeps re-sending SIGKILL, and a transient
|
|
415
|
+
* refusal still lands. Only after {@link KILL_ATTEMPTS_BEFORE_REPORTING}
|
|
416
|
+
* refusals does it give up â and it gives up by REPORTING, naming the pid,
|
|
417
|
+
* never by claiming the child never started.
|
|
418
|
+
*
|
|
419
|
+
* â ïļ Reachability of `EPERM` on one's own child is REASONED rather than
|
|
420
|
+
* reproduced â it is rare on macOS and Linux. Nothing in production was
|
|
421
|
+
* contorted to make it testable; the branch ordering and the message
|
|
422
|
+
* composition are pure functions and are pinned as such.
|
|
423
|
+
*
|
|
424
|
+
* @param options - The run
|
|
425
|
+
* @param options.args - The child's argv after the node binary, `--cost-log` included
|
|
426
|
+
* @param options.logPath - The progress log both sides agreed on
|
|
427
|
+
* @param options.budgetMs - How long silence is allowed to last
|
|
428
|
+
* @returns How the child ended, and what it left behind
|
|
429
|
+
*/
|
|
430
|
+
export async function superviseCheck(options) {
|
|
431
|
+
const startedAt = Date.now();
|
|
432
|
+
const binary = resolveVatBinPath();
|
|
433
|
+
const child = spawn(process.execPath, [binary, ...options.args], {
|
|
434
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
435
|
+
});
|
|
436
|
+
const chunks = [];
|
|
437
|
+
child.stdout?.on('data', (chunk) => chunks.push(chunk));
|
|
438
|
+
let killed = false;
|
|
439
|
+
let killAttempts = 0;
|
|
440
|
+
let killFailure;
|
|
441
|
+
let spawnError;
|
|
442
|
+
let state = { bytesSeen: 0, quietSince: startedAt };
|
|
443
|
+
// ðŠĪ Declared out here and started INSIDE the promise, so the watchdog can
|
|
444
|
+
// settle the run itself when it runs out of kills. Starting it outside would
|
|
445
|
+
// put the escalation out of the timer's reach and force the `error` handler
|
|
446
|
+
// back into resolving on a child that is still alive.
|
|
447
|
+
let timer;
|
|
448
|
+
const resolution = await new Promise((resolve) => {
|
|
449
|
+
// ðŠĪ `error` does not always end the process, and it is not always followed
|
|
450
|
+
// by `close` â so a SPAWN failure is remembered and resolved on immediately.
|
|
451
|
+
// A promise settles once, so whichever event arrives first wins and the
|
|
452
|
+
// other is a no-op. A failed KILL is the opposite case: see this function's
|
|
453
|
+
// header for why it must not settle here.
|
|
454
|
+
child.on('error', (cause) => {
|
|
455
|
+
if (killed) {
|
|
456
|
+
killFailure = { detail: cause.message, pid: child.pid };
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
spawnError = { binary, detail: cause.message };
|
|
460
|
+
resolve(resolveChildEnding({ code: null, signal: null, killed, spawnError }));
|
|
461
|
+
});
|
|
462
|
+
// â BOTH arguments. `exitCode ?? 0` here read every signal death as a
|
|
463
|
+
// successful run â see {@link resolveChildEnding}.
|
|
464
|
+
child.on('close', (code, signal) => {
|
|
465
|
+
resolve(resolveChildEnding({ code, signal, killed, spawnError, killFailure }));
|
|
466
|
+
});
|
|
467
|
+
timer = setInterval(() => {
|
|
468
|
+
const poll = pollWatchdog(state, {
|
|
469
|
+
bytes: logSize(options.logPath),
|
|
470
|
+
now: Date.now(),
|
|
471
|
+
budgetMs: options.budgetMs,
|
|
472
|
+
});
|
|
473
|
+
state = poll.state;
|
|
474
|
+
if (!poll.breach)
|
|
475
|
+
return;
|
|
476
|
+
killed = true;
|
|
477
|
+
killAttempts += 1;
|
|
478
|
+
// SIGKILL, because no code the child could ever gain can handle, ignore
|
|
479
|
+
// or block it. See this module's header â and note the reason is NOT that
|
|
480
|
+
// SIGTERM fails to reach a blocked process, which was measured false.
|
|
481
|
+
child.kill('SIGKILL');
|
|
482
|
+
if (killFailure !== undefined && killAttempts >= KILL_ATTEMPTS_BEFORE_REPORTING) {
|
|
483
|
+
resolve(resolveChildEnding({ code: null, signal: null, killed, killFailure }));
|
|
484
|
+
}
|
|
485
|
+
}, pollIntervalMs(options.budgetMs));
|
|
486
|
+
});
|
|
487
|
+
clearInterval(timer);
|
|
488
|
+
const stdout = Buffer.concat(chunks).toString('utf-8');
|
|
489
|
+
// ðĻ AFTER the ending is resolved, never inside it. A child that exited with a
|
|
490
|
+
// code and published nothing did not complete, whatever `close` said â see
|
|
491
|
+
// {@link resolveSilentCompletion} for the platform this is about.
|
|
492
|
+
const ending = resolveSilentCompletion(resolution, stdout);
|
|
493
|
+
if (ending.kind === 'completed') {
|
|
494
|
+
return { outcome: 'completed', code: ending.code, stdout };
|
|
495
|
+
}
|
|
496
|
+
const wreckage = { log: readLog(options.logPath), elapsedMs: Date.now() - startedAt };
|
|
497
|
+
return ending.kind === 'killed'
|
|
498
|
+
? { outcome: 'killed', ...wreckage }
|
|
499
|
+
: { outcome: 'abnormal', death: ending.death, ...wreckage };
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* How often to look at the log.
|
|
503
|
+
*
|
|
504
|
+
* Derived from the budget rather than fixed, so `--budget 2` notices in 200 ms
|
|
505
|
+
* while `--budget 600` does not wake the event loop 12,000 times for nothing.
|
|
506
|
+
*
|
|
507
|
+
* @param budgetMs - The budget
|
|
508
|
+
* @returns The poll interval in milliseconds
|
|
509
|
+
*/
|
|
510
|
+
function pollIntervalMs(budgetMs) {
|
|
511
|
+
return Math.min(MAX_POLL_MS, Math.max(MIN_POLL_MS, budgetMs / POLLS_PER_BUDGET));
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* The progress log's size, or 0 before the child has written anything.
|
|
515
|
+
*
|
|
516
|
+
* ðŠĪ A missing file is 0, not an error. The child creates the log on its first
|
|
517
|
+
* emission, so every poll before that legitimately finds nothing â and throwing
|
|
518
|
+
* there would kill the supervisor instead of the runaway.
|
|
519
|
+
*
|
|
520
|
+
* @param path - The progress log
|
|
521
|
+
* @returns Its size in bytes
|
|
522
|
+
*/
|
|
523
|
+
function logSize(path) {
|
|
524
|
+
try {
|
|
525
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- a path this process minted for this run
|
|
526
|
+
return statSync(path).size;
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
return 0;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Whatever of the log survived the kill.
|
|
534
|
+
*
|
|
535
|
+
* @param path - The progress log
|
|
536
|
+
* @returns Its contents, or the empty string when the child never wrote one
|
|
537
|
+
*/
|
|
538
|
+
function readLog(path) {
|
|
539
|
+
try {
|
|
540
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- a path this process minted for this run
|
|
541
|
+
return readFileSync(path, 'utf-8');
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
return '';
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Run `work` with a fresh progress log, and remove it however that ends.
|
|
549
|
+
*
|
|
550
|
+
* â ïļ Ephemeral, per run, and it carries NO format version â see
|
|
551
|
+
* `check-progress.ts`. It is written and read by one process pair within one run
|
|
552
|
+
* of one build, so the reader's own `.strict()` schema is the entire contract.
|
|
553
|
+
*
|
|
554
|
+
* @param work - Given the log's path
|
|
555
|
+
* @returns Whatever `work` returned
|
|
556
|
+
*/
|
|
557
|
+
export async function withProgressLog(work) {
|
|
558
|
+
const dir = mkdtempSync(safePath.join(normalizedTmpdir(), 'vat-check-'));
|
|
559
|
+
try {
|
|
560
|
+
return await work(safePath.join(dir, 'progress.jsonl'));
|
|
561
|
+
}
|
|
562
|
+
finally {
|
|
563
|
+
rmSync(dir, { recursive: true, force: true });
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
//# sourceMappingURL=check-supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-supervisor.js","sourceRoot":"","sources":["../../../src/commands/resources/check-supervisor.ts"],"names":[],"mappings":"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"}
|