@descryy/runtime-controller 0.0.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 (61) hide show
  1. package/dist/capability-registry.d.ts +24 -0
  2. package/dist/capability-registry.d.ts.map +1 -0
  3. package/dist/capability-registry.js +42 -0
  4. package/dist/capability-registry.js.map +1 -0
  5. package/dist/collector-version.d.ts +10 -0
  6. package/dist/collector-version.d.ts.map +1 -0
  7. package/dist/collector-version.js +12 -0
  8. package/dist/collector-version.js.map +1 -0
  9. package/dist/container-sandbox.d.ts +188 -0
  10. package/dist/container-sandbox.d.ts.map +1 -0
  11. package/dist/container-sandbox.js +233 -0
  12. package/dist/container-sandbox.js.map +1 -0
  13. package/dist/controller.d.ts +162 -0
  14. package/dist/controller.d.ts.map +1 -0
  15. package/dist/controller.js +433 -0
  16. package/dist/controller.js.map +1 -0
  17. package/dist/dependency-version-check.d.ts +90 -0
  18. package/dist/dependency-version-check.d.ts.map +1 -0
  19. package/dist/dependency-version-check.js +121 -0
  20. package/dist/dependency-version-check.js.map +1 -0
  21. package/dist/env.d.ts +16 -0
  22. package/dist/env.d.ts.map +1 -0
  23. package/dist/env.js +18 -0
  24. package/dist/env.js.map +1 -0
  25. package/dist/environment-metadata.d.ts +23 -0
  26. package/dist/environment-metadata.d.ts.map +1 -0
  27. package/dist/environment-metadata.js +80 -0
  28. package/dist/environment-metadata.js.map +1 -0
  29. package/dist/environment-version-check.d.ts +83 -0
  30. package/dist/environment-version-check.d.ts.map +1 -0
  31. package/dist/environment-version-check.js +218 -0
  32. package/dist/environment-version-check.js.map +1 -0
  33. package/dist/execution-safety.d.ts +154 -0
  34. package/dist/execution-safety.d.ts.map +1 -0
  35. package/dist/execution-safety.js +194 -0
  36. package/dist/execution-safety.js.map +1 -0
  37. package/dist/index.d.ts +35 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +14 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/orchestration.d.ts +70 -0
  42. package/dist/orchestration.d.ts.map +1 -0
  43. package/dist/orchestration.js +255 -0
  44. package/dist/orchestration.js.map +1 -0
  45. package/dist/process-collector.d.ts +34 -0
  46. package/dist/process-collector.d.ts.map +1 -0
  47. package/dist/process-collector.js +148 -0
  48. package/dist/process-collector.js.map +1 -0
  49. package/dist/process-manager.d.ts +133 -0
  50. package/dist/process-manager.d.ts.map +1 -0
  51. package/dist/process-manager.js +333 -0
  52. package/dist/process-manager.js.map +1 -0
  53. package/dist/readiness.d.ts +120 -0
  54. package/dist/readiness.d.ts.map +1 -0
  55. package/dist/readiness.js +179 -0
  56. package/dist/readiness.js.map +1 -0
  57. package/dist/sandbox.d.ts +139 -0
  58. package/dist/sandbox.d.ts.map +1 -0
  59. package/dist/sandbox.js +271 -0
  60. package/dist/sandbox.js.map +1 -0
  61. package/package.json +29 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Runtime controller (plan §4, §5, §6; plan §45's "Core" bucket: runtime
3
+ * controller, execution IDs, process lifecycle, readiness, timeout
4
+ * handling, cleanup, configuration). Owns Execution lifecycle, spawns every
5
+ * service declared in configuration, waits for each one's real readiness
6
+ * (never "process exists"), and guarantees cleanup — including on partial
7
+ * or total failure and on timeout. Builds no collectors and knows nothing
8
+ * about any specific language or framework (plan principle 8).
9
+ */
10
+ import { type Execution, type ProcessHandle } from "@descryy/runtime-contracts";
11
+ import { type ReadinessCheck, type ReadinessResult } from "./readiness.ts";
12
+ import type { ExecutionConfiguration } from "@descryy/runtime-contracts";
13
+ export interface CreateExecutionInput {
14
+ readonly application: string;
15
+ readonly repository: string;
16
+ readonly commit: string;
17
+ readonly configuration: ExecutionConfiguration;
18
+ }
19
+ export interface ServiceReadinessSpec {
20
+ readonly checks: (info: {
21
+ readonly port: number;
22
+ }) => readonly ReadinessCheck[];
23
+ readonly timeoutMs: number;
24
+ }
25
+ export interface RunOptions {
26
+ /** One entry required per key in `configuration.services` — RT-032 refuses rather than default a missing one to "spawn success is enough" (RT-024's own rule, applied per service). */
27
+ readonly readiness: Readonly<Record<string, ServiceReadinessSpec>>;
28
+ /**
29
+ * Optional live observer for `ProcessLifecycleEvent`. Called synchronously
30
+ * at each transition, in order, for the lifetime of this execution
31
+ * (including after `run()` itself has returned, for a service that exits
32
+ * later). A throwing observer is not caught here — the same "must not
33
+ * hang, may not silently swallow its own failure" discipline `Collector`
34
+ * already imposes on every callback shape in this repo; a caller wiring a
35
+ * `ProcessCollector`'s emit path in here that itself throws deserves to
36
+ * see that, not have it disappear into a promise this module owns.
37
+ */
38
+ readonly onProcessLifecycleEvent?: (event: ProcessLifecycleEvent) => void;
39
+ }
40
+ export interface ServiceStartResult {
41
+ readonly serviceName: string;
42
+ readonly stage: "spawn" | "readiness" | "not-attempted";
43
+ readonly succeeded: boolean;
44
+ /** Set when `stage === "readiness"`. RT-024/RT-031 apply to each entry independently — N of these is N liveness claims about N addresses, never a joint identity proof. */
45
+ readonly readiness: ReadinessResult | null;
46
+ /** Set when `stage === "spawn"` and spawning itself threw. */
47
+ readonly error: string | null;
48
+ }
49
+ /**
50
+ * Fires at the exact moments `run()` itself observes a service's process
51
+ * lifecycle — spawned, readiness settled (either way), reaped. This is a
52
+ * pure notification channel: `ExecutionController` stays exactly as
53
+ * ignorant of Evidence/Collector as its own module doc already claims
54
+ * ("knows nothing about any specific language or framework") — nothing
55
+ * here decides whether a lifecycle transition becomes Evidence, only that
56
+ * a caller who wants to know *when* one happened, live, has a way to ask
57
+ * that isn't "reconstruct it later from `Execution.processes`."
58
+ *
59
+ * That reconstruction is precisely what doesn't work for the case this
60
+ * exists to make observable: `Execution.processes` and
61
+ * `ServiceStartResults` are only complete once `run()` has already
62
+ * returned, but a service that is spawned, then *running* (READY reached),
63
+ * has not exited yet at that point — its own eventual `process-exited`
64
+ * event lies in the future relative to `run()` resolving. A consumer that
65
+ * only reads the final `Execution` after `run()` settles can see "started"
66
+ * and "ready" (or "ready-failed") for every service, but can never see
67
+ * "exited" for one still healthy when `run()` returned — only this live
68
+ * channel can.
69
+ */
70
+ export type ProcessLifecycleEvent = {
71
+ readonly kind: "process-started";
72
+ readonly serviceName: string;
73
+ readonly handle: ProcessHandle;
74
+ } | {
75
+ readonly kind: "process-ready";
76
+ readonly serviceName: string;
77
+ readonly handle: ProcessHandle;
78
+ readonly readiness: ReadinessResult;
79
+ } | {
80
+ readonly kind: "process-ready-failed";
81
+ readonly serviceName: string;
82
+ readonly handle: ProcessHandle;
83
+ readonly readiness: ReadinessResult;
84
+ }
85
+ /**
86
+ * `phase` distinguishes "this service's own readiness had not yet
87
+ * succeeded when it exited" (`"starting"`) from "it had already reached
88
+ * READY at least once" (`"running"`) — the process-level echo of
89
+ * `FAILED_START` vs `FAILED` on `ExecutionState` itself (§30). Without
90
+ * it, `PROCESS_EXITED` alone cannot tell "crashed on boot" from
91
+ * "finished/was stopped after running successfully," which collapses
92
+ * exactly the distinction the execution lifecycle already exists to
93
+ * carry. Computed from THIS service's own readiness history, not from
94
+ * `Execution.state` at the moment of exit — a crash mid-readiness-poll
95
+ * fires before `run()` has itself observed the failure and moved the
96
+ * execution to `FAILED_START`, so reading execution state at exit time
97
+ * would misreport `"starting"` as whatever the execution's not-yet-
98
+ * updated state still says.
99
+ */
100
+ | {
101
+ readonly kind: "process-exited";
102
+ readonly serviceName: string;
103
+ readonly handle: ProcessHandle;
104
+ readonly phase: "starting" | "running";
105
+ };
106
+ /**
107
+ * `run()`'s timeout timer and a caller's `stop()`/`cancel()` can both
108
+ * attempt to move a finishing execution to a terminal state at nearly the
109
+ * same instant. Every transition attempted after an `await` goes through
110
+ * `#tryTransition` (returns false instead of throwing) rather than the
111
+ * throwing `#transition`, so whichever path loses the race degrades to a
112
+ * no-op instead of an unhandled rejection from a fire-and-forget timer
113
+ * callback.
114
+ */
115
+ export declare class ExecutionController {
116
+ #private;
117
+ private constructor();
118
+ static create(input: CreateExecutionInput): ExecutionController;
119
+ get execution(): Execution;
120
+ /** One entry per service configured, in spawn order — including services never attempted because an earlier one failed (`stage: "not-attempted"`). Empty before `run()` returns its first result. */
121
+ get serviceStartResults(): readonly ServiceStartResult[];
122
+ /** Non-null only when `run()` refused before spawning anything — a bad `dependsOn` reference, a cycle, an undeclared placeholder dependency, or a service with no matching `RunOptions.readiness` entry. */
123
+ get validationError(): string | null;
124
+ /** Combined stdout+stderr of the named service's process so far, or "" if it was never spawned. */
125
+ readProcessOutput(serviceName: string): string;
126
+ /**
127
+ * Starts every service in `configuration.services`, in the dependency
128
+ * order `validateServicesConfiguration` computes — spawned, each on its
129
+ * own ephemeral port unless it opts into a fixed one, or attached to
130
+ * (`service.attach`, `attachManagedProcess`) when it names an already-
131
+ * running pid/log file instead of a `command`. Reaches RUNNING only if
132
+ * every service starts and reaches its own readiness. On the first
133
+ * failure (spawn/attach throw, or readiness never succeeding), every
134
+ * already-started service is killed in reverse order (a no-op for an
135
+ * attached one — see `attachManagedProcess`'s own doc) and every
136
+ * not-yet-attempted service is recorded as such — a caller can always
137
+ * tell "never tried" from "tried and failed" via `serviceStartResults`.
138
+ *
139
+ * Configuration itself is validated first, atomically: a bad `dependsOn`
140
+ * reference, a cycle, an undeclared placeholder dependency, a service
141
+ * declaring neither or both of `command`/`attach`, or a missing
142
+ * `RunOptions.readiness` entry refuses before anything is spawned, never
143
+ * discovered mid-sequence with other services already running.
144
+ *
145
+ * Every **spawned** service receives `PORT` (its own resolved port,
146
+ * ephemeral or fixed) in its environment unconditionally, on top of
147
+ * `service.env` — a service has no other way to learn the port RT-024
148
+ * chose for it. This is a real, load-bearing convention (matches
149
+ * `broken-invoices`'s fixture apps), not an implementation detail: a
150
+ * `ServiceConfiguration` whose command reads its listen port from
151
+ * anywhere other than `PORT` will not be reachable through readiness
152
+ * checks built from the same `port` this function resolves. An
153
+ * **attached** service receives no env and no port allocation — it is
154
+ * already running on whatever port it bound at its own launch, and
155
+ * `service.port` (when the caller supplies it) is a declaration of that
156
+ * fact, not a request this function can act on.
157
+ */
158
+ run(options: RunOptions): Promise<Execution>;
159
+ stop(): Promise<Execution>;
160
+ cancel(): Promise<Execution>;
161
+ }
162
+ //# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,aAAa,EACnB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAW3F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,sBAAsB,CAAC;CAChD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,cAAc,EAAE,CAAC;IAChF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,uLAAuL;IACvL,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACnE;;;;;;;;;OASG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,eAAe,CAAC;IACxD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,2KAA2K;IAC3K,QAAQ,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAAE,GAClG;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;CAAE,GACrI;IAAE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;CAAE;AAC9I;;;;;;;;;;;;;;GAcG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAA;CAAE,CAAC;AAE9I;;;;;;;;GAQG;AACH,qBAAa,mBAAmB;;IAO9B,OAAO;IAIP,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,mBAAmB;IAsB/D,IAAI,SAAS,IAAI,SAAS,CAEzB;IAED,qMAAqM;IACrM,IAAI,mBAAmB,IAAI,SAAS,kBAAkB,EAAE,CAEvD;IAED,4MAA4M;IAC5M,IAAI,eAAe,IAAI,MAAM,GAAG,IAAI,CAEnC;IAED,mGAAmG;IACnG,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IA2B9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACG,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAwQ5C,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC;IAW1B,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;CA6CnC"}
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Runtime controller (plan §4, §5, §6; plan §45's "Core" bucket: runtime
3
+ * controller, execution IDs, process lifecycle, readiness, timeout
4
+ * handling, cleanup, configuration). Owns Execution lifecycle, spawns every
5
+ * service declared in configuration, waits for each one's real readiness
6
+ * (never "process exists"), and guarantees cleanup — including on partial
7
+ * or total failure and on timeout. Builds no collectors and knows nothing
8
+ * about any specific language or framework (plan principle 8).
9
+ */
10
+ import { randomUUID } from "node:crypto";
11
+ import { isTerminalExecutionState, isValidExecutionTransition, } from "@descryy/runtime-contracts";
12
+ import { spawnProcess, attachManagedProcess, allocateEphemeralPort } from "./process-manager.js";
13
+ import { awaitReadiness } from "./readiness.js";
14
+ import { withDefaultProcessEnv } from "./env.js";
15
+ import { validateServicesConfiguration, substitutePortPlaceholders, tokenizeCommand } from "./orchestration.js";
16
+ import { resourceLimitCapability, networkPolicyCapability } from "./execution-safety.js";
17
+ import { filesystemIsolationCapability, unsupportedNetworkPolicyReason } from "./sandbox.js";
18
+ import { containerFilesystemIsolationCapability, containerNetworkIsolationCapability, unsupportedContainerNetworkPolicyReason, } from "./container-sandbox.js";
19
+ import { captureEnvironmentMetadata } from "./environment-metadata.js";
20
+ /**
21
+ * `run()`'s timeout timer and a caller's `stop()`/`cancel()` can both
22
+ * attempt to move a finishing execution to a terminal state at nearly the
23
+ * same instant. Every transition attempted after an `await` goes through
24
+ * `#tryTransition` (returns false instead of throwing) rather than the
25
+ * throwing `#transition`, so whichever path loses the race degrades to a
26
+ * no-op instead of an unhandled rejection from a fire-and-forget timer
27
+ * callback.
28
+ */
29
+ export class ExecutionController {
30
+ #execution;
31
+ #processes = new Map();
32
+ #timeoutHandle = null;
33
+ #serviceStartResults = [];
34
+ #validationError = null;
35
+ constructor(execution) {
36
+ this.#execution = execution;
37
+ }
38
+ static create(input) {
39
+ const execution = {
40
+ executionId: randomUUID(),
41
+ application: input.application,
42
+ repository: input.repository,
43
+ commit: input.commit,
44
+ environmentMetadata: null,
45
+ configuration: input.configuration,
46
+ state: "CREATED",
47
+ startedAt: null,
48
+ endedAt: null,
49
+ processes: [],
50
+ browserSessions: [],
51
+ eventIds: [],
52
+ traceIds: [],
53
+ failureIds: [],
54
+ correlationIds: [],
55
+ findingIds: [],
56
+ };
57
+ return new ExecutionController(execution);
58
+ }
59
+ get execution() {
60
+ return this.#execution;
61
+ }
62
+ /** One entry per service configured, in spawn order — including services never attempted because an earlier one failed (`stage: "not-attempted"`). Empty before `run()` returns its first result. */
63
+ get serviceStartResults() {
64
+ return this.#serviceStartResults;
65
+ }
66
+ /** Non-null only when `run()` refused before spawning anything — a bad `dependsOn` reference, a cycle, an undeclared placeholder dependency, or a service with no matching `RunOptions.readiness` entry. */
67
+ get validationError() {
68
+ return this.#validationError;
69
+ }
70
+ /** Combined stdout+stderr of the named service's process so far, or "" if it was never spawned. */
71
+ readProcessOutput(serviceName) {
72
+ return this.#processes.get(serviceName)?.readOutput() ?? "";
73
+ }
74
+ #transition(to) {
75
+ if (!isValidExecutionTransition(this.#execution.state, to)) {
76
+ throw new Error(`invalid execution transition: ${this.#execution.state} -> ${to}`);
77
+ }
78
+ this.#execution = { ...this.#execution, state: to };
79
+ }
80
+ #tryTransition(to) {
81
+ if (!isValidExecutionTransition(this.#execution.state, to)) {
82
+ return false;
83
+ }
84
+ this.#transition(to);
85
+ return true;
86
+ }
87
+ #recordProcesses() {
88
+ const handles = [];
89
+ for (const managed of this.#processes.values()) {
90
+ handles.push(managed.handle());
91
+ }
92
+ this.#execution = { ...this.#execution, processes: handles };
93
+ }
94
+ /**
95
+ * Starts every service in `configuration.services`, in the dependency
96
+ * order `validateServicesConfiguration` computes — spawned, each on its
97
+ * own ephemeral port unless it opts into a fixed one, or attached to
98
+ * (`service.attach`, `attachManagedProcess`) when it names an already-
99
+ * running pid/log file instead of a `command`. Reaches RUNNING only if
100
+ * every service starts and reaches its own readiness. On the first
101
+ * failure (spawn/attach throw, or readiness never succeeding), every
102
+ * already-started service is killed in reverse order (a no-op for an
103
+ * attached one — see `attachManagedProcess`'s own doc) and every
104
+ * not-yet-attempted service is recorded as such — a caller can always
105
+ * tell "never tried" from "tried and failed" via `serviceStartResults`.
106
+ *
107
+ * Configuration itself is validated first, atomically: a bad `dependsOn`
108
+ * reference, a cycle, an undeclared placeholder dependency, a service
109
+ * declaring neither or both of `command`/`attach`, or a missing
110
+ * `RunOptions.readiness` entry refuses before anything is spawned, never
111
+ * discovered mid-sequence with other services already running.
112
+ *
113
+ * Every **spawned** service receives `PORT` (its own resolved port,
114
+ * ephemeral or fixed) in its environment unconditionally, on top of
115
+ * `service.env` — a service has no other way to learn the port RT-024
116
+ * chose for it. This is a real, load-bearing convention (matches
117
+ * `broken-invoices`'s fixture apps), not an implementation detail: a
118
+ * `ServiceConfiguration` whose command reads its listen port from
119
+ * anywhere other than `PORT` will not be reachable through readiness
120
+ * checks built from the same `port` this function resolves. An
121
+ * **attached** service receives no env and no port allocation — it is
122
+ * already running on whatever port it bound at its own launch, and
123
+ * `service.port` (when the caller supplies it) is a declaration of that
124
+ * fact, not a request this function can act on.
125
+ */
126
+ async run(options) {
127
+ this.#transition("STARTING");
128
+ this.#armTimeout();
129
+ const services = this.#execution.configuration.services;
130
+ const validation = validateServicesConfiguration(services);
131
+ if (!validation.valid) {
132
+ return this.#refuseToStart(validation.reason, Object.keys(services));
133
+ }
134
+ // §21's execution boundary (RT-070): the same "refuse atomically before
135
+ // anything is spawned" discipline `tokenizeCommand` already gets, one
136
+ // check up. Requesting a limit this platform cannot enforce and finding
137
+ // out only when the first `spawnProcess()` throws would mean earlier
138
+ // services in `validation.order` are already running by the time the
139
+ // refusal happens -- recoverable via the existing cleanup path, but
140
+ // needlessly, since nothing about this check needs a process to exist.
141
+ const resourceLimits = this.#execution.configuration.resourceLimits;
142
+ if (resourceLimits !== undefined) {
143
+ const capability = resourceLimitCapability();
144
+ if (capability.availability !== "available") {
145
+ return this.#refuseToStart(`resourceLimits was configured but cannot be enforced on this platform: ${capability.reason}`, Object.keys(services));
146
+ }
147
+ }
148
+ // Which real mechanism backs filesystemPolicy/networkPolicy for this
149
+ // execution -- absent means "bwrap", exactly today's behavior. See
150
+ // ExecutionConfiguration.sandboxBackend's own comment.
151
+ const sandboxBackend = this.#execution.configuration.sandboxBackend ?? "bwrap";
152
+ // RT-193, same atomic-refusal placement as resourceLimits above.
153
+ // `networkPolicyCapability()` is real on Linux now (sandbox-isolation
154
+ // lane: a bwrap network namespace) but still `unavailable` on
155
+ // macOS/Windows, and even where the mechanism exists it only
156
+ // implements one shape (full denial) -- both refusals happen here,
157
+ // atomically, before anything is spawned, never silently accepted and
158
+ // left unenforced. `sandboxBackend: "container"` checks the container
159
+ // mechanism's own capability/shape functions instead -- same refusal
160
+ // discipline, different real mechanism underneath.
161
+ const networkPolicy = this.#execution.configuration.networkPolicy;
162
+ if (networkPolicy !== undefined) {
163
+ const shapeReason = sandboxBackend === "container" ? unsupportedContainerNetworkPolicyReason(networkPolicy) : unsupportedNetworkPolicyReason(networkPolicy);
164
+ if (shapeReason !== null) {
165
+ return this.#refuseToStart(`networkPolicy was configured but cannot be enforced: ${shapeReason}`, Object.keys(services));
166
+ }
167
+ const capability = sandboxBackend === "container" ? containerNetworkIsolationCapability() : networkPolicyCapability();
168
+ if (capability.availability !== "available") {
169
+ return this.#refuseToStart(`networkPolicy was configured but cannot be enforced on this platform: ${capability.reason}`, Object.keys(services));
170
+ }
171
+ }
172
+ // Sandbox-isolation lane, filesystem half: same atomic-refusal
173
+ // placement and precedent -- real on Linux via `applySandbox`'s bwrap
174
+ // mount namespace, refused rather than silently unconstrained where
175
+ // that mechanism doesn't exist. `sandboxBackend: "container"` checks
176
+ // Docker's own capability instead.
177
+ const filesystemPolicy = this.#execution.configuration.filesystemPolicy;
178
+ if (filesystemPolicy !== undefined) {
179
+ const capability = sandboxBackend === "container" ? containerFilesystemIsolationCapability() : filesystemIsolationCapability();
180
+ if (capability.availability !== "available") {
181
+ return this.#refuseToStart(`filesystemPolicy was configured but cannot be enforced on this platform: ${capability.reason}`, Object.keys(services));
182
+ }
183
+ }
184
+ for (const name of validation.order) {
185
+ if (options.readiness[name] === undefined) {
186
+ return this.#refuseToStart(`service "${name}" has no matching RunOptions.readiness entry`, validation.order);
187
+ }
188
+ }
189
+ // RT-193: captured once real spawning is committed to happen, from the
190
+ // real host -- see `EnvironmentMetadata`'s own comment for why `commit`
191
+ // alone does not make a run reproducible.
192
+ const environmentMetadata = await captureEnvironmentMetadata(services);
193
+ this.#execution = { ...this.#execution, startedAt: new Date().toISOString(), environmentMetadata };
194
+ const resolvedPorts = new Map();
195
+ for (let i = 0; i < validation.order.length; i++) {
196
+ const name = validation.order[i];
197
+ if (name === undefined)
198
+ continue;
199
+ if (isTerminalExecutionState(this.#execution.state)) {
200
+ // A concurrent timeout/stop/cancel already finished this execution
201
+ // while we were mid-sequence.
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 (RT-attach-mode-service-configuration): the target is
214
+ // already running and already bound to whatever it bound at its own
215
+ // launch -- nothing here can allocate a port on its behalf, only
216
+ // record the one the caller already knows about. `0` (never a real
217
+ // listening port) when they did not supply one, so a readiness
218
+ // check that actually needs a port fails legibly against it
219
+ // ("nothing answered") instead of this function inventing an
220
+ // ephemeral port that has nothing to do with the real target.
221
+ // `resourceLimits`/`filesystemPolicy`/`networkPolicy`/`sandboxBackend`
222
+ // are deliberately never passed through — see `ServiceConfiguration.attach`'s
223
+ // own comment on why they cannot apply to a process this runtime
224
+ // did not spawn.
225
+ port = service.port ?? 0;
226
+ try {
227
+ managed = attachManagedProcess({
228
+ processId: randomUUID(),
229
+ attach: service.attach,
230
+ serviceName: name,
231
+ ...(service.port === undefined ? {} : { port: service.port }),
232
+ });
233
+ }
234
+ catch (err) {
235
+ this.#serviceStartResults.push({
236
+ serviceName: name,
237
+ stage: "spawn",
238
+ succeeded: false,
239
+ readiness: null,
240
+ error: err instanceof Error ? err.message : String(err),
241
+ });
242
+ this.#markRemainingNotAttempted(validation.order, i + 1);
243
+ this.#tryTransition("FAILED_START");
244
+ await this.#cleanup();
245
+ this.#finish();
246
+ throw err;
247
+ }
248
+ }
249
+ else {
250
+ port = service.port ?? (await allocateEphemeralPort());
251
+ const env = withDefaultProcessEnv({
252
+ PORT: String(port),
253
+ ...substitutePortPlaceholders(service.env, resolvedPorts),
254
+ });
255
+ // `validateServicesConfiguration` already proved every service
256
+ // declares exactly one of `command`/`attach` and, when it's
257
+ // `command`, that it tokenizes -- both branches below are
258
+ // unreachable in practice, kept because the alternative is a
259
+ // non-null assertion that would become a real crash if the two
260
+ // ever drift apart.
261
+ if (service.command === undefined) {
262
+ return this.#refuseToStart(`service "${name}": neither "command" nor "attach" is set`, validation.order);
263
+ }
264
+ const tokenized = tokenizeCommand(service.command);
265
+ if (!tokenized.ok) {
266
+ return this.#refuseToStart(`service "${name}": ${tokenized.reason}`, validation.order);
267
+ }
268
+ const { command, args } = tokenized;
269
+ try {
270
+ managed = spawnProcess({
271
+ processId: randomUUID(),
272
+ command,
273
+ args,
274
+ cwd: service.cwd,
275
+ env,
276
+ serviceName: name,
277
+ port,
278
+ ...(resourceLimits === undefined ? {} : { resourceLimits }),
279
+ ...(filesystemPolicy === undefined ? {} : { filesystemPolicy }),
280
+ ...(networkPolicy === undefined ? {} : { networkPolicy }),
281
+ ...(sandboxBackend === "bwrap" ? {} : { sandboxBackend }),
282
+ });
283
+ }
284
+ catch (err) {
285
+ this.#serviceStartResults.push({
286
+ serviceName: name,
287
+ stage: "spawn",
288
+ succeeded: false,
289
+ readiness: null,
290
+ error: err instanceof Error ? err.message : String(err),
291
+ });
292
+ this.#markRemainingNotAttempted(validation.order, i + 1);
293
+ this.#tryTransition("FAILED_START");
294
+ await this.#cleanup();
295
+ this.#finish();
296
+ throw err;
297
+ }
298
+ }
299
+ this.#processes.set(name, managed);
300
+ resolvedPorts.set(name, port);
301
+ this.#recordProcesses();
302
+ options.onProcessLifecycleEvent?.({ kind: "process-started", serviceName: name, handle: managed.handle() });
303
+ // Read by the exit watcher below, whenever it actually fires — true
304
+ // only once THIS service's own readiness has succeeded, which is
305
+ // what lets `phase` distinguish "died before ever confirmed up" from
306
+ // "died after running" regardless of how much later exit occurs.
307
+ let reachedReady = false;
308
+ // Fire-and-forget: exit can happen at any point in this process's
309
+ // life, including long after `run()` itself has returned (a healthy
310
+ // service that later crashes, or the eventual `#cleanup()` reap) —
311
+ // it must not be awaited inline here, which would block every
312
+ // service after it in `validation.order` on this one's exit.
313
+ void managed.waitForExit().then(() => {
314
+ options.onProcessLifecycleEvent?.({
315
+ kind: "process-exited",
316
+ serviceName: name,
317
+ handle: managed.handle(),
318
+ phase: reachedReady ? "running" : "starting",
319
+ });
320
+ });
321
+ const readinessResult = await awaitReadiness(readinessSpec.checks({ port }), {
322
+ timeoutMs: readinessSpec.timeoutMs,
323
+ });
324
+ if (readinessResult.ready)
325
+ reachedReady = true;
326
+ this.#serviceStartResults.push({
327
+ serviceName: name,
328
+ stage: "readiness",
329
+ succeeded: readinessResult.ready,
330
+ readiness: readinessResult,
331
+ error: null,
332
+ });
333
+ options.onProcessLifecycleEvent?.({
334
+ kind: readinessResult.ready ? "process-ready" : "process-ready-failed",
335
+ serviceName: name,
336
+ handle: managed.handle(),
337
+ readiness: readinessResult,
338
+ });
339
+ if (isTerminalExecutionState(this.#execution.state)) {
340
+ return this.#execution;
341
+ }
342
+ if (!readinessResult.ready) {
343
+ this.#markRemainingNotAttempted(validation.order, i + 1);
344
+ if (this.#tryTransition("FAILED_START")) {
345
+ await this.#cleanup();
346
+ }
347
+ return this.#finish();
348
+ }
349
+ }
350
+ if (this.#tryTransition("READY")) {
351
+ this.#tryTransition("RUNNING");
352
+ }
353
+ return this.#execution;
354
+ }
355
+ #refuseToStart(reason, allServiceNames) {
356
+ this.#validationError = reason;
357
+ this.#serviceStartResults = allServiceNames.map((serviceName) => ({
358
+ serviceName,
359
+ stage: "not-attempted",
360
+ succeeded: false,
361
+ readiness: null,
362
+ error: null,
363
+ }));
364
+ this.#tryTransition("FAILED_START");
365
+ return this.#finish();
366
+ }
367
+ #markRemainingNotAttempted(order, fromIndex) {
368
+ for (let i = fromIndex; i < order.length; i++) {
369
+ const serviceName = order[i];
370
+ if (serviceName === undefined)
371
+ continue;
372
+ this.#serviceStartResults.push({
373
+ serviceName,
374
+ stage: "not-attempted",
375
+ succeeded: false,
376
+ readiness: null,
377
+ error: null,
378
+ });
379
+ }
380
+ }
381
+ async stop() {
382
+ if (isTerminalExecutionState(this.#execution.state) || this.#execution.state === "STOPPING") {
383
+ return this.#execution;
384
+ }
385
+ if (this.#tryTransition("STOPPING")) {
386
+ await this.#cleanup();
387
+ this.#tryTransition("COMPLETED");
388
+ }
389
+ return this.#finish();
390
+ }
391
+ async cancel() {
392
+ if (isTerminalExecutionState(this.#execution.state) || this.#execution.state === "STOPPING") {
393
+ return this.#execution;
394
+ }
395
+ await this.#cleanup();
396
+ this.#tryTransition("CANCELLED");
397
+ return this.#finish();
398
+ }
399
+ #armTimeout() {
400
+ this.#timeoutHandle = setTimeout(() => {
401
+ void this.#onTimeout();
402
+ }, this.#execution.configuration.timeoutMs);
403
+ }
404
+ async #onTimeout() {
405
+ if (isTerminalExecutionState(this.#execution.state)) {
406
+ return;
407
+ }
408
+ await this.#cleanup();
409
+ this.#tryTransition("TIMED_OUT");
410
+ this.#finish();
411
+ }
412
+ /** Kills every spawned service in reverse start order — last up, first down. */
413
+ async #cleanup() {
414
+ if (this.#timeoutHandle !== null) {
415
+ clearTimeout(this.#timeoutHandle);
416
+ this.#timeoutHandle = null;
417
+ }
418
+ const managed = [...this.#processes.values()].reverse();
419
+ for (const proc of managed) {
420
+ await proc.kill();
421
+ }
422
+ if (managed.length > 0) {
423
+ this.#recordProcesses();
424
+ }
425
+ }
426
+ #finish() {
427
+ if (this.#execution.endedAt === null) {
428
+ this.#execution = { ...this.#execution, endedAt: new Date().toISOString() };
429
+ }
430
+ return this.#execution;
431
+ }
432
+ }
433
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}