@theford/runly 0.0.0-stage → 0.20.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 (128) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +157 -2
  3. package/dist/cli/client.d.ts +80 -0
  4. package/dist/cli/client.js +294 -0
  5. package/dist/cli/client.js.map +1 -0
  6. package/dist/cli/daemon-unit.d.ts +58 -0
  7. package/dist/cli/daemon-unit.js +289 -0
  8. package/dist/cli/daemon-unit.js.map +1 -0
  9. package/dist/cli/index.d.ts +2 -0
  10. package/dist/cli/index.js +1482 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/cli/install.d.ts +9 -0
  13. package/dist/cli/install.js +53 -0
  14. package/dist/cli/install.js.map +1 -0
  15. package/dist/cli/logs.d.ts +43 -0
  16. package/dist/cli/logs.js +232 -0
  17. package/dist/cli/logs.js.map +1 -0
  18. package/dist/cli/tether.d.ts +3 -0
  19. package/dist/cli/tether.js +65 -0
  20. package/dist/cli/tether.js.map +1 -0
  21. package/dist/cli/watchdog.d.ts +1 -0
  22. package/dist/cli/watchdog.js +81 -0
  23. package/dist/cli/watchdog.js.map +1 -0
  24. package/dist/core/builds.d.ts +20 -0
  25. package/dist/core/builds.js +93 -0
  26. package/dist/core/builds.js.map +1 -0
  27. package/dist/core/caller-env.d.ts +17 -0
  28. package/dist/core/caller-env.js +77 -0
  29. package/dist/core/caller-env.js.map +1 -0
  30. package/dist/core/diagnostics.d.ts +68 -0
  31. package/dist/core/diagnostics.js +34 -0
  32. package/dist/core/diagnostics.js.map +1 -0
  33. package/dist/core/env-vars.d.ts +35 -0
  34. package/dist/core/env-vars.js +32 -0
  35. package/dist/core/env-vars.js.map +1 -0
  36. package/dist/core/events.d.ts +10 -0
  37. package/dist/core/events.js +42 -0
  38. package/dist/core/events.js.map +1 -0
  39. package/dist/core/exec.d.ts +22 -0
  40. package/dist/core/exec.js +84 -0
  41. package/dist/core/exec.js.map +1 -0
  42. package/dist/core/journal.d.ts +227 -0
  43. package/dist/core/journal.js +395 -0
  44. package/dist/core/journal.js.map +1 -0
  45. package/dist/core/logs.d.ts +56 -0
  46. package/dist/core/logs.js +259 -0
  47. package/dist/core/logs.js.map +1 -0
  48. package/dist/core/manifest.d.ts +292 -0
  49. package/dist/core/manifest.js +179 -0
  50. package/dist/core/manifest.js.map +1 -0
  51. package/dist/core/namespaces.d.ts +7 -0
  52. package/dist/core/namespaces.js +59 -0
  53. package/dist/core/namespaces.js.map +1 -0
  54. package/dist/core/paths.d.ts +31 -0
  55. package/dist/core/paths.js +65 -0
  56. package/dist/core/paths.js.map +1 -0
  57. package/dist/core/policy.d.ts +94 -0
  58. package/dist/core/policy.js +122 -0
  59. package/dist/core/policy.js.map +1 -0
  60. package/dist/core/ports.d.ts +52 -0
  61. package/dist/core/ports.js +94 -0
  62. package/dist/core/ports.js.map +1 -0
  63. package/dist/core/presets.d.ts +25 -0
  64. package/dist/core/presets.js +86 -0
  65. package/dist/core/presets.js.map +1 -0
  66. package/dist/core/procscan.d.ts +111 -0
  67. package/dist/core/procscan.js +342 -0
  68. package/dist/core/procscan.js.map +1 -0
  69. package/dist/core/retention.d.ts +97 -0
  70. package/dist/core/retention.js +322 -0
  71. package/dist/core/retention.js.map +1 -0
  72. package/dist/core/sleep.d.ts +51 -0
  73. package/dist/core/sleep.js +99 -0
  74. package/dist/core/sleep.js.map +1 -0
  75. package/dist/core/tree-ledger.d.ts +39 -0
  76. package/dist/core/tree-ledger.js +92 -0
  77. package/dist/core/tree-ledger.js.map +1 -0
  78. package/dist/core/types.d.ts +21 -0
  79. package/dist/core/types.js +6 -0
  80. package/dist/core/types.js.map +1 -0
  81. package/dist/core/units.d.ts +20 -0
  82. package/dist/core/units.js +69 -0
  83. package/dist/core/units.js.map +1 -0
  84. package/dist/core/upkeep.d.ts +90 -0
  85. package/dist/core/upkeep.js +176 -0
  86. package/dist/core/upkeep.js.map +1 -0
  87. package/dist/core/util.d.ts +46 -0
  88. package/dist/core/util.js +168 -0
  89. package/dist/core/util.js.map +1 -0
  90. package/dist/core/version.d.ts +39 -0
  91. package/dist/core/version.js +144 -0
  92. package/dist/core/version.js.map +1 -0
  93. package/dist/core/worktree.d.ts +51 -0
  94. package/dist/core/worktree.js +309 -0
  95. package/dist/core/worktree.js.map +1 -0
  96. package/dist/daemon/budget.d.ts +155 -0
  97. package/dist/daemon/budget.js +288 -0
  98. package/dist/daemon/budget.js.map +1 -0
  99. package/dist/daemon/election.d.ts +9 -0
  100. package/dist/daemon/election.js +140 -0
  101. package/dist/daemon/election.js.map +1 -0
  102. package/dist/daemon/engine.d.ts +1703 -0
  103. package/dist/daemon/engine.js +5305 -0
  104. package/dist/daemon/engine.js.map +1 -0
  105. package/dist/daemon/index.d.ts +2 -0
  106. package/dist/daemon/index.js +412 -0
  107. package/dist/daemon/index.js.map +1 -0
  108. package/dist/daemon/proxy.d.ts +108 -0
  109. package/dist/daemon/proxy.js +615 -0
  110. package/dist/daemon/proxy.js.map +1 -0
  111. package/dist/daemon/supervisor.d.ts +101 -0
  112. package/dist/daemon/supervisor.js +493 -0
  113. package/dist/daemon/supervisor.js.map +1 -0
  114. package/dist/drivers/appliances.d.ts +11 -0
  115. package/dist/drivers/appliances.js +174 -0
  116. package/dist/drivers/appliances.js.map +1 -0
  117. package/dist/drivers/datastores.d.ts +144 -0
  118. package/dist/drivers/datastores.js +775 -0
  119. package/dist/drivers/datastores.js.map +1 -0
  120. package/dist/drivers/preview.d.ts +69 -0
  121. package/dist/drivers/preview.js +605 -0
  122. package/dist/drivers/preview.js.map +1 -0
  123. package/dist/drivers/types.d.ts +70 -0
  124. package/dist/drivers/types.js +13 -0
  125. package/dist/drivers/types.js.map +1 -0
  126. package/package.json +63 -4
  127. package/schema/backlot.schema.json +549 -0
  128. package/schema/runly.schema.json +556 -0
