@volter/world-runtime 2.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 (164) hide show
  1. package/LICENSE +202 -0
  2. package/dist/known-external-services.json +1108 -0
  3. package/dist/src/ancestry.d.ts +2 -0
  4. package/dist/src/ancestry.js +42 -0
  5. package/dist/src/app-url.d.ts +47 -0
  6. package/dist/src/app-url.js +239 -0
  7. package/dist/src/attach.d.ts +48 -0
  8. package/dist/src/attach.js +87 -0
  9. package/dist/src/branch.d.ts +20 -0
  10. package/dist/src/branch.js +65 -0
  11. package/dist/src/browser-proxy-cli.d.ts +2 -0
  12. package/dist/src/browser-proxy-cli.js +41 -0
  13. package/dist/src/ca-trust.d.ts +5 -0
  14. package/dist/src/ca-trust.js +64 -0
  15. package/dist/src/catalog.d.ts +31 -0
  16. package/dist/src/catalog.js +148 -0
  17. package/dist/src/changeset.d.ts +142 -0
  18. package/dist/src/changeset.js +570 -0
  19. package/dist/src/cli.d.ts +2 -0
  20. package/dist/src/cli.js +1262 -0
  21. package/dist/src/command-lifetime.d.ts +15 -0
  22. package/dist/src/command-lifetime.js +98 -0
  23. package/dist/src/configs.d.ts +18 -0
  24. package/dist/src/configs.js +119 -0
  25. package/dist/src/console-apart.d.ts +38 -0
  26. package/dist/src/console-apart.js +107 -0
  27. package/dist/src/consumers.d.ts +46 -0
  28. package/dist/src/consumers.js +200 -0
  29. package/dist/src/covers.d.ts +183 -0
  30. package/dist/src/covers.js +800 -0
  31. package/dist/src/fixture-env.d.ts +42 -0
  32. package/dist/src/fixture-env.js +221 -0
  33. package/dist/src/host-cli.d.ts +2 -0
  34. package/dist/src/host-cli.js +92 -0
  35. package/dist/src/host-fault-fixture.d.ts +32 -0
  36. package/dist/src/host-fault-fixture.js +100 -0
  37. package/dist/src/host-worker.d.ts +1 -0
  38. package/dist/src/host-worker.js +23 -0
  39. package/dist/src/host.d.ts +38 -0
  40. package/dist/src/host.js +135 -0
  41. package/dist/src/index.d.ts +48 -0
  42. package/dist/src/index.js +35 -0
  43. package/dist/src/infra-cli.d.ts +2 -0
  44. package/dist/src/infra-cli.js +136 -0
  45. package/dist/src/init.d.ts +227 -0
  46. package/dist/src/init.js +1117 -0
  47. package/dist/src/inject-map.d.ts +34 -0
  48. package/dist/src/inject-map.js +56 -0
  49. package/dist/src/lifecycle-record.d.ts +47 -0
  50. package/dist/src/lifecycle-record.js +196 -0
  51. package/dist/src/origin.d.ts +31 -0
  52. package/dist/src/origin.js +139 -0
  53. package/dist/src/pack-facts.d.ts +75 -0
  54. package/dist/src/pack-facts.js +98 -0
  55. package/dist/src/pglite-backing.d.ts +21 -0
  56. package/dist/src/pglite-backing.js +158 -0
  57. package/dist/src/pglite-host.mjs +147 -0
  58. package/dist/src/placeholder.d.ts +20 -0
  59. package/dist/src/placeholder.js +100 -0
  60. package/dist/src/prerequisites.d.ts +21 -0
  61. package/dist/src/prerequisites.js +49 -0
  62. package/dist/src/process-groups.d.ts +4 -0
  63. package/dist/src/process-groups.js +49 -0
  64. package/dist/src/project-inspect.d.ts +109 -0
  65. package/dist/src/project-inspect.js +827 -0
  66. package/dist/src/proxy-daemon.d.ts +2 -0
  67. package/dist/src/proxy-daemon.js +18 -0
  68. package/dist/src/redirect-proxy.d.ts +105 -0
  69. package/dist/src/redirect-proxy.js +665 -0
  70. package/dist/src/reflect.d.ts +74 -0
  71. package/dist/src/reflect.js +392 -0
  72. package/dist/src/resources.d.ts +26 -0
  73. package/dist/src/resources.js +22 -0
  74. package/dist/src/root.d.ts +114 -0
  75. package/dist/src/root.js +312 -0
  76. package/dist/src/run-task-worker.d.ts +1 -0
  77. package/dist/src/run-task-worker.js +38 -0
  78. package/dist/src/run-task.d.ts +18 -0
  79. package/dist/src/run-task.js +48 -0
  80. package/dist/src/runtime-test-support.d.ts +59 -0
  81. package/dist/src/runtime-test-support.js +205 -0
  82. package/dist/src/runtime.d.ts +256 -0
  83. package/dist/src/runtime.js +3502 -0
  84. package/dist/src/schema.d.ts +449 -0
  85. package/dist/src/schema.js +605 -0
  86. package/dist/src/serve.d.ts +30 -0
  87. package/dist/src/serve.js +82 -0
  88. package/dist/src/served-world.d.ts +194 -0
  89. package/dist/src/served-world.js +986 -0
  90. package/dist/src/service-exit.d.ts +46 -0
  91. package/dist/src/service-exit.js +195 -0
  92. package/dist/src/service-recorder.d.ts +1 -0
  93. package/dist/src/service-recorder.js +121 -0
  94. package/dist/src/sibling.d.ts +1 -0
  95. package/dist/src/sibling.js +9 -0
  96. package/dist/src/signals.d.ts +1 -0
  97. package/dist/src/signals.js +11 -0
  98. package/dist/src/storage-capacity.d.ts +8 -0
  99. package/dist/src/storage-capacity.js +61 -0
  100. package/dist/src/tail.d.ts +30 -0
  101. package/dist/src/tail.js +160 -0
  102. package/dist/src/tcp-port.d.ts +2 -0
  103. package/dist/src/tcp-port.js +36 -0
  104. package/dist/src/up-task-worker.d.ts +1 -0
  105. package/dist/src/up-task-worker.js +61 -0
  106. package/dist/src/up-task.d.ts +17 -0
  107. package/dist/src/up-task.js +49 -0
  108. package/dist/src/websocket-relay.d.ts +3 -0
  109. package/dist/src/websocket-relay.js +40 -0
  110. package/known-external-services.json +1108 -0
  111. package/package.json +83 -0
  112. package/src/ancestry.ts +36 -0
  113. package/src/app-url.ts +253 -0
  114. package/src/attach.ts +117 -0
  115. package/src/branch.ts +63 -0
  116. package/src/browser-proxy-cli.ts +44 -0
  117. package/src/ca-trust.ts +57 -0
  118. package/src/catalog.ts +156 -0
  119. package/src/changeset.ts +627 -0
  120. package/src/cli.ts +1111 -0
  121. package/src/command-lifetime.ts +79 -0
  122. package/src/configs.ts +110 -0
  123. package/src/console-apart.ts +90 -0
  124. package/src/consumers.ts +185 -0
  125. package/src/covers.ts +934 -0
  126. package/src/fixture-env.ts +230 -0
  127. package/src/host-cli.ts +90 -0
  128. package/src/host-worker.ts +23 -0
  129. package/src/host.ts +169 -0
  130. package/src/index.ts +171 -0
  131. package/src/infra-cli.ts +133 -0
  132. package/src/init.ts +1316 -0
  133. package/src/inject-map.ts +72 -0
  134. package/src/lifecycle-record.ts +168 -0
  135. package/src/origin.ts +134 -0
  136. package/src/pack-facts.ts +128 -0
  137. package/src/pglite-backing.ts +141 -0
  138. package/src/pglite-host.mjs +147 -0
  139. package/src/placeholder.ts +89 -0
  140. package/src/prerequisites.ts +66 -0
  141. package/src/process-groups.ts +33 -0
  142. package/src/project-inspect.ts +770 -0
  143. package/src/proxy-daemon.ts +21 -0
  144. package/src/redirect-proxy.ts +684 -0
  145. package/src/reflect.ts +440 -0
  146. package/src/resources.ts +22 -0
  147. package/src/root.ts +290 -0
  148. package/src/run-task-worker.ts +27 -0
  149. package/src/run-task.ts +44 -0
  150. package/src/runtime-test-support.ts +208 -0
  151. package/src/runtime.ts +3357 -0
  152. package/src/schema.ts +922 -0
  153. package/src/serve.ts +102 -0
  154. package/src/served-world.ts +812 -0
  155. package/src/service-exit.ts +175 -0
  156. package/src/service-recorder.ts +89 -0
  157. package/src/sibling.ts +10 -0
  158. package/src/signals.ts +10 -0
  159. package/src/storage-capacity.ts +60 -0
  160. package/src/tail.ts +205 -0
  161. package/src/tcp-port.ts +35 -0
  162. package/src/up-task-worker.ts +40 -0
  163. package/src/up-task.ts +45 -0
  164. package/src/websocket-relay.ts +32 -0
