@volter/world-runtime 3.0.38 → 3.0.39

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 (168) hide show
  1. package/dist/known-external-services.json +3 -10
  2. package/dist/src/actions-runner-cli.js +3 -3
  3. package/dist/src/actions-runner.js +2 -1
  4. package/dist/src/app-url.js +2 -1
  5. package/dist/src/backings.d.ts +10 -1
  6. package/dist/src/backings.js +66 -7
  7. package/dist/src/branch.js +85 -52
  8. package/dist/src/browser-routes.cjs +59 -0
  9. package/dist/src/ca-trust.js +4 -3
  10. package/dist/src/changeset.js +4 -3
  11. package/dist/src/cli-commands.js +25 -9
  12. package/dist/src/cli.js +2 -1
  13. package/dist/src/command-lifetime.d.mts +12 -0
  14. package/dist/src/command-lifetime.d.ts +1 -15
  15. package/dist/src/command-lifetime.js +1 -98
  16. package/dist/src/command-lifetime.mjs +90 -0
  17. package/dist/src/configs.js +1 -1
  18. package/dist/src/console-apart.js +3 -2
  19. package/dist/src/covers.d.ts +3 -2
  20. package/dist/src/covers.js +95 -18
  21. package/dist/src/error-message.d.mts +1 -0
  22. package/dist/src/error-message.d.ts +1 -0
  23. package/dist/src/error-message.js +2 -0
  24. package/dist/src/error-message.mjs +10 -0
  25. package/dist/src/guest-command.d.mts +4 -0
  26. package/dist/src/guest-command.mjs +26 -0
  27. package/dist/src/guest-control.d.mts +1 -0
  28. package/dist/src/guest-control.mjs +17 -0
  29. package/dist/src/guest-entry.mjs +146 -0
  30. package/dist/src/guest-processes.d.mts +9 -0
  31. package/dist/src/guest-processes.mjs +47 -0
  32. package/dist/src/guest-runner.d.ts +6 -0
  33. package/dist/src/guest-runner.js +567 -0
  34. package/dist/src/host.js +5 -4
  35. package/dist/src/index.d.ts +1 -0
  36. package/dist/src/index.js +1 -0
  37. package/dist/src/infra-cli.js +4 -0
  38. package/dist/src/infra.js +133 -23
  39. package/dist/src/init.js +60 -23
  40. package/dist/src/lifecycle-record.d.ts +3 -1
  41. package/dist/src/lifecycle-record.js +29 -6
  42. package/dist/src/livekit-backing.mjs +37 -0
  43. package/dist/src/local-branches.js +11 -3
  44. package/dist/src/mongodb-backing.d.ts +7 -0
  45. package/dist/src/mongodb-backing.js +58 -0
  46. package/dist/src/native-postgres-backing.d.ts +4 -0
  47. package/dist/src/native-postgres-backing.js +404 -0
  48. package/dist/src/origin.js +2 -1
  49. package/dist/src/owned-path.d.ts +4 -0
  50. package/dist/src/owned-path.js +37 -0
  51. package/dist/src/pack-facts.d.ts +1 -0
  52. package/dist/src/pglite-backing.d.ts +4 -13
  53. package/dist/src/pglite-backing.js +198 -106
  54. package/dist/src/pglite-host.mjs +47 -17
  55. package/dist/src/placeholder.d.ts +3 -2
  56. package/dist/src/placeholder.js +9 -4
  57. package/dist/src/process-groups.d.mts +2 -0
  58. package/dist/src/process-groups.d.ts +1 -4
  59. package/dist/src/process-groups.js +1 -49
  60. package/dist/src/process-groups.mjs +59 -0
  61. package/dist/src/process-receipt.d.mts +6 -0
  62. package/dist/src/process-receipt.d.ts +11 -0
  63. package/dist/src/process-receipt.js +3 -0
  64. package/dist/src/process-receipt.mjs +45 -0
  65. package/dist/src/process-signal.d.mts +9 -0
  66. package/dist/src/process-signal.mjs +92 -0
  67. package/dist/src/project-inspect.d.ts +1 -1
  68. package/dist/src/project-inspect.js +4 -2
  69. package/dist/src/proxy-daemon.js +2 -1
  70. package/dist/src/redirect-proxy.js +186 -19
  71. package/dist/src/redis-backing.js +99 -35
  72. package/dist/src/reflect.js +2 -1
  73. package/dist/src/root.js +7 -6
  74. package/dist/src/run-task-worker.js +2 -1
  75. package/dist/src/runtime.d.ts +24 -7
  76. package/dist/src/runtime.js +703 -286
  77. package/dist/src/schema.d.ts +12 -1
  78. package/dist/src/schema.js +20 -5
  79. package/dist/src/served-world.d.ts +1 -0
  80. package/dist/src/served-world.js +26 -14
  81. package/dist/src/service-recorder.js +8 -3
  82. package/dist/src/signals.d.mts +1 -0
  83. package/dist/src/signals.d.ts +1 -1
  84. package/dist/src/signals.js +1 -11
  85. package/dist/src/signals.mjs +10 -0
  86. package/dist/src/tail.d.ts +2 -0
  87. package/dist/src/tail.js +9 -2
  88. package/dist/src/tcp-port.js +27 -13
  89. package/dist/src/temporal-backing.mjs +28 -0
  90. package/dist/src/up-cli.js +8 -3
  91. package/dist/src/up-task-worker.js +2 -1
  92. package/dist/src/websocket-relay.d.ts +1 -1
  93. package/dist/src/websocket-relay.js +23 -10
  94. package/dist/src/world-view.js +2 -2
  95. package/known-external-services.json +3 -10
  96. package/package.json +12 -7
  97. package/src/actions-runner-cli.ts +3 -3
  98. package/src/actions-runner.ts +2 -1
  99. package/src/app-url.ts +2 -1
  100. package/src/backings.ts +65 -7
  101. package/src/branch.ts +73 -34
  102. package/src/browser-routes.cjs +59 -0
  103. package/src/ca-trust.ts +4 -3
  104. package/src/changeset.ts +4 -3
  105. package/src/cli-commands.ts +21 -9
  106. package/src/cli.ts +2 -1
  107. package/src/command-lifetime.d.mts +12 -0
  108. package/src/command-lifetime.mjs +90 -0
  109. package/src/command-lifetime.ts +1 -79
  110. package/src/configs.ts +1 -1
  111. package/src/console-apart.ts +2 -1
  112. package/src/covers.ts +97 -19
  113. package/src/error-message.d.mts +1 -0
  114. package/src/error-message.mjs +10 -0
  115. package/src/error-message.ts +2 -0
  116. package/src/guest-command.d.mts +4 -0
  117. package/src/guest-command.mjs +26 -0
  118. package/src/guest-control.d.mts +1 -0
  119. package/src/guest-control.mjs +17 -0
  120. package/src/guest-entry.mjs +146 -0
  121. package/src/guest-processes.d.mts +9 -0
  122. package/src/guest-processes.mjs +47 -0
  123. package/src/guest-runner.ts +414 -0
  124. package/src/host.ts +5 -4
  125. package/src/index.ts +1 -0
  126. package/src/infra-cli.ts +4 -0
  127. package/src/infra.ts +103 -22
  128. package/src/init.ts +55 -22
  129. package/src/lifecycle-record.ts +21 -6
  130. package/src/livekit-backing.mjs +37 -0
  131. package/src/local-branches.ts +6 -3
  132. package/src/mongodb-backing.ts +55 -0
  133. package/src/native-postgres-backing.ts +329 -0
  134. package/src/origin.ts +2 -1
  135. package/src/owned-path.ts +32 -0
  136. package/src/pack-facts.ts +1 -0
  137. package/src/pglite-backing.ts +155 -79
  138. package/src/pglite-host.mjs +47 -17
  139. package/src/placeholder.ts +10 -5
  140. package/src/process-groups.d.mts +2 -0
  141. package/src/process-groups.mjs +59 -0
  142. package/src/process-groups.ts +1 -33
  143. package/src/process-receipt.d.mts +6 -0
  144. package/src/process-receipt.mjs +45 -0
  145. package/src/process-receipt.ts +5 -0
  146. package/src/process-signal.d.mts +9 -0
  147. package/src/process-signal.mjs +92 -0
  148. package/src/project-inspect.ts +4 -2
  149. package/src/proxy-daemon.ts +2 -1
  150. package/src/redirect-proxy.ts +153 -18
  151. package/src/redis-backing.ts +74 -25
  152. package/src/reflect.ts +2 -1
  153. package/src/root.ts +7 -6
  154. package/src/run-task-worker.ts +2 -1
  155. package/src/runtime.ts +596 -265
  156. package/src/schema.ts +30 -6
  157. package/src/served-world.ts +21 -15
  158. package/src/service-recorder.ts +4 -2
  159. package/src/signals.d.mts +1 -0
  160. package/src/signals.mjs +10 -0
  161. package/src/signals.ts +1 -10
  162. package/src/tail.ts +10 -3
  163. package/src/tcp-port.ts +24 -13
  164. package/src/temporal-backing.mjs +28 -0
  165. package/src/up-cli.ts +6 -3
  166. package/src/up-task-worker.ts +2 -1
  167. package/src/websocket-relay.ts +18 -11
  168. package/src/world-view.ts +2 -2
