@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.
Files changed (77) hide show
  1. package/dist/attach-discovery.d.ts +94 -0
  2. package/dist/attach-discovery.d.ts.map +1 -0
  3. package/dist/attach-discovery.js +238 -0
  4. package/dist/attach-discovery.js.map +1 -0
  5. package/dist/attach-fencing.d.ts +109 -0
  6. package/dist/attach-fencing.d.ts.map +1 -0
  7. package/dist/attach-fencing.js +215 -0
  8. package/dist/attach-fencing.js.map +1 -0
  9. package/dist/capability-registry.d.ts +9 -0
  10. package/dist/capability-registry.d.ts.map +1 -0
  11. package/dist/capability-registry.js +32 -0
  12. package/dist/capability-registry.js.map +1 -0
  13. package/dist/collector-version.d.ts +3 -0
  14. package/dist/collector-version.d.ts.map +1 -0
  15. package/dist/collector-version.js +5 -0
  16. package/dist/collector-version.js.map +1 -0
  17. package/dist/container-sandbox.d.ts +104 -0
  18. package/dist/container-sandbox.d.ts.map +1 -0
  19. package/dist/container-sandbox.js +152 -0
  20. package/dist/container-sandbox.js.map +1 -0
  21. package/dist/controller.d.ts +152 -0
  22. package/dist/controller.d.ts.map +1 -0
  23. package/dist/controller.js +464 -0
  24. package/dist/controller.js.map +1 -0
  25. package/dist/dependency-version-check.d.ts +36 -0
  26. package/dist/dependency-version-check.d.ts.map +1 -0
  27. package/dist/dependency-version-check.js +72 -0
  28. package/dist/dependency-version-check.js.map +1 -0
  29. package/dist/env.d.ts +10 -0
  30. package/dist/env.d.ts.map +1 -0
  31. package/dist/env.js +12 -0
  32. package/dist/env.js.map +1 -0
  33. package/dist/environment-metadata.d.ts +15 -0
  34. package/dist/environment-metadata.d.ts.map +1 -0
  35. package/dist/environment-metadata.js +63 -0
  36. package/dist/environment-metadata.js.map +1 -0
  37. package/dist/environment-version-check.d.ts +40 -0
  38. package/dist/environment-version-check.d.ts.map +1 -0
  39. package/dist/environment-version-check.js +176 -0
  40. package/dist/environment-version-check.js.map +1 -0
  41. package/dist/execution-safety.d.ts +96 -0
  42. package/dist/execution-safety.d.ts.map +1 -0
  43. package/dist/execution-safety.js +140 -0
  44. package/dist/execution-safety.js.map +1 -0
  45. package/dist/index.d.ts +33 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +18 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/orchestration.d.ts +55 -0
  50. package/dist/orchestration.d.ts.map +1 -0
  51. package/dist/orchestration.js +219 -0
  52. package/dist/orchestration.js.map +1 -0
  53. package/dist/preflight.d.ts +122 -0
  54. package/dist/preflight.d.ts.map +1 -0
  55. package/dist/preflight.js +177 -0
  56. package/dist/preflight.js.map +1 -0
  57. package/dist/process-collector.d.ts +20 -0
  58. package/dist/process-collector.d.ts.map +1 -0
  59. package/dist/process-collector.js +116 -0
  60. package/dist/process-collector.js.map +1 -0
  61. package/dist/process-identity.d.ts +46 -0
  62. package/dist/process-identity.d.ts.map +1 -0
  63. package/dist/process-identity.js +186 -0
  64. package/dist/process-identity.js.map +1 -0
  65. package/dist/process-manager.d.ts +137 -0
  66. package/dist/process-manager.d.ts.map +1 -0
  67. package/dist/process-manager.js +382 -0
  68. package/dist/process-manager.js.map +1 -0
  69. package/dist/readiness.d.ts +122 -0
  70. package/dist/readiness.d.ts.map +1 -0
  71. package/dist/readiness.js +214 -0
  72. package/dist/readiness.js.map +1 -0
  73. package/dist/sandbox.d.ts +94 -0
  74. package/dist/sandbox.d.ts.map +1 -0
  75. package/dist/sandbox.js +228 -0
  76. package/dist/sandbox.js.map +1 -0
  77. package/package.json +2 -2
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Runtime controller (§4/§5/§6/§45 "Core"). Owns Execution lifecycle, spawns every
3
+ * configured service, waits for each one's real readiness (never "process exists"),
4
+ * guarantees cleanup on partial/total failure and timeout. Knows nothing about any
5
+ * specific language or framework (principle 8).
6
+ */
7
+ import { type Execution, type ProcessHandle } from "@descryy/runtime-contracts";
8
+ import type { AttachFence } from "./attach-fencing.ts";
9
+ import { type ProcessOutputReader, type ReadinessCheck, type ReadinessResult } from "./readiness.ts";
10
+ import type { ExecutionConfiguration } from "@descryy/runtime-contracts";
11
+ export interface CreateExecutionInput {
12
+ readonly application: string;
13
+ readonly repository: string;
14
+ readonly commit: string;
15
+ readonly configuration: ExecutionConfiguration;
16
+ }
17
+ export interface ServiceReadinessSpec {
18
+ /**
19
+ * `outputReader` is a real `ProcessOutputReader` bound to THIS spawned (or
20
+ * attached) process's own output — the only way to build a `log-pattern`
21
+ * check that type-checks (`readiness.ts`'s `ProcessOutputReader` brand).
22
+ * RT-024: this closes "log-pattern only carries the stronger this-run
23
+ * guarantee as caller discipline, not enforced by the type" by making the
24
+ * discipline the only path the type allows.
25
+ */
26
+ readonly checks: (info: {
27
+ readonly port: number;
28
+ readonly outputReader: ProcessOutputReader;
29
+ }) => readonly ReadinessCheck[];
30
+ readonly timeoutMs: number;
31
+ }
32
+ export interface RunOptions {
33
+ /** One entry required per key in `configuration.services` -- refuses rather than defaulting a missing one to "spawn success is enough" (RT-024/RT-032). */
34
+ readonly readiness: Readonly<Record<string, ServiceReadinessSpec>>;
35
+ /**
36
+ * Optional live observer for `ProcessLifecycleEvent`, called synchronously at each
37
+ * transition for the lifetime of this execution (including after `run()` returns).
38
+ * A throwing observer is not caught here -- same "must not hang, may not silently
39
+ * swallow its own failure" discipline `Collector` imposes on every callback here.
40
+ */
41
+ readonly onProcessLifecycleEvent?: (event: ProcessLifecycleEvent) => void;
42
+ }
43
+ export interface ServiceStartResult {
44
+ readonly serviceName: string;
45
+ readonly stage: "spawn" | "readiness" | "not-attempted";
46
+ readonly succeeded: boolean;
47
+ /** Set when `stage === "readiness"`. RT-024/RT-031 apply per entry -- N of these is N liveness claims, never a joint identity proof. */
48
+ readonly readiness: ReadinessResult | null;
49
+ /** Set when `stage === "spawn"` and spawning itself threw. */
50
+ readonly error: string | null;
51
+ }
52
+ /**
53
+ * Fires at the exact moments `run()` observes a service's lifecycle -- spawned,
54
+ * readiness settled either way, reaped. A pure notification channel; nothing here
55
+ * decides whether a transition becomes Evidence.
56
+ *
57
+ * Needed because `Execution.processes`/`ServiceStartResults` are only complete once
58
+ * `run()` returns, but a service still running when `run()` resolves hasn't exited
59
+ * yet -- its eventual `process-exited` lies in the future. Only this live channel
60
+ * can observe that exit; reading the final `Execution` later cannot.
61
+ */
62
+ export type ProcessLifecycleEvent = {
63
+ readonly kind: "process-started";
64
+ readonly serviceName: string;
65
+ readonly handle: ProcessHandle;
66
+ } | {
67
+ readonly kind: "process-ready";
68
+ readonly serviceName: string;
69
+ readonly handle: ProcessHandle;
70
+ readonly readiness: ReadinessResult;
71
+ } | {
72
+ readonly kind: "process-ready-failed";
73
+ readonly serviceName: string;
74
+ readonly handle: ProcessHandle;
75
+ readonly readiness: ReadinessResult;
76
+ }
77
+ /**
78
+ * `phase` distinguishes "readiness hadn't yet succeeded when it exited"
79
+ * (`"starting"`) from "had already reached READY" (`"running"`) -- the
80
+ * process-level echo of `FAILED_START` vs `FAILED` (§30). Computed from this
81
+ * service's own readiness history, not `Execution.state` at exit time -- a crash
82
+ * mid-readiness-poll fires before the execution's own state has caught up.
83
+ */
84
+ | {
85
+ readonly kind: "process-exited";
86
+ readonly serviceName: string;
87
+ readonly handle: ProcessHandle;
88
+ readonly phase: "starting" | "running";
89
+ };
90
+ export declare class ExecutionController {
91
+ #private;
92
+ private constructor();
93
+ static create(input: CreateExecutionInput): ExecutionController;
94
+ get execution(): Execution;
95
+ /** One entry per configured service, in spawn order, including never-attempted ones (`stage: "not-attempted"`). Empty until `run()` returns its first result. */
96
+ get serviceStartResults(): readonly ServiceStartResult[];
97
+ /** Non-null only when `run()` refused before spawning anything. */
98
+ get validationError(): string | null;
99
+ /** Combined stdout+stderr of the named service's process so far, or "" if it was never spawned. */
100
+ readProcessOutput(serviceName: string): string;
101
+ /**
102
+ * RT-N3's fence for the named service's process, or `null` for a spawned
103
+ * service (nothing predates observation) or one this controller never
104
+ * started. A caller building a live `ProcessOutputSource` over
105
+ * `readProcessOutput` needs this to mark pre-existing content instead of
106
+ * reporting it as freshly observed — see `attach-fencing.ts`'s own header.
107
+ * Bug found here: `runInstrumentedExecution` (`@descryy/runtime-orchestrator`)
108
+ * built its polling source with no way to reach the fence at all, so an
109
+ * attached service's whole pre-existing backlog — including a crash
110
+ * already in the log before this run ever attached — read as this run's
111
+ * own evidence. This getter is the seam that fix threads through.
112
+ */
113
+ attachFenceFor(serviceName: string): AttachFence | null;
114
+ /**
115
+ * Starts every service in dependency order, spawned (each on its own ephemeral
116
+ * port unless fixed) or attached (`service.attach`, for an already-running
117
+ * pid/log file). Reaches RUNNING only if every service starts and reaches
118
+ * readiness. On first failure, every started service is killed in reverse order
119
+ * (no-op for attached ones) and remaining services are recorded "not-attempted" --
120
+ * `serviceStartResults` always distinguishes "never tried" from "tried and failed".
121
+ *
122
+ * Configuration is validated atomically first -- a bad `dependsOn`, a cycle, an
123
+ * undeclared placeholder, ambiguous `command`/`attach`, or a missing readiness
124
+ * entry refuses before anything spawns.
125
+ *
126
+ * Every **spawned** service gets `PORT` (its resolved port) unconditionally --
127
+ * load-bearing (matches the fixture apps), not incidental: a command reading its
128
+ * port from anywhere else won't be reachable via readiness checks built from the
129
+ * same `port`. An **attached** service gets no env/port allocation -- it's already
130
+ * running on whatever it bound at launch.
131
+ */
132
+ run(options: RunOptions): Promise<Execution>;
133
+ stop(): Promise<Execution>;
134
+ /**
135
+ * Stop everything and record `TIMED_OUT` -- same transition `#onTimeout` makes,
136
+ * for a caller that knows the budget expired before the internal timer noticed.
137
+ *
138
+ * **Why not `stop()`:** the orchestrator clamps its observation window to
139
+ * whatever's left of `timeoutMs`, making `TIMED_OUT` a race between `#onTimeout`
140
+ * and `stop()` at the deadline -- measured: the same case reported `TIMED_OUT` at
141
+ * 1534ms and `COMPLETED` at 1515ms. `execution.state` is what `observe_runtime`
142
+ * reads to choose `ok` vs `timed_out`, so losing that race silently hides a real
143
+ * cutoff. A caller that already decided it truncated the run shouldn't have to
144
+ * win a race to report it.
145
+ *
146
+ * Idempotent and terminal-safe like `cancel()`: if the timer fired first, returns
147
+ * the execution it already finished.
148
+ */
149
+ timeout(): Promise<Execution>;
150
+ cancel(): Promise<Execution>;
151
+ }
152
+ //# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,464 @@
1
+ /**
2
+ * Runtime controller (§4/§5/§6/§45 "Core"). Owns Execution lifecycle, spawns every
3
+ * configured service, waits for each one's real readiness (never "process exists"),
4
+ * guarantees cleanup on partial/total failure and timeout. Knows nothing about any
5
+ * specific language or framework (principle 8).
6
+ */
7
+ import { randomUUID } from "node:crypto";
8
+ import { isTerminalExecutionState, isValidExecutionTransition, } from "@descryy/runtime-contracts";
9
+ import { spawnProcess, attachManagedProcess, allocateEphemeralPort } from "./process-manager.js";
10
+ import { awaitReadiness, createProcessOutputReader } from "./readiness.js";
11
+ import { withDefaultProcessEnv } from "./env.js";
12
+ import { validateServicesConfiguration, substitutePortPlaceholders, tokenizeCommand } from "./orchestration.js";
13
+ import { resourceLimitCapability, networkPolicyCapability } from "./execution-safety.js";
14
+ import { filesystemIsolationCapability, unsupportedNetworkPolicyReason } from "./sandbox.js";
15
+ import { containerFilesystemIsolationCapability, containerNetworkIsolationCapability, unsupportedContainerNetworkPolicyReason, } from "./container-sandbox.js";
16
+ import { captureEnvironmentMetadata } from "./environment-metadata.js";
17
+ /**
18
+ * RT-024: binds every `tcp-port`/`http` check this controller runs to the
19
+ * pid of the process it just spawned or attached to, unless the caller
20
+ * already declared one. Applied automatically so a caller does not have to
21
+ * opt in per check to get the stronger guarantee — see `readiness.ts`'s
22
+ * `identityCheckedConnection` for what happens with it.
23
+ *
24
+ * **Not applied for `sandboxBackend: "container"`.** A published Docker port
25
+ * is commonly fronted by a host-side `docker-proxy` process, not the pid
26
+ * this function would bind to — auto-injecting there would produce a false
27
+ * `"mismatch"` refusal against a perfectly healthy container. Disclosed as
28
+ * `identity: "unverified"` for that backend instead of a wrong verdict.
29
+ */
30
+ function bindChecksToProcessIdentity(checks, pid, sandboxBackend) {
31
+ if (pid === null || sandboxBackend === "container")
32
+ return checks;
33
+ return checks.map((check) => {
34
+ if ((check.kind === "tcp-port" || check.kind === "http") && check.expectedPid === undefined) {
35
+ return { ...check, expectedPid: pid };
36
+ }
37
+ return check;
38
+ });
39
+ }
40
+ // `run()`'s timeout timer and a caller's stop()/cancel() can race to move a
41
+ // finishing execution to a terminal state. Every transition after an `await` uses
42
+ // `#tryTransition` (returns false) rather than the throwing `#transition`, so the
43
+ // losing path degrades to a no-op instead of an unhandled rejection.
44
+ export class ExecutionController {
45
+ #execution;
46
+ #processes = new Map();
47
+ #timeoutHandle = null;
48
+ #serviceStartResults = [];
49
+ #validationError = null;
50
+ constructor(execution) {
51
+ this.#execution = execution;
52
+ }
53
+ static create(input) {
54
+ const execution = {
55
+ executionId: randomUUID(),
56
+ application: input.application,
57
+ repository: input.repository,
58
+ commit: input.commit,
59
+ environmentMetadata: null,
60
+ configuration: input.configuration,
61
+ state: "CREATED",
62
+ startedAt: null,
63
+ endedAt: null,
64
+ processes: [],
65
+ browserSessions: [],
66
+ eventIds: [],
67
+ traceIds: [],
68
+ failureIds: [],
69
+ correlationIds: [],
70
+ findingIds: [],
71
+ };
72
+ return new ExecutionController(execution);
73
+ }
74
+ get execution() {
75
+ return this.#execution;
76
+ }
77
+ /** One entry per configured service, in spawn order, including never-attempted ones (`stage: "not-attempted"`). Empty until `run()` returns its first result. */
78
+ get serviceStartResults() {
79
+ return this.#serviceStartResults;
80
+ }
81
+ /** Non-null only when `run()` refused before spawning anything. */
82
+ get validationError() {
83
+ return this.#validationError;
84
+ }
85
+ /** Combined stdout+stderr of the named service's process so far, or "" if it was never spawned. */
86
+ readProcessOutput(serviceName) {
87
+ return this.#processes.get(serviceName)?.readOutput() ?? "";
88
+ }
89
+ /**
90
+ * RT-N3's fence for the named service's process, or `null` for a spawned
91
+ * service (nothing predates observation) or one this controller never
92
+ * started. A caller building a live `ProcessOutputSource` over
93
+ * `readProcessOutput` needs this to mark pre-existing content instead of
94
+ * reporting it as freshly observed — see `attach-fencing.ts`'s own header.
95
+ * Bug found here: `runInstrumentedExecution` (`@descryy/runtime-orchestrator`)
96
+ * built its polling source with no way to reach the fence at all, so an
97
+ * attached service's whole pre-existing backlog — including a crash
98
+ * already in the log before this run ever attached — read as this run's
99
+ * own evidence. This getter is the seam that fix threads through.
100
+ */
101
+ attachFenceFor(serviceName) {
102
+ return this.#processes.get(serviceName)?.attachFence() ?? null;
103
+ }
104
+ #transition(to) {
105
+ if (!isValidExecutionTransition(this.#execution.state, to)) {
106
+ throw new Error(`invalid execution transition: ${this.#execution.state} -> ${to}`);
107
+ }
108
+ this.#execution = { ...this.#execution, state: to };
109
+ }
110
+ #tryTransition(to) {
111
+ if (!isValidExecutionTransition(this.#execution.state, to)) {
112
+ return false;
113
+ }
114
+ this.#transition(to);
115
+ return true;
116
+ }
117
+ #recordProcesses() {
118
+ const handles = [];
119
+ for (const managed of this.#processes.values()) {
120
+ handles.push(managed.handle());
121
+ }
122
+ this.#execution = { ...this.#execution, processes: handles };
123
+ }
124
+ /**
125
+ * Starts every service in dependency order, spawned (each on its own ephemeral
126
+ * port unless fixed) or attached (`service.attach`, for an already-running
127
+ * pid/log file). Reaches RUNNING only if every service starts and reaches
128
+ * readiness. On first failure, every started service is killed in reverse order
129
+ * (no-op for attached ones) and remaining services are recorded "not-attempted" --
130
+ * `serviceStartResults` always distinguishes "never tried" from "tried and failed".
131
+ *
132
+ * Configuration is validated atomically first -- a bad `dependsOn`, a cycle, an
133
+ * undeclared placeholder, ambiguous `command`/`attach`, or a missing readiness
134
+ * entry refuses before anything spawns.
135
+ *
136
+ * Every **spawned** service gets `PORT` (its resolved port) unconditionally --
137
+ * load-bearing (matches the fixture apps), not incidental: a command reading its
138
+ * port from anywhere else won't be reachable via readiness checks built from the
139
+ * same `port`. An **attached** service gets no env/port allocation -- it's already
140
+ * running on whatever it bound at launch.
141
+ */
142
+ async run(options) {
143
+ this.#transition("STARTING");
144
+ this.#armTimeout();
145
+ const services = this.#execution.configuration.services;
146
+ const validation = validateServicesConfiguration(services);
147
+ if (!validation.valid) {
148
+ return this.#refuseToStart(validation.reason, Object.keys(services));
149
+ }
150
+ // §21/RT-070: refuse atomically before spawning, same discipline as
151
+ // tokenizeCommand -- finding out only when spawnProcess() throws would mean
152
+ // earlier services are already running by the time of refusal.
153
+ const resourceLimits = this.#execution.configuration.resourceLimits;
154
+ if (resourceLimits !== undefined) {
155
+ const capability = resourceLimitCapability();
156
+ if (capability.availability !== "available") {
157
+ return this.#refuseToStart(`resourceLimits was configured but cannot be enforced on this platform: ${capability.reason}`, Object.keys(services));
158
+ }
159
+ }
160
+ // Which real mechanism backs filesystemPolicy/networkPolicy -- absent means "bwrap".
161
+ const sandboxBackend = this.#execution.configuration.sandboxBackend ?? "bwrap";
162
+ // RT-193, same atomic-refusal placement. networkPolicyCapability() is real on
163
+ // Linux (bwrap network namespace), unavailable on macOS/Windows, and only
164
+ // implements full denial even where it exists -- both refused here before
165
+ // spawning. "container" checks the container mechanism's own functions instead.
166
+ const networkPolicy = this.#execution.configuration.networkPolicy;
167
+ if (networkPolicy !== undefined) {
168
+ const shapeReason = sandboxBackend === "container" ? unsupportedContainerNetworkPolicyReason(networkPolicy) : unsupportedNetworkPolicyReason(networkPolicy);
169
+ if (shapeReason !== null) {
170
+ return this.#refuseToStart(`networkPolicy was configured but cannot be enforced: ${shapeReason}`, Object.keys(services));
171
+ }
172
+ const capability = sandboxBackend === "container" ? containerNetworkIsolationCapability() : networkPolicyCapability();
173
+ if (capability.availability !== "available") {
174
+ return this.#refuseToStart(`networkPolicy was configured but cannot be enforced on this platform: ${capability.reason}`, Object.keys(services));
175
+ }
176
+ }
177
+ // Filesystem half, same atomic-refusal placement -- real on Linux via bwrap's
178
+ // mount namespace, refused where it doesn't exist. "container" checks Docker's
179
+ // own capability instead.
180
+ const filesystemPolicy = this.#execution.configuration.filesystemPolicy;
181
+ if (filesystemPolicy !== undefined) {
182
+ const capability = sandboxBackend === "container" ? containerFilesystemIsolationCapability() : filesystemIsolationCapability();
183
+ if (capability.availability !== "available") {
184
+ return this.#refuseToStart(`filesystemPolicy was configured but cannot be enforced on this platform: ${capability.reason}`, Object.keys(services));
185
+ }
186
+ }
187
+ for (const name of validation.order) {
188
+ if (options.readiness[name] === undefined) {
189
+ return this.#refuseToStart(`service "${name}" has no matching RunOptions.readiness entry`, validation.order);
190
+ }
191
+ }
192
+ // RT-193: captured once spawning is committed, from the real host.
193
+ const environmentMetadata = await captureEnvironmentMetadata(services);
194
+ this.#execution = { ...this.#execution, startedAt: new Date().toISOString(), environmentMetadata };
195
+ const resolvedPorts = new Map();
196
+ for (let i = 0; i < validation.order.length; i++) {
197
+ const name = validation.order[i];
198
+ if (name === undefined)
199
+ continue;
200
+ if (isTerminalExecutionState(this.#execution.state)) {
201
+ // A concurrent timeout/stop/cancel already finished this execution.
202
+ return this.#execution;
203
+ }
204
+ const service = services[name];
205
+ if (service === undefined)
206
+ continue;
207
+ const readinessSpec = options.readiness[name];
208
+ if (readinessSpec === undefined)
209
+ continue;
210
+ let managed;
211
+ let port;
212
+ if (service.attach !== undefined) {
213
+ // Attach mode: the target is already running and bound to whatever it
214
+ // bound at launch -- nothing here can allocate a port for it, only record
215
+ // the one the caller already knows. `0` when unsupplied, so a readiness
216
+ // check needing a port fails legibly ("nothing answered") rather than
217
+ // this function inventing an ephemeral port unrelated to the real target.
218
+ // resourceLimits/filesystemPolicy/networkPolicy/sandboxBackend never pass
219
+ // through -- they can't apply to a process this runtime didn't spawn.
220
+ port = service.port ?? 0;
221
+ try {
222
+ managed = attachManagedProcess({
223
+ processId: randomUUID(),
224
+ attach: service.attach,
225
+ serviceName: name,
226
+ ...(service.port === undefined ? {} : { port: service.port }),
227
+ });
228
+ }
229
+ catch (err) {
230
+ this.#serviceStartResults.push({
231
+ serviceName: name,
232
+ stage: "spawn",
233
+ succeeded: false,
234
+ readiness: null,
235
+ error: err instanceof Error ? err.message : String(err),
236
+ });
237
+ this.#markRemainingNotAttempted(validation.order, i + 1);
238
+ this.#tryTransition("FAILED_START");
239
+ await this.#cleanup();
240
+ this.#finish();
241
+ throw err;
242
+ }
243
+ }
244
+ else {
245
+ port = service.port ?? (await allocateEphemeralPort());
246
+ const env = withDefaultProcessEnv({
247
+ PORT: String(port),
248
+ ...substitutePortPlaceholders(service.env, resolvedPorts),
249
+ });
250
+ // validateServicesConfiguration already proved this, so both branches
251
+ // below are unreachable in practice -- kept over a non-null assertion
252
+ // that would crash for real if the two ever drift apart.
253
+ if (service.command === undefined) {
254
+ return this.#refuseToStart(`service "${name}": neither "command" nor "attach" is set`, validation.order);
255
+ }
256
+ const tokenized = tokenizeCommand(service.command);
257
+ if (!tokenized.ok) {
258
+ return this.#refuseToStart(`service "${name}": ${tokenized.reason}`, validation.order);
259
+ }
260
+ const { command, args } = tokenized;
261
+ // Instrumentation flags (`--import <preload>`) go through NODE_OPTIONS, not
262
+ // spliced into the spawned command's own argv. `command` is a raw
263
+ // user-supplied token (`tokenizeCommand`'s output) with no proof it is
264
+ // literally `node` -- it may be a shebang-wrapped CLI ("next", "vite")
265
+ // that the kernel resolves to `node <script>` transparently, in which case
266
+ // anything appended to `args` lands in THAT CLI's own argv, not Node's.
267
+ // Frameworks fronted by a strict argv parser (cac, used by next/vite) then
268
+ // refuse to start. NODE_OPTIONS is consumed by Node itself before the
269
+ // target ever sees `process.argv`, so it works whether `command` is `node`,
270
+ // a shebang script, or anything else -- and appends to (never clobbers) any
271
+ // NODE_OPTIONS the caller already declared, in `env` or inherited from this
272
+ // process's own environment.
273
+ if (service.interpreterArgs !== undefined && service.interpreterArgs.length > 0) {
274
+ const existing = env.NODE_OPTIONS ?? process.env.NODE_OPTIONS;
275
+ const addition = service.interpreterArgs.join(" ");
276
+ env.NODE_OPTIONS = existing === undefined || existing === "" ? addition : `${existing} ${addition}`;
277
+ }
278
+ try {
279
+ managed = spawnProcess({
280
+ processId: randomUUID(),
281
+ command,
282
+ args,
283
+ cwd: service.cwd,
284
+ env,
285
+ serviceName: name,
286
+ port,
287
+ ...(resourceLimits === undefined ? {} : { resourceLimits }),
288
+ ...(filesystemPolicy === undefined ? {} : { filesystemPolicy }),
289
+ ...(networkPolicy === undefined ? {} : { networkPolicy }),
290
+ ...(sandboxBackend === "bwrap" ? {} : { sandboxBackend }),
291
+ });
292
+ }
293
+ catch (err) {
294
+ this.#serviceStartResults.push({
295
+ serviceName: name,
296
+ stage: "spawn",
297
+ succeeded: false,
298
+ readiness: null,
299
+ error: err instanceof Error ? err.message : String(err),
300
+ });
301
+ this.#markRemainingNotAttempted(validation.order, i + 1);
302
+ this.#tryTransition("FAILED_START");
303
+ await this.#cleanup();
304
+ this.#finish();
305
+ throw err;
306
+ }
307
+ }
308
+ this.#processes.set(name, managed);
309
+ resolvedPorts.set(name, port);
310
+ this.#recordProcesses();
311
+ options.onProcessLifecycleEvent?.({ kind: "process-started", serviceName: name, handle: managed.handle() });
312
+ // Read by the exit watcher below -- true only once readiness succeeded, which
313
+ // lets `phase` distinguish "died before confirmed up" from "died after running".
314
+ let reachedReady = false;
315
+ // Fire-and-forget: exit can happen long after run() returns (a later crash, or
316
+ // #cleanup()'s reap) -- must not be awaited inline, which would block every
317
+ // later service in validation.order on this one's exit.
318
+ void managed.waitForExit().then(() => {
319
+ options.onProcessLifecycleEvent?.({
320
+ kind: "process-exited",
321
+ serviceName: name,
322
+ handle: managed.handle(),
323
+ phase: reachedReady ? "running" : "starting",
324
+ });
325
+ });
326
+ const outputReader = createProcessOutputReader(() => managed.readOutput());
327
+ const declaredChecks = readinessSpec.checks({ port, outputReader });
328
+ const identityBoundChecks = bindChecksToProcessIdentity(declaredChecks, managed.handle().pid, sandboxBackend);
329
+ const readinessResult = await awaitReadiness(identityBoundChecks, {
330
+ timeoutMs: readinessSpec.timeoutMs,
331
+ });
332
+ if (readinessResult.ready)
333
+ reachedReady = true;
334
+ this.#serviceStartResults.push({
335
+ serviceName: name,
336
+ stage: "readiness",
337
+ succeeded: readinessResult.ready,
338
+ readiness: readinessResult,
339
+ error: null,
340
+ });
341
+ options.onProcessLifecycleEvent?.({
342
+ kind: readinessResult.ready ? "process-ready" : "process-ready-failed",
343
+ serviceName: name,
344
+ handle: managed.handle(),
345
+ readiness: readinessResult,
346
+ });
347
+ if (isTerminalExecutionState(this.#execution.state)) {
348
+ return this.#execution;
349
+ }
350
+ if (!readinessResult.ready) {
351
+ this.#markRemainingNotAttempted(validation.order, i + 1);
352
+ if (this.#tryTransition("FAILED_START")) {
353
+ await this.#cleanup();
354
+ }
355
+ return this.#finish();
356
+ }
357
+ }
358
+ if (this.#tryTransition("READY")) {
359
+ this.#tryTransition("RUNNING");
360
+ }
361
+ return this.#execution;
362
+ }
363
+ #refuseToStart(reason, allServiceNames) {
364
+ this.#validationError = reason;
365
+ this.#serviceStartResults = allServiceNames.map((serviceName) => ({
366
+ serviceName,
367
+ stage: "not-attempted",
368
+ succeeded: false,
369
+ readiness: null,
370
+ error: null,
371
+ }));
372
+ this.#tryTransition("FAILED_START");
373
+ return this.#finish();
374
+ }
375
+ #markRemainingNotAttempted(order, fromIndex) {
376
+ for (let i = fromIndex; i < order.length; i++) {
377
+ const serviceName = order[i];
378
+ if (serviceName === undefined)
379
+ continue;
380
+ this.#serviceStartResults.push({
381
+ serviceName,
382
+ stage: "not-attempted",
383
+ succeeded: false,
384
+ readiness: null,
385
+ error: null,
386
+ });
387
+ }
388
+ }
389
+ async stop() {
390
+ if (isTerminalExecutionState(this.#execution.state) || this.#execution.state === "STOPPING") {
391
+ return this.#execution;
392
+ }
393
+ if (this.#tryTransition("STOPPING")) {
394
+ await this.#cleanup();
395
+ this.#tryTransition("COMPLETED");
396
+ }
397
+ return this.#finish();
398
+ }
399
+ /**
400
+ * Stop everything and record `TIMED_OUT` -- same transition `#onTimeout` makes,
401
+ * for a caller that knows the budget expired before the internal timer noticed.
402
+ *
403
+ * **Why not `stop()`:** the orchestrator clamps its observation window to
404
+ * whatever's left of `timeoutMs`, making `TIMED_OUT` a race between `#onTimeout`
405
+ * and `stop()` at the deadline -- measured: the same case reported `TIMED_OUT` at
406
+ * 1534ms and `COMPLETED` at 1515ms. `execution.state` is what `observe_runtime`
407
+ * reads to choose `ok` vs `timed_out`, so losing that race silently hides a real
408
+ * cutoff. A caller that already decided it truncated the run shouldn't have to
409
+ * win a race to report it.
410
+ *
411
+ * Idempotent and terminal-safe like `cancel()`: if the timer fired first, returns
412
+ * the execution it already finished.
413
+ */
414
+ async timeout() {
415
+ if (isTerminalExecutionState(this.#execution.state) || this.#execution.state === "STOPPING") {
416
+ return this.#execution;
417
+ }
418
+ await this.#cleanup();
419
+ this.#tryTransition("TIMED_OUT");
420
+ return this.#finish();
421
+ }
422
+ async cancel() {
423
+ if (isTerminalExecutionState(this.#execution.state) || this.#execution.state === "STOPPING") {
424
+ return this.#execution;
425
+ }
426
+ await this.#cleanup();
427
+ this.#tryTransition("CANCELLED");
428
+ return this.#finish();
429
+ }
430
+ #armTimeout() {
431
+ this.#timeoutHandle = setTimeout(() => {
432
+ void this.#onTimeout();
433
+ }, this.#execution.configuration.timeoutMs);
434
+ }
435
+ async #onTimeout() {
436
+ if (isTerminalExecutionState(this.#execution.state)) {
437
+ return;
438
+ }
439
+ await this.#cleanup();
440
+ this.#tryTransition("TIMED_OUT");
441
+ this.#finish();
442
+ }
443
+ /** Kills every spawned service in reverse start order — last up, first down. */
444
+ async #cleanup() {
445
+ if (this.#timeoutHandle !== null) {
446
+ clearTimeout(this.#timeoutHandle);
447
+ this.#timeoutHandle = null;
448
+ }
449
+ const managed = [...this.#processes.values()].reverse();
450
+ for (const proc of managed) {
451
+ await proc.kill();
452
+ }
453
+ if (managed.length > 0) {
454
+ this.#recordProcesses();
455
+ }
456
+ }
457
+ #finish() {
458
+ if (this.#execution.endedAt === null) {
459
+ this.#execution = { ...this.#execution, endedAt: new Date().toISOString() };
460
+ }
461
+ return this.#execution;
462
+ }
463
+ }
464
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}