@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,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readiness (§6): "readiness must not equal 'process exists.'" Mechanisms: HTTP
|
|
3
|
+
* check, TCP/port check, log pattern, subprocess command, or a caller hook
|
|
4
|
+
* (consolidates the plan's "framework readiness hook" and "user-defined readiness"
|
|
5
|
+
* into one predicate shape).
|
|
6
|
+
*
|
|
7
|
+
* **`command` is not `custom-hook` (RT-193).** `custom-hook` runs a caller's
|
|
8
|
+
* in-process predicate; `command` runs a real subprocess and reads its exit code --
|
|
9
|
+
* built once so a caller with a real CLI health check (`pg_isready`) doesn't have to
|
|
10
|
+
* shell out from inside a hand-written hook.
|
|
11
|
+
*
|
|
12
|
+
* **What `ready: true` does not claim (RT-024).** All mechanisms answer "is
|
|
13
|
+
* something listening/responding," none confirm it's the process *this* spawn
|
|
14
|
+
* produced -- a stale listener from an already-dead run satisfies `http`/`tcp-port`
|
|
15
|
+
* identically. `log-pattern` only carries the stronger this-run guarantee if `read`
|
|
16
|
+
* is wired to this execution's own `readOutput()` (caller discipline, not enforced
|
|
17
|
+
* by the type). An ephemeral port closes this gap by construction; a fixed,
|
|
18
|
+
* guessable port doesn't. Found via a real 30s collector timeout that looked like a
|
|
19
|
+
* Playwright bug and was actually a wait on an orphaned process from a dead session
|
|
20
|
+
* (RT-024).
|
|
21
|
+
*
|
|
22
|
+
* **A single probe attempt cannot outlive `timeoutMs` (RT-030).** `http`/`tcp-port`
|
|
23
|
+
* bind I/O to the remaining time until `deadline`, not a fresh per-attempt budget --
|
|
24
|
+
* measured: an unbound `fetch` here let `awaitReadiness` run 8s+ past a 3s budget.
|
|
25
|
+
* `command` gets the same treatment via `execFile`'s `timeout`. `custom-hook` is not
|
|
26
|
+
* wrapped this way -- a caller predicate owns its own timeout, same as every other
|
|
27
|
+
* `Collector` callback contract here.
|
|
28
|
+
*/
|
|
29
|
+
export declare const READINESS_MECHANISMS: readonly ["http", "tcp-port", "log-pattern", "command", "custom-hook"];
|
|
30
|
+
export type ReadinessMechanism = (typeof READINESS_MECHANISMS)[number];
|
|
31
|
+
/**
|
|
32
|
+
* How strongly a successful readiness result establishes that the answering
|
|
33
|
+
* process is THIS run's own -- RT-024. `"verified"`: a real kernel-state
|
|
34
|
+
* check (`process-identity.ts`) or a mechanism that is this-run-bound by
|
|
35
|
+
* construction (a directly-executed `command`, or `log-pattern` reading a
|
|
36
|
+
* branded `ProcessOutputReader`) confirmed it. `"unverified"`: the mechanism
|
|
37
|
+
* succeeded but nothing confirmed identity -- disclosed, not hidden.
|
|
38
|
+
* `"not-applicable"`: nothing succeeded, so no identity claim is being made.
|
|
39
|
+
*/
|
|
40
|
+
export declare const READINESS_IDENTITY_LEVELS: readonly ["verified", "unverified", "not-applicable"];
|
|
41
|
+
export type ReadinessIdentity = (typeof READINESS_IDENTITY_LEVELS)[number];
|
|
42
|
+
export interface HttpReadinessCheck {
|
|
43
|
+
readonly kind: "http";
|
|
44
|
+
readonly url: string;
|
|
45
|
+
readonly expectedStatus?: number;
|
|
46
|
+
/**
|
|
47
|
+
* The pid this run spawned (or attached to) for this service. When set, a
|
|
48
|
+
* successful connection is only accepted once `verifyListeningSocketOwner`
|
|
49
|
+
* confirms this pid actually owns the listening socket -- otherwise the
|
|
50
|
+
* probe reports not-ready rather than trusting whoever answered. Omitted
|
|
51
|
+
* means the caller has no pid to bind to (or the mechanism backing this
|
|
52
|
+
* service, e.g. a container, can't be verified this way) and the result is
|
|
53
|
+
* disclosed as `identity: "unverified"`, never silently upgraded.
|
|
54
|
+
*/
|
|
55
|
+
readonly expectedPid?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface TcpPortReadinessCheck {
|
|
58
|
+
readonly kind: "tcp-port";
|
|
59
|
+
readonly host: string;
|
|
60
|
+
readonly port: number;
|
|
61
|
+
/** See `HttpReadinessCheck.expectedPid` -- same guarantee, same disclosure. */
|
|
62
|
+
readonly expectedPid?: number;
|
|
63
|
+
}
|
|
64
|
+
declare const PROCESS_OUTPUT_READER_BRAND: unique symbol;
|
|
65
|
+
/**
|
|
66
|
+
* A `LogPatternReadinessCheck.read` may only be one of these, and the only
|
|
67
|
+
* way to make one is `createProcessOutputReader`. This is RT-024's own
|
|
68
|
+
* finding turned into a type: "log-pattern only carries the stronger
|
|
69
|
+
* this-run guarantee if `read` is wired to this execution's own
|
|
70
|
+
* `readOutput()` -- caller discipline, not enforced by the type." A plain
|
|
71
|
+
* `() => string` closure -- however it's actually wired -- no longer
|
|
72
|
+
* type-checks; a caller must go through the constructor below, which exists
|
|
73
|
+
* precisely so the only inputs are a real `ManagedProcess.readOutput` (or an
|
|
74
|
+
* equivalent this execution actually owns).
|
|
75
|
+
*/
|
|
76
|
+
export interface ProcessOutputReader {
|
|
77
|
+
readonly [PROCESS_OUTPUT_READER_BRAND]: true;
|
|
78
|
+
read(): string;
|
|
79
|
+
}
|
|
80
|
+
/** The only way to mint a `ProcessOutputReader` -- wrap the real output source this execution owns (`ManagedProcess.readOutput`, or an equivalent). */
|
|
81
|
+
export declare function createProcessOutputReader(readOutput: () => string): ProcessOutputReader;
|
|
82
|
+
export interface LogPatternReadinessCheck {
|
|
83
|
+
readonly kind: "log-pattern";
|
|
84
|
+
/**
|
|
85
|
+
* Tested against `read.read()`'s full return value on every poll. Backed by
|
|
86
|
+
* `readOutput()`, an unbounded, growing stdout+stderr buffer -- an unbounded
|
|
87
|
+
* quantifier before an absent-able literal (the ReDoS shape RT-069/RT-070 already
|
|
88
|
+
* fixed elsewhere) would backtrack catastrophically here on every poll. Disclosed
|
|
89
|
+
* risk, not fixed: the regex is the caller's to bound, not this module's.
|
|
90
|
+
*/
|
|
91
|
+
readonly pattern: RegExp;
|
|
92
|
+
/** A `ProcessOutputReader` — see that type's doc. Only `createProcessOutputReader` can produce one, which is what makes this-execution's-own-output a type guarantee rather than a convention. */
|
|
93
|
+
readonly read: ProcessOutputReader;
|
|
94
|
+
}
|
|
95
|
+
export interface CommandReadinessCheck {
|
|
96
|
+
readonly kind: "command";
|
|
97
|
+
readonly command: string;
|
|
98
|
+
readonly args?: readonly string[];
|
|
99
|
+
/** Defaults to this process's own `cwd` (`execFile`'s own default) when omitted. */
|
|
100
|
+
readonly cwd?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface CustomHookReadinessCheck {
|
|
103
|
+
readonly kind: "custom-hook";
|
|
104
|
+
readonly check: () => Promise<boolean>;
|
|
105
|
+
}
|
|
106
|
+
export type ReadinessCheck = HttpReadinessCheck | TcpPortReadinessCheck | LogPatternReadinessCheck | CommandReadinessCheck | CustomHookReadinessCheck;
|
|
107
|
+
export interface ReadinessResult {
|
|
108
|
+
readonly ready: boolean;
|
|
109
|
+
/** Which mechanism succeeded — null when none did within the timeout. */
|
|
110
|
+
readonly mechanism: ReadinessMechanism | null;
|
|
111
|
+
readonly elapsedMs: number;
|
|
112
|
+
readonly reason: string | null;
|
|
113
|
+
/** How strongly `ready: true` establishes this-run's-own-process (RT-024). See `ReadinessIdentity`. `"not-applicable"` whenever `ready` is `false` — nothing succeeded, so no identity claim is made either way. */
|
|
114
|
+
readonly identity: ReadinessIdentity;
|
|
115
|
+
}
|
|
116
|
+
export interface AwaitReadinessOptions {
|
|
117
|
+
readonly timeoutMs: number;
|
|
118
|
+
readonly pollIntervalMs?: number;
|
|
119
|
+
}
|
|
120
|
+
export declare function awaitReadiness(checks: readonly ReadinessCheck[], options: AwaitReadinessOptions): Promise<ReadinessResult>;
|
|
121
|
+
export {};
|
|
122
|
+
//# sourceMappingURL=readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,eAAO,MAAM,oBAAoB,wEAAyE,CAAC;AAC3G,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,uDAAwD,CAAC;AAC/F,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAOD,QAAA,MAAM,2BAA2B,eAAgC,CAAC;AAElE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,CAAC,2BAA2B,CAAC,EAAE,IAAI,CAAC;IAC7C,IAAI,IAAI,MAAM,CAAC;CAChB;AAED,uJAAuJ;AACvJ,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,MAAM,GAAG,mBAAmB,CAEvF;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kMAAkM;IAClM,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,oFAAoF;IACpF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACxC;AAED,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,qBAAqB,GACrB,wBAAwB,GACxB,qBAAqB,GACrB,wBAAwB,CAAC;AAE7B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oNAAoN;IACpN,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;CACtC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAID,wBAAsB,cAAc,CAClC,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAyC1B"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readiness (§6): "readiness must not equal 'process exists.'" Mechanisms: HTTP
|
|
3
|
+
* check, TCP/port check, log pattern, subprocess command, or a caller hook
|
|
4
|
+
* (consolidates the plan's "framework readiness hook" and "user-defined readiness"
|
|
5
|
+
* into one predicate shape).
|
|
6
|
+
*
|
|
7
|
+
* **`command` is not `custom-hook` (RT-193).** `custom-hook` runs a caller's
|
|
8
|
+
* in-process predicate; `command` runs a real subprocess and reads its exit code --
|
|
9
|
+
* built once so a caller with a real CLI health check (`pg_isready`) doesn't have to
|
|
10
|
+
* shell out from inside a hand-written hook.
|
|
11
|
+
*
|
|
12
|
+
* **What `ready: true` does not claim (RT-024).** All mechanisms answer "is
|
|
13
|
+
* something listening/responding," none confirm it's the process *this* spawn
|
|
14
|
+
* produced -- a stale listener from an already-dead run satisfies `http`/`tcp-port`
|
|
15
|
+
* identically. `log-pattern` only carries the stronger this-run guarantee if `read`
|
|
16
|
+
* is wired to this execution's own `readOutput()` (caller discipline, not enforced
|
|
17
|
+
* by the type). An ephemeral port closes this gap by construction; a fixed,
|
|
18
|
+
* guessable port doesn't. Found via a real 30s collector timeout that looked like a
|
|
19
|
+
* Playwright bug and was actually a wait on an orphaned process from a dead session
|
|
20
|
+
* (RT-024).
|
|
21
|
+
*
|
|
22
|
+
* **A single probe attempt cannot outlive `timeoutMs` (RT-030).** `http`/`tcp-port`
|
|
23
|
+
* bind I/O to the remaining time until `deadline`, not a fresh per-attempt budget --
|
|
24
|
+
* measured: an unbound `fetch` here let `awaitReadiness` run 8s+ past a 3s budget.
|
|
25
|
+
* `command` gets the same treatment via `execFile`'s `timeout`. `custom-hook` is not
|
|
26
|
+
* wrapped this way -- a caller predicate owns its own timeout, same as every other
|
|
27
|
+
* `Collector` callback contract here.
|
|
28
|
+
*/
|
|
29
|
+
import { createConnection } from "node:net";
|
|
30
|
+
import { execFile } from "node:child_process";
|
|
31
|
+
import { verifyListeningSocketOwner } from "./process-identity.js";
|
|
32
|
+
export const READINESS_MECHANISMS = ["http", "tcp-port", "log-pattern", "command", "custom-hook"];
|
|
33
|
+
/**
|
|
34
|
+
* How strongly a successful readiness result establishes that the answering
|
|
35
|
+
* process is THIS run's own -- RT-024. `"verified"`: a real kernel-state
|
|
36
|
+
* check (`process-identity.ts`) or a mechanism that is this-run-bound by
|
|
37
|
+
* construction (a directly-executed `command`, or `log-pattern` reading a
|
|
38
|
+
* branded `ProcessOutputReader`) confirmed it. `"unverified"`: the mechanism
|
|
39
|
+
* succeeded but nothing confirmed identity -- disclosed, not hidden.
|
|
40
|
+
* `"not-applicable"`: nothing succeeded, so no identity claim is being made.
|
|
41
|
+
*/
|
|
42
|
+
export const READINESS_IDENTITY_LEVELS = ["verified", "unverified", "not-applicable"];
|
|
43
|
+
// Unexported on purpose: a real, module-private `unique symbol` (TS infers
|
|
44
|
+
// that type for a `const` initialised with `Symbol()`) that only this module
|
|
45
|
+
// can name. Nothing outside `readiness.ts` can construct a
|
|
46
|
+
// structurally-compatible value even though TS is structurally typed
|
|
47
|
+
// elsewhere -- the brand is the enforcement. See `ProcessOutputReader` below.
|
|
48
|
+
const PROCESS_OUTPUT_READER_BRAND = Symbol("ProcessOutputReader");
|
|
49
|
+
/** The only way to mint a `ProcessOutputReader` -- wrap the real output source this execution owns (`ManagedProcess.readOutput`, or an equivalent). */
|
|
50
|
+
export function createProcessOutputReader(readOutput) {
|
|
51
|
+
return { [PROCESS_OUTPUT_READER_BRAND]: true, read: readOutput };
|
|
52
|
+
}
|
|
53
|
+
const DEFAULT_POLL_INTERVAL_MS = 200;
|
|
54
|
+
export async function awaitReadiness(checks, options) {
|
|
55
|
+
const startedAt = Date.now();
|
|
56
|
+
const deadline = startedAt + options.timeoutMs;
|
|
57
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
58
|
+
if (checks.length === 0) {
|
|
59
|
+
return { ready: false, mechanism: null, elapsedMs: 0, reason: "no readiness checks configured", identity: "not-applicable" };
|
|
60
|
+
}
|
|
61
|
+
// C-F4: last real diagnostic any probe captured before giving up (chiefly
|
|
62
|
+
// `command`'s stderr/exit code -- http/tcp-port have nothing richer than
|
|
63
|
+
// "connection refused"). Kept as "last seen", most likely to explain the timeout.
|
|
64
|
+
let lastDetail = null;
|
|
65
|
+
do {
|
|
66
|
+
for (const check of checks) {
|
|
67
|
+
const result = await probe(check, deadline);
|
|
68
|
+
if (result.ok) {
|
|
69
|
+
return { ready: true, mechanism: check.kind, elapsedMs: Date.now() - startedAt, reason: null, identity: result.identity };
|
|
70
|
+
}
|
|
71
|
+
if (result.detail !== null)
|
|
72
|
+
lastDetail = `${check.kind}: ${result.detail}`;
|
|
73
|
+
}
|
|
74
|
+
await sleep(pollIntervalMs);
|
|
75
|
+
} while (Date.now() < deadline);
|
|
76
|
+
// Names what was actually probed, not just that nothing answered -- the only
|
|
77
|
+
// cheap mitigation for the ephemeral-port race in `allocateEphemeralPort`
|
|
78
|
+
// (bind 0, read, close, spawn): if another process grabs the port first, the
|
|
79
|
+
// child never comes up and this message is the only symptom. Naming the exact
|
|
80
|
+
// address probed sends the reader to the port, not the application.
|
|
81
|
+
return {
|
|
82
|
+
ready: false,
|
|
83
|
+
mechanism: null,
|
|
84
|
+
elapsedMs: Date.now() - startedAt,
|
|
85
|
+
// "timeout" stays in the wording deliberately -- §41's hung-process test
|
|
86
|
+
// matches on that word.
|
|
87
|
+
reason: `no readiness mechanism succeeded within the ${options.timeoutMs}ms timeout — probed ${checks.map(describeCheck).join(", ")}` +
|
|
88
|
+
(lastDetail !== null ? `; last observed failure — ${lastDetail}` : ""),
|
|
89
|
+
identity: "not-applicable",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function describeCheck(check) {
|
|
93
|
+
switch (check.kind) {
|
|
94
|
+
case "http":
|
|
95
|
+
return check.url;
|
|
96
|
+
case "tcp-port":
|
|
97
|
+
return `tcp ${check.host}:${check.port}`;
|
|
98
|
+
case "log-pattern":
|
|
99
|
+
return `log pattern ${String(check.pattern)}`;
|
|
100
|
+
case "command":
|
|
101
|
+
return `command ${[check.command, ...(check.args ?? [])].join(" ")}`;
|
|
102
|
+
case "custom-hook":
|
|
103
|
+
return "custom hook";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function ok(identity = "not-applicable") {
|
|
107
|
+
return { ok: true, detail: null, identity };
|
|
108
|
+
}
|
|
109
|
+
function notReady(detail = null) {
|
|
110
|
+
return { ok: false, detail, identity: "not-applicable" };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Applies RT-024's identity check to a successful TCP-level connection
|
|
114
|
+
* (shared by `http` and `tcp-port`, which are both "something answered on
|
|
115
|
+
* this host:port"). No `expectedPid` supplied: succeeds, disclosed as
|
|
116
|
+
* `"unverified"` -- the caller gave this module nothing to check against, so
|
|
117
|
+
* it makes no claim either way rather than a silent pass reading as a
|
|
118
|
+
* guarantee. `expectedPid` supplied: only a real, kernel-confirmed
|
|
119
|
+
* `"verified"` is accepted as ready; `"mismatch"` is refused outright — a
|
|
120
|
+
* connection accepted by someone else's listener is not this run being
|
|
121
|
+
* ready; `"unsupported"` (non-Linux, or the pid can't be inspected) degrades
|
|
122
|
+
* to `"unverified"` rather than blocking a run this module simply cannot
|
|
123
|
+
* check on this platform.
|
|
124
|
+
*/
|
|
125
|
+
function identityCheckedConnection(port, expectedPid) {
|
|
126
|
+
if (expectedPid === undefined)
|
|
127
|
+
return ok("unverified");
|
|
128
|
+
const verification = verifyListeningSocketOwner(port, expectedPid);
|
|
129
|
+
if (verification === "verified")
|
|
130
|
+
return ok("verified");
|
|
131
|
+
if (verification === "unsupported")
|
|
132
|
+
return ok("unverified");
|
|
133
|
+
return notReady(`connected, but the listening socket on port ${String(port)} is not owned by pid ${String(expectedPid)} ` +
|
|
134
|
+
"(process-identity check via /proc) -- likely a stale listener from a different process, not this run's own");
|
|
135
|
+
}
|
|
136
|
+
async function probe(check, deadline) {
|
|
137
|
+
switch (check.kind) {
|
|
138
|
+
case "http":
|
|
139
|
+
return probeHttp(check, deadline);
|
|
140
|
+
case "tcp-port":
|
|
141
|
+
return probeTcpPort(check, deadline);
|
|
142
|
+
case "log-pattern":
|
|
143
|
+
// `read` is a branded ProcessOutputReader -- sourced from this
|
|
144
|
+
// execution's own output by construction, so a match is `"verified"`.
|
|
145
|
+
return check.pattern.test(check.read.read()) ? ok("verified") : notReady();
|
|
146
|
+
case "command":
|
|
147
|
+
return probeCommand(check, deadline);
|
|
148
|
+
case "custom-hook":
|
|
149
|
+
// An arbitrary caller predicate -- this module has no way to know
|
|
150
|
+
// whether it actually checks this run's own process, so it never
|
|
151
|
+
// claims more than "unverified" for a caller that passes.
|
|
152
|
+
return (await check.check()) ? ok("unverified") : notReady();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function probeHttp(check, deadline) {
|
|
156
|
+
try {
|
|
157
|
+
const response = await fetch(check.url, { signal: AbortSignal.timeout(Math.max(0, deadline - Date.now())) });
|
|
158
|
+
if (response.status !== (check.expectedStatus ?? 200)) {
|
|
159
|
+
return notReady(`responded with status ${String(response.status)}, expected ${String(check.expectedStatus ?? 200)}`);
|
|
160
|
+
}
|
|
161
|
+
const url = new URL(check.url);
|
|
162
|
+
const port = url.port !== "" ? Number(url.port) : url.protocol === "https:" ? 443 : 80;
|
|
163
|
+
return identityCheckedConnection(port, check.expectedPid);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return notReady(error instanceof Error ? error.message : String(error));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function probeTcpPort(check, deadline) {
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
const socket = createConnection({ host: check.host, port: check.port });
|
|
172
|
+
socket.setTimeout(Math.max(0, deadline - Date.now()));
|
|
173
|
+
const finish = (result) => {
|
|
174
|
+
socket.destroy();
|
|
175
|
+
resolve(result);
|
|
176
|
+
};
|
|
177
|
+
socket.once("connect", () => finish(identityCheckedConnection(check.port, check.expectedPid)));
|
|
178
|
+
socket.once("error", (error) => finish(notReady(error.message)));
|
|
179
|
+
socket.once("timeout", () => finish(notReady("connection attempt timed out")));
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Ready when `check.command` exits 0 within the remaining time until `deadline`
|
|
184
|
+
* (RT-030). Non-zero exit, spawn failure, and timeout kill are all "not ready yet" --
|
|
185
|
+
* same as every other mechanism's `false`.
|
|
186
|
+
*
|
|
187
|
+
* C-F4: stdout/stderr are the actual health-check output, the richest diagnostic
|
|
188
|
+
* available here. Preferring stderr, then stdout, then the bare error message
|
|
189
|
+
* mirrors `runAttacher`'s ordering in `attach-to-running-jvm-process.ts`.
|
|
190
|
+
*/
|
|
191
|
+
function probeCommand(check, deadline) {
|
|
192
|
+
return new Promise((resolve) => {
|
|
193
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
194
|
+
if (remainingMs === 0) {
|
|
195
|
+
resolve(notReady("no time remaining before the readiness deadline"));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
execFile(check.command, check.args ?? [], { cwd: check.cwd, timeout: remainingMs }, (error, stdout, stderr) => {
|
|
199
|
+
if (error === null) {
|
|
200
|
+
// A directly `execFile`d subprocess is this-run-bound by
|
|
201
|
+
// construction -- there is no "which process answered" ambiguity
|
|
202
|
+
// the way a network probe has.
|
|
203
|
+
resolve(ok("verified"));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const detail = stderr.trim() || stdout.trim() || error.message;
|
|
207
|
+
resolve(notReady(detail));
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function sleep(ms) {
|
|
212
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=readiness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §21's execution boundary, real isolation half. RT-070/RT-193 gave this runtime a
|
|
3
|
+
* *named* boundary (trusted-local, resource limits, privilege reporting) but not an
|
|
4
|
+
* *isolating* one -- nothing stopped a spawned process from reading any file, or
|
|
5
|
+
* reaching any host, the invoking user could. This module closes that gap for real,
|
|
6
|
+
* on Linux, for processes this runtime SPAWNS.
|
|
7
|
+
*
|
|
8
|
+
* **Architectural constraint this module cannot solve around:** isolation can only
|
|
9
|
+
* wrap a process Descry itself launches. Attach-mode reaches a process already
|
|
10
|
+
* running, unconfined, before Descry touched it -- there is no honest way to
|
|
11
|
+
* retroactively contain it (ptrace-based interception was investigated and rejected
|
|
12
|
+
* elsewhere as not honestly generalizable). Attach-mode stays permanently and
|
|
13
|
+
* explicitly unsandboxed -- `SANDBOX_BOUNDARY_DISCLOSURE` in `execution-safety.ts`
|
|
14
|
+
* discloses this in-product; this module does not and structurally cannot change it.
|
|
15
|
+
*
|
|
16
|
+
* **Mechanism: bubblewrap (bwrap), not raw `unshare`/`clone`.** bwrap is the
|
|
17
|
+
* unprivileged sandboxing helper behind Flatpak -- small, auditable, actively
|
|
18
|
+
* maintained, not a bespoke native module this repo builds per-platform. Runs
|
|
19
|
+
* setuid-free on any kernel with unprivileged user namespaces enabled (checked
|
|
20
|
+
* below, never assumed) and gives one process a new mount namespace (real
|
|
21
|
+
* filesystem visibility control) and network namespace (real network denial) in one
|
|
22
|
+
* exec. Composes with `applyResourceLimits`'s `prlimit` wrap and with
|
|
23
|
+
* `process-manager.ts`'s process-group kill -- verified directly: bwrap does not
|
|
24
|
+
* create a new process group unless asked, so the existing group-kill reaches it
|
|
25
|
+
* unchanged. The one dependency this buys: `bwrap` must be on PATH, reported as a
|
|
26
|
+
* capability exactly like `prlimit` -- refused, never silently unconstrained, when
|
|
27
|
+
* requested and absent.
|
|
28
|
+
*
|
|
29
|
+
* **NOT attempted, deliberately: a native macOS or Windows sandbox.** `sandbox-exec`/
|
|
30
|
+
* Seatbelt is deprecated with no public API replacement; the Endpoint Security
|
|
31
|
+
* Framework needs an Apple entitlement this project doesn't have; Windows Job
|
|
32
|
+
* Objects + AppContainer + WFP would be a second and third bespoke native sandbox.
|
|
33
|
+
* This is an explicit decision not to build native sandboxes for those platforms at
|
|
34
|
+
* all -- `../container-sandbox.ts` closes the gap instead, routing macOS/Windows
|
|
35
|
+
* through a real Docker container that reuses this module's proven Linux mechanism
|
|
36
|
+
* (see that module's doc for what's real-tested there vs unverified on real
|
|
37
|
+
* hardware). `filesystemIsolationCapability`/`networkIsolationCapability` below
|
|
38
|
+
* still report `unavailable` with a reason on non-Linux -- never silently claimed
|
|
39
|
+
* as covered.
|
|
40
|
+
*/
|
|
41
|
+
import type { CapabilityStatus, FilesystemPolicy, NetworkPolicy } from "@descryy/runtime-contracts";
|
|
42
|
+
/** Real capability check, same shape as `resourceLimitCapability`: PATH existence first, then the Linux-specific precondition existence alone doesn't prove -- unprivileged user namespaces actually usable, not just bwrap installed. */
|
|
43
|
+
export declare function filesystemIsolationCapability(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): CapabilityStatus;
|
|
44
|
+
/** Same underlying mechanism as `filesystemIsolationCapability` (one bwrap namespace set covers both) -- delegates rather than duplicating. Kept as its own function because the two policies are declared and reasoned about independently. */
|
|
45
|
+
export declare function networkIsolationCapability(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): CapabilityStatus;
|
|
46
|
+
/**
|
|
47
|
+
* `NetworkPolicy`'s general shape (arbitrary allow/deny host lists) is not what
|
|
48
|
+
* this mechanism enforces -- only full denial. Returns the reason a policy can't be
|
|
49
|
+
* applied, or `null` if it's the one shape that can. Checked independently of
|
|
50
|
+
* `networkIsolationCapability`: "nothing here can enforce any policy" and "this
|
|
51
|
+
* specific policy asks for something not built yet" are different claims.
|
|
52
|
+
*/
|
|
53
|
+
export declare function unsupportedNetworkPolicyReason(policy: NetworkPolicy): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Resolves the real, symlink-followed directory containing the executable
|
|
56
|
+
* `command` would run as -- the one extra host path a sandboxed process needs
|
|
57
|
+
* beyond standard OS dirs and whatever the caller declared. Computed generically,
|
|
58
|
+
* not per-language: an interpreter installed outside `/usr` (nvm, rbenv, pyenv,
|
|
59
|
+
* sdkman) would otherwise be invisible inside the sandbox regardless of language.
|
|
60
|
+
* Returns `null` when unresolvable -- the caller proceeds without an extra bind;
|
|
61
|
+
* the exec fails inside the sandbox the same honest way it would outside one.
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveExecutableDirectory(command: string, cwd: string, env: NodeJS.ProcessEnv): string | null;
|
|
64
|
+
export interface SandboxOptions {
|
|
65
|
+
/** Original, un-resource-limit-wrapped command (e.g. "node") -- used only to resolve which extra directory the interpreter needs bound in. */
|
|
66
|
+
readonly interpreterCommand: string;
|
|
67
|
+
/** Command to actually exec inside the sandbox -- already passed through `applyResourceLimits`, so may be "prlimit" with the real command in `args`. */
|
|
68
|
+
readonly command: string;
|
|
69
|
+
readonly args: readonly string[];
|
|
70
|
+
readonly cwd: string;
|
|
71
|
+
readonly filesystemPolicy?: FilesystemPolicy;
|
|
72
|
+
readonly networkPolicy?: NetworkPolicy;
|
|
73
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
74
|
+
readonly platform?: NodeJS.Platform;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Wraps `command`/`args` with `bwrap` so the OS enforces `filesystemPolicy`/
|
|
78
|
+
* `networkPolicy`. Neither declared returns them unchanged -- same non-regression
|
|
79
|
+
* contract as `applyResourceLimits`.
|
|
80
|
+
*
|
|
81
|
+
* **Throws rather than silently spawning unconstrained** when a policy is
|
|
82
|
+
* requested and the real mechanism can't back it -- no mechanism on this platform,
|
|
83
|
+
* or a declared shape this mechanism doesn't implement.
|
|
84
|
+
*
|
|
85
|
+
* Composes with `applyResourceLimits` by wrapping *outside* it (`bwrap … --
|
|
86
|
+
* prlimit … -- realCommand`) -- the mount namespace must exist before `prlimit` or
|
|
87
|
+
* the real command run inside it; `prlimit` (in `/usr/bin`) is reachable through
|
|
88
|
+
* the same base OS binding every sandboxed process gets.
|
|
89
|
+
*/
|
|
90
|
+
export declare function applySandbox(options: SandboxOptions): {
|
|
91
|
+
readonly command: string;
|
|
92
|
+
readonly args: readonly string[];
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAkCpG,0OAA0O;AAC1O,wBAAgB,6BAA6B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,gBAAgB,CAoBlJ;AAED,gPAAgP;AAChP,wBAAgB,0BAA0B,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,gBAAgB,CAE/I;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAOnF;AAOD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,IAAI,CAwB9G;AAED,MAAM,WAAW,cAAc;IAC7B,8IAA8I;IAC9I,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,wJAAwJ;IACxJ,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAkFpH"}
|