@@ -1,4 +1 @@
1
- /** Inspect only groups already owned by the lifecycle. A dead leader does not prove that its
2
- * descendants exited. Zombies hold no resources; unknown inspection remains live. This is
3
- * retirement evidence, never an ownership token or a way to discover processes to kill. */
4
- export declare function survivingOwnedGroups(pids: number[]): number[];
1
+ export { survivingOwnedGroups, processStarts } from './process-groups.mjs';
@@ -1,49 +1 @@
1
- import { spawnSync } from 'node:child_process';
2
- /** Inspect only groups already owned by the lifecycle. A dead leader does not prove that its
3
- * descendants exited. Zombies hold no resources; unknown inspection remains live. This is
4
- * retirement evidence, never an ownership token or a way to discover processes to kill. */
5
- export function survivingOwnedGroups(pids) {
6
- const candidates = pids.filter(pid => {
7
- try {
8
- process.kill(process.platform === 'win32' ? pid : -pid, 0);
9
- return true;
10
- }
11
- catch (error) {
12
- if (error.code !== 'ESRCH')
13
- return true;
14
- try {
15
- process.kill(pid, 0);
16
- return true;
17
- }
18
- catch (cause) {
19
- return cause.code !== 'ESRCH';
20
- }
21
- }
22
- });
23
- if (!candidates.length || process.platform === 'win32')
24
- return candidates;
25
- const result = spawnSync('ps', ['-axo', 'pid=,pgid=,stat='], { windowsHide: true,
26
- encoding: 'utf8', timeout: 1000, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024,
27
- });
28
- if (result.status !== 0 || result.error)
29
- return candidates;
30
- const rows = result.stdout.trim().split('\n').map(line => line.trim().split(/\s+/));
31
- return candidates.filter(pid => {
32
- const members = rows.filter(([id, group]) => Number(group) === pid || Number(id) === pid);
33
- // Visibility restrictions can omit a live group. Only ESRCH proves absence.
34
- if (!members.length) {
35
- for (const target of [-pid, pid]) {
36
- try {
37
- process.kill(target, 0);
38
- return true;
39
- }
40
- catch (error) {
41
- if (error.code !== 'ESRCH')
42
- return true;
43
- }
44
- }
45
- return false;
46
- }
47
- return members.some(([, , state]) => !state || !state.startsWith('Z'));
48
- });
49
- }
1
+ export { survivingOwnedGroups, processStarts } from './process-groups.mjs';
@@ -0,0 +1,59 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+
4
+ /** Inspect only groups already owned by the lifecycle. A dead leader does not prove that its
5
+ * descendants exited. Zombies hold no resources; unknown inspection remains live. This is
6
+ * retirement evidence, never an ownership token or a way to discover processes to kill. */
7
+ export function survivingOwnedGroups(pids) {
8
+ const candidates = pids.filter(pid => {
9
+ try { process.kill(process.platform === 'win32' ? pid : -pid, 0); return true; }
10
+ catch (error) {
11
+ if (error.code !== 'ESRCH') return true;
12
+ try { process.kill(pid, 0); return true; }
13
+ catch (cause) { return cause.code !== 'ESRCH'; }
14
+ }
15
+ });
16
+ if (!candidates.length || process.platform === 'win32') return candidates;
17
+ const result = spawnSync('ps', ['-axo', 'pid=,pgid=,stat='], { windowsHide: true,
18
+ encoding: 'utf8', timeout: 1000, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024,
19
+ });
20
+ if (result.status !== 0 || result.error) return candidates;
21
+ const rows = result.stdout.trim().split('\n').map(line => line.trim().split(/\s+/));
22
+ return candidates.filter(pid => {
23
+ const members = rows.filter(([id, group]) => Number(group) === pid || Number(id) === pid);
24
+ // Visibility restrictions can omit a live group. Only ESRCH proves absence.
25
+ if (!members.length) {
26
+ for (const target of [-pid, pid]) {
27
+ try { process.kill(target, 0); return true; }
28
+ catch (error) { if (error.code !== 'ESRCH') return true; }
29
+ }
30
+ return false;
31
+ }
32
+ return members.some(([, , state]) => !state || !state.startsWith('Z'));
33
+ });
34
+ }
35
+
36
+ /** Each running pid's start time; a pid with no process is absent. Printed in one fixed zone and locale, so the same
37
+ * process reads the same whoever asks (`lstart` otherwise follows the caller's TZ and language). One `ps` per pid:
38
+ * given two or more pids, macOS `ps -p` walks the whole process table (measured 2026-09-30 on Darwin 25.4, 944
39
+ * processes: `/usr/bin/time -p ps -o pid=,lstart= -p 1,$$` 0.14–0.35 s, `… -p 1` 0.00 s), which every `status`
40
+ * paid. */
41
+ export function processStarts(pids) {
42
+ const starts = new Map();
43
+ if (!pids.length || process.platform === 'win32') return starts;
44
+ if (process.platform === 'linux' && existsSync('/proc/self/stat')) {
45
+ // Linux: the start tick since boot (field 22), which a wall-clock step does not move (lstart would).
46
+ for (const pid of pids) {
47
+ try { const stat = readFileSync(`/proc/${pid}/stat`, 'utf8'); starts.set(pid, `tick ${stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19]}`); } catch { /* no process */ }
48
+ }
49
+ return starts;
50
+ }
51
+ const env = { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' };
52
+ for (const pid of pids) {
53
+ const result = spawnSync('ps', ['-o', 'pid=,lstart=', '-p', String(pid)], { windowsHide: true, encoding: 'utf8', timeout: 1000, killSignal: 'SIGKILL', env });
54
+ const match = /^\s*(\d+)\s+(.+?)\s*$/.exec((result.stdout ?? '').trim());
55
+ if (match && Number(match[1]) === pid) starts.set(pid, match[2]);
56
+ }
57
+ return starts;
58
+ }
59
+
@@ -0,0 +1,6 @@
1
+ import type { ReceiptRead, SpawnIdentity } from './process-receipt.js';
2
+ export { spawnIdentity, verifySpawnIdentity, signalOwnedProcess } from './process-signal.mjs';
3
+ export function publishProcessReceipt(path: string, text: string): void;
4
+ export function readProcessReceipt<T>(path: string, parse: (text: string) => T): ReceiptRead<T>;
5
+ export function spawnReceiptPath(path: string): string;
6
+ export function readSpawnReceipt(path: string): ReceiptRead<SpawnIdentity>;
@@ -0,0 +1,11 @@
1
+ export { spawnIdentity, verifySpawnIdentity, signalOwnedProcess } from './process-signal.mjs';
2
+ export { publishProcessReceipt, readProcessReceipt, spawnReceiptPath, readSpawnReceipt } from './process-receipt.mjs';
3
+ export interface ReceiptRead<T> {
4
+ present: boolean;
5
+ value?: T;
6
+ error?: Error;
7
+ }
8
+ export interface SpawnIdentity {
9
+ pid: number;
10
+ start?: string;
11
+ }
@@ -0,0 +1,3 @@
1
+ // Native Node guests and the World use one atomic process-receipt implementation.
2
+ export { spawnIdentity, verifySpawnIdentity, signalOwnedProcess } from './process-signal.mjs';
3
+ export { publishProcessReceipt, readProcessReceipt, spawnReceiptPath, readSpawnReceipt } from './process-receipt.mjs';
@@ -0,0 +1,45 @@
1
+ import fs from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ export { spawnIdentity, verifySpawnIdentity, signalOwnedProcess } from './process-signal.mjs';
5
+
6
+ /** A failed publication never truncates the previous receipt. */
7
+ export function publishProcessReceipt(path, text) {
8
+ const temporary = `${path}.${randomUUID()}.tmp`;
9
+ let descriptor;
10
+ const failures = [];
11
+ try {
12
+ descriptor = fs.openSync(temporary, 'wx', 0o600);
13
+ fs.writeFileSync(descriptor, text);
14
+ fs.fsyncSync(descriptor);
15
+ fs.closeSync(descriptor); descriptor = undefined;
16
+ fs.renameSync(temporary, path);
17
+ descriptor = fs.openSync(dirname(path), 'r');
18
+ fs.fsyncSync(descriptor);
19
+ } catch (error) { failures.push(error); }
20
+ if (descriptor !== undefined) {
21
+ try { fs.closeSync(descriptor); } catch (error) { failures.push(error); }
22
+ }
23
+ try { fs.rmSync(temporary, { force: true }); } catch (error) { failures.push(error); }
24
+ if (failures.length) throw new AggregateError(failures, `Process receipt publication failed: ${path}`);
25
+ }
26
+
27
+ /** Unreadable is uncertainty, not absence; a separate spawn receipt may still establish ownership. */
28
+ export function readProcessReceipt(path, parse) {
29
+ try { return { present: true, value: parse(fs.readFileSync(path, 'utf8')) }; }
30
+ catch (cause) {
31
+ if (cause.code === 'ENOENT') return { present: false };
32
+ return { present: true, error: new Error(`Process receipt unreadable; ownership uncertain: ${path}`, { cause }) };
33
+ }
34
+ }
35
+
36
+ export const spawnReceiptPath = (path) => `${path}.spawn.json`;
37
+ export function readSpawnReceipt(path) {
38
+ return readProcessReceipt(spawnReceiptPath(path), text => {
39
+ const value = JSON.parse(text);
40
+ if (!Number.isInteger(value?.pid) || value.pid <= 0 || (typeof value.start !== 'string' || !value.start.trim())) {
41
+ throw new Error('Invalid process spawn identity');
42
+ }
43
+ return value;
44
+ });
45
+ }
@@ -0,0 +1,9 @@
1
+ import type { SpawnIdentity } from './process-receipt.js';
2
+ import type { ChildProcess } from 'node:child_process';
3
+ export function processBirth(pid: number): Promise<string | undefined>;
4
+ export function spawnIdentity(pid: number): Promise<SpawnIdentity>;
5
+ export function verifySpawnIdentity(identity: SpawnIdentity | undefined): Promise<void>;
6
+ export function signalOwnedProcess(identity: SpawnIdentity | undefined, signal: NodeJS.Signals, group?: boolean): Promise<boolean>;
7
+ export function signalChild(child: ChildProcess, signal: NodeJS.Signals, group?: boolean): boolean;
8
+ export function signalHeldChild(child: ChildProcess, signal: NodeJS.Signals, group?: boolean, birth?: Promise<SpawnIdentity | undefined>): Promise<boolean>;
9
+ export function childIdentity(child: ChildProcess): Promise<SpawnIdentity | undefined>;
@@ -0,0 +1,92 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+
4
+ // Retain the existing 1000 ms inspection ceiling, not a new timeout chosen from one sample.
5
+ // Measured 2026-10-03 on shared Darwin 25.4.0 ARM64, Bun 1.3.11 (process.version
6
+ // v24.3.0), parent 38b5290 plus this change; no Cal server, one live driver PID:
7
+ // `/usr/bin/time -p /bin/ps -o pid=,lstart= -p <driver-pid>` with LC_ALL/LANG=C,
8
+ // TZ=UTC took 9.26 ms from spawn to close; time printed real/user/sys 0.00 s.
9
+ // This includes the time wrapper/event-loop overhead, not an isolated benchmark.
10
+ // Full command/output: cookbook/calcom/output/held-backing-lifecycle.txt.
11
+ const BIRTH_LOOKUP_MS = 1000;
12
+ /** No subprocess on Linux; elsewhere the lookup never blocks the lifecycle event loop. */
13
+ export async function processBirth(pid) {
14
+ if (process.platform === 'win32') return undefined;
15
+ if (process.platform === 'linux') {
16
+ try {
17
+ const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
18
+ const tick = stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19];
19
+ return /^\d+$/.test(tick ?? '') ? `tick ${tick}` : undefined;
20
+ } catch { return undefined; }
21
+ }
22
+ return new Promise(resolve => {
23
+ const child = spawn('ps', ['-o', 'pid=,lstart=', '-p', String(pid)], {
24
+ windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'],
25
+ env: { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' },
26
+ });
27
+ let output = '', settled = false;
28
+ const finish = value => { if (!settled) { settled = true; clearTimeout(timer); child.stdout.destroy(); resolve(value); } };
29
+ // This is our still-held inspector, not a PID from a receipt.
30
+ const timer = setTimeout(() => {
31
+ try { signalChild(child, 'SIGKILL'); } catch { /* failed inspection remains unavailable */ }
32
+ finish(undefined); // an exited wrapper's descendant may keep stdout open indefinitely
33
+ }, BIRTH_LOOKUP_MS);
34
+ child.stdout.on('data', chunk => { output += chunk; });
35
+ child.once('error', () => finish(undefined));
36
+ child.once('close', code => {
37
+ const match = /^\s*(\d+)\s+(.+?)\s*$/.exec(output.trim());
38
+ finish(code === 0 && match && Number(match[1]) === pid ? match[2] : undefined);
39
+ });
40
+ });
41
+ }
42
+ /** Capture at spawn for durable ownership, never when retrying a retained PID. */
43
+ export async function spawnIdentity(pid) {
44
+ const start = await processBirth(pid);
45
+ return { pid, ...(start === undefined ? {} : { start }) };
46
+ }
47
+ /** An async capture is accepted only while its original child is still held and live. */
48
+ export async function childIdentity(child) {
49
+ if (!child.pid) return undefined;
50
+ const identity = await spawnIdentity(child.pid);
51
+ return child.exitCode === null && child.signalCode === null ? identity : undefined;
52
+ }
53
+ /** A birth token is required even when an absent leader leaves a surviving group. */
54
+ export async function verifySpawnIdentity(identity) {
55
+ if (!identity || !Number.isInteger(identity.pid) || identity.pid <= 0 || typeof identity.start !== 'string' || !identity.start.trim()) {
56
+ throw new Error('Process recorded birth token missing; retirement refused; ownership retained');
57
+ }
58
+ const current = await processBirth(identity.pid);
59
+ if (current !== undefined && current !== identity.start) {
60
+ throw new Error(`Process ${identity.pid} no longer matches its recorded spawn identity; retirement refused`);
61
+ }
62
+ if (current === undefined) {
63
+ try { process.kill(identity.pid, 0); }
64
+ catch (error) { if (error.code === 'ESRCH') return; throw error; }
65
+ throw new Error(`Process ${identity.pid} identity cannot be established; ownership retained`);
66
+ }
67
+ }
68
+ /** Stored or retained numbers use birth verification before every delivery. */
69
+ export async function signalOwnedProcess(identity, signal, group = false) {
70
+ await verifySpawnIdentity(identity);
71
+ try { return process.kill(group && process.platform !== 'win32' ? -identity.pid : identity.pid, signal); }
72
+ catch (error) { if (error.code === 'ESRCH') return false; throw error; }
73
+ }
74
+ /** A live ChildProcess is ownership until exit. A group additionally needs its leader alive.
75
+ * False means no held delivery was possible; a caller with surviving descendants needs a birth. */
76
+ export function signalChild(child, signal, group = false) {
77
+ if (!child.pid || child.exitCode !== null || child.signalCode !== null) return false;
78
+ if (!group || process.platform === 'win32') return child.kill(signal);
79
+ try { process.kill(child.pid, 0); }
80
+ catch (error) { if (error.code === 'ESRCH') return false; throw error; }
81
+ try { return process.kill(-child.pid, signal); }
82
+ catch (error) { if (error.code === 'ESRCH') return false; throw error; }
83
+ }
84
+
85
+ /** Held first; an orphaned group then becomes retained numeric ownership. */
86
+ export async function signalHeldChild(child, signal, group = false, birth = Promise.resolve(undefined)) {
87
+ if (signalChild(child, signal, group)) return true;
88
+ if (!group || process.platform === 'win32' || !child.pid) return false;
89
+ try { process.kill(-child.pid, 0); }
90
+ catch (error) { if (error.code === 'ESRCH') return false; throw error; }
91
+ return signalOwnedProcess(await birth, signal, true);
92
+ }
@@ -91,7 +91,7 @@ export declare function connectionProtocol(value: string): string | null;
91
91
  /** Native connections are independent of the vendor's HTTP SDK. Inspect ordinary URL schemes
92
92
  * and Prisma datasource declarations; never infer a selected Prisma adapter from its presence
93
93
  * in package.json. Dynamic/config-file-only datasources remain unknown, as do unbound drivers. */
94
- export declare function projectConnections(input?: string, attributedEnv?: Set<string>): ProjectConnection[];
94
+ export declare function projectConnections(input?: string, attributedEnv?: Set<string>, boundProtocols?: ReadonlyMap<string, string>): ProjectConnection[];
95
95
  /** Literal HTTP(S) destinations passed directly to the platform fetch API in production source.
96
96
  *
97
97
  * This is the third vendor signal beside dependencies and env names: hand-written REST clients
@@ -592,7 +592,7 @@ export function connectionProtocol(value) {
592
592
  /** Native connections are independent of the vendor's HTTP SDK. Inspect ordinary URL schemes
593
593
  * and Prisma datasource declarations; never infer a selected Prisma adapter from its presence
594
594
  * in package.json. Dynamic/config-file-only datasources remain unknown, as do unbound drivers. */
595
- export function projectConnections(input = process.cwd(), attributedEnv = new Set()) {
595
+ export function projectConnections(input = process.cwd(), attributedEnv = new Set(), boundProtocols = new Map()) {
596
596
  const root = resolve(input);
597
597
  const found = [];
598
598
  const dependencies = projectDependencies(root);
@@ -616,7 +616,9 @@ export function projectConnections(input = process.cwd(), attributedEnv = new Se
616
616
  if (connectionProtocol(resolved) === null && attributedEnv.has(match[1]) &&
617
617
  (/^https?:/i.test(resolved) || /_REST_URL$/.test(match[1])))
618
618
  continue;
619
- found.push({ protocol: connectionProtocol(resolved), env: match[1], sources: [`env: ${relative(root, resolve(dir, file))}:${index + 1} ${match[1]}`] });
619
+ // An empty example leaves the actual value to environment injection. Only an owned binding supplies its protocol.
620
+ const protocol = connectionProtocol(resolved) ?? (value.trim() === '' ? boundProtocols.get(match[1]) ?? null : null);
621
+ found.push({ protocol, env: match[1], sources: [`env: ${relative(root, resolve(dir, file))}:${index + 1} ${match[1]}`] });
620
622
  }
621
623
  }
622
624
  }
@@ -2,6 +2,7 @@
2
2
  // Detached entry for the ambient TLS redirect proxy. `activate` spawns this as its own process so a
3
3
  // long-lived proxy can serve the activated shell(s) for the world's lifetime; it records its
4
4
  // pid/url/CA in the instance dir and is stopped by `volter-world down`. Not a user-facing command.
5
+ import { errorMessage } from "./error-message.js";
5
6
  import { runProxyDaemon } from "./runtime.js";
6
7
  const [name, ...rest] = process.argv.slice(2);
7
8
  const rootIndex = rest.indexOf('--root');
@@ -13,6 +14,6 @@ if (!name) {
13
14
  process.exit(1);
14
15
  }
15
16
  runProxyDaemon(name, root, envFile).catch((error) => {
16
- process.stderr.write(`proxy-daemon: ${error instanceof Error ? error.message : String(error)}\n`);
17
+ process.stderr.write(`proxy-daemon: ${errorMessage(error)}\n`);
17
18
  process.exit(1);
18
19
  });