@@ -0,0 +1,205 @@
1
+ // Shared setup for the runtime test files. Extracted so runtime.test.ts could be split by
2
+ // concern: 25 sequential tests in one process floored the gate at ~24s, and the gate shards
3
+ // per FILE. Every helper lives here — including the five that used to sit BETWEEN tests,
4
+ // whose loss broke the first attempt at this split.
5
+ import { afterEach } from 'bun:test';
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import net from 'node:net';
9
+ import { downWorld, statusWorld } from "./runtime.js";
10
+ import { stateDirName } from '@volter/world-core';
11
+ /** Test-only seam (see runtime.ts `proxyDaemonDeadlineMs`): force the daemon-poll deadline to `ms`
12
+ * for the duration of `fn`, then restore whatever was there before — deterministic, no sleeps. */
13
+ export async function withProxyDeadline(ms, fn) {
14
+ const prev = process.env.VOLTER_PROXY_DEADLINE_MS;
15
+ process.env.VOLTER_PROXY_DEADLINE_MS = String(ms);
16
+ try {
17
+ return await fn();
18
+ }
19
+ finally {
20
+ if (prev === undefined)
21
+ delete process.env.VOLTER_PROXY_DEADLINE_MS;
22
+ else
23
+ process.env.VOLTER_PROXY_DEADLINE_MS = prev;
24
+ }
25
+ }
26
+ /** Best-effort cleanup for the forced-timeout tests below: with the deadline forced to 0, the
27
+ * detached proxy-daemon child is still spawned and may go on to write `proxy.json` a little later
28
+ * (asynchronously, after our synchronous poll already gave up) — kill it so it doesn't linger as an
29
+ * orphan process once the test's tmp root is discarded. Never gates test pass/fail. */
30
+ export async function reapLeakedProxyDaemon(root, name) {
31
+ const proxyJsonPath = join(root, stateDirName(), 'worlds', name, 'proxy.json');
32
+ const deadline = Date.now() + 3000;
33
+ while (Date.now() < deadline) {
34
+ if (existsSync(proxyJsonPath)) {
35
+ try {
36
+ const state = JSON.parse(readFileSync(proxyJsonPath, 'utf8'));
37
+ if (state.pid) {
38
+ try {
39
+ process.kill(state.pid, 'SIGTERM');
40
+ }
41
+ catch { /* already gone */ }
42
+ }
43
+ }
44
+ catch { /* ignore parse races */ }
45
+ return;
46
+ }
47
+ await new Promise((r) => setTimeout(r, 100));
48
+ }
49
+ }
50
+ /** A hostname under the IANA-reserved `.invalid` TLD (RFC 2606) — guaranteed to NEVER resolve, on
51
+ * any network, forever. Used as the "untwinned public host" below: it is unambiguously non-local
52
+ * (so `isLocalOrPrivateHost` never exempts it) and unambiguously not a vendor (so it is never
53
+ * redirected to a twin), and a real (non-strict-egress) attempt to reach it fails FAST on DNS
54
+ * resolution — offline and deterministic, no dependency on real internet access. */
55
+ export const UNTWINNED_HOST = 'definitely-untwinned.invalid';
56
+ /** Drive a raw CONNECT through the ambient redirect proxy and collect whatever bytes come back
57
+ * before the socket closes (or `timeoutMs` elapses, as a safety net — a blind-tunnel attempt to an
58
+ * unresolvable host closes with NO data at all, so this never actually waits the full timeout). */
59
+ export function connectThroughProxy(port, host, targetHost, targetPort = 443, timeoutMs = 3000) {
60
+ return new Promise((resolvePromise, reject) => {
61
+ const sock = net.connect(port, host, () => {
62
+ sock.write(`CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n\r\n`);
63
+ });
64
+ let data = '';
65
+ const timer = setTimeout(() => { sock.destroy(); resolvePromise(data); }, timeoutMs);
66
+ sock.on('data', (chunk) => { data += chunk.toString('utf8'); });
67
+ sock.on('close', () => { clearTimeout(timer); resolvePromise(data); });
68
+ sock.on('error', (err) => { clearTimeout(timer); reject(err); });
69
+ });
70
+ }
71
+ /** Force `opensslAvailable()` to false via its TEST-ONLY seam (see `redirect-proxy.ts`). A
72
+ * PATH-shim (fake `openssl` binary prepended to PATH) was tried first and does NOT reliably work
73
+ * here: `opensslAvailable()`'s `spawnSync('openssl', …)` call passes no explicit `env`, and this
74
+ * runtime resolves the executable against a PATH snapshotted at process start rather than a
75
+ * same-process `process.env.PATH` mutation made mid-test (verified: a fresh child process DOES
76
+ * see a shimmed PATH, but mutating PATH in the already-running test process does not change what
77
+ * THIS process's own `spawnSync` resolves) — so the deterministic, seam-based override is used. */
78
+ export function withNoOpenssl() {
79
+ const previous = process.env.VOLTER_TEST_NO_OPENSSL;
80
+ process.env.VOLTER_TEST_NO_OPENSSL = '1';
81
+ return {
82
+ restore: () => {
83
+ if (previous === undefined)
84
+ delete process.env.VOLTER_TEST_NO_OPENSSL;
85
+ else
86
+ process.env.VOLTER_TEST_NO_OPENSSL = previous;
87
+ },
88
+ };
89
+ }
90
+ export const worlds = [];
91
+ /**
92
+ * Register the teardown hook for the CALLING test file. Every file that pushes to `worlds` must
93
+ * call this at top level.
94
+ *
95
+ * It has to be a function call rather than a module-scope `afterEach`, and that distinction was an
96
+ * eight-file process leak. A hook registered while this module's body runs belongs to whichever
97
+ * file happened to import it FIRST; module caching means the body never runs again, so every other
98
+ * file got no hook at all. Each file individually looked clean — it was the first importer — and
99
+ * only a whole-directory run leaked, which is exactly the shape that reads as flakiness. The
100
+ * stray proxy daemons and tunnel stubs then held ports until unrelated HTTP tests started failing.
101
+ *
102
+ * Teardown also settles ALL worlds before reporting: the list is spliced empty up front, so a bare
103
+ * `for … await` loop that rejected on the first world abandoned the rest with nothing left holding
104
+ * a reference to them. Nothing about a world that failed to stop makes the next one less important
105
+ * to stop.
106
+ */
107
+ export function useWorldCleanup() {
108
+ afterEach(async () => {
109
+ const pending = worlds.splice(0);
110
+ const failures = await Promise.allSettled(pending.map((w) => downWorld(w.name, w.root)));
111
+ const errors = failures.filter((f) => f.status === 'rejected');
112
+ // Reported, never swallowed: a world that cannot be torn down is a real finding about the
113
+ // runtime, and silently ignoring it is how this stayed invisible for so long.
114
+ if (errors.length) {
115
+ throw new Error(`world teardown failed for ${errors.length}/${pending.length} world(s): ${errors.map((e) => String(e.reason)).join('; ')}`);
116
+ }
117
+ });
118
+ }
119
+ export function writeRaceConfig(root, id) {
120
+ mkdirSync(join(root, 'worlds', 'configs'), { recursive: true });
121
+ writeFileSync(join(root, 'worlds', 'configs', `${id}.json`), JSON.stringify({
122
+ id,
123
+ services: [
124
+ {
125
+ id: 'app',
126
+ type: 'process',
127
+ command: 'node',
128
+ args: ['-e', "require('node:http').createServer((_req,res)=>res.end('ok')).listen(Number(process.env.PORT),'127.0.0.1')"],
129
+ env: { NODE_OPTIONS: '' },
130
+ portArg: false,
131
+ rootArg: false,
132
+ },
133
+ ],
134
+ }));
135
+ }
136
+ /** Bounded wait (no fixed sleeps) for SIGTERM'd services to actually exit after downWorld. */
137
+ export async function waitForWorldDown(name, root) {
138
+ const deadline = Date.now() + 5_000;
139
+ while (Date.now() < deadline) {
140
+ if (!statusWorld(name, root).running)
141
+ return true;
142
+ await new Promise((r) => setTimeout(r, 50));
143
+ }
144
+ return !statusWorld(name, root).running;
145
+ }
146
+ export function pidAlive(pid) {
147
+ try {
148
+ process.kill(pid, 0);
149
+ return true;
150
+ }
151
+ catch {
152
+ return false;
153
+ }
154
+ }
155
+ // --- TWIN-60: `--mode sealed` must actually ENFORCE strict egress (VOLTER_TWIN_STRICT_EGRESS),
156
+ // not just record intent (VOLTER_WORLD_SEALED) that nothing consumes. One vendor twin (s3) is
157
+ // enough to exercise both enforcement points: the Node injector (control-plane/inject.cjs, HTTP
158
+ // path) and the ambient TLS redirect proxy (redirect-proxy.ts, CONNECT path).
159
+ export function writeSingleTwinConfig(root, id) {
160
+ mkdirSync(join(root, 'worlds', 'configs'), { recursive: true });
161
+ writeFileSync(join(root, 'worlds', 'configs', `${id}.json`), JSON.stringify({
162
+ id,
163
+ services: [
164
+ {
165
+ id: 's3',
166
+ command: 'node',
167
+ args: ['-e', "require('node:http').createServer((_req,res)=>res.end('s3')).listen(Number(process.env.PORT),'127.0.0.1')"],
168
+ env: { NODE_OPTIONS: '' },
169
+ injectEnv: 'S3_TWIN_URL',
170
+ portArg: false,
171
+ rootArg: false,
172
+ },
173
+ ],
174
+ }));
175
+ }
176
+ /** Twin + a second (non-twin) service — the proxy-attempt loop tries the ambient proxy again
177
+ * before starting each subsequent service, so this reaches the ambient proxy regardless of
178
+ * whether the single/last-service re-evaluation (TWIN-64) has landed yet — kept independent of
179
+ * that fix so this CONNECT-level test exercises TWIN-60's wiring in isolation. */
180
+ export function writeTwinPlusAppConfig(root, id) {
181
+ mkdirSync(join(root, 'worlds', 'configs'), { recursive: true });
182
+ writeFileSync(join(root, 'worlds', 'configs', `${id}.json`), JSON.stringify({
183
+ id,
184
+ services: [
185
+ {
186
+ id: 's3',
187
+ command: 'node',
188
+ args: ['-e', "require('node:http').createServer((_req,res)=>res.end('s3')).listen(Number(process.env.PORT),'127.0.0.1')"],
189
+ env: { NODE_OPTIONS: '' },
190
+ injectEnv: 'S3_TWIN_URL',
191
+ portArg: false,
192
+ rootArg: false,
193
+ },
194
+ {
195
+ id: 'app',
196
+ type: 'process',
197
+ command: 'node',
198
+ args: ['-e', "require('node:http').createServer((_req,res)=>res.end('ok')).listen(Number(process.env.PORT),'127.0.0.1')"],
199
+ env: { NODE_OPTIONS: '' },
200
+ portArg: false,
201
+ rootArg: false,
202
+ },
203
+ ],
204
+ }));
205
+ }
@@ -0,0 +1,256 @@
1
+ import { type CaTrustInputs } from './ca-trust.js';
2
+ import type { WorldInstance, WorldIsolation, WorldMode, WorldRunOutcome } from './schema.js';
3
+ /** The World's events file: every service's start and end, and the World's own stops, one JSON line each. */
4
+ export declare function worldEventsPath(logs: string): string;
5
+ export type UpWorldOptions = {
6
+ owner?: string;
7
+ name?: string;
8
+ root?: string;
9
+ mode?: WorldMode;
10
+ /** Overrides the config's `isolation` for this boot (e.g. force 'process' for a share world). */
11
+ isolation?: WorldIsolation;
12
+ envFile?: string;
13
+ share?: Omit<ShareWorldOptions, 'root' | 'service' | 'verifyPath'> & {
14
+ verifyPath?: string | false;
15
+ };
16
+ /** `checkout`: bring a stopped world back with its state — the data dir, marks, changesets and
17
+ * episodes survive the boot; logs and the pid file start clean. Never the default: `up` starts
18
+ * clean, and the world dir is the reproducible story. */
19
+ keepState?: boolean;
20
+ };
21
+ export type ShareWorldOptions = {
22
+ root?: string;
23
+ service?: string;
24
+ provider?: 'cloudflare-quick' | 'command';
25
+ command?: string;
26
+ args?: string[];
27
+ ephemeral?: boolean;
28
+ timeoutMs?: number;
29
+ verifyPath?: string | false;
30
+ };
31
+ export type ShareWorldServicesOptions = Omit<ShareWorldOptions, 'service' | 'verifyPath'> & {
32
+ service?: string;
33
+ verifyPath?: string | false;
34
+ };
35
+ export type WorldDoctorCheck = {
36
+ id: string;
37
+ ok: boolean;
38
+ message: string;
39
+ };
40
+ export type WorldDoctorReport = {
41
+ name: string;
42
+ ok: boolean;
43
+ checks: WorldDoctorCheck[];
44
+ };
45
+ export type RunWorldOptions = UpWorldOptions & {
46
+ keep?: boolean;
47
+ verbose?: boolean;
48
+ };
49
+ export type WorldUrlInfo = {
50
+ name: string;
51
+ mode: WorldMode;
52
+ services: Record<string, {
53
+ localUrl: string;
54
+ publicUrl?: string;
55
+ publicReady?: boolean;
56
+ }>;
57
+ };
58
+ /** Exported for the reflect attachment (reflect.ts), whose routes file lives in
59
+ * the instance dir — one layout owner, no duplicated path knowledge. */
60
+ /** THE WORLD CLOCK file (TWIN-PROGRAMMING-MODEL P3): a single frozen ISO instant every twin
61
+ * stamps from (kernel worldNow()); absent → real wall-clock. Written only by the
62
+ * `volter-world clock` operator door — time is physics, advanced explicitly, never drifting. */
63
+ export declare function clockFile(root: string, name: string): string;
64
+ export declare function instanceDir(root: string, name: string): string;
65
+ export type SharingClaim = {
66
+ token: string;
67
+ pid: number;
68
+ hostname: string;
69
+ serviceId?: string;
70
+ claimedAt: string;
71
+ heartbeatAt: string;
72
+ };
73
+ /** Exported for direct drive: the liveness rule, pure over a claim. */
74
+ export declare function sharingClaimAlive(claim: SharingClaim): boolean;
75
+ /** The process's environment as the world admits it: what `stripEnv` names stays outside. */
76
+ export declare function admittedEnv(strip: string[] | undefined, env?: NodeJS.ProcessEnv): Record<string, string>;
77
+ export declare function saveWorldInstance(instance: WorldInstance): void;
78
+ export declare function upWorld(configId: string, options?: UpWorldOptions, abort?: AbortSignal): Promise<WorldInstance>;
79
+ export declare function downWorld(name: string, root?: string, options?: {
80
+ graceMs?: number;
81
+ purge?: boolean;
82
+ expectedCreatedAt?: string;
83
+ }): Promise<{
84
+ name: string;
85
+ stopped: number[];
86
+ escalated: number[];
87
+ externalStopped: string[];
88
+ externalErrors: string[];
89
+ notSignalled?: string[];
90
+ purged?: string;
91
+ }>;
92
+ /** A service's state as a process, not only its leader: `lingering` is a service whose World parent ended while its
93
+ * process group still holds live processes (a server's workers, a runtime's child), which `down` still stops. */
94
+ export type ServiceState = 'running' | 'stopped' | 'lingering' | 'gave-up' | 'external';
95
+ export declare function statusWorld(name: string, root?: string, options?: {
96
+ readOnly?: boolean;
97
+ light?: boolean;
98
+ }): WorldInstance & {
99
+ running: boolean;
100
+ livePids: number[];
101
+ serviceStates: Record<string, ServiceState>;
102
+ degraded: boolean;
103
+ };
104
+ export type WorldCleanupEntry = {
105
+ name: string;
106
+ path: string;
107
+ eligible: boolean;
108
+ reason: string;
109
+ stoppedAt?: string;
110
+ logicalBytes?: number;
111
+ removed?: boolean;
112
+ };
113
+ /** Preview by default. Apply holds up's lock across fresh eligibility checking and deletion.
114
+ * Never call down here: that would turn a stale preview into permission to kill a new World. */
115
+ export declare function pruneWorlds(options?: {
116
+ root?: string;
117
+ name?: string;
118
+ apply?: boolean;
119
+ }): {
120
+ root: string;
121
+ apply: boolean;
122
+ entries: WorldCleanupEntry[];
123
+ };
124
+ export declare function parseCloudflareQuickPublicUrl(candidate: string): string | undefined;
125
+ export declare function shareWorld(name: string, options?: ShareWorldOptions): Promise<WorldInstance>;
126
+ export declare function shareWorldServices(name: string, options?: ShareWorldServicesOptions): Promise<WorldInstance>;
127
+ /** TWIN-62: `unshare` must give tunnels the same contract plain `downWorld` already gives owned
128
+ * services — SIGTERM, confirm dead (escalating to SIGKILL past the grace), and only THEN erase the
129
+ * tunnel facts/pids. A best-effort SIGTERM with no confirmation can leave a `cloudflared` that
130
+ * ignores SIGTERM serving the public URL forever while instance.json (and the pids file) say
131
+ * nothing is shared — the exact "half-shared" state docs/concepts/worlds.md promises cannot exist. Stays
132
+ * synchronous (via `killSurvivorsAfterGraceSync`) to match the existing signature/call sites
133
+ * (the CLI does not await it). Returns the instance plus the tunnel pids that needed escalating,
134
+ * for callers that want to report it. */
135
+ export declare function unshareWorld(name: string, options?: {
136
+ root?: string;
137
+ service?: string;
138
+ graceMs?: number;
139
+ }): WorldInstance & {
140
+ escalated: number[];
141
+ };
142
+ export declare function doctorWorld(name: string, options?: {
143
+ root?: string;
144
+ verifyPublic?: boolean;
145
+ timeoutMs?: number;
146
+ }): Promise<WorldDoctorReport>;
147
+ export declare function urlsWorld(name: string, root?: string): WorldUrlInfo;
148
+ /**
149
+ * The clean (unquoted) base URL(s) of a RUNNING world's services — the `volter-world url`
150
+ * surface. Resolved from the generated instance, so callers never shell-parse `world.env`
151
+ * (its values are single-quoted). One `{ id, url }` when `service` is named; every
152
+ * URL-bearing service otherwise ('external' services have no assigned URL — their endpoints
153
+ * are discovered env vars, so they are omitted from the list and refused by name).
154
+ */
155
+ export declare function urlWorld(name: string, options?: {
156
+ service?: string;
157
+ root?: string;
158
+ }): Array<{
159
+ id: string;
160
+ url: string;
161
+ }>;
162
+ export declare function runWorld(configId: string, command: string[], options?: RunWorldOptions): Promise<{
163
+ instance: WorldInstance;
164
+ exitCode: number;
165
+ outcome: WorldRunOutcome;
166
+ }>;
167
+ /** Internal task entry. The lifetime owner exists before admission/startup. Cancellation during
168
+ * boot is latched and handled after startup settles, so no abandoned startup can create a late
169
+ * service after teardown. Persistent --keep runs use the caller directly. */
170
+ export declare function runWorldOwned(configId: string, command: string[], options: RunWorldOptions, abort?: AbortSignal): Promise<{
171
+ instance: WorldInstance;
172
+ exitCode: number;
173
+ outcome: WorldRunOutcome;
174
+ }>;
175
+ export declare function listWorlds(root?: string): Array<{
176
+ name: string;
177
+ running: boolean;
178
+ degraded?: boolean;
179
+ config?: string;
180
+ }>;
181
+ /** Runs the command in the world's env in the foreground and resolves to its exit code. The child is
182
+ * awaited, not blocked on: a runtime that runs no synchronous child (the browser engine) still runs
183
+ * this one, and the caller sees no difference — stdio is inherited either way. */
184
+ export declare function runWithWorldEnv(name: string, command: string[], root?: string, options?: {
185
+ cwd?: string;
186
+ verbose?: boolean;
187
+ }): Promise<number>;
188
+ /** Local CLI attachment with one instance-bound consumer record per concurrent command.
189
+ * The synchronous SDK method remains compatible; its consumers are explicitly untracked. */
190
+ export type ConsumerRetirement = {
191
+ id: string;
192
+ owner?: string;
193
+ retired: boolean;
194
+ reason: string;
195
+ };
196
+ /**
197
+ * The owner's explicit resolution of uncertain attachments (never automatic, never implied by age).
198
+ * A record is retired only when it belongs to this host and the running instance, its runner and
199
+ * its command are both gone (ESRCH), and no live process remains in the command's process group
200
+ * (`survivingOwnedGroups`, the lifecycle's own retirement evidence); anything else is kept with the
201
+ * reason, surviving members named by their group. Active consumers are never touched.
202
+ */
203
+ export declare function retireWorldConsumers(name: string, root?: string, options?: {
204
+ consumer?: string;
205
+ }): ConsumerRetirement[];
206
+ export declare function attachWorld(name: string, command: string[], root?: string, options?: {
207
+ cwd?: string;
208
+ owner?: string;
209
+ verbose?: boolean;
210
+ }): Promise<number>;
211
+ /** The shell + env + cwd a `volter-world shell` subshell launches with (extracted for testability):
212
+ * the world env (twin `*_URL`s + fake keys + the Node injector + cliRedirect endpoint vars) on top
213
+ * of the caller's env, plus `VOLTER_WORLD` so a prompt can show which world is active. The ambient
214
+ * TLS-proxy env (HTTPS_PROXY + the CA-trust vars) is layered in by `shellWorld` once the proxy is up,
215
+ * since starting a server is async; this pure helper stays synchronous + side-effect-free for tests. */
216
+ export declare function worldShellEnv(name: string, root?: string): {
217
+ shell: string;
218
+ cwd: string;
219
+ env: Record<string, string>;
220
+ };
221
+ /** Drop into an interactive subshell with the world active — the contained sibling of `activate`
222
+ * (which wires your CURRENT shell + sets the `(world:<name>)` prompt marker). The subshell gets the
223
+ * world env AND, when openssl is available, an in-process ambient TLS redirect proxy: unmodified
224
+ * `gh`/`stripe`/`curl`/app in the subshell transparently hit the twins. Vendor calls in this subshell
225
+ * land in the twins; `exit` restores normal (the proxy + its session CA are torn down). The CA is
226
+ * trusted ONLY via this subshell's env — never installed system-wide. See docs/guides/route-a-cli-through-the-world.md. */
227
+ export declare function shellWorld(name: string, root?: string): Promise<number>;
228
+ /** Ensure a DETACHED ambient-redirect proxy daemon is running for this world (used by `activate`,
229
+ * which only emits a script and can't host an in-process server for the life of the shell). Idempotent:
230
+ * reuses a live daemon. Returns its proxy env, or null if openssl is missing (env-only fallback). The
231
+ * daemon is recorded in `proxy.json` and stopped by `down`. */
232
+ export declare function ensureWorldProxy(name: string, root?: string, caller?: CaTrustInputs): {
233
+ url: string;
234
+ caCertPath: string;
235
+ env: Record<string, string>;
236
+ } | null;
237
+ /** The detached proxy daemon's body (invoked as its own process by `ensureWorldProxy`). Starts the
238
+ * redirect proxy bound to an ephemeral port, records its pid/url/CA in `proxy.json`, and stays up
239
+ * until killed by `down`. Exported so the daemon entry file can delegate to it. */
240
+ export declare function runProxyDaemon(name: string, root?: string, envFile?: string): Promise<void>;
241
+ /**
242
+ * Emit a POSIX (bash/zsh) script to `eval` that ACTIVATES the world in the current shell — a
243
+ * virtualenv for vendor APIs. It exports the world env (twin `*_URL`s + fake keys + the Node
244
+ * injector + any `cliRedirect` endpoint vars), prepends `(world:<name>)` to the prompt so you can
245
+ * always see you're pointed at twins (a safety signal, not cosmetic), and defines a `deactivate`
246
+ * function that restores the prompt and unsets the vars. Usage: `eval "$(volter-world activate dev)"`.
247
+ *
248
+ * It ALSO starts (idempotently) the ambient TLS redirect proxy daemon for this world and exports
249
+ * `HTTPS_PROXY`/`HTTP_PROXY` + the CA-trust vars (`NODE_EXTRA_CA_CERTS`, `CURL_CA_BUNDLE`, …) so an
250
+ * UNMODIFIED `gh`/`stripe`/`curl`/app in the shell transparently hits the twins with zero per-tool
251
+ * config (WORLD_ACTIVATE.md tier 1). The session CA is trusted ONLY via these per-shell exports,
252
+ * never system-wide; `down` stops the daemon and drops the CA. If openssl is unavailable the script
253
+ * still emits the env-only redirect (Phase-1 behavior) — it just skips the proxy exports.
254
+ * See docs/guides/route-a-cli-through-the-world.md.
255
+ */
256
+ export declare function activateScript(name: string, root?: string): string;