@descryy/runtime-controller 0.3.8 → 0.4.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.
- package/dist/attach-discovery.d.ts +94 -0
- package/dist/attach-discovery.d.ts.map +1 -0
- package/dist/attach-discovery.js +238 -0
- package/dist/attach-discovery.js.map +1 -0
- package/dist/attach-fencing.d.ts +109 -0
- package/dist/attach-fencing.d.ts.map +1 -0
- package/dist/attach-fencing.js +215 -0
- package/dist/attach-fencing.js.map +1 -0
- package/dist/capability-registry.d.ts +9 -0
- package/dist/capability-registry.d.ts.map +1 -0
- package/dist/capability-registry.js +32 -0
- package/dist/capability-registry.js.map +1 -0
- package/dist/collector-version.d.ts +3 -0
- package/dist/collector-version.d.ts.map +1 -0
- package/dist/collector-version.js +5 -0
- package/dist/collector-version.js.map +1 -0
- package/dist/container-sandbox.d.ts +104 -0
- package/dist/container-sandbox.d.ts.map +1 -0
- package/dist/container-sandbox.js +152 -0
- package/dist/container-sandbox.js.map +1 -0
- package/dist/controller.d.ts +152 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +464 -0
- package/dist/controller.js.map +1 -0
- package/dist/dependency-version-check.d.ts +36 -0
- package/dist/dependency-version-check.d.ts.map +1 -0
- package/dist/dependency-version-check.js +72 -0
- package/dist/dependency-version-check.js.map +1 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +12 -0
- package/dist/env.js.map +1 -0
- package/dist/environment-metadata.d.ts +15 -0
- package/dist/environment-metadata.d.ts.map +1 -0
- package/dist/environment-metadata.js +63 -0
- package/dist/environment-metadata.js.map +1 -0
- package/dist/environment-version-check.d.ts +40 -0
- package/dist/environment-version-check.d.ts.map +1 -0
- package/dist/environment-version-check.js +176 -0
- package/dist/environment-version-check.js.map +1 -0
- package/dist/execution-safety.d.ts +96 -0
- package/dist/execution-safety.d.ts.map +1 -0
- package/dist/execution-safety.js +140 -0
- package/dist/execution-safety.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestration.d.ts +55 -0
- package/dist/orchestration.d.ts.map +1 -0
- package/dist/orchestration.js +219 -0
- package/dist/orchestration.js.map +1 -0
- package/dist/preflight.d.ts +122 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +177 -0
- package/dist/preflight.js.map +1 -0
- package/dist/process-collector.d.ts +20 -0
- package/dist/process-collector.d.ts.map +1 -0
- package/dist/process-collector.js +116 -0
- package/dist/process-collector.js.map +1 -0
- package/dist/process-identity.d.ts +46 -0
- package/dist/process-identity.d.ts.map +1 -0
- package/dist/process-identity.js +186 -0
- package/dist/process-identity.js.map +1 -0
- package/dist/process-manager.d.ts +137 -0
- package/dist/process-manager.d.ts.map +1 -0
- package/dist/process-manager.js +382 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/readiness.d.ts +122 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +214 -0
- package/dist/readiness.js.map +1 -0
- package/dist/sandbox.d.ts +94 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +228 -0
- package/dist/sandbox.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process manager (§5): command execution, cwd, env, process-group tracking,
|
|
3
|
+
* stdout/stderr capture, exit-code capture, graceful shutdown, orphan prevention.
|
|
4
|
+
*
|
|
5
|
+
* Orphan prevention and group-kill use POSIX process groups (`detached: true` +
|
|
6
|
+
* `process.kill(-pid, signal)`) -- not portable to Windows, out of scope for the V1
|
|
7
|
+
* fixture-app slice (§34).
|
|
8
|
+
*/
|
|
9
|
+
import type { FilesystemPolicy, NetworkPolicy, ProcessHandle, ResourceLimits, ServiceAttachConfiguration } from "@descryy/runtime-contracts";
|
|
10
|
+
import { type AttachFence } from "./attach-fencing.ts";
|
|
11
|
+
/**
|
|
12
|
+
* Binds port 0, reads it, releases immediately. A real gap remains between "free
|
|
13
|
+
* when checked" and "still free when the child binds it" (RT-024's gap, one layer
|
|
14
|
+
* down) -- unavoidable without binding the real service directly to the probe
|
|
15
|
+
* socket, which the child-process model doesn't support.
|
|
16
|
+
*
|
|
17
|
+
* **Examined and deliberately not "fixed".** Holding the probe socket longer widens
|
|
18
|
+
* the window rather than narrowing it (the child can't bind while we hold it).
|
|
19
|
+
* Instead, `awaitReadiness` names the endpoint it probed, so a collision reads as
|
|
20
|
+
* "nothing answered on this port" rather than an unexplained failure. Window is a
|
|
21
|
+
* handful of ms in practice; a genuine collision still fails loudly, just correctly
|
|
22
|
+
* attributed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function allocateEphemeralPort(): Promise<number>;
|
|
25
|
+
export interface SpawnProcessOptions {
|
|
26
|
+
readonly processId: string;
|
|
27
|
+
readonly command: string;
|
|
28
|
+
readonly args?: readonly string[];
|
|
29
|
+
readonly cwd?: string;
|
|
30
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
31
|
+
/** Joins the resulting ProcessHandle back to ExecutionConfiguration.services (RT-023). Omit for no corresponding named service. */
|
|
32
|
+
readonly serviceName?: string;
|
|
33
|
+
/** Real port this process is bound to, when the caller already knows it. Recorded on the handle only. */
|
|
34
|
+
readonly port?: number;
|
|
35
|
+
/** §21 execution boundary (RT-070), via `applyResourceLimits`. Re-applied on every (re)spawn including `restart()`. */
|
|
36
|
+
readonly resourceLimits?: ResourceLimits;
|
|
37
|
+
/** §21 execution boundary, filesystem half, via `applySandbox`. Re-applied on every (re)spawn. */
|
|
38
|
+
readonly filesystemPolicy?: FilesystemPolicy;
|
|
39
|
+
/** §21 execution boundary, network half, via `applySandbox`. Re-applied on every (re)spawn. */
|
|
40
|
+
readonly networkPolicy?: NetworkPolicy;
|
|
41
|
+
/**
|
|
42
|
+
* Selects the mechanism enforcing `filesystemPolicy`/`networkPolicy`. Absent means
|
|
43
|
+
* `"bwrap"` (unchanged default); `"container"` routes through `applyContainerSandbox`.
|
|
44
|
+
*
|
|
45
|
+
* **Does not compose with `resourceLimits` in this pass** -- the container backend
|
|
46
|
+
* skips `applyResourceLimits`'s `prlimit` wrap entirely (a minimal image isn't
|
|
47
|
+
* guaranteed to carry `prlimit`, Docker's own limit flags aren't wired up).
|
|
48
|
+
* Disclosed in `container-sandbox.ts`, not silently dropped.
|
|
49
|
+
*/
|
|
50
|
+
readonly sandboxBackend?: "bwrap" | "container";
|
|
51
|
+
}
|
|
52
|
+
export interface ProcessExitInfo {
|
|
53
|
+
readonly exitCode: number | null;
|
|
54
|
+
readonly signal: NodeJS.Signals | null;
|
|
55
|
+
}
|
|
56
|
+
export interface ManagedProcess {
|
|
57
|
+
handle(): ProcessHandle;
|
|
58
|
+
/**
|
|
59
|
+
* Combined stdout+stderr captured so far, in arrival order -- backing store
|
|
60
|
+
* for log-pattern readiness checks.
|
|
61
|
+
*
|
|
62
|
+
* **RT-261: bounded, not whole.** Holds the first `OUTPUT_HEAD_BYTES` and
|
|
63
|
+
* the last `OUTPUT_TAIL_BYTES` of everything the process has written; once
|
|
64
|
+
* `outputInfo().truncated` is true, the string also contains one inline
|
|
65
|
+
* marker naming how much was dropped between them. Readiness log-pattern
|
|
66
|
+
* checks run against the live head/tail as data arrives, so a pattern that
|
|
67
|
+
* only ever appears in the (dropped) middle is a real, disclosed gap, not
|
|
68
|
+
* a silent one -- see `outputInfo()`.
|
|
69
|
+
*/
|
|
70
|
+
readOutput(): string;
|
|
71
|
+
/** Real total bytes seen and whether `readOutput()` is a truncated view of them (RT-261). */
|
|
72
|
+
outputInfo(): ProcessOutputInfo;
|
|
73
|
+
waitForExit(): Promise<ProcessExitInfo>;
|
|
74
|
+
/** SIGTERM the process group, wait up to `gracePeriodMs`, then SIGKILL. Idempotent. */
|
|
75
|
+
kill(gracePeriodMs?: number): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Kills the current process (no-op wait if already exited) and spawns a fresh one
|
|
78
|
+
* with the same command/args/cwd/env. `handle()`/`readOutput()`/`waitForExit()`
|
|
79
|
+
* observe the new process on the same object -- no re-fetch needed. Resolves once
|
|
80
|
+
* the new process exists, not once it's ready (readiness is the caller's job).
|
|
81
|
+
*/
|
|
82
|
+
restart(gracePeriodMs?: number): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* The attach-time fence (RT-N3), or `null` for a process this runtime spawned
|
|
85
|
+
* itself -- fencing only exists to mark content that predates OBSERVATION, and
|
|
86
|
+
* a spawned process has no output before Descry started it. See
|
|
87
|
+
* `computeAttachFence` (`attach-fencing.ts`) for what it records and why.
|
|
88
|
+
*/
|
|
89
|
+
attachFence(): AttachFence | null;
|
|
90
|
+
}
|
|
91
|
+
export interface ProcessOutputInfo {
|
|
92
|
+
/** Every byte this process has written to stdout+stderr, whether kept or dropped. */
|
|
93
|
+
readonly totalBytesSeen: number;
|
|
94
|
+
/** True once `totalBytesSeen` exceeds what head+tail can hold -- `readOutput()` is a bounded view, not the whole thing. */
|
|
95
|
+
readonly truncated: boolean;
|
|
96
|
+
}
|
|
97
|
+
export declare function spawnProcess(options: SpawnProcessOptions): ManagedProcess;
|
|
98
|
+
/**
|
|
99
|
+
* `process.kill(pid, 0)` sends no signal, only asks the kernel if delivery is
|
|
100
|
+
* possible. `ESRCH` = no such pid; `EPERM` = a pid we don't own but that exists.
|
|
101
|
+
*
|
|
102
|
+
* Deliberate second copy of `orchestrator/src/attach-to-running-process.ts`'s
|
|
103
|
+
* same-named function, not an import -- `orchestrator` depends on `controller`, so
|
|
104
|
+
* the reverse import would be circular. Tiny and pure enough to duplicate safely.
|
|
105
|
+
*/
|
|
106
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
107
|
+
export interface AttachProcessOptions {
|
|
108
|
+
readonly processId: string;
|
|
109
|
+
readonly attach: ServiceAttachConfiguration;
|
|
110
|
+
readonly serviceName?: string;
|
|
111
|
+
readonly port?: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* `ManagedProcess` for a service attached to rather than spawned. Reads
|
|
115
|
+
* `attach.logFilePath` fresh on every `readOutput()` -- same growing-whole-buffer
|
|
116
|
+
* contract as a spawned process's in-memory pipe buffer.
|
|
117
|
+
*
|
|
118
|
+
* **`kill()` is a deliberate no-op, not a stub.** Descry did not spawn this process,
|
|
119
|
+
* and `ATTACH_MODE_SCOPE_DISCLOSURE` is unconditional: attach mode never executes
|
|
120
|
+
* code in or applies changes to what it observes. Ending observation is the honest
|
|
121
|
+
* equivalent of "stop" -- the target keeps running. `restart()` throws for the same
|
|
122
|
+
* reason: no command exists to relaunch it with.
|
|
123
|
+
*
|
|
124
|
+
* **Attach fencing (RT-N3).** `computeAttachFence` snapshots, at construction time
|
|
125
|
+
* (i.e. the moment of attach), how much of `logFilePath` already existed and
|
|
126
|
+
* whether some other process also holds it open -- see `attach-fencing.ts`. Exposed
|
|
127
|
+
* via `attachFence()` for a caller building a `ProcessOutputSource` over
|
|
128
|
+
* `readOutput()` to mark pre-existing content instead of reporting it as freshly
|
|
129
|
+
* observed. `readOutput()` itself additionally refuses (rather than silently
|
|
130
|
+
* mis-reading) if the file is ever found shorter than the largest size already
|
|
131
|
+
* seen -- the same "cannot reconstruct a rotated/truncated buffer" rule
|
|
132
|
+
* `orchestrator/src/polling-output-source.ts` enforces one layer up, duplicated
|
|
133
|
+
* here for the same reason `isProcessAlive` is: this module cannot depend on
|
|
134
|
+
* that one without a cycle, and the check is small and pure enough to repeat.
|
|
135
|
+
*/
|
|
136
|
+
export declare function attachManagedProcess(options: AttachProcessOptions): ManagedProcess;
|
|
137
|
+
//# sourceMappingURL=process-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../src/process-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAI7I,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAE3E;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC,CAevD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,mIAAmI;IACnI,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,yGAAyG;IACzG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,uHAAuH;IACvH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,kGAAkG;IAClG,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,+FAA+F;IAC/F,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;CACjD;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,IAAI,aAAa,CAAC;IACxB;;;;;;;;;;;OAWG;IACH,UAAU,IAAI,MAAM,CAAC;IACrB,6FAA6F;IAC7F,UAAU,IAAI,iBAAiB,CAAC;IAChC,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACxC,uFAAuF;IACvF,IAAI,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;;;OAKG;IACH,WAAW,IAAI,WAAW,GAAG,IAAI,CAAC;CACnC;AAoBD,MAAM,WAAW,iBAAiB;IAChC,qFAAqF;IACrF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,2HAA2H;IAC3H,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAkLzE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,cAAc,CAoHlF"}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process manager (§5): command execution, cwd, env, process-group tracking,
|
|
3
|
+
* stdout/stderr capture, exit-code capture, graceful shutdown, orphan prevention.
|
|
4
|
+
*
|
|
5
|
+
* Orphan prevention and group-kill use POSIX process groups (`detached: true` +
|
|
6
|
+
* `process.kill(-pid, signal)`) -- not portable to Windows, out of scope for the V1
|
|
7
|
+
* fixture-app slice (§34).
|
|
8
|
+
*/
|
|
9
|
+
import { execFile, spawn } from "node:child_process";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { createServer } from "node:net";
|
|
12
|
+
import { promisify } from "node:util";
|
|
13
|
+
import { applyResourceLimits } from "./execution-safety.js";
|
|
14
|
+
import { applySandbox } from "./sandbox.js";
|
|
15
|
+
import { applyContainerSandbox } from "./container-sandbox.js";
|
|
16
|
+
import { computeAttachFence } from "./attach-fencing.js";
|
|
17
|
+
/**
|
|
18
|
+
* Binds port 0, reads it, releases immediately. A real gap remains between "free
|
|
19
|
+
* when checked" and "still free when the child binds it" (RT-024's gap, one layer
|
|
20
|
+
* down) -- unavoidable without binding the real service directly to the probe
|
|
21
|
+
* socket, which the child-process model doesn't support.
|
|
22
|
+
*
|
|
23
|
+
* **Examined and deliberately not "fixed".** Holding the probe socket longer widens
|
|
24
|
+
* the window rather than narrowing it (the child can't bind while we hold it).
|
|
25
|
+
* Instead, `awaitReadiness` names the endpoint it probed, so a collision reads as
|
|
26
|
+
* "nothing answered on this port" rather than an unexplained failure. Window is a
|
|
27
|
+
* handful of ms in practice; a genuine collision still fails loudly, just correctly
|
|
28
|
+
* attributed.
|
|
29
|
+
*/
|
|
30
|
+
export function allocateEphemeralPort() {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const probe = createServer();
|
|
33
|
+
probe.once("error", reject);
|
|
34
|
+
probe.listen(0, () => {
|
|
35
|
+
const address = probe.address();
|
|
36
|
+
if (address === null || typeof address === "string") {
|
|
37
|
+
probe.close();
|
|
38
|
+
reject(new Error("could not determine an ephemeral port"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const { port } = address;
|
|
42
|
+
probe.close(() => resolve(port));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const DEFAULT_GRACE_PERIOD_MS = 5000;
|
|
47
|
+
/**
|
|
48
|
+
* RT-261: `outputBuffer += chunk` grew without bound for the process's whole
|
|
49
|
+
* life -- a chatty service (or one stuck in a crash-log loop) grew Descry's
|
|
50
|
+
* own memory, not the app's. Bounded to head + tail per service instead: the
|
|
51
|
+
* first bytes usually carry the startup/config story, the last bytes carry
|
|
52
|
+
* whatever just happened, and the (likely repetitive) middle is the part a
|
|
53
|
+
* reader least needs verbatim. Starting values, not measurements.
|
|
54
|
+
*/
|
|
55
|
+
const OUTPUT_HEAD_BYTES = 64 * 1024;
|
|
56
|
+
const OUTPUT_TAIL_BYTES = 1024 * 1024;
|
|
57
|
+
/** Marks the gap between head and tail; never confused with real process output because it names its own bound. */
|
|
58
|
+
function truncationMarker(totalBytes, keptBytes) {
|
|
59
|
+
return `\n\n...[Descry truncated ${String(totalBytes - keptBytes)} of ${String(totalBytes)} bytes of output here -- head/tail kept, middle dropped]...\n\n`;
|
|
60
|
+
}
|
|
61
|
+
export function spawnProcess(options) {
|
|
62
|
+
let startedAt = "";
|
|
63
|
+
let exitedAt = null;
|
|
64
|
+
let exitCode = null;
|
|
65
|
+
let signal = null;
|
|
66
|
+
// Bounded head/tail buffer (RT-261) -- see OUTPUT_HEAD_BYTES/OUTPUT_TAIL_BYTES.
|
|
67
|
+
let headBuffer = "";
|
|
68
|
+
let tailBuffer = "";
|
|
69
|
+
let totalBytesSeen = 0;
|
|
70
|
+
let killStarted = false;
|
|
71
|
+
let child;
|
|
72
|
+
let settled = false;
|
|
73
|
+
// Set only for the container backend -- see kill() for why it needs a separate
|
|
74
|
+
// stop mechanism from bwrap's group-kill.
|
|
75
|
+
let containerName = null;
|
|
76
|
+
let resolveExit;
|
|
77
|
+
let exitPromise;
|
|
78
|
+
// Everything a (re)spawn needs to reset, in one place -- construction and
|
|
79
|
+
// restart() must not drift into resetting different subsets of this state.
|
|
80
|
+
function spawnChild() {
|
|
81
|
+
startedAt = new Date().toISOString();
|
|
82
|
+
exitedAt = null;
|
|
83
|
+
exitCode = null;
|
|
84
|
+
signal = null;
|
|
85
|
+
headBuffer = "";
|
|
86
|
+
tailBuffer = "";
|
|
87
|
+
totalBytesSeen = 0;
|
|
88
|
+
killStarted = false;
|
|
89
|
+
settled = false;
|
|
90
|
+
exitPromise = new Promise((resolve) => {
|
|
91
|
+
resolveExit = resolve;
|
|
92
|
+
});
|
|
93
|
+
// "container" skips applyResourceLimits entirely (see SpawnProcessOptions.
|
|
94
|
+
// sandboxBackend) -- "bwrap" (default) is unchanged.
|
|
95
|
+
const wrapped = options.sandboxBackend === "container"
|
|
96
|
+
? applyContainerSandbox({
|
|
97
|
+
interpreterCommand: options.command,
|
|
98
|
+
command: options.command,
|
|
99
|
+
args: options.args ?? [],
|
|
100
|
+
cwd: options.cwd ?? process.cwd(),
|
|
101
|
+
processEnv: { ...process.env, ...options.env },
|
|
102
|
+
...(options.filesystemPolicy === undefined ? {} : { filesystemPolicy: options.filesystemPolicy }),
|
|
103
|
+
...(options.networkPolicy === undefined ? {} : { networkPolicy: options.networkPolicy }),
|
|
104
|
+
})
|
|
105
|
+
: applySandbox({
|
|
106
|
+
interpreterCommand: options.command,
|
|
107
|
+
...applyResourceLimits(options.command, options.args ?? [], options.resourceLimits),
|
|
108
|
+
cwd: options.cwd ?? process.cwd(),
|
|
109
|
+
...(options.filesystemPolicy === undefined ? {} : { filesystemPolicy: options.filesystemPolicy }),
|
|
110
|
+
...(options.networkPolicy === undefined ? {} : { networkPolicy: options.networkPolicy }),
|
|
111
|
+
});
|
|
112
|
+
containerName = wrapped.containerName ?? null;
|
|
113
|
+
child = spawn(wrapped.command, wrapped.args, {
|
|
114
|
+
cwd: options.cwd,
|
|
115
|
+
env: { ...process.env, ...options.env },
|
|
116
|
+
detached: true,
|
|
117
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
118
|
+
});
|
|
119
|
+
// Shared by both streams -- stdout/stderr are already interleaved into one
|
|
120
|
+
// combined buffer (this module's existing contract), so head/tail bounding
|
|
121
|
+
// applies to the combined stream, not per-fd.
|
|
122
|
+
function appendOutput(chunk) {
|
|
123
|
+
totalBytesSeen += chunk.length;
|
|
124
|
+
if (headBuffer.length < OUTPUT_HEAD_BYTES) {
|
|
125
|
+
headBuffer += chunk.toString("utf8");
|
|
126
|
+
if (headBuffer.length > OUTPUT_HEAD_BYTES) {
|
|
127
|
+
headBuffer = headBuffer.slice(0, OUTPUT_HEAD_BYTES);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
tailBuffer += chunk.toString("utf8");
|
|
131
|
+
if (tailBuffer.length > OUTPUT_TAIL_BYTES) {
|
|
132
|
+
tailBuffer = tailBuffer.slice(tailBuffer.length - OUTPUT_TAIL_BYTES);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
child.stdout?.on("data", appendOutput);
|
|
136
|
+
child.stderr?.on("data", appendOutput);
|
|
137
|
+
// A bad command (ENOENT) surfaces as an `'error'` event, not a thrown exception,
|
|
138
|
+
// and per Node's docs `'exit'` may never fire after `'error'`. Both events settle
|
|
139
|
+
// the same promise exactly once, so a failed spawn reads as an ordinary
|
|
140
|
+
// never-became-ready process rather than crashing the caller or hanging cleanup.
|
|
141
|
+
function settle(info) {
|
|
142
|
+
if (settled)
|
|
143
|
+
return;
|
|
144
|
+
settled = true;
|
|
145
|
+
exitedAt = new Date().toISOString();
|
|
146
|
+
exitCode = info.exitCode;
|
|
147
|
+
signal = info.signal;
|
|
148
|
+
resolveExit(info);
|
|
149
|
+
}
|
|
150
|
+
child.once("exit", (code, exitSignal) => settle({ exitCode: code, signal: exitSignal }));
|
|
151
|
+
child.once("error", () => settle({ exitCode: null, signal: null }));
|
|
152
|
+
}
|
|
153
|
+
spawnChild();
|
|
154
|
+
function handle() {
|
|
155
|
+
return {
|
|
156
|
+
processId: options.processId,
|
|
157
|
+
command: options.command,
|
|
158
|
+
pid: child.pid ?? null,
|
|
159
|
+
startedAt,
|
|
160
|
+
exitedAt,
|
|
161
|
+
exitCode,
|
|
162
|
+
signal,
|
|
163
|
+
serviceName: options.serviceName ?? null,
|
|
164
|
+
port: options.port ?? null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async function kill(gracePeriodMs = DEFAULT_GRACE_PERIOD_MS) {
|
|
168
|
+
if (killStarted || exitedAt !== null) {
|
|
169
|
+
await exitPromise;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
killStarted = true;
|
|
173
|
+
const pid = child.pid;
|
|
174
|
+
if (pid === undefined) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (containerName !== null) {
|
|
178
|
+
// Found via escape testing: the local `docker` CLI process is not the
|
|
179
|
+
// container -- it runs under `dockerd`, a separate process tree.
|
|
180
|
+
// Signaling the CLI's process group left a real orphaned container running.
|
|
181
|
+
// `docker stop` (by name) is the real stop mechanism here; `--rm` then
|
|
182
|
+
// removes it. Still falls through to group-kill below as a CLI backstop.
|
|
183
|
+
try {
|
|
184
|
+
await execFileAsync("docker", ["stop", "--time", String(Math.max(1, Math.round(gracePeriodMs / 1000))), containerName]);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// Already stopped/removed -- not worth surfacing.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
signalGroup(pid, "SIGTERM");
|
|
191
|
+
const exited = await Promise.race([exitPromise.then(() => true), sleep(gracePeriodMs).then(() => false)]);
|
|
192
|
+
if (!exited) {
|
|
193
|
+
signalGroup(pid, "SIGKILL");
|
|
194
|
+
await exitPromise;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function restart(gracePeriodMs = DEFAULT_GRACE_PERIOD_MS) {
|
|
198
|
+
// Safe regardless of the old process's state -- kill() is idempotent, and this
|
|
199
|
+
// waits for it to be fully gone before spawnChild() reassigns state, so no
|
|
200
|
+
// outgoing event can land on the incoming process.
|
|
201
|
+
await kill(gracePeriodMs);
|
|
202
|
+
spawnChild();
|
|
203
|
+
}
|
|
204
|
+
function readOutput() {
|
|
205
|
+
const droppedBytes = totalBytesSeen - headBuffer.length - tailBuffer.length;
|
|
206
|
+
if (droppedBytes <= 0) {
|
|
207
|
+
// No real gap yet -- tailBuffer alone already covers everything seen so
|
|
208
|
+
// far (headBuffer is a redundant prefix of it in this regime, since it
|
|
209
|
+
// hasn't been outgrown by the tail window sliding past it).
|
|
210
|
+
return tailBuffer;
|
|
211
|
+
}
|
|
212
|
+
return headBuffer + truncationMarker(totalBytesSeen, headBuffer.length + tailBuffer.length) + tailBuffer;
|
|
213
|
+
}
|
|
214
|
+
function outputInfo() {
|
|
215
|
+
return { totalBytesSeen, truncated: totalBytesSeen > headBuffer.length + tailBuffer.length };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
handle,
|
|
219
|
+
readOutput,
|
|
220
|
+
outputInfo,
|
|
221
|
+
waitForExit: () => exitPromise,
|
|
222
|
+
kill,
|
|
223
|
+
restart,
|
|
224
|
+
// A spawned process has no output that predates this runtime observing it.
|
|
225
|
+
attachFence: () => null,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* `process.kill(pid, 0)` sends no signal, only asks the kernel if delivery is
|
|
230
|
+
* possible. `ESRCH` = no such pid; `EPERM` = a pid we don't own but that exists.
|
|
231
|
+
*
|
|
232
|
+
* Deliberate second copy of `orchestrator/src/attach-to-running-process.ts`'s
|
|
233
|
+
* same-named function, not an import -- `orchestrator` depends on `controller`, so
|
|
234
|
+
* the reverse import would be circular. Tiny and pure enough to duplicate safely.
|
|
235
|
+
*/
|
|
236
|
+
export function isProcessAlive(pid) {
|
|
237
|
+
try {
|
|
238
|
+
process.kill(pid, 0);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
return error.code === "EPERM";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const DEFAULT_ATTACH_POLL_INTERVAL_MS = 50;
|
|
246
|
+
/**
|
|
247
|
+
* `ManagedProcess` for a service attached to rather than spawned. Reads
|
|
248
|
+
* `attach.logFilePath` fresh on every `readOutput()` -- same growing-whole-buffer
|
|
249
|
+
* contract as a spawned process's in-memory pipe buffer.
|
|
250
|
+
*
|
|
251
|
+
* **`kill()` is a deliberate no-op, not a stub.** Descry did not spawn this process,
|
|
252
|
+
* and `ATTACH_MODE_SCOPE_DISCLOSURE` is unconditional: attach mode never executes
|
|
253
|
+
* code in or applies changes to what it observes. Ending observation is the honest
|
|
254
|
+
* equivalent of "stop" -- the target keeps running. `restart()` throws for the same
|
|
255
|
+
* reason: no command exists to relaunch it with.
|
|
256
|
+
*
|
|
257
|
+
* **Attach fencing (RT-N3).** `computeAttachFence` snapshots, at construction time
|
|
258
|
+
* (i.e. the moment of attach), how much of `logFilePath` already existed and
|
|
259
|
+
* whether some other process also holds it open -- see `attach-fencing.ts`. Exposed
|
|
260
|
+
* via `attachFence()` for a caller building a `ProcessOutputSource` over
|
|
261
|
+
* `readOutput()` to mark pre-existing content instead of reporting it as freshly
|
|
262
|
+
* observed. `readOutput()` itself additionally refuses (rather than silently
|
|
263
|
+
* mis-reading) if the file is ever found shorter than the largest size already
|
|
264
|
+
* seen -- the same "cannot reconstruct a rotated/truncated buffer" rule
|
|
265
|
+
* `orchestrator/src/polling-output-source.ts` enforces one layer up, duplicated
|
|
266
|
+
* here for the same reason `isProcessAlive` is: this module cannot depend on
|
|
267
|
+
* that one without a cycle, and the check is small and pure enough to repeat.
|
|
268
|
+
*/
|
|
269
|
+
export function attachManagedProcess(options) {
|
|
270
|
+
const { pid, logFilePath } = options.attach;
|
|
271
|
+
if (!isProcessAlive(pid)) {
|
|
272
|
+
throw new Error(`attachManagedProcess: pid ${String(pid)} for "${options.processId}" is not running — attach requires ` +
|
|
273
|
+
`a target that already exists.`);
|
|
274
|
+
}
|
|
275
|
+
// H-attach-log-discovery: `logFilePath` is now optional -- absent means either
|
|
276
|
+
// a stream source was discovered instead (`options.attach.discoveredLogSource`,
|
|
277
|
+
// consumed by the orchestrator directly, never through this file-tail path) or
|
|
278
|
+
// nothing was found at all. Either way there is no file for RT-N3's fence to
|
|
279
|
+
// reason about, so `fence` is honestly `null` here, same as `spawnProcess`'s own
|
|
280
|
+
// "nothing predates observation" case -- `ManagedProcess.attachFence()` already
|
|
281
|
+
// admits `null` for exactly this.
|
|
282
|
+
const fence = logFilePath === undefined ? null : computeAttachFence(pid, logFilePath);
|
|
283
|
+
let maxObservedByteLength = fence?.attachOffsetBytes ?? 0;
|
|
284
|
+
const startedAt = new Date().toISOString();
|
|
285
|
+
let exitedAt = null;
|
|
286
|
+
let exitPromise = null;
|
|
287
|
+
function handle() {
|
|
288
|
+
if (exitedAt === null && !isProcessAlive(pid)) {
|
|
289
|
+
exitedAt = new Date().toISOString();
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
processId: options.processId,
|
|
293
|
+
// No real command to report -- disclosed rather than guessed.
|
|
294
|
+
command: `<attached: pid ${String(pid)}${logFilePath === undefined ? "" : `, ${logFilePath}`}>`,
|
|
295
|
+
pid,
|
|
296
|
+
startedAt,
|
|
297
|
+
exitedAt,
|
|
298
|
+
// Not observable for a process we don't spawn or reap.
|
|
299
|
+
exitCode: null,
|
|
300
|
+
signal: null,
|
|
301
|
+
serviceName: options.serviceName ?? null,
|
|
302
|
+
port: options.port ?? null,
|
|
303
|
+
attached: true,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function readOutput() {
|
|
307
|
+
// No file to tail -- either a stream source was discovered (the
|
|
308
|
+
// orchestrator reads that directly, never through here) or nothing was
|
|
309
|
+
// found at all. Empty is the honest answer, not a guess standing in for
|
|
310
|
+
// one: this function has never had anything to read in that case.
|
|
311
|
+
if (logFilePath === undefined)
|
|
312
|
+
return "";
|
|
313
|
+
let content;
|
|
314
|
+
try {
|
|
315
|
+
content = readFileSync(logFilePath, "utf8");
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
319
|
+
throw new Error(`attachManagedProcess: could not read log file "${logFilePath}" for pid ${String(pid)}: ${detail}`);
|
|
320
|
+
}
|
|
321
|
+
const byteLength = Buffer.byteLength(content, "utf8");
|
|
322
|
+
if (byteLength < maxObservedByteLength) {
|
|
323
|
+
throw new Error(`attachManagedProcess: log file "${logFilePath}" for pid ${String(pid)} shrank from ${String(maxObservedByteLength)} ` +
|
|
324
|
+
`to ${String(byteLength)} bytes between reads -- this looks like log rotation or truncation, not new output. ` +
|
|
325
|
+
"The file-tail contract can't reconstruct a rotated/truncated buffer without either duplicating or dropping " +
|
|
326
|
+
// Non-null: `fence` is only ever null when `logFilePath` is undefined (set together,
|
|
327
|
+
// once, above), and this line is only reached when it is defined (the early return
|
|
328
|
+
// above covers the other case) -- the two are the same fact, just not one narrowing
|
|
329
|
+
// TS can see across this closure on its own.
|
|
330
|
+
`lines. Reported rather than guessed. (This run's own attach offset was ${String(fence.attachOffsetBytes)} bytes.)`);
|
|
331
|
+
}
|
|
332
|
+
maxObservedByteLength = byteLength;
|
|
333
|
+
return content;
|
|
334
|
+
}
|
|
335
|
+
function waitForExit() {
|
|
336
|
+
exitPromise ??= new Promise((resolve) => {
|
|
337
|
+
const poll = () => {
|
|
338
|
+
if (!isProcessAlive(pid)) {
|
|
339
|
+
exitedAt ??= new Date().toISOString();
|
|
340
|
+
resolve({ exitCode: null, signal: null });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
setTimeout(poll, DEFAULT_ATTACH_POLL_INTERVAL_MS);
|
|
344
|
+
};
|
|
345
|
+
poll();
|
|
346
|
+
});
|
|
347
|
+
return exitPromise;
|
|
348
|
+
}
|
|
349
|
+
function kill() {
|
|
350
|
+
// Deliberate no-op -- see module doc above.
|
|
351
|
+
return Promise.resolve();
|
|
352
|
+
}
|
|
353
|
+
function restart() {
|
|
354
|
+
return Promise.reject(new Error(`attachManagedProcess: cannot restart an attached process (pid ${String(pid)}) — Descry did not spawn ` +
|
|
355
|
+
`it and has no command to relaunch it with.`));
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* RT-261's head/tail bound is a spawnProcess concern -- this path re-reads
|
|
359
|
+
* `logFilePath` fresh on every call rather than accumulating a growing
|
|
360
|
+
* in-memory buffer, so there is nothing here for that bound to apply to.
|
|
361
|
+
* `truncated` stays honestly false; `totalBytesSeen` reports the real file
|
|
362
|
+
* size observed on the most recent read (0 before the first one).
|
|
363
|
+
*/
|
|
364
|
+
function outputInfo() {
|
|
365
|
+
return { totalBytesSeen: maxObservedByteLength, truncated: false };
|
|
366
|
+
}
|
|
367
|
+
return { handle, readOutput, outputInfo, waitForExit, kill, restart, attachFence: () => fence };
|
|
368
|
+
}
|
|
369
|
+
const execFileAsync = promisify(execFile);
|
|
370
|
+
function signalGroup(pid, signal) {
|
|
371
|
+
try {
|
|
372
|
+
// Negative pid targets the whole process group (orphan-prevention mechanism).
|
|
373
|
+
process.kill(-pid, signal);
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// Group already gone -- not an error.
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function sleep(ms) {
|
|
380
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
381
|
+
}
|
|
382
|
+
//# sourceMappingURL=process-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-manager.js","sourceRoot":"","sources":["../src/process-manager.ts"],"names":[],"mappings":"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"}
|