rightsize 0.1.2 → 0.2.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 (99) hide show
  1. package/README.md +28 -4
  2. package/dist/backend-docker/backend.d.ts +53 -1
  3. package/dist/backend-docker/backend.d.ts.map +1 -1
  4. package/dist/backend-docker/backend.js +106 -13
  5. package/dist/backend-docker/backend.js.map +1 -1
  6. package/dist/backend-docker/labels.d.ts +27 -5
  7. package/dist/backend-docker/labels.d.ts.map +1 -1
  8. package/dist/backend-docker/labels.js +33 -5
  9. package/dist/backend-docker/labels.js.map +1 -1
  10. package/dist/backend-msb/backend.d.ts +35 -6
  11. package/dist/backend-msb/backend.d.ts.map +1 -1
  12. package/dist/backend-msb/backend.js +69 -14
  13. package/dist/backend-msb/backend.js.map +1 -1
  14. package/dist/backend-msb/provider.d.ts.map +1 -1
  15. package/dist/backend-msb/provider.js +7 -11
  16. package/dist/backend-msb/provider.js.map +1 -1
  17. package/dist/backend-msb/provisioner.d.ts.map +1 -1
  18. package/dist/backend-msb/provisioner.js +1 -20
  19. package/dist/backend-msb/provisioner.js.map +1 -1
  20. package/dist/core/backend.d.ts +73 -0
  21. package/dist/core/backend.d.ts.map +1 -1
  22. package/dist/core/backends.d.ts +16 -2
  23. package/dist/core/backends.d.ts.map +1 -1
  24. package/dist/core/backends.js +39 -5
  25. package/dist/core/backends.js.map +1 -1
  26. package/dist/core/cache-dir.d.ts +17 -0
  27. package/dist/core/cache-dir.d.ts.map +1 -0
  28. package/dist/core/cache-dir.js +29 -0
  29. package/dist/core/cache-dir.js.map +1 -0
  30. package/dist/core/cleanup.d.ts +19 -4
  31. package/dist/core/cleanup.d.ts.map +1 -1
  32. package/dist/core/cleanup.js +14 -27
  33. package/dist/core/cleanup.js.map +1 -1
  34. package/dist/core/diagnostics.d.ts +33 -0
  35. package/dist/core/diagnostics.d.ts.map +1 -0
  36. package/dist/core/diagnostics.js +76 -0
  37. package/dist/core/diagnostics.js.map +1 -0
  38. package/dist/core/errors.d.ts +41 -0
  39. package/dist/core/errors.d.ts.map +1 -1
  40. package/dist/core/errors.js +56 -0
  41. package/dist/core/errors.js.map +1 -1
  42. package/dist/core/generic-container.d.ts +107 -1
  43. package/dist/core/generic-container.d.ts.map +1 -1
  44. package/dist/core/generic-container.js +417 -3
  45. package/dist/core/generic-container.js.map +1 -1
  46. package/dist/core/model.d.ts +22 -0
  47. package/dist/core/model.d.ts.map +1 -1
  48. package/dist/core/network.d.ts.map +1 -1
  49. package/dist/core/network.js +2 -0
  50. package/dist/core/network.js.map +1 -1
  51. package/dist/core/reaper/env.d.ts +14 -0
  52. package/dist/core/reaper/env.d.ts.map +1 -0
  53. package/dist/core/reaper/env.js +22 -0
  54. package/dist/core/reaper/env.js.map +1 -0
  55. package/dist/core/reaper/init.d.ts +39 -0
  56. package/dist/core/reaper/init.d.ts.map +1 -0
  57. package/dist/core/reaper/init.js +140 -0
  58. package/dist/core/reaper/init.js.map +1 -0
  59. package/dist/core/reaper/ledger.d.ts +32 -0
  60. package/dist/core/reaper/ledger.d.ts.map +1 -0
  61. package/dist/core/reaper/ledger.js +160 -0
  62. package/dist/core/reaper/ledger.js.map +1 -0
  63. package/dist/core/reaper/process-liveness.d.ts +39 -0
  64. package/dist/core/reaper/process-liveness.d.ts.map +1 -0
  65. package/dist/core/reaper/process-liveness.js +117 -0
  66. package/dist/core/reaper/process-liveness.js.map +1 -0
  67. package/dist/core/reaper/run-record.d.ts +48 -0
  68. package/dist/core/reaper/run-record.d.ts.map +1 -0
  69. package/dist/core/reaper/run-record.js +58 -0
  70. package/dist/core/reaper/run-record.js.map +1 -0
  71. package/dist/core/reaper/sweep.d.ts +23 -0
  72. package/dist/core/reaper/sweep.d.ts.map +1 -0
  73. package/dist/core/reaper/sweep.js +76 -0
  74. package/dist/core/reaper/sweep.js.map +1 -0
  75. package/dist/core/reaper/watchdog.d.ts +100 -0
  76. package/dist/core/reaper/watchdog.d.ts.map +1 -0
  77. package/dist/core/reaper/watchdog.js +412 -0
  78. package/dist/core/reaper/watchdog.js.map +1 -0
  79. package/dist/core/reuse/env.d.ts +12 -0
  80. package/dist/core/reuse/env.d.ts.map +1 -0
  81. package/dist/core/reuse/env.js +15 -0
  82. package/dist/core/reuse/env.js.map +1 -0
  83. package/dist/core/reuse/hash.d.ts +34 -0
  84. package/dist/core/reuse/hash.d.ts.map +1 -0
  85. package/dist/core/reuse/hash.js +52 -0
  86. package/dist/core/reuse/hash.js.map +1 -0
  87. package/dist/core/reuse/registry.d.ts +50 -0
  88. package/dist/core/reuse/registry.d.ts.map +1 -0
  89. package/dist/core/reuse/registry.js +75 -0
  90. package/dist/core/reuse/registry.js.map +1 -0
  91. package/dist/index.d.ts +5 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +2 -1
  94. package/dist/index.js.map +1 -1
  95. package/package.json +1 -1
  96. package/dist/backend-msb/reaper.d.ts +0 -10
  97. package/dist/backend-msb/reaper.d.ts.map +0 -1
  98. package/dist/backend-msb/reaper.js +0 -73
  99. package/dist/backend-msb/reaper.js.map +0 -1
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The two backend families the reaping ledger can name — deliberately not
3
+ * `SandboxBackend["name"]` (`"microsandbox"` / `"docker"`) verbatim: this is
4
+ * the cross-language wire format every rightsize implementation reads and
5
+ * writes (a Kotlin or Rust process sweeping a dead Node run's ledger parses
6
+ * this same field), so it uses the short, language-neutral spelling the
7
+ * feature spec defines rather than any one language's own backend-name
8
+ * string.
9
+ */
10
+ export type LedgerBackendKind = "msb" | "docker";
11
+ /**
12
+ * One `runs/<run-id>.json` record: written atomically before this process's
13
+ * first sandbox is created, and read by every OTHER process's sweep to
14
+ * decide whether this run is still alive. Every field here is part of the
15
+ * cross-language contract — a Kotlin or Rust sweep must be able to parse
16
+ * exactly this shape.
17
+ */
18
+ export interface RunRecord {
19
+ /** This process's OS pid. */
20
+ readonly pid: number;
21
+ /** ISO-8601 instant the *process* started (not when this record was written) — defeats PID reuse. */
22
+ readonly startedIso: string;
23
+ /** Which backend family this run's sandboxes belong to. */
24
+ readonly backend: LedgerBackendKind;
25
+ /** Absolute path to the provisioned `msb` binary this run used. Present only for `backend: "msb"`. */
26
+ readonly msbPath?: string;
27
+ }
28
+ /** `<cacheDir>/runs` — the directory every run's ledger files live under. */
29
+ export declare function runsDir(cacheDir: string): string;
30
+ /** `runs/<run-id>.json` — the run record. */
31
+ export declare function recordPath(cacheDir: string, runId: string): string;
32
+ /** `runs/<run-id>.sandboxes` — one sandbox name per line, append-before-create/remove-after-stop. */
33
+ export declare function sandboxesPath(cacheDir: string, runId: string): string;
34
+ /** `runs/<run-id>.networks` — same protocol as `.sandboxes`, for created networks. */
35
+ export declare function networksPath(cacheDir: string, runId: string): string;
36
+ /**
37
+ * Parses a `runs/<run-id>.json` body. Returns `undefined` for anything that
38
+ * isn't a well-shaped record — malformed JSON, a missing/mistyped required
39
+ * field, or a `backend` value this contract doesn't know — rather than
40
+ * throwing, so a sweep can treat "unparseable" as its own liveness case (see
41
+ * the sweep spec: fresh unparseable JSON is skipped, stale unparseable JSON
42
+ * is cleaned) instead of crashing on a record from a future rightsize
43
+ * version.
44
+ */
45
+ export declare function parseRunRecord(text: string): RunRecord | undefined;
46
+ /** Serializes a `RunRecord` for `runs/<run-id>.json`. */
47
+ export declare function serializeRunRecord(record: RunRecord): string;
48
+ //# sourceMappingURL=run-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-record.d.ts","sourceRoot":"","sources":["../../../src/core/reaper/run-record.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,sGAAsG;IACtG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,6CAA6C;AAC7C,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,qGAAqG;AACrG,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAsBlE;AAED,yDAAyD;AACzD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,58 @@
1
+ import * as path from "node:path";
2
+ /** `<cacheDir>/runs` — the directory every run's ledger files live under. */
3
+ export function runsDir(cacheDir) {
4
+ return path.join(cacheDir, "runs");
5
+ }
6
+ /** `runs/<run-id>.json` — the run record. */
7
+ export function recordPath(cacheDir, runId) {
8
+ return path.join(runsDir(cacheDir), `${runId}.json`);
9
+ }
10
+ /** `runs/<run-id>.sandboxes` — one sandbox name per line, append-before-create/remove-after-stop. */
11
+ export function sandboxesPath(cacheDir, runId) {
12
+ return path.join(runsDir(cacheDir), `${runId}.sandboxes`);
13
+ }
14
+ /** `runs/<run-id>.networks` — same protocol as `.sandboxes`, for created networks. */
15
+ export function networksPath(cacheDir, runId) {
16
+ return path.join(runsDir(cacheDir), `${runId}.networks`);
17
+ }
18
+ function isLedgerBackendKind(value) {
19
+ return value === "msb" || value === "docker";
20
+ }
21
+ /**
22
+ * Parses a `runs/<run-id>.json` body. Returns `undefined` for anything that
23
+ * isn't a well-shaped record — malformed JSON, a missing/mistyped required
24
+ * field, or a `backend` value this contract doesn't know — rather than
25
+ * throwing, so a sweep can treat "unparseable" as its own liveness case (see
26
+ * the sweep spec: fresh unparseable JSON is skipped, stale unparseable JSON
27
+ * is cleaned) instead of crashing on a record from a future rightsize
28
+ * version.
29
+ */
30
+ export function parseRunRecord(text) {
31
+ let obj;
32
+ try {
33
+ obj = JSON.parse(text);
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ if (typeof obj !== "object" || obj === null) {
39
+ return undefined;
40
+ }
41
+ const rec = obj;
42
+ const pid = rec["pid"];
43
+ const startedIso = rec["startedIso"];
44
+ const backend = rec["backend"];
45
+ if (typeof pid !== "number" || typeof startedIso !== "string" || !isLedgerBackendKind(backend)) {
46
+ return undefined;
47
+ }
48
+ const msbPath = rec["msbPath"];
49
+ if (msbPath !== undefined && typeof msbPath !== "string") {
50
+ return undefined;
51
+ }
52
+ return msbPath === undefined ? { pid, startedIso, backend } : { pid, startedIso, backend, msbPath };
53
+ }
54
+ /** Serializes a `RunRecord` for `runs/<run-id>.json`. */
55
+ export function serializeRunRecord(record) {
56
+ return JSON.stringify(record);
57
+ }
58
+ //# sourceMappingURL=run-record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-record.js","sourceRoot":"","sources":["../../../src/core/reaper/run-record.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AA+BlC,6EAA6E;AAC7E,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,KAAa;IACxD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC;AACvD,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,KAAa;IAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,YAAY,CAAC,CAAC;AAC5D,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,KAAa;IAC1D,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,WAAW,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc;IACzC,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,QAAQ,CAAC;AAC/C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,GAAG,GAAG,GAA8B,CAAC;IAC3C,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/F,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/B,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACtG,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,kBAAkB,CAAC,MAAiB;IAClD,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type LedgerBackendKind } from "./run-record.js";
2
+ import { type ProcessTimeSource } from "./process-liveness.js";
3
+ /** What one sweep pass needs: where the ledger lives, which run/backend to exempt, and how to actually remove a sandbox/network by name. */
4
+ export interface SweepDeps {
5
+ readonly cacheDir: string;
6
+ /** This process's own run id — always skipped, alive or not. */
7
+ readonly thisRunId: string;
8
+ /** This process's active backend — a dead run is only reaped if its recorded backend matches; cross-backend leftovers are left for a process on that backend. */
9
+ readonly backendKind: LedgerBackendKind;
10
+ readonly removeByName: (name: string) => Promise<void>;
11
+ readonly removeNetwork: (networkId: string) => Promise<void>;
12
+ readonly timeSource: ProcessTimeSource;
13
+ }
14
+ /**
15
+ * One full sweep pass: every `runs/*.json` other than this process's own is
16
+ * inspected once. Dead runs whose recorded backend matches this process's
17
+ * active backend are reaped (their sandboxes/networks removed, then their
18
+ * ledger files deleted); alive runs, this run, and cross-backend runs are
19
+ * left untouched. Never throws — a failure reaping one run must not stop
20
+ * the sweep from considering the rest.
21
+ */
22
+ export declare function sweepOnce(deps: SweepDeps): Promise<void>;
23
+ //# sourceMappingURL=sweep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sweep.d.ts","sourceRoot":"","sources":["../../../src/core/reaper/sweep.ts"],"names":[],"mappings":"AAOA,OAAO,EAAkB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAK9E,4IAA4I;AAC5I,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iKAAiK;IACjK,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,QAAQ,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;CACxC;AAoBD;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAa9D"}
@@ -0,0 +1,76 @@
1
+ import { deleteRunRecordFiles, listRunIds, readNetworkIds, readRunRecordRaw, readSandboxNames, } from "./ledger.js";
2
+ import { parseRunRecord } from "./run-record.js";
3
+ import { isRecordAlive } from "./process-liveness.js";
4
+ /** An unparseable record younger than this is presumed mid-write by its own process, not genuinely corrupt — skipped rather than reaped. */
5
+ const UNPARSEABLE_FRESH_AGE_MS = 60 * 60 * 1000; // 1 hour
6
+ async function reapRun(deps, runId) {
7
+ const names = await readSandboxNames(deps.cacheDir, runId);
8
+ for (const name of names) {
9
+ await deps.removeByName(name).catch(() => {
10
+ // Best-effort: "not found" is expected (another sweep may have won the
11
+ // race), and any other failure must not stop the rest of the reap.
12
+ });
13
+ }
14
+ const networkIds = await readNetworkIds(deps.cacheDir, runId);
15
+ for (const networkId of networkIds) {
16
+ await deps.removeNetwork(networkId).catch(() => {
17
+ // Best-effort: an in-use network (another still-alive run sharing it,
18
+ // in principle) is left alone rather than failing the whole sweep.
19
+ });
20
+ }
21
+ await deleteRunRecordFiles(deps.cacheDir, runId);
22
+ }
23
+ /**
24
+ * One full sweep pass: every `runs/*.json` other than this process's own is
25
+ * inspected once. Dead runs whose recorded backend matches this process's
26
+ * active backend are reaped (their sandboxes/networks removed, then their
27
+ * ledger files deleted); alive runs, this run, and cross-backend runs are
28
+ * left untouched. Never throws — a failure reaping one run must not stop
29
+ * the sweep from considering the rest.
30
+ */
31
+ export async function sweepOnce(deps) {
32
+ const runIds = await listRunIds(deps.cacheDir);
33
+ for (const runId of runIds) {
34
+ if (runId === deps.thisRunId) {
35
+ continue;
36
+ }
37
+ try {
38
+ await sweepOneRun(deps, runId);
39
+ }
40
+ catch {
41
+ // A single run's sweep failing (a stat race, a malformed removeByName
42
+ // implementation, ...) must not abort the sweep of every other run.
43
+ }
44
+ }
45
+ }
46
+ async function sweepOneRun(deps, runId) {
47
+ const raw = await readRunRecordRaw(deps.cacheDir, runId);
48
+ if (raw === undefined) {
49
+ // Vanished mid-scan: either this run cleanly shut down or another
50
+ // sweep already reaped it. Nothing left to do.
51
+ return;
52
+ }
53
+ const record = parseRunRecord(raw.text);
54
+ if (record === undefined) {
55
+ const ageMs = Date.now() - raw.mtimeMs;
56
+ if (ageMs <= UNPARSEABLE_FRESH_AGE_MS) {
57
+ // Could be a torn read of a record still being written by its owning
58
+ // process (writeRunRecord's rename is atomic, but a stat+read pair
59
+ // here is not synchronized with it) — too young to trust as corrupt.
60
+ return;
61
+ }
62
+ await reapRun(deps, runId);
63
+ return;
64
+ }
65
+ if (record.backend !== deps.backendKind) {
66
+ // A docker process cannot remove msb sandboxes and vice versa — leave
67
+ // this run for a process on its own backend to sweep.
68
+ return;
69
+ }
70
+ const alive = await isRecordAlive(deps.timeSource, record.pid, record.startedIso);
71
+ if (alive) {
72
+ return;
73
+ }
74
+ await reapRun(deps, runId);
75
+ }
76
+ //# sourceMappingURL=sweep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sweep.js","sourceRoot":"","sources":["../../../src/core/reaper/sweep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ import type { ChildProcess } from "node:child_process";
2
+ /**
3
+ * The argv PREFIXES (a sandbox/network name is appended by the watchdog
4
+ * script itself) a backend's own CLI uses to reap something by name.
5
+ * `stop`/`removeNetwork` may be empty arrays where a backend has no separate
6
+ * stop step (docker's `rm -f` does both) or no native network object to
7
+ * remove (msb emulates networks in-guest; there is nothing to tear down).
8
+ */
9
+ export interface ReaperKillCommand {
10
+ readonly stop: ReadonlyArray<string>;
11
+ readonly remove: ReadonlyArray<string>;
12
+ readonly removeNetwork: ReadonlyArray<string>;
13
+ }
14
+ export declare function watchdogDir(cacheDir: string): string;
15
+ /**
16
+ * `watchdog-<12 hex of SHA-256(content)>.<ext>` — the filename derives from the
17
+ * script's own bytes. The `reaper/` directory is shared with the sibling rightsize
18
+ * libraries (Kotlin, Rust) and with other versions of this package, each shipping
19
+ * its own script with its own argv contract; content-derived names make it
20
+ * impossible to execute a script whose contract this code doesn't match, and
21
+ * impossible for a write to clobber a script another process's `sh` is executing.
22
+ * Exported for tests.
23
+ */
24
+ export declare function watchdogScriptFilename(content: string): string;
25
+ /** Writes the platform-appropriate watchdog script under `<cacheDir>/reaper/` (skipped when its content-named file already exists) and returns its path. */
26
+ export declare function ensureWatchdogScript(cacheDir: string): Promise<string>;
27
+ /**
28
+ * Paths the debug affordance below writes to when `RIGHTSIZE_REAPER_DEBUG=1`
29
+ * — the watchdog's own stdout/stderr (normally `"ignore"`d) plus a meta log
30
+ * of spawn/error/exit events, all under the same `reaper/` directory the
31
+ * watchdog script itself lives in. Exported so a test can locate them
32
+ * without duplicating the naming scheme.
33
+ */
34
+ export declare function watchdogDebugPaths(cacheDir: string, runId: string): {
35
+ out: string;
36
+ err: string;
37
+ meta: string;
38
+ };
39
+ /**
40
+ * Where the Windows script's own in-script trace (see windowsScriptContent's
41
+ * doc) lands: same directory as the run record, named by the watchdog
42
+ * process's own pid — the one piece of the script's identity a caller can
43
+ * know ahead of the script writing anything itself. POSIX has no equivalent
44
+ * (its script has no such tracing; its debug story is the stdout/stderr
45
+ * capture above, which is enough there since `sh -x` isn't needed to tell
46
+ * the difference between "never started" and "died partway").
47
+ */
48
+ export declare function watchdogTracePath(recordPathValue: string, pid: number): string;
49
+ export interface SpawnWatchdogOptions {
50
+ readonly cacheDir: string;
51
+ readonly sandboxesPath: string;
52
+ readonly networksPath: string;
53
+ readonly recordPath: string;
54
+ readonly killCommand: ReaperKillCommand;
55
+ /**
56
+ * This process's own pid. Used only by the Windows script (see
57
+ * windowsScriptContent's doc for why it polls instead of blocking on
58
+ * stdin EOF); harmless to pass on POSIX, which ignores it.
59
+ */
60
+ readonly ownerPid: number;
61
+ }
62
+ export interface WatchdogHandle {
63
+ readonly process: ChildProcess;
64
+ /** Closes the held pipe early — test-only, POSIX-only (the Windows script no longer reads stdin at all; production code lets the POSIX process exit close its pipe naturally). */
65
+ closeForTests(): void;
66
+ }
67
+ /**
68
+ * Spawns the watchdog detached, unref()'d so it never keeps this process's
69
+ * event loop alive.
70
+ *
71
+ * On POSIX, its stdin is a pipe whose write end ONLY this process holds —
72
+ * that write end is `handle.process.stdin`, returned so the caller can
73
+ * keep a live reference for the rest of the process's life (letting it get
74
+ * garbage-collected or explicitly closing it would arm the watchdog
75
+ * early). `detached: true` there just means the standard POSIX
76
+ * survive-your-parent behavior (setsid); this has always worked and this
77
+ * investigation never found a POSIX failure.
78
+ *
79
+ * On Windows, `detached: true` is load-bearing for a completely different
80
+ * reason, proven with a minimal control experiment (a plain `node -e`
81
+ * heartbeat writer — see git history): a NON-detached Windows child is
82
+ * killed by the OS itself the instant this process is torn down, via
83
+ * Windows Job Object semantics (Node/libuv ties a non-detached child to a
84
+ * job object that closes, killing every member, when the owner's own job
85
+ * handle closes) — regardless of what the child is doing. `detached: true`
86
+ * escapes that job and reliably survives (confirmed: 60s+ of continued
87
+ * heartbeats past the owner's death). PowerShell was the original design
88
+ * for the Windows script, but BOTH available hosts (legacy `powershell`
89
+ * and `pwsh`) die within ~200ms of being spawned with `detached: true`,
90
+ * before running a single line — so the Windows watchdog is `node` running
91
+ * a small generated `.js` file instead (see windowsScriptContent), since
92
+ * plain `node` IS proven to survive `detached: true` here. Its stdin is
93
+ * `"ignore"` — it polls for this process's death rather than reading
94
+ * anything, so there is no pipe to leak into any later child this process
95
+ * spawns either. All other stdio is `"ignore"` (or, under
96
+ * `RIGHTSIZE_REAPER_DEBUG`, captured to files) so CI runners never hang on
97
+ * an inherited handle.
98
+ */
99
+ export declare function spawnWatchdog(opts: SpawnWatchdogOptions): Promise<WatchdogHandle>;
100
+ //# sourceMappingURL=watchdog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watchdog.d.ts","sourceRoot":"","sources":["../../../src/core/reaper/watchdog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,oBAAoB,CAAC;AAMrE;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC/C;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG9D;AAkND,4JAA4J;AAC5J,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4B5E;AAqBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAO9G;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9E;AAWD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,kLAAkL;IAClL,aAAa,IAAI,IAAI,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0EvF"}