@@ -0,0 +1,101 @@
1
+ import type { ServiceSpec } from '../core/manifest.js';
2
+ import type { ServicePid } from '../core/types.js';
3
+ export interface ServiceEvent {
4
+ at: number;
5
+ service: string;
6
+ event: string;
7
+ }
8
+ /** How a service that runly gave up on last ended (decision 0039). */
9
+ export interface ServiceExit {
10
+ /** Exit code of the last process, null when a signal ended it or it never spawned. */
11
+ code: number | null;
12
+ signal: string | null;
13
+ /** `crash-loop`, `daemonized`, `spawn-failed`, or `boot-failed` (it never became ready in a bind, 0.18.1). */
14
+ reason: 'crash-loop' | 'daemonized' | 'spawn-failed' | 'boot-failed';
15
+ /** For `boot-failed`: what the readiness check said. */
16
+ detail?: string;
17
+ }
18
+ /** What the engine learns about a service between starts (decision 0035). */
19
+ export interface SupervisorHooks {
20
+ /** It exited on its own and a restart is pending: hold its port's connections. */
21
+ onCrashed?: (service: string) => void;
22
+ /** The pending restart launched a new process: probe it and forward again. */
23
+ onRelaunched?: (service: string) => void;
24
+ /** It gave up restarting (flapping, or a daemonized service). */
25
+ onGaveUp?: (service: string) => void;
26
+ }
27
+ export declare class EnvSupervisor {
28
+ readonly envId: string;
29
+ /** The caller's worktree — services run in place there (decision 0032). */
30
+ private readonly root;
31
+ private readonly logDir;
32
+ /** Fired when a service flaps past its restart budget (decision 0007/0010), with how it last ended. */
33
+ private readonly onDegraded?;
34
+ /** Fired whenever a service's pid changes (start/restart) so the journal
35
+ * stays truthful for recovery — a stale pid gets an innocent SIGTERM and
36
+ * misses the real orphan holding the port. */
37
+ private readonly onPidsChanged?;
38
+ /** Fired after a service was stopped on purpose — the proxy stops forwarding to it (decision 0033). */
39
+ private readonly onStopped?;
40
+ private readonly hooks;
41
+ private services;
42
+ readonly events: ServiceEvent[];
43
+ constructor(envId: string,
44
+ /** The caller's worktree — services run in place there (decision 0032). */
45
+ root: string, logDir: string,
46
+ /** Fired when a service flaps past its restart budget (decision 0007/0010), with how it last ended. */
47
+ onDegraded?: ((service: string, exit: ServiceExit) => void) | undefined,
48
+ /** Fired whenever a service's pid changes (start/restart) so the journal
49
+ * stays truthful for recovery — a stale pid gets an innocent SIGTERM and
50
+ * misses the real orphan holding the port. */
51
+ onPidsChanged?: (() => void) | undefined,
52
+ /** Fired after a service was stopped on purpose — the proxy stops forwarding to it (decision 0033). */
53
+ onStopped?: ((service: string) => void) | undefined, hooks?: SupervisorHooks);
54
+ private note;
55
+ logPath(name: string): string;
56
+ logs(name: string): string;
57
+ pids(): Record<string, ServicePid>;
58
+ /** The exit code of a service's last process; null while it runs, or when a signal ended it. */
59
+ exitCodeOf(name: string): number | null;
60
+ allHealthyPids(): boolean;
61
+ start(name: string, spec: ServiceSpec, env: NodeJS.ProcessEnv, secrets?: string[]): void;
62
+ waitReady(name: string, spec: ServiceSpec, url: string | undefined, env: NodeJS.ProcessEnv): Promise<void>;
63
+ /** Stop every service; returns any that refused to die (empty is the norm). */
64
+ stopAll(): Promise<Record<string, ServicePid>>;
65
+ /**
66
+ * Stop just these services (an `up` restarting only the ones whose build
67
+ * output changed, decision 0032); the rest keep running. Same contract as
68
+ * stopAll: survivors are returned so the caller can reap and record them.
69
+ */
70
+ stopSome(names: string[]): Promise<Record<string, ServicePid>>;
71
+ }
72
+ /**
73
+ * SIGTERM a group, wait for it to actually die, then SIGKILL and confirm.
74
+ *
75
+ * The previous recovery path fired one SIGTERM and moved on, which anything
76
+ * with a graceful-shutdown handler (a .NET host, `ng serve` under a trapping
77
+ * shell) simply outlives — and since the caller then dropped the pid, the
78
+ * survivor became unreachable forever. Escalation plus a verified verdict is
79
+ * what makes "reaped" mean reaped.
80
+ *
81
+ * Returns true only when the observed and supplied recorded groups are empty.
82
+ * `observeGroup` hands the current group to reapPids before signalling so its
83
+ * ownership survives even if the recorded process exits during reclamation.
84
+ */
85
+ export declare function killGroupVerified(pid: number, recordedStart?: number, graceMs?: number, recordedGroup?: number, observeGroup?: (pgid: number) => void): Promise<boolean>;
86
+ /**
87
+ * SIGTERM one process, then SIGKILL it after `graceMs` — never its group, and
88
+ * only while it is still the process recorded with `recordedStart`. For a
89
+ * process that shares a group with something runly does not own (`runly db
90
+ * with`'s command at a terminal, decision 0039). True once it is gone.
91
+ */
92
+ export declare function killPidVerified(pid: number, recordedStart?: number, graceMs?: number): Promise<boolean>;
93
+ /**
94
+ * Kill PIDs recorded by a previous daemon life (recovery, decision 0009).
95
+ *
96
+ * Returns the entries that were NOT confirmed dead. The caller must keep those
97
+ * in the journal — a forgotten pid is an orphan nobody can ever reclaim.
98
+ */
99
+ export declare function reapPids(pids: Record<string, ServicePid>, kill?: typeof killGroupVerified): Promise<Record<string, ServicePid>>;
100
+ export declare function serviceGroups(rec: ServicePid): number[];
101
+ export declare function mergeServicePids(...sets: Array<Record<string, ServicePid> | undefined>): Record<string, ServicePid>;
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Daemon-as-parent service supervision (decision 0009/0010): spawn with env
3
+ * injection, capture logs, probe readiness (http/log/cmd) with fatal-log
4
+ * fast-fail, restart session services with bounded backoff, and never let a
5
+ * crash pass silently.
6
+ */
7
+ import { spawn } from 'node:child_process';
8
+ import { StringDecoder } from 'node:string_decoder';
9
+ import { mkdirSync } from 'node:fs';
10
+ import { BrokerError, environmentHint, now, safeJoin } from '../core/util.js';
11
+ import { runBounded } from '../core/exec.js';
12
+ import { redactStream } from '../core/caller-env.js';
13
+ import { stateRoot } from '../core/paths.js';
14
+ import { groupAlive, processGroup, sameProcess, serviceTag, startTime } from '../core/procscan.js';
15
+ import { LogWriter, logFileOf, startMarker } from '../core/logs.js';
16
+ /** Uptime past which a crash counts as fresh rather than part of a flap. */
17
+ const STABLE_MS = () => Number(process.env.BACKLOT_STABLE_MS ?? 60_000);
18
+ export class EnvSupervisor {
19
+ envId;
20
+ root;
21
+ logDir;
22
+ onDegraded;
23
+ onPidsChanged;
24
+ onStopped;
25
+ hooks;
26
+ services = new Map();
27
+ events = [];
28
+ constructor(envId,
29
+ /** The caller's worktree — services run in place there (decision 0032). */
30
+ root, logDir,
31
+ /** Fired when a service flaps past its restart budget (decision 0007/0010), with how it last ended. */
32
+ onDegraded,
33
+ /** Fired whenever a service's pid changes (start/restart) so the journal
34
+ * stays truthful for recovery — a stale pid gets an innocent SIGTERM and
35
+ * misses the real orphan holding the port. */
36
+ onPidsChanged,
37
+ /** Fired after a service was stopped on purpose — the proxy stops forwarding to it (decision 0033). */
38
+ onStopped, hooks = {}) {
39
+ this.envId = envId;
40
+ this.root = root;
41
+ this.logDir = logDir;
42
+ this.onDegraded = onDegraded;
43
+ this.onPidsChanged = onPidsChanged;
44
+ this.onStopped = onStopped;
45
+ this.hooks = hooks;
46
+ mkdirSync(logDir, { recursive: true });
47
+ }
48
+ note(service, event) {
49
+ this.events.push({ at: Date.now(), service, event });
50
+ if (this.events.length > 100)
51
+ this.events.shift();
52
+ }
53
+ logPath(name) {
54
+ return logFileOf(this.logDir, name);
55
+ }
56
+ logs(name) {
57
+ return this.services.get(name)?.buf ?? '';
58
+ }
59
+ pids() {
60
+ const out = {};
61
+ for (const [name, r] of this.services) {
62
+ if (r.proc?.pid && r.proc.exitCode === null)
63
+ out[name] = { pid: r.proc.pid, startTime: r.startTime };
64
+ }
65
+ return out;
66
+ }
67
+ /** The exit code of a service's last process; null while it runs, or when a signal ended it. */
68
+ exitCodeOf(name) {
69
+ return this.services.get(name)?.proc?.exitCode ?? null;
70
+ }
71
+ allHealthyPids() {
72
+ return [...this.services.values()].every((r) => r.proc.exitCode === null);
73
+ }
74
+ start(name, spec, env, secrets = []) {
75
+ // A start while the previous incarnation waits out a crash backoff (it is
76
+ // missing from pids(), so a wake or an `up` starts it again): the pending
77
+ // relaunch is cancelled first. Left armed, it spawned a second process
78
+ // the map no longer named — untracked, and squatting the internal port.
79
+ const previous = this.services.get(name);
80
+ if (previous) {
81
+ previous.expectedExit = true;
82
+ if (previous.restartTimer) {
83
+ clearTimeout(previous.restartTimer);
84
+ previous.restartTimer = null;
85
+ }
86
+ if (previous.proc?.pid && previous.proc.exitCode === null && previous.proc.signalCode === null) {
87
+ // Still running (a caller that should have stopped it first): its
88
+ // whole group goes, rather than run on beside the new one.
89
+ try {
90
+ process.kill(-previous.proc.pid, 'SIGKILL');
91
+ }
92
+ catch {
93
+ /* already gone */
94
+ }
95
+ }
96
+ this.note(name, 'replaced by a new start');
97
+ }
98
+ const cmd = spec.run;
99
+ // A repo can already run arbitrary shell here, so this is not a privilege
100
+ // boundary — it makes an ACCIDENT loud. `cwd: ../sibling` silently ran the
101
+ // service outside the worktree it is bound to, against files runly never
102
+ // fingerprinted.
103
+ const cwd = spec.cwd ? safeJoin(this.root, spec.cwd, `service '${name}' cwd`) : this.root;
104
+ const running = { proc: null, buf: '', probeBuf: '', restarts: 0, expectedExit: false, restartTimer: null, startedAt: now(), log: new LogWriter(this.logPath(name)) };
105
+ let launches = 0;
106
+ const launch = () => {
107
+ running.restartTimer = null;
108
+ // A teardown that landed while this restart was pending: do not respawn.
109
+ if (running.expectedExit)
110
+ return;
111
+ launches++;
112
+ const proc = spawn('sh', ['-c', cmd], {
113
+ cwd,
114
+ // The tag rides in the environment so it is INHERITED by every
115
+ // descendant. After an ungraceful death the sh -c wrapper is gone but
116
+ // the real server still carries it, which is what lets a later daemon
117
+ // find and reclaim an orphan it never spawned itself (issue #5).
118
+ env: { ...process.env, ...env, ...serviceTag(this.envId, name, stateRoot()) },
119
+ stdio: ['ignore', 'pipe', 'pipe'],
120
+ // Own process group, so signals reach the service and not just the
121
+ // sh -c wrapper. On Linux (dash) the wrapper forks instead of
122
+ // exec-ing, so killing only proc.pid orphaned the actual server —
123
+ // it kept the port and every rebind-to-same-port flow failed with
124
+ // "occupied by a foreign process" (BACKLOG P1, 2026-07-11).
125
+ // Services still survive a daemon crash (crash-recovery contract):
126
+ // nothing signals the group when the daemon itself dies.
127
+ detached: true,
128
+ });
129
+ running.proc = proc;
130
+ running.startedAt = now();
131
+ // Capture identity immediately: once the pid exits this is unreadable,
132
+ // and an un-pinned pid is one the reaper must refuse to signal.
133
+ running.startTime = proc.pid ? startTime(proc.pid) : undefined;
134
+ // `--since up` reads from the last of these (decision 0038).
135
+ running.log.line(startMarker(name, proc.pid, launches > 1 ? `restart ${launches - 1} after it exited` : ''));
136
+ this.onPidsChanged?.();
137
+ if (launches > 1)
138
+ this.hooks.onRelaunched?.(name);
139
+ for (const [label, stream] of [['out', proc.stdout], ['err', proc.stderr]]) {
140
+ const decoder = new StringDecoder('utf8');
141
+ const redact = redactStream(secrets);
142
+ const sink = (s) => {
143
+ running.buf = (running.buf + s).slice(-64_000);
144
+ running.log.write(label, s);
145
+ };
146
+ stream.on('data', (d) => {
147
+ const text = decoder.write(d);
148
+ running.probeBuf = (running.probeBuf + text).slice(-64_000);
149
+ sink(redact(text));
150
+ });
151
+ stream.on('end', () => {
152
+ sink(redact(decoder.end(), true));
153
+ running.log.end(label);
154
+ });
155
+ }
156
+ // A spawn failure (EAGAIN/EMFILE under fleet load) emits 'error'; with no
157
+ // listener it becomes an uncaught exception that kills the whole daemon.
158
+ proc.on('error', (err) => {
159
+ this.note(name, `spawn error: ${err.message}`);
160
+ // A spawn failure emits 'error' with NO 'exit', so the restart logic
161
+ // below never ran: supervision simply stopped while the environment
162
+ // stayed 'hot' and reported healthy. Drive the same path as an exit.
163
+ if (running.expectedExit || running.spawnFailed)
164
+ return;
165
+ running.spawnFailed = true;
166
+ this.onPidsChanged?.();
167
+ if (running.restarts < 3) {
168
+ running.restarts++;
169
+ this.hooks.onCrashed?.(name);
170
+ this.note(name, `restarting after spawn failure (attempt ${running.restarts})`);
171
+ running.restartTimer = setTimeout(() => {
172
+ running.spawnFailed = false;
173
+ launch();
174
+ }, 500 * running.restarts);
175
+ running.restartTimer.unref();
176
+ }
177
+ else {
178
+ this.note(name, 'spawn keeps failing — giving up');
179
+ this.hooks.onGaveUp?.(name);
180
+ this.onDegraded?.(name, { code: null, signal: null, reason: 'spawn-failed' });
181
+ }
182
+ });
183
+ proc.on('exit', (code, signal) => {
184
+ if (running.expectedExit)
185
+ return;
186
+ // A service that DAEMONIZES (forks and returns 0 immediately) is not a
187
+ // supervised service: runly restarts it, each restart forks another
188
+ // background copy, and those copies escape both the group kill and, if
189
+ // they detach far enough, the tag scan. Refuse it rather than
190
+ // multiplying processes nobody can reclaim.
191
+ if (code === 0 && now() - running.startedAt < 2000 && running.restarts === 0) {
192
+ this.note(name, 'exited 0 immediately — a service must stay in the FOREGROUND (it looks daemonized)');
193
+ running.expectedExit = true;
194
+ this.hooks.onGaveUp?.(name);
195
+ this.onDegraded?.(name, { code, signal, reason: 'daemonized' });
196
+ return;
197
+ }
198
+ this.note(name, signal ? `exited (signal ${signal})` : `exited (${code})`);
199
+ this.onPidsChanged?.(); // the dead pid must leave the journal
200
+ // The budget is for FLAPPING — a tight crash loop — not for a service's
201
+ // whole lifetime. Without this reset, three unrelated crashes hours
202
+ // apart degraded a long-lived environment that was never unhealthy.
203
+ if (now() - running.startedAt > STABLE_MS())
204
+ running.restarts = 0;
205
+ // Bounded restart for long-lived services (decision 0010).
206
+ if (running.restarts < 3) {
207
+ running.restarts++;
208
+ // Hold, don't refuse (decision 0035): a client arriving between the
209
+ // crash and the relaunch waits on the public port.
210
+ this.hooks.onCrashed?.(name);
211
+ this.note(name, `restarting (attempt ${running.restarts})`);
212
+ running.restartTimer = setTimeout(launch, 500 * running.restarts);
213
+ running.restartTimer.unref();
214
+ }
215
+ else {
216
+ this.note(name, 'flapping — giving up');
217
+ this.hooks.onGaveUp?.(name);
218
+ this.onDegraded?.(name, { code, signal, reason: 'crash-loop' });
219
+ }
220
+ });
221
+ };
222
+ launch();
223
+ this.services.set(name, running);
224
+ this.note(name, 'started');
225
+ }
226
+ async waitReady(name, spec, url, env) {
227
+ const ready = spec.ready ?? (spec.port ? { http: '/' } : { log: '.' });
228
+ const timeoutMs = (ready.timeout ?? 120) * 1000;
229
+ const fatal = spec.fatal_logs ? new RegExp(spec.fatal_logs) : undefined;
230
+ const start = Date.now();
231
+ const running = this.services.get(name);
232
+ if (!running)
233
+ throw new BrokerError('env-error', `service '${name}' was never started`, name);
234
+ for (;;) {
235
+ const buf = running.buf;
236
+ if (fatal && fatal.test(running.probeBuf)) {
237
+ throw new BrokerError('work-error', `service '${name}' hit a fatal log marker during boot`, name, tail(buf));
238
+ }
239
+ // The daemonization detector (exit 0 in <2s) already gave up on this
240
+ // service — polling its dead probe for the rest of ready.timeout only
241
+ // delayed the verdict and then blamed the ENVIRONMENT for the repo's
242
+ // own backgrounding run command.
243
+ if (running.expectedExit && running.proc.exitCode === 0 && running.restarts === 0) {
244
+ throw new BrokerError('work-error', `service '${name}' exited 0 immediately — a service must stay in the FOREGROUND (it looks daemonized)`, name, tail(buf));
245
+ }
246
+ if (running.proc.exitCode !== null && running.restarts >= 3) {
247
+ // Still the service's own failure (decision 0039 marks it failed and
248
+ // keeps the rest running); a missing tool or runtime says so.
249
+ throw new BrokerError('work-error', `service '${name}' exited during boot (${running.proc.exitCode})${environmentHint(tail(buf)) ?? ''}`, name, tail(buf));
250
+ }
251
+ if (ready.http && url) {
252
+ // Probe every address the advertised host can mean. `localhost` is
253
+ // ambiguous on a dual-stack machine: macOS resolves it to ::1 first,
254
+ // and a service bound IPv4-only (the common case — see
255
+ // examples/hello-python) is simply not there. The readiness probe then
256
+ // times out against a service that IS running and healthy.
257
+ for (const candidate of probeUrls(url)) {
258
+ try {
259
+ const res = await fetch(`${candidate}${ready.http}`, { signal: AbortSignal.timeout(1500) });
260
+ if (res.status === 200)
261
+ return;
262
+ }
263
+ catch {
264
+ /* not on this address — try the next */
265
+ }
266
+ }
267
+ }
268
+ else if (ready.log) {
269
+ if (new RegExp(ready.log).test(running.probeBuf))
270
+ return;
271
+ }
272
+ else if (ready.cmd) {
273
+ // Bounded by what remains of the readiness budget: a probe command
274
+ // that itself hangs used to block this loop long past ready.timeout.
275
+ const remainingS = Math.max(1, Math.ceil((timeoutMs - (Date.now() - start)) / 1000));
276
+ const r = await runBounded(ready.cmd, this.root, remainingS, { ...process.env, ...env });
277
+ if (r.code === 0 && !r.timedOut)
278
+ return;
279
+ }
280
+ if (Date.now() - start > timeoutMs) {
281
+ throw new BrokerError('env-error', `service '${name}' not ready after ${ready.timeout ?? 120}s`, name, tail(running.buf));
282
+ }
283
+ await new Promise((r) => setTimeout(r, 150));
284
+ }
285
+ }
286
+ /** Stop every service; returns any that refused to die (empty is the norm). */
287
+ async stopAll() {
288
+ return this.stopSome([...this.services.keys()]);
289
+ }
290
+ /**
291
+ * Stop just these services (an `up` restarting only the ones whose build
292
+ * output changed, decision 0032); the rest keep running. Same contract as
293
+ * stopAll: survivors are returned so the caller can reap and record them.
294
+ */
295
+ async stopSome(names) {
296
+ const survivors = {};
297
+ for (const name of names) {
298
+ const r = this.services.get(name);
299
+ if (!r)
300
+ continue;
301
+ r.expectedExit = true;
302
+ // Cancel any pending restart BEFORE it can fire — otherwise launch()
303
+ // respawns an untracked process that squats the port after teardown.
304
+ if (r.restartTimer) {
305
+ clearTimeout(r.restartTimer);
306
+ r.restartTimer = null;
307
+ }
308
+ // NOTE the exitCode check is deliberately absent: `sh -c` forks, so the
309
+ // wrapper can be gone while the real server lives on in its group. The
310
+ // identity check inside killGroupVerified fails safe for a dead leader, so
311
+ // survivors get RECORDED for tag reclaim instead of vanishing silently.
312
+ if (r.proc && r.proc.pid) {
313
+ // Verify the group is actually gone rather than resolving as soon as
314
+ // SIGKILL is *sent* — the caller goes on to delete the env root and
315
+ // drop the pid, so a survivor here becomes an untrackable orphan.
316
+ const remaining = await reapPids({ [name]: { pid: r.proc.pid, startTime: r.startTime } });
317
+ if (remaining[name]) {
318
+ this.note(name, `still running after SIGKILL (pid ${r.proc.pid})`);
319
+ survivors[name] = remaining[name];
320
+ }
321
+ }
322
+ this.note(name, survivors[name] ? 'stop failed' : 'stopped');
323
+ this.services.delete(name);
324
+ this.onStopped?.(name);
325
+ }
326
+ return survivors;
327
+ }
328
+ }
329
+ /**
330
+ * The addresses an advertised URL may actually be served on.
331
+ *
332
+ * Keeps the CONSUMER-facing url unchanged (that is a contract) while making the
333
+ * internal readiness probe robust to IPv4/IPv6 resolution order.
334
+ */
335
+ function probeUrls(url) {
336
+ if (!url.includes('//localhost'))
337
+ return [url];
338
+ return [url, url.replace('//localhost', '//127.0.0.1')];
339
+ }
340
+ const tail = (s, n = 600) => s.slice(-n);
341
+ /** Signal a whole process group, falling back to the single pid we know of. */
342
+ function signalGroup(pgid, pid, signal) {
343
+ try {
344
+ process.kill(-pgid, signal);
345
+ }
346
+ catch {
347
+ // Not a group leader (or the group is already gone) — at minimum reach the
348
+ // one process we can name.
349
+ try {
350
+ process.kill(pid, signal);
351
+ }
352
+ catch {
353
+ /* already gone */
354
+ }
355
+ }
356
+ }
357
+ /**
358
+ * SIGTERM a group, wait for it to actually die, then SIGKILL and confirm.
359
+ *
360
+ * The previous recovery path fired one SIGTERM and moved on, which anything
361
+ * with a graceful-shutdown handler (a .NET host, `ng serve` under a trapping
362
+ * shell) simply outlives — and since the caller then dropped the pid, the
363
+ * survivor became unreachable forever. Escalation plus a verified verdict is
364
+ * what makes "reaped" mean reaped.
365
+ *
366
+ * Returns true only when the observed and supplied recorded groups are empty.
367
+ * `observeGroup` hands the current group to reapPids before signalling so its
368
+ * ownership survives even if the recorded process exits during reclamation.
369
+ */
370
+ export async function killGroupVerified(pid, recordedStart, graceMs = 2000, recordedGroup, observeGroup) {
371
+ if (!sameProcess(pid, recordedStart)) {
372
+ // The pid is no longer the process we recorded. Whatever now sits in that
373
+ // group may be a stranger's — our old children and an unrelated new leader
374
+ // are indistinguishable by pid alone — so signalling it is never safe.
375
+ // Report gone only if the group is genuinely empty; otherwise leave it
376
+ // unresolved for tag-based reclaim, which pid reuse cannot confuse.
377
+ return !groupAlive(recordedGroup ?? pid);
378
+ }
379
+ // Resolve the group BEFORE signalling: once the leader exits its /proc entry
380
+ // is gone and the surviving siblings become unattributable.
381
+ const pgid = processGroup(pid) ?? pid;
382
+ observeGroup?.(pgid);
383
+ const gone = () => !groupAlive(pgid) && (recordedGroup === undefined || !groupAlive(recordedGroup));
384
+ signalGroup(pgid, pid, 'SIGTERM');
385
+ const deadline = Date.now() + graceMs;
386
+ while (Date.now() < deadline) {
387
+ await new Promise((r) => setTimeout(r, 50));
388
+ if (gone())
389
+ return true;
390
+ }
391
+ // The leader dying is NOT the success condition. `sh -c` commonly forks, so
392
+ // the wrapper exits on SIGTERM while the real server — which ignored it —
393
+ // keeps its port and its ~1 GB. Escalate against the whole group.
394
+ signalGroup(pgid, pid, 'SIGKILL');
395
+ // SIGKILL is not instantaneous: the kernel still has to tear the group down,
396
+ // and reporting "reaped" early would let the caller drop the pid one poll
397
+ // before the process is actually off the table.
398
+ const hard = Date.now() + 2000;
399
+ while (Date.now() < hard) {
400
+ await new Promise((r) => setTimeout(r, 50));
401
+ if (gone())
402
+ return true;
403
+ }
404
+ return gone();
405
+ }
406
+ /**
407
+ * SIGTERM one process, then SIGKILL it after `graceMs` — never its group, and
408
+ * only while it is still the process recorded with `recordedStart`. For a
409
+ * process that shares a group with something runly does not own (`runly db
410
+ * with`'s command at a terminal, decision 0039). True once it is gone.
411
+ */
412
+ export async function killPidVerified(pid, recordedStart, graceMs = 2000) {
413
+ const ours = () => sameProcess(pid, recordedStart);
414
+ if (!ours())
415
+ return true;
416
+ try {
417
+ process.kill(pid, 'SIGTERM');
418
+ }
419
+ catch {
420
+ return !ours();
421
+ }
422
+ const deadline = Date.now() + graceMs;
423
+ while (Date.now() < deadline) {
424
+ await new Promise((r) => setTimeout(r, 50));
425
+ if (!ours())
426
+ return true;
427
+ }
428
+ try {
429
+ process.kill(pid, 'SIGKILL');
430
+ }
431
+ catch {
432
+ /* gone meanwhile */
433
+ }
434
+ const hard = Date.now() + 2000;
435
+ while (Date.now() < hard) {
436
+ await new Promise((r) => setTimeout(r, 50));
437
+ if (!ours())
438
+ return true;
439
+ }
440
+ return !ours();
441
+ }
442
+ /**
443
+ * Kill PIDs recorded by a previous daemon life (recovery, decision 0009).
444
+ *
445
+ * Returns the entries that were NOT confirmed dead. The caller must keep those
446
+ * in the journal — a forgotten pid is an orphan nobody can ever reclaim.
447
+ */
448
+ export async function reapPids(pids, kill = killGroupVerified) {
449
+ const survivors = {};
450
+ await Promise.all(Object.entries(pids).map(async ([name, rec]) => {
451
+ // Discovery can record nonleaders, and a process can move groups. Keep
452
+ // every observed live group even after its identifying process exits;
453
+ // only a verified process identity authorizes signalling a current group.
454
+ const groups = new Set(serviceGroups(rec));
455
+ if (sameProcess(rec.pid, rec.startTime))
456
+ groups.add(processGroup(rec.pid) ?? rec.pid);
457
+ const dead = await kill(rec.pid, rec.startTime, undefined, rec.pgid, group => groups.add(group));
458
+ const remaining = [...groups].filter(groupAlive);
459
+ if (remaining.length > 0)
460
+ survivors[name] = withServiceGroups(rec, remaining);
461
+ else if (!dead && sameProcess(rec.pid, rec.startTime))
462
+ survivors[name] = rec;
463
+ }));
464
+ return survivors;
465
+ }
466
+ export function serviceGroups(rec) {
467
+ return [...new Set([rec.pgid ?? rec.pid, ...(rec.pgids ?? [])])];
468
+ }
469
+ function withServiceGroups(rec, groups) {
470
+ const identity = { ...rec };
471
+ delete identity.pgid;
472
+ delete identity.pgids;
473
+ const [first, ...rest] = groups;
474
+ return { ...identity, ...(first !== rec.pid ? { pgid: first } : {}), ...(rest.length ? { pgids: rest } : {}) };
475
+ }
476
+ export function mergeServicePids(...sets) {
477
+ const merged = {};
478
+ for (const set of sets)
479
+ for (const [name, rec] of Object.entries(set ?? {})) {
480
+ const match = Object.entries(merged).find(([, other]) => other.pid === rec.pid && other.startTime === rec.startTime);
481
+ if (match) {
482
+ merged[match[0]] = withServiceGroups(rec, [...new Set([...serviceGroups(match[1]), ...serviceGroups(rec)])]);
483
+ }
484
+ else {
485
+ let key = name;
486
+ while (Object.hasOwn(merged, key))
487
+ key += ':';
488
+ merged[key] = rec;
489
+ }
490
+ }
491
+ return merged;
492
+ }
493
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../src/daemon/supervisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ApplianceSpec } from '../core/manifest.js';
2
+ export declare function probeTcp(target: string, timeoutMs?: number): Promise<boolean>;
3
+ export type ApplianceResult = 'up' | 'started' | 'adopted';
4
+ /**
5
+ * Probe; if down and a `start:` is declared, start it (once, machine-wide) and
6
+ * wait for the probe (and optional `ready:` gate) to accept. Throws infra-error
7
+ * when the appliance stays unreachable — with the start output when we tried.
8
+ */
9
+ export declare function ensureAppliance(name: string, spec: ApplianceSpec, root: string, say: (msg: string) => void): Promise<ApplianceResult>;
10
+ /** Explicit stop verb only — runly never stops an appliance on its own. */
11
+ export declare function stopAppliance(name: string, spec: ApplianceSpec, root: string): Promise<void>;