@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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §21/RT-070's execution boundary: resource limits and privilege reporting for a
|
|
3
|
+
* runtime declared trusted-local -- every spawned process runs with the invoking
|
|
4
|
+
* user's own OS privileges, no filesystem/network restriction. Names the boundary
|
|
5
|
+
* rather than leaving it absent (§38); real isolation (containers/namespaces) is a
|
|
6
|
+
* separate decision this module does not invent (§29).
|
|
7
|
+
*
|
|
8
|
+
* Enforces for real, via the OS: CPU time, virtual memory, process count, through
|
|
9
|
+
* `prlimit` -- an exec wrapper, never a shell (a shell would undo RT-036's
|
|
10
|
+
* `tokenizeCommand` refusal of shell metacharacters). `prlimit` is util-linux, absent
|
|
11
|
+
* on macOS, so it's reported as a capability, never assumed.
|
|
12
|
+
*/
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
import { networkIsolationCapability } from "./sandbox.js";
|
|
15
|
+
/** Whether `prlimit` is reachable on PATH. `env` is a parameter (default `process.env`) so the "unavailable" branch is testable without mutating the real environment. */
|
|
16
|
+
export function resourceLimitCapability(env = process.env) {
|
|
17
|
+
try {
|
|
18
|
+
execFileSync("prlimit", ["--version"], { env, stdio: "ignore" });
|
|
19
|
+
return { availability: "available", reason: null };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {
|
|
23
|
+
availability: "unavailable",
|
|
24
|
+
reason: "prlimit (util-linux) is not on PATH -- CPU/memory/process limits cannot be enforced on this platform without reintroducing a shell, which this runtime refuses to do (RT-036)",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* RT-193 originally made this always `unavailable`. Now delegates to
|
|
30
|
+
* `networkIsolationCapability()` (`./sandbox.ts`), real on Linux via a bwrap network
|
|
31
|
+
* namespace -- kept as the stable name every caller already imports (the seam
|
|
32
|
+
* RT-193 anticipated). Still unconditionally `unavailable` on macOS/Windows.
|
|
33
|
+
*/
|
|
34
|
+
export function networkPolicyCapability(env = process.env) {
|
|
35
|
+
return networkIsolationCapability(env);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Wraps `command`/`args` so the OS enforces `limits`, via `prlimit`. `undefined` or
|
|
39
|
+
* empty `limits` returns them unchanged -- opt-in only.
|
|
40
|
+
*
|
|
41
|
+
* **Throws rather than silently spawning unconstrained** when a limit was requested
|
|
42
|
+
* and `prlimit` is unavailable (refuse-not-guess, per RT-036) -- the alternative is a
|
|
43
|
+
* caller asking for a memory cap and silently getting an unbounded process, the
|
|
44
|
+
* "sandbox that doesn't sandbox" shape §38 bans.
|
|
45
|
+
*/
|
|
46
|
+
export function applyResourceLimits(command, args, limits, env = process.env) {
|
|
47
|
+
if (limits === undefined)
|
|
48
|
+
return { command, args };
|
|
49
|
+
const flags = [];
|
|
50
|
+
if (limits.maxMemoryBytes !== undefined)
|
|
51
|
+
flags.push(`--as=${limits.maxMemoryBytes}`);
|
|
52
|
+
if (limits.maxCpuSeconds !== undefined)
|
|
53
|
+
flags.push(`--cpu=${limits.maxCpuSeconds}`);
|
|
54
|
+
if (limits.maxProcesses !== undefined)
|
|
55
|
+
flags.push(`--nproc=${limits.maxProcesses}`);
|
|
56
|
+
if (flags.length === 0)
|
|
57
|
+
return { command, args };
|
|
58
|
+
const capability = resourceLimitCapability(env);
|
|
59
|
+
if (capability.availability !== "available") {
|
|
60
|
+
throw new Error(`resource limits were requested but cannot be enforced on this platform: ${capability.reason}`);
|
|
61
|
+
}
|
|
62
|
+
return { command: "prlimit", args: [...flags, "--", command, ...args] };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* DEC-272: the sandbox boundary (RT-070) must be stated to the user directly, not
|
|
66
|
+
* left as a silent doc gap. Fixed, not derived from `runningAsRoot` -- the boundary
|
|
67
|
+
* is the same regardless of privilege level found.
|
|
68
|
+
*
|
|
69
|
+
* **Scope: this is the unconditional-unisolated text, not a universal one.** Accurate
|
|
70
|
+
* for every execution before `sandbox.ts` existed; no longer true once spawn-mode got
|
|
71
|
+
* real bwrap-backed isolation (`applySandbox`) -- using this unconditionally for an
|
|
72
|
+
* isolated process would understate its boundary, which is not the safe direction to
|
|
73
|
+
* be wrong in. `sandboxBoundaryDisclosureFor()` below is the mode-aware replacement;
|
|
74
|
+
* this constant remains its no-policy-applied return value and attach-mode's own
|
|
75
|
+
* disclosure (attach-mode never has anything more accurate to say).
|
|
76
|
+
*/
|
|
77
|
+
export const SANDBOX_BOUNDARY_DISCLOSURE = "This app runs with your own user permissions, the same as if you started it yourself. " +
|
|
78
|
+
"The filesystem and network are not isolated -- anything your own account could read, write, " +
|
|
79
|
+
"or reach, this run can too.";
|
|
80
|
+
const NO_POLICIES_APPLIED = { filesystemPolicyApplied: false, networkPolicyApplied: false };
|
|
81
|
+
/**
|
|
82
|
+
* Mode-aware replacement for reading `SANDBOX_BOUNDARY_DISCLOSURE` unconditionally.
|
|
83
|
+
* Defaults to no-policy-applied, so a no-arg call matches the old constant exactly.
|
|
84
|
+
*
|
|
85
|
+
* **Only ever true for spawn-mode.** Attach-mode has no path to produce
|
|
86
|
+
* `filesystemPolicyApplied`/`networkPolicyApplied: true` for a process it didn't
|
|
87
|
+
* spawn (structural, see `sandbox.ts`) -- attach-mode callers should never pass
|
|
88
|
+
* anything but the default.
|
|
89
|
+
*/
|
|
90
|
+
export function sandboxBoundaryDisclosureFor(policies = NO_POLICIES_APPLIED) {
|
|
91
|
+
const { filesystemPolicyApplied, networkPolicyApplied } = policies;
|
|
92
|
+
if (!filesystemPolicyApplied && !networkPolicyApplied)
|
|
93
|
+
return SANDBOX_BOUNDARY_DISCLOSURE;
|
|
94
|
+
if (filesystemPolicyApplied && networkPolicyApplied) {
|
|
95
|
+
return ("This process was spawned inside a real OS-level sandbox (bubblewrap): filesystem access is restricted " +
|
|
96
|
+
"to its declared allowedRoots (plus its own working directory) -- nothing else on this machine is visible " +
|
|
97
|
+
"to it, not merely unwritable -- and network access is fully denied, including this machine's own loopback. " +
|
|
98
|
+
"This is real isolation, not a name-only boundary.");
|
|
99
|
+
}
|
|
100
|
+
if (filesystemPolicyApplied) {
|
|
101
|
+
return ("This process was spawned inside a real OS-level sandbox (bubblewrap): filesystem access is restricted " +
|
|
102
|
+
"to its declared allowedRoots (plus its own working directory) -- nothing else on this machine is visible " +
|
|
103
|
+
"to it, not merely unwritable. Network access was NOT restricted for this run -- it can reach anything " +
|
|
104
|
+
"your own account's network could reach.");
|
|
105
|
+
}
|
|
106
|
+
return ("This process was spawned inside a real OS-level sandbox (bubblewrap): network access is fully denied " +
|
|
107
|
+
"for this run, including this machine's own loopback. Filesystem access was NOT restricted for this run -- " +
|
|
108
|
+
"it can read, write, or reach anything your own account's filesystem could.");
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Attach mode's contractual scope guarantee -- distinct from the sandbox-boundary
|
|
112
|
+
* disclosure above: this is what an attach-mode `Collector` is permitted to *do*, not
|
|
113
|
+
* what OS boundary wraps it. Mirrors `Collector` in `@descryy/runtime-contracts`.
|
|
114
|
+
* Stated once so every attach-mode caller surfaces the identical sentence.
|
|
115
|
+
*/
|
|
116
|
+
export const ATTACH_MODE_SCOPE_DISCLOSURE = "Attach mode observes an already-running process; it never executes code in it or applies changes to it. " +
|
|
117
|
+
"This is a permanent, documented architectural limitation, not a temporary gap -- there is no honest way to " +
|
|
118
|
+
"retroactively sandbox a process that was already running, unconfined, before this tool touched it. " +
|
|
119
|
+
"Respawn-and-supervise and the cgroup resource-limit move are real alternatives next to attach mode, not " +
|
|
120
|
+
"fixes to it; see their own documentation for what each actually gets you.";
|
|
121
|
+
/**
|
|
122
|
+
* Reports, never enforces. §21's policy for trusted-local (RT-070) is not "refuse to
|
|
123
|
+
* run as root" -- CI containers commonly do, and a hard refusal would turn a
|
|
124
|
+
* documented boundary into an undocumented outage. The privilege level just becomes
|
|
125
|
+
* observable instead of invisible.
|
|
126
|
+
*
|
|
127
|
+
* A required parameter, not defaulted: `undefined` is the real value a no-uid-concept
|
|
128
|
+
* platform produces, and a default can't distinguish "explicitly undefined" from
|
|
129
|
+
* "omitted." `currentPrivilegeReport()` below is the convenience wrapper; this makes
|
|
130
|
+
* both branches testable without actually running as root or on Windows.
|
|
131
|
+
*/
|
|
132
|
+
export function privilegeReportFor(getuid) {
|
|
133
|
+
if (getuid === undefined)
|
|
134
|
+
return { runningAsRoot: null, disclosure: SANDBOX_BOUNDARY_DISCLOSURE };
|
|
135
|
+
return { runningAsRoot: getuid() === 0, disclosure: SANDBOX_BOUNDARY_DISCLOSURE };
|
|
136
|
+
}
|
|
137
|
+
export function currentPrivilegeReport() {
|
|
138
|
+
return privilegeReportFor(process.getuid?.bind(process));
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=execution-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-safety.js","sourceRoot":"","sources":["../src/execution-safety.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export { withDefaultProcessEnv } from "./env.ts";
|
|
2
|
+
export type { SpawnProcessOptions, ProcessExitInfo, ManagedProcess, AttachProcessOptions, ProcessOutputInfo } from "./process-manager.ts";
|
|
3
|
+
export { spawnProcess, attachManagedProcess, isProcessAlive } from "./process-manager.ts";
|
|
4
|
+
export type { AttachFence, ProcessStartTimeResult, OtherLogWriterCheck } from "./attach-fencing.ts";
|
|
5
|
+
export { computeAttachFence, getProcessStartTimeMs, detectOtherLogWriters } from "./attach-fencing.ts";
|
|
6
|
+
export type { ReadinessMechanism, ReadinessIdentity, HttpReadinessCheck, TcpPortReadinessCheck, LogPatternReadinessCheck, CommandReadinessCheck, CustomHookReadinessCheck, ReadinessCheck, ReadinessResult, AwaitReadinessOptions, ProcessOutputReader, } from "./readiness.ts";
|
|
7
|
+
export { READINESS_MECHANISMS, READINESS_IDENTITY_LEVELS, awaitReadiness, createProcessOutputReader } from "./readiness.ts";
|
|
8
|
+
export type { IdentityVerification } from "./process-identity.ts";
|
|
9
|
+
export { verifyListeningSocketOwner } from "./process-identity.ts";
|
|
10
|
+
export type { ProcReader, PortOwnerLookup, LogSourceDescriptor } from "./attach-discovery.ts";
|
|
11
|
+
export { REAL_PROC_READER, findPortOwner, resolveLogSourceForPid } from "./attach-discovery.ts";
|
|
12
|
+
export type { CreateExecutionInput, RunOptions, ProcessLifecycleEvent } from "./controller.ts";
|
|
13
|
+
export { ExecutionController } from "./controller.ts";
|
|
14
|
+
export type { ProcessCollector } from "./process-collector.ts";
|
|
15
|
+
export { createProcessCollector } from "./process-collector.ts";
|
|
16
|
+
export type { RegisteredCollectorCapabilities, CollectorCapabilityKey } from "./capability-registry.ts";
|
|
17
|
+
export { buildExecutionCapabilities, bestCapabilityStatus } from "./capability-registry.ts";
|
|
18
|
+
export type { PrivilegeReport, AppliedIsolationPolicies } from "./execution-safety.ts";
|
|
19
|
+
export { resourceLimitCapability, applyResourceLimits, networkPolicyCapability, privilegeReportFor, currentPrivilegeReport, SANDBOX_BOUNDARY_DISCLOSURE, sandboxBoundaryDisclosureFor, ATTACH_MODE_SCOPE_DISCLOSURE, } from "./execution-safety.ts";
|
|
20
|
+
export type { SandboxOptions } from "./sandbox.ts";
|
|
21
|
+
export { filesystemIsolationCapability, networkIsolationCapability, unsupportedNetworkPolicyReason, resolveExecutableDirectory, applySandbox, } from "./sandbox.ts";
|
|
22
|
+
export type { ContainerSandboxOptions } from "./container-sandbox.ts";
|
|
23
|
+
export { containerRuntimeCapability, containerFilesystemIsolationCapability, containerNetworkIsolationCapability, unsupportedContainerNetworkPolicyReason, resolveContainerImage, applyContainerSandbox, } from "./container-sandbox.ts";
|
|
24
|
+
export { captureEnvironmentMetadata } from "./environment-metadata.ts";
|
|
25
|
+
export type { DependencyVersionMismatch, DependencyVersionCheckResult } from "./dependency-version-check.ts";
|
|
26
|
+
export { checkDependencyVersions } from "./dependency-version-check.ts";
|
|
27
|
+
export type { DeclaredVersionSource, EnvironmentVersionCheckResult } from "./environment-version-check.ts";
|
|
28
|
+
export { DECLARED_VERSION_SOURCES, checkEnvironmentVersion, satisfiesDeclaredNodeVersion } from "./environment-version-check.ts";
|
|
29
|
+
export type { CommandTokenization, ServicesValidation } from "./orchestration.ts";
|
|
30
|
+
export { tokenizeCommand, validateServicesConfiguration, substitutePortPlaceholders } from "./orchestration.ts";
|
|
31
|
+
export type { PreflightCheckKind, PreflightStatus, PreflightProbeOutcome, PreflightCheckSpec, PreflightCheckResult, PreflightResult, } from "./preflight.ts";
|
|
32
|
+
export { PREFLIGHT_CHECK_KINDS, PREFLIGHT_STATUSES, PREFLIGHT_CHECK_TIMEOUT_MS, runPreflight, portFreeCheck, portAnswersCheck, readinessTargetCheck, databaseReachableCheck, customCheck, } from "./preflight.ts";
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { withDefaultProcessEnv } from "./env.js";
|
|
2
|
+
export { spawnProcess, attachManagedProcess, isProcessAlive } from "./process-manager.js";
|
|
3
|
+
export { computeAttachFence, getProcessStartTimeMs, detectOtherLogWriters } from "./attach-fencing.js";
|
|
4
|
+
export { READINESS_MECHANISMS, READINESS_IDENTITY_LEVELS, awaitReadiness, createProcessOutputReader } from "./readiness.js";
|
|
5
|
+
export { verifyListeningSocketOwner } from "./process-identity.js";
|
|
6
|
+
export { REAL_PROC_READER, findPortOwner, resolveLogSourceForPid } from "./attach-discovery.js";
|
|
7
|
+
export { ExecutionController } from "./controller.js";
|
|
8
|
+
export { createProcessCollector } from "./process-collector.js";
|
|
9
|
+
export { buildExecutionCapabilities, bestCapabilityStatus } from "./capability-registry.js";
|
|
10
|
+
export { resourceLimitCapability, applyResourceLimits, networkPolicyCapability, privilegeReportFor, currentPrivilegeReport, SANDBOX_BOUNDARY_DISCLOSURE, sandboxBoundaryDisclosureFor, ATTACH_MODE_SCOPE_DISCLOSURE, } from "./execution-safety.js";
|
|
11
|
+
export { filesystemIsolationCapability, networkIsolationCapability, unsupportedNetworkPolicyReason, resolveExecutableDirectory, applySandbox, } from "./sandbox.js";
|
|
12
|
+
export { containerRuntimeCapability, containerFilesystemIsolationCapability, containerNetworkIsolationCapability, unsupportedContainerNetworkPolicyReason, resolveContainerImage, applyContainerSandbox, } from "./container-sandbox.js";
|
|
13
|
+
export { captureEnvironmentMetadata } from "./environment-metadata.js";
|
|
14
|
+
export { checkDependencyVersions } from "./dependency-version-check.js";
|
|
15
|
+
export { DECLARED_VERSION_SOURCES, checkEnvironmentVersion, satisfiesDeclaredNodeVersion } from "./environment-version-check.js";
|
|
16
|
+
export { tokenizeCommand, validateServicesConfiguration, substitutePortPlaceholders } from "./orchestration.js";
|
|
17
|
+
export { PREFLIGHT_CHECK_KINDS, PREFLIGHT_STATUSES, PREFLIGHT_CHECK_TIMEOUT_MS, runPreflight, portFreeCheck, portAnswersCheck, readinessTargetCheck, databaseReachableCheck, customCheck, } from "./preflight.js";
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAGjD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG1F,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAevG,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAG5H,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAGnE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAGhG,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAG5F,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,2BAA2B,EAC3B,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,6BAA6B,EAC7B,0BAA0B,EAC1B,8BAA8B,EAC9B,0BAA0B,EAC1B,YAAY,GACb,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,0BAA0B,EAC1B,sCAAsC,EACtC,mCAAmC,EACnC,uCAAuC,EACvC,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAGvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAGxE,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAMjI,OAAO,EAAE,eAAe,EAAE,6BAA6B,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAYhH,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,0BAA0B,EAC1B,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,GACZ,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a declarative `services` map (§6, RT-028) into a concrete spawn order for
|
|
3
|
+
* `ExecutionController.run()` (RT-032). Pure and side-effect-free -- validated and
|
|
4
|
+
* ordered before anything is spawned, so a bad reference is refused atomically
|
|
5
|
+
* rather than discovered mid-sequence.
|
|
6
|
+
*
|
|
7
|
+
* Start order and port injection are declared, never inferred (RT-023's "ask, don't
|
|
8
|
+
* guess"): a `${name.port}` reference alone doesn't create an ordering guarantee --
|
|
9
|
+
* `name` must also appear in the referencing service's `dependsOn`, or validation refuses.
|
|
10
|
+
*/
|
|
11
|
+
import type { ServiceConfiguration } from "@descryy/runtime-contracts";
|
|
12
|
+
export type ServicesValidation = {
|
|
13
|
+
readonly valid: true;
|
|
14
|
+
readonly order: readonly string[];
|
|
15
|
+
} | {
|
|
16
|
+
readonly valid: false;
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Validates a `services` map and returns a topological spawn order. Checks, all
|
|
21
|
+
* before anything is spawned: every `dependsOn` names a real key; the `dependsOn`
|
|
22
|
+
* graph has no cycle; every `${name}.port` placeholder in a service's `env` has
|
|
23
|
+
* `name` in that service's own `dependsOn`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateServicesConfiguration(services: Readonly<Record<string, ServiceConfiguration>>): ServicesValidation;
|
|
26
|
+
/**
|
|
27
|
+
* Replaces every `${name}.port` in `env`'s values with the resolved port for `name`.
|
|
28
|
+
* Assumes `validateServicesConfiguration` already confirmed the reference. An
|
|
29
|
+
* unresolvable reference is left untouched, not dropped -- a caller that skipped
|
|
30
|
+
* validation sees an obviously-wrong literal instead of a silent empty substitution.
|
|
31
|
+
*/
|
|
32
|
+
export declare function substitutePortPlaceholders(env: Readonly<Record<string, string>> | undefined, resolvedPorts: ReadonlyMap<string, number>): Record<string, string>;
|
|
33
|
+
export type CommandTokenization = {
|
|
34
|
+
readonly ok: true;
|
|
35
|
+
readonly command: string;
|
|
36
|
+
readonly args: readonly string[];
|
|
37
|
+
} | {
|
|
38
|
+
readonly ok: false;
|
|
39
|
+
readonly reason: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* POSIX-style tokenizer for `ServiceConfiguration.command`: single quotes (literal),
|
|
43
|
+
* double quotes (`\` escapes `"` and `\`), backslash escapes outside quotes. Needed
|
|
44
|
+
* because config carries one string (§6) but `spawnProcess` wants `command`/`args`
|
|
45
|
+
* separately, and plain whitespace-split breaks paths with spaces.
|
|
46
|
+
*
|
|
47
|
+
* Refuses rather than guesses on an unterminated quote, trailing backslash, or
|
|
48
|
+
* unquoted shell operator -- a plausible-but-wrong tokenization would disguise a
|
|
49
|
+
* config error as an application failure.
|
|
50
|
+
*
|
|
51
|
+
* Globs and `~` pass through literally, unrefused -- exactly what happens with no
|
|
52
|
+
* shell, and frequently intended literally.
|
|
53
|
+
*/
|
|
54
|
+
export declare function tokenizeCommand(raw: string): CommandTokenization;
|
|
55
|
+
//# sourceMappingURL=orchestration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestration.d.ts","sourceRoot":"","sources":["../src/orchestration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAUvE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC3D;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,GACvD,kBAAkB,CAyDpB;AAkCD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,EACjD,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACzC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxB;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACjF;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAWpD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CA6EhE"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a declarative `services` map (§6, RT-028) into a concrete spawn order for
|
|
3
|
+
* `ExecutionController.run()` (RT-032). Pure and side-effect-free -- validated and
|
|
4
|
+
* ordered before anything is spawned, so a bad reference is refused atomically
|
|
5
|
+
* rather than discovered mid-sequence.
|
|
6
|
+
*
|
|
7
|
+
* Start order and port injection are declared, never inferred (RT-023's "ask, don't
|
|
8
|
+
* guess"): a `${name.port}` reference alone doesn't create an ordering guarantee --
|
|
9
|
+
* `name` must also appear in the referencing service's `dependsOn`, or validation refuses.
|
|
10
|
+
*/
|
|
11
|
+
// Bounded at {1,256}, not `+`: RT-070's regex audit (after RT-069's ReDoS finding in
|
|
12
|
+
// evidence-store/redaction.ts) flagged this exact shape -- unbounded class followed by
|
|
13
|
+
// a literal (`.port}`) that can be absent -- as O(n^2) backtracking on a long
|
|
14
|
+
// unterminated string. Low-risk since `service.env` is developer-authored, not
|
|
15
|
+
// adversarial, but the shape matches a real same-day DoS elsewhere. 256 is far more
|
|
16
|
+
// than any real name needs.
|
|
17
|
+
const PORT_PLACEHOLDER = /\$\{([^}]{1,256})\.port\}/g;
|
|
18
|
+
/**
|
|
19
|
+
* Validates a `services` map and returns a topological spawn order. Checks, all
|
|
20
|
+
* before anything is spawned: every `dependsOn` names a real key; the `dependsOn`
|
|
21
|
+
* graph has no cycle; every `${name}.port` placeholder in a service's `env` has
|
|
22
|
+
* `name` in that service's own `dependsOn`.
|
|
23
|
+
*/
|
|
24
|
+
export function validateServicesConfiguration(services) {
|
|
25
|
+
const names = Object.keys(services);
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
for (const dep of services[name]?.dependsOn ?? []) {
|
|
28
|
+
if (!(dep in services)) {
|
|
29
|
+
return { valid: false, reason: `service "${name}" depends on "${dep}", which is not in services` };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const orderResult = topologicalOrder(services, names);
|
|
34
|
+
if (!orderResult.valid) {
|
|
35
|
+
return orderResult;
|
|
36
|
+
}
|
|
37
|
+
for (const name of names) {
|
|
38
|
+
const declared = new Set(services[name]?.dependsOn ?? []);
|
|
39
|
+
for (const value of Object.values(services[name]?.env ?? {})) {
|
|
40
|
+
for (const match of value.matchAll(PORT_PLACEHOLDER)) {
|
|
41
|
+
const referenced = match[1];
|
|
42
|
+
if (referenced !== undefined && !declared.has(referenced)) {
|
|
43
|
+
return {
|
|
44
|
+
valid: false,
|
|
45
|
+
reason: `service "${name}" references "\${${referenced}.port}" in env but does not declare "${referenced}" in dependsOn`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Exactly one of `command`/`attach` per service, checked before spawning.
|
|
52
|
+
// Neither: nothing tells run() how to start it. Both: ambiguous, don't guess.
|
|
53
|
+
for (const name of names) {
|
|
54
|
+
const service = services[name];
|
|
55
|
+
const hasCommand = service?.command !== undefined;
|
|
56
|
+
const hasAttach = service?.attach !== undefined;
|
|
57
|
+
if (hasCommand === hasAttach) {
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
reason: `service "${name}" must declare exactly one of "command" (spawn) or "attach" (attach to an already-running process), not ${hasCommand ? "both" : "neither"}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// A command that can't be tokenized is a config error -- check before spawning
|
|
65
|
+
// rather than discovering it mid-sequence with earlier services already running.
|
|
66
|
+
for (const name of names) {
|
|
67
|
+
const command = services[name]?.command;
|
|
68
|
+
if (command === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
const tokenized = tokenizeCommand(command);
|
|
71
|
+
if (!tokenized.ok) {
|
|
72
|
+
return { valid: false, reason: `service "${name}": ${tokenized.reason}` };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return orderResult;
|
|
76
|
+
}
|
|
77
|
+
function topologicalOrder(services, names) {
|
|
78
|
+
const order = [];
|
|
79
|
+
const visited = new Set();
|
|
80
|
+
const visiting = new Set();
|
|
81
|
+
function visit(name) {
|
|
82
|
+
if (visited.has(name))
|
|
83
|
+
return null;
|
|
84
|
+
if (visiting.has(name))
|
|
85
|
+
return `cycle detected in dependsOn involving "${name}"`;
|
|
86
|
+
visiting.add(name);
|
|
87
|
+
for (const dep of services[name]?.dependsOn ?? []) {
|
|
88
|
+
const err = visit(dep);
|
|
89
|
+
if (err !== null)
|
|
90
|
+
return err;
|
|
91
|
+
}
|
|
92
|
+
visiting.delete(name);
|
|
93
|
+
visited.add(name);
|
|
94
|
+
order.push(name);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
for (const name of names) {
|
|
98
|
+
const err = visit(name);
|
|
99
|
+
if (err !== null) {
|
|
100
|
+
return { valid: false, reason: err };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { valid: true, order };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Replaces every `${name}.port` in `env`'s values with the resolved port for `name`.
|
|
107
|
+
* Assumes `validateServicesConfiguration` already confirmed the reference. An
|
|
108
|
+
* unresolvable reference is left untouched, not dropped -- a caller that skipped
|
|
109
|
+
* validation sees an obviously-wrong literal instead of a silent empty substitution.
|
|
110
|
+
*/
|
|
111
|
+
export function substitutePortPlaceholders(env, resolvedPorts) {
|
|
112
|
+
const result = {};
|
|
113
|
+
for (const [key, value] of Object.entries(env ?? {})) {
|
|
114
|
+
result[key] = value.replace(PORT_PLACEHOLDER, (literal, name) => {
|
|
115
|
+
const port = resolvedPorts.get(name);
|
|
116
|
+
return port === undefined ? literal : String(port);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Shell operators that only mean anything to a shell. `spawnProcess` uses
|
|
123
|
+
* `child_process.spawn` without one -- no implicit shell, no injection surface. A
|
|
124
|
+
* command containing these was never going to behave as written; refusing beats the
|
|
125
|
+
* old whitespace-split behavior of silently passing `> out.log` as literal args to
|
|
126
|
+
* `node`. `sh -c "..."` still works for anyone who wants a real shell.
|
|
127
|
+
*/
|
|
128
|
+
const SHELL_OPERATORS = new Set(["|", "&", ";", "<", ">", "`", "$"]);
|
|
129
|
+
/**
|
|
130
|
+
* POSIX-style tokenizer for `ServiceConfiguration.command`: single quotes (literal),
|
|
131
|
+
* double quotes (`\` escapes `"` and `\`), backslash escapes outside quotes. Needed
|
|
132
|
+
* because config carries one string (§6) but `spawnProcess` wants `command`/`args`
|
|
133
|
+
* separately, and plain whitespace-split breaks paths with spaces.
|
|
134
|
+
*
|
|
135
|
+
* Refuses rather than guesses on an unterminated quote, trailing backslash, or
|
|
136
|
+
* unquoted shell operator -- a plausible-but-wrong tokenization would disguise a
|
|
137
|
+
* config error as an application failure.
|
|
138
|
+
*
|
|
139
|
+
* Globs and `~` pass through literally, unrefused -- exactly what happens with no
|
|
140
|
+
* shell, and frequently intended literally.
|
|
141
|
+
*/
|
|
142
|
+
export function tokenizeCommand(raw) {
|
|
143
|
+
const tokens = [];
|
|
144
|
+
let current = "";
|
|
145
|
+
let started = false; // distinguishes an empty quoted token ('') from no token
|
|
146
|
+
let quote = null;
|
|
147
|
+
for (let i = 0; i < raw.length; i++) {
|
|
148
|
+
const char = raw[i];
|
|
149
|
+
if (quote === "'") {
|
|
150
|
+
if (char === "'")
|
|
151
|
+
quote = null;
|
|
152
|
+
else
|
|
153
|
+
current += char;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (quote === '"') {
|
|
157
|
+
if (char === "\\") {
|
|
158
|
+
const next = raw[i + 1];
|
|
159
|
+
if (next === undefined)
|
|
160
|
+
return { ok: false, reason: 'command ends with a trailing backslash inside a double-quoted string, which escapes nothing' };
|
|
161
|
+
// Only `"` and `\` are escapable in double quotes; else keep the backslash.
|
|
162
|
+
if (next === '"' || next === "\\") {
|
|
163
|
+
current += next;
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
current += char;
|
|
168
|
+
}
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (char === '"')
|
|
172
|
+
quote = null;
|
|
173
|
+
else
|
|
174
|
+
current += char;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (char === "'" || char === '"') {
|
|
178
|
+
quote = char;
|
|
179
|
+
started = true;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (char === "\\") {
|
|
183
|
+
const next = raw[i + 1];
|
|
184
|
+
if (next === undefined)
|
|
185
|
+
return { ok: false, reason: "command ends with a trailing backslash, which escapes nothing" };
|
|
186
|
+
current += next;
|
|
187
|
+
started = true;
|
|
188
|
+
i++;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (SHELL_OPERATORS.has(char)) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
reason: `command contains the shell operator "${char}" outside quotes, but processes are spawned without a shell — ` +
|
|
195
|
+
`it would be passed to the program as a literal argument rather than interpreted. ` +
|
|
196
|
+
`Use an explicit shell (sh -c "...") if that is what you meant.`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (/\s/.test(char)) {
|
|
200
|
+
if (started) {
|
|
201
|
+
tokens.push(current);
|
|
202
|
+
current = "";
|
|
203
|
+
started = false;
|
|
204
|
+
}
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
current += char;
|
|
208
|
+
started = true;
|
|
209
|
+
}
|
|
210
|
+
if (quote !== null)
|
|
211
|
+
return { ok: false, reason: `command has an unterminated ${quote === '"' ? "double" : "single"} quote` };
|
|
212
|
+
if (started)
|
|
213
|
+
tokens.push(current);
|
|
214
|
+
const [head, ...rest] = tokens;
|
|
215
|
+
if (head === undefined || head === "")
|
|
216
|
+
return { ok: false, reason: "command is empty" };
|
|
217
|
+
return { ok: true, command: head, args: rest };
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=orchestration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestration.js","sourceRoot":"","sources":["../src/orchestration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ready check before a run (RT-263). A broken setup used to be discovered only after a
|
|
3
|
+
* full readiness wait (up to `RunOptions.readiness[*].timeoutMs`, often ~30s-2min) had
|
|
4
|
+
* already failed. This runs a short, read-only pass first — the same declared checks a
|
|
5
|
+
* run is about to depend on, single-shot rather than polled to a full timeout — and
|
|
6
|
+
* reports a plain-words problem and fix before the caller starts anything.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately generic: this module owns the engine (status, timing, disclosure), never
|
|
9
|
+
* a specific check's mechanism it would have to import a language, a browser, a graph
|
|
10
|
+
* store or a database driver to perform. `customCheck` is the seam every one of those
|
|
11
|
+
* uses — `chromiumCanLaunch`, `graphExists`, and any future "database reachable" check
|
|
12
|
+
* are built by the caller that already depends on the thing being checked, and handed in
|
|
13
|
+
* as a probe. Nothing here decides what a broken setup is, only how many of the caller's
|
|
14
|
+
* own answers it takes to call one "ready", "partial" or "broken".
|
|
15
|
+
*
|
|
16
|
+
* **Read-only, always.** Every probe built here (`portFreeCheck`, `portAnswersCheck`,
|
|
17
|
+
* `readinessTargetCheck`) only ever connects or asks; none binds a port, starts a
|
|
18
|
+
* process or writes anything. A `customCheck` probe is the caller's own code and this
|
|
19
|
+
* module cannot enforce that discipline on it, but every caller in this codebase that
|
|
20
|
+
* builds one must hold to it too (Build A step 4).
|
|
21
|
+
*/
|
|
22
|
+
import { type ReadinessCheck } from "./readiness.ts";
|
|
23
|
+
export declare const PREFLIGHT_CHECK_KINDS: readonly ["port", "readiness", "chromium", "graph", "database", "custom"];
|
|
24
|
+
export type PreflightCheckKind = (typeof PREFLIGHT_CHECK_KINDS)[number];
|
|
25
|
+
export declare const PREFLIGHT_STATUSES: readonly ["ready", "partial", "broken"];
|
|
26
|
+
export type PreflightStatus = (typeof PREFLIGHT_STATUSES)[number];
|
|
27
|
+
/**
|
|
28
|
+
* Bounds one check's own probe — not the whole preflight. Every check in a call runs
|
|
29
|
+
* concurrently (`Promise.all`), so a caller with several checks still finishes near this
|
|
30
|
+
* bound rather than their sum. Starting value, not a measurement: it is what lets a
|
|
31
|
+
* `broken` setup report within the lane's 5s target while still giving a slow-but-honest
|
|
32
|
+
* TCP probe (a loaded machine, a container cold-starting) more than an instant to answer.
|
|
33
|
+
*/
|
|
34
|
+
export declare const PREFLIGHT_CHECK_TIMEOUT_MS = 4000;
|
|
35
|
+
export interface PreflightProbeOutcome {
|
|
36
|
+
readonly ok: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Plain words, always — this is what the user sees. When `!ok`, names the exact
|
|
39
|
+
* problem AND the fix ("Nothing is listening on localhost:3000. Is your app
|
|
40
|
+
* running?"), never a bare boolean or a stack trace. Ignored (by convention "ok")
|
|
41
|
+
* when `ok` is true.
|
|
42
|
+
*/
|
|
43
|
+
readonly message: string;
|
|
44
|
+
}
|
|
45
|
+
export interface PreflightCheckSpec {
|
|
46
|
+
readonly kind: PreflightCheckKind;
|
|
47
|
+
/** Named for the report — "port 3000 is free", "chromium can launch". */
|
|
48
|
+
readonly label: string;
|
|
49
|
+
/**
|
|
50
|
+
* A failing required check makes the whole preflight `broken` — this run cannot
|
|
51
|
+
* proceed without it. A failing non-required check makes it `partial` instead: the
|
|
52
|
+
* run continues, and the failure becomes a disclosed gap (what won't be observed),
|
|
53
|
+
* never silently dropped (rule 7).
|
|
54
|
+
*/
|
|
55
|
+
readonly required: boolean;
|
|
56
|
+
readonly probe: () => Promise<PreflightProbeOutcome>;
|
|
57
|
+
}
|
|
58
|
+
export interface PreflightCheckResult {
|
|
59
|
+
readonly kind: PreflightCheckKind;
|
|
60
|
+
readonly label: string;
|
|
61
|
+
readonly required: boolean;
|
|
62
|
+
readonly ok: boolean;
|
|
63
|
+
readonly message: string;
|
|
64
|
+
}
|
|
65
|
+
export interface PreflightResult {
|
|
66
|
+
readonly status: PreflightStatus;
|
|
67
|
+
readonly elapsedMs: number;
|
|
68
|
+
readonly checks: readonly PreflightCheckResult[];
|
|
69
|
+
/**
|
|
70
|
+
* Plain-word problems from every failed non-required check — what this run will
|
|
71
|
+
* proceed without observing. Empty exactly when `status === "ready"`. A `broken`
|
|
72
|
+
* result's problems are read off `checks` instead (every failed required check),
|
|
73
|
+
* because those stop the run rather than being disclosed alongside it.
|
|
74
|
+
*/
|
|
75
|
+
readonly disclosures: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Runs every check concurrently and folds the results into one status. Never throws —
|
|
79
|
+
* a probe that rejects or hangs past its own bound is recorded as a failed check
|
|
80
|
+
* (`runOne` below), not a crash of the preflight itself, because "the check broke" and
|
|
81
|
+
* "the setup is broken" are different facts the caller still needs told apart... except
|
|
82
|
+
* they aren't told apart here: a check that cannot even answer is exactly as blocking as
|
|
83
|
+
* one that answered "no", which is why it is folded in as `!ok` rather than a third
|
|
84
|
+
* outcome. What is preserved is the message, which always names which check and why.
|
|
85
|
+
*/
|
|
86
|
+
export declare function runPreflight(checks: readonly PreflightCheckSpec[]): Promise<PreflightResult>;
|
|
87
|
+
/**
|
|
88
|
+
* Boot precondition: nothing may already be listening at `host:port` before this run
|
|
89
|
+
* spawns its own process there — a stale listener from a dead session would otherwise
|
|
90
|
+
* be mistaken for this run's own (the same RT-024 hazard `readiness.ts` names). `ready`
|
|
91
|
+
* is `!answered` — the inverse polarity of `portAnswersCheck` below, for the attach case.
|
|
92
|
+
*/
|
|
93
|
+
export declare function portFreeCheck(host: string, port: number, label?: string): PreflightCheckSpec;
|
|
94
|
+
/**
|
|
95
|
+
* Attach precondition: the caller is attaching to an app it expects to already be
|
|
96
|
+
* running, so something must already answer at `host:port`. `ready` is `answered` —
|
|
97
|
+
* the inverse polarity of `portFreeCheck` above, for the boot case.
|
|
98
|
+
*/
|
|
99
|
+
export declare function portAnswersCheck(host: string, port: number, label?: string): PreflightCheckSpec;
|
|
100
|
+
/**
|
|
101
|
+
* Wraps a run's own declared `ReadinessCheck`s as a single-shot preflight probe — one
|
|
102
|
+
* pass at `PREFLIGHT_CHECK_TIMEOUT_MS`, never the full poll loop `awaitReadiness` runs
|
|
103
|
+
* once the run actually starts. Reuses `awaitReadiness` itself rather than a second
|
|
104
|
+
* probing mechanism that could drift from what the real readiness wait checks.
|
|
105
|
+
*
|
|
106
|
+
* Required by default: a target this run declared and cannot reach is this run being
|
|
107
|
+
* broken, not a gap it can disclose and continue past. A caller may mark a secondary
|
|
108
|
+
* target `required: false` when the run can genuinely proceed without it.
|
|
109
|
+
*/
|
|
110
|
+
export declare function readinessTargetCheck(label: string, checks: readonly ReadinessCheck[], options?: {
|
|
111
|
+
readonly required?: boolean;
|
|
112
|
+
}): PreflightCheckSpec;
|
|
113
|
+
export declare function databaseReachableCheck(host: string, port: number, label?: string): PreflightCheckSpec;
|
|
114
|
+
/**
|
|
115
|
+
* The seam for anything this package must not name (rule 1's dependency-direction
|
|
116
|
+
* cousin: `@descryy/runtime-controller` has no browser, graph or database dependency,
|
|
117
|
+
* and must not gain one just to phrase a preflight check). A caller that already
|
|
118
|
+
* depends on the thing being checked — `@descryy/runtime-browser` for Chromium,
|
|
119
|
+
* `@descryy/mcp` for the graph — builds the probe and hands it in with this.
|
|
120
|
+
*/
|
|
121
|
+
export declare function customCheck(kind: PreflightCheckKind, label: string, required: boolean, probe: () => Promise<PreflightProbeOutcome>): PreflightCheckSpec;
|
|
122
|
+
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErE,eAAO,MAAM,qBAAqB,2EAA4E,CAAC;AAC/G,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,eAAO,MAAM,kBAAkB,yCAA0C,CAAC;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,OAAQ,CAAC;AAEhD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAUlG;AA+CD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAgB5F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAW/F;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,OAAO,GAAE;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAC5C,kBAAkB,CAUpB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAarG;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,kBAAkB,EACxB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,GAC1C,kBAAkB,CAEpB"}
|