@volter/world-runtime 2.0.0 → 2.0.2

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 (78) hide show
  1. package/dist/known-external-services.json +0 -8
  2. package/dist/src/app-url.js +1 -1
  3. package/dist/src/branch.js +79 -2
  4. package/dist/src/catalog.js +1 -1
  5. package/dist/src/cli.js +50 -22
  6. package/dist/src/console-apart.d.ts +1 -0
  7. package/dist/src/console-apart.js +7 -0
  8. package/dist/src/covers.js +4 -2
  9. package/dist/src/fixture-env.d.ts +3 -0
  10. package/dist/src/fixture-env.js +24 -0
  11. package/dist/src/host-worker.js +2 -9
  12. package/dist/src/host.js +2 -9
  13. package/dist/src/import-module.d.ts +1 -0
  14. package/dist/src/import-module.js +16 -0
  15. package/dist/src/index.d.ts +7 -2
  16. package/dist/src/index.js +4 -1
  17. package/dist/src/infra-cli.js +61 -12
  18. package/dist/src/init.d.ts +1 -1
  19. package/dist/src/init.js +34 -5
  20. package/dist/src/local-branches.d.ts +37 -0
  21. package/dist/src/local-branches.js +193 -0
  22. package/dist/src/pglite-backing.d.ts +8 -0
  23. package/dist/src/pglite-backing.js +121 -35
  24. package/dist/src/pglite-host.mjs +520 -14
  25. package/dist/src/prerequisites.js +1 -1
  26. package/dist/src/process-groups.js +1 -1
  27. package/dist/src/redirect-proxy.d.ts +1 -1
  28. package/dist/src/redirect-proxy.js +4 -4
  29. package/dist/src/redis-backing.d.ts +8 -0
  30. package/dist/src/redis-backing.js +120 -0
  31. package/dist/src/root.d.ts +23 -0
  32. package/dist/src/root.js +22 -10
  33. package/dist/src/run-task.js +1 -1
  34. package/dist/src/runtime.d.ts +1 -0
  35. package/dist/src/runtime.js +37 -20
  36. package/dist/src/schema.d.ts +5 -0
  37. package/dist/src/schema.js +10 -1
  38. package/dist/src/served-world.d.ts +196 -9
  39. package/dist/src/served-world.js +847 -103
  40. package/dist/src/service-recorder.js +1 -1
  41. package/dist/src/storage-capacity.js +2 -2
  42. package/dist/src/up-task.js +1 -1
  43. package/dist/src/world-origins.d.ts +13 -0
  44. package/dist/src/world-origins.js +37 -0
  45. package/dist/src/world-view.d.ts +25 -0
  46. package/dist/src/world-view.js +110 -0
  47. package/known-external-services.json +0 -8
  48. package/package.json +10 -4
  49. package/src/app-url.ts +1 -1
  50. package/src/branch.ts +66 -2
  51. package/src/catalog.ts +1 -1
  52. package/src/cli.ts +43 -21
  53. package/src/console-apart.ts +7 -1
  54. package/src/covers.ts +4 -2
  55. package/src/fixture-env.ts +25 -0
  56. package/src/host-worker.ts +2 -1
  57. package/src/host.ts +2 -1
  58. package/src/import-module.ts +9 -0
  59. package/src/index.ts +7 -2
  60. package/src/infra-cli.ts +56 -12
  61. package/src/init.ts +34 -5
  62. package/src/local-branches.ts +173 -0
  63. package/src/pglite-backing.ts +112 -36
  64. package/src/pglite-host.mjs +520 -14
  65. package/src/prerequisites.ts +1 -1
  66. package/src/process-groups.ts +1 -1
  67. package/src/redirect-proxy.ts +4 -4
  68. package/src/redis-backing.ts +107 -0
  69. package/src/root.ts +27 -2
  70. package/src/run-task.ts +1 -1
  71. package/src/runtime.ts +38 -20
  72. package/src/schema.ts +11 -1
  73. package/src/served-world.ts +762 -85
  74. package/src/service-recorder.ts +1 -1
  75. package/src/storage-capacity.ts +2 -2
  76. package/src/up-task.ts +1 -1
  77. package/src/world-origins.ts +38 -0
  78. package/src/world-view.ts +100 -0
@@ -0,0 +1,8 @@
1
+ import type { InfraService } from './pglite-backing.js';
2
+ /** The redis twin's cli: installed beside this runtime (its node_modules walk), else beside the World's config. */
3
+ export declare function redisTwinCli(from: string[]): string | undefined;
4
+ /** Where the service's keys live: the World's tree for the `redis` service, under the World's data. */
5
+ export declare const redisTwinRoot: (dataDir: string) => string;
6
+ export declare function redisTwinUp(services: InfraService[], dataDir: string, cli: string): Promise<void>;
7
+ export declare function redisTwinStatus(services: InfraService[], dataDir: string): Promise<number>;
8
+ export declare function redisTwinDown(services: InfraService[], dataDir: string): Promise<void>;
@@ -0,0 +1,120 @@
1
+ // The containerless backing for a World's managed REDIS: the redis twin (@volter/twin-redis) serving Redis's
2
+ // own protocol on the loopback port the definition declares, its keys held in the World's tree under
3
+ // VOLTER_WORLD_DATA. Chosen by `volter-world-infra` for every redis service unless the operator forces the
4
+ // container backing (VOLTER_WORLD_INFRA_BACKING=docker); the definition, the injected REDIS_URL and the declared
5
+ // service contract are the same either way, as the boundary comment in infra-cli.ts promises.
6
+ //
7
+ // Why the twin and not a container: a World's state branches, checkpoints and rolls back as one tree, and a Redis
8
+ // in a container is state outside it (a branch of the World would share the container's queue and cache). The
9
+ // twin keeps each key a subject of the World (packages/twin/redis/src/redis-storage.ts). It needs nothing
10
+ // installed: no container runtime, no redis-server binary.
11
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
12
+ import { connect } from 'node:net';
13
+ import { dirname, join } from 'node:path';
14
+ import { spawn } from 'node:child_process';
15
+ import { findInstalledPackage, packCli } from "./catalog.js";
16
+ const PACKAGE = '@volter/twin-redis';
17
+ const READY_TIMEOUT_MS = 60_000;
18
+ /** The redis twin's cli: installed beside this runtime (its node_modules walk), else beside the World's config. */
19
+ export function redisTwinCli(from) {
20
+ for (const dir of from) {
21
+ const found = findInstalledPackage(dir, PACKAGE);
22
+ const cli = found === undefined ? undefined : packCli(found);
23
+ if (cli !== undefined && existsSync(cli))
24
+ return cli;
25
+ }
26
+ return undefined;
27
+ }
28
+ const pidPath = (dataDir, service) => join(dataDir, `redis-twin-${service.hostPort}.pid`);
29
+ /** Where the service's keys live: the World's tree for the `redis` service, under the World's data. */
30
+ export const redisTwinRoot = (dataDir) => join(dataDir, 'redis-twin');
31
+ function alive(pid) {
32
+ try {
33
+ process.kill(pid, 0);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ function listening(port) {
41
+ return new Promise((resolve) => {
42
+ const socket = connect({ host: '127.0.0.1', port, timeout: 1000 });
43
+ // a port that answers PING with PONG is this service; anything else is not ready (or not ours)
44
+ socket.once('connect', () => socket.write('PING\r\n'));
45
+ socket.once('data', (chunk) => { socket.destroy(); resolve(chunk.toString('latin1').startsWith('+PONG')); });
46
+ socket.on('error', () => resolve(false));
47
+ socket.on('timeout', () => { socket.destroy(); resolve(false); });
48
+ });
49
+ }
50
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
51
+ export async function redisTwinUp(services, dataDir, cli) {
52
+ for (const service of services) {
53
+ const pidFile = pidPath(dataDir, service);
54
+ if (existsSync(pidFile) && alive(Number(readFileSync(pidFile, 'utf8').trim())) && await listening(service.hostPort))
55
+ continue;
56
+ // a port that already answers is another process's (another World's twin, a redis-server): serving there would
57
+ // hand this World's app someone else's keys, so it is refused, never taken as ready
58
+ if (await listening(service.hostPort))
59
+ throw new Error(`port ${service.hostPort} already serves Redis for another process`);
60
+ const root = redisTwinRoot(dataDir);
61
+ mkdirSync(root, { recursive: true });
62
+ const log = join(dataDir, `redis-twin-${service.hostPort}.log`);
63
+ mkdirSync(dirname(log), { recursive: true });
64
+ // a real fd, not a pipe: the server outlives this process untethered
65
+ const logFd = openSync(log, 'a');
66
+ // the runtime this runs on: Bun runs the pack's TypeScript as it is; Node (volter-world-infra's shebang) needs
67
+ // type transformation, not only stripping (the kernel declares constructor parameter properties)
68
+ const runtime = typeof globalThis.Bun !== 'undefined' || !cli.endsWith('.ts') ? [] : ['--experimental-transform-types', '--no-warnings'];
69
+ const child = spawn(process.execPath, [...runtime, cli, 'serve', '--port', String(service.hostPort), '--root', root], {
70
+ detached: true,
71
+ stdio: ['ignore', logFd, logFd],
72
+ });
73
+ child.unref();
74
+ closeSync(logFd);
75
+ writeFileSync(pidFile, `${child.pid}\n`);
76
+ const deadline = Date.now() + READY_TIMEOUT_MS;
77
+ while (!(await listening(service.hostPort)) || !alive(child.pid)) {
78
+ if (!alive(child.pid))
79
+ throw new Error(`redis exited before serving — see ${log}`);
80
+ if (Date.now() > deadline)
81
+ throw new Error(`redis did not serve port ${service.hostPort} within ${READY_TIMEOUT_MS / 1000}s — see ${log}`);
82
+ await sleep(200);
83
+ }
84
+ }
85
+ }
86
+ export async function redisTwinStatus(services, dataDir) {
87
+ let ready = 0;
88
+ for (const service of services) {
89
+ const pidFile = pidPath(dataDir, service);
90
+ if (!existsSync(pidFile))
91
+ continue;
92
+ if (alive(Number(readFileSync(pidFile, 'utf8').trim())) && await listening(service.hostPort))
93
+ ready += 1;
94
+ }
95
+ return ready;
96
+ }
97
+ export async function redisTwinDown(services, dataDir) {
98
+ for (const service of services) {
99
+ const pidFile = pidPath(dataDir, service);
100
+ if (!existsSync(pidFile))
101
+ continue;
102
+ const pid = Number(readFileSync(pidFile, 'utf8').trim());
103
+ if (alive(pid)) {
104
+ try {
105
+ process.kill(pid, 'SIGTERM');
106
+ }
107
+ catch { /* already gone */ }
108
+ const deadline = Date.now() + 5000;
109
+ while (alive(pid) && Date.now() < deadline)
110
+ await sleep(100);
111
+ if (alive(pid)) {
112
+ try {
113
+ process.kill(pid, 'SIGKILL');
114
+ }
115
+ catch { /* raced */ }
116
+ }
117
+ }
118
+ rmSync(pidFile, { force: true });
119
+ }
120
+ }
@@ -36,6 +36,29 @@ export declare function rootForControlRoot(controlRoot: string, service: string)
36
36
  /** The pack's mirror module (`src/<vendor>-mirror-ui.ts`: its shell, client and stylesheet), read as the
37
37
  * hosted mirror build reads it (apps/cloud/scripts/mirrors.ts); a pack's index need not re-export them. */
38
38
  export declare function packMirrorExports(vendor: string, from: string): Promise<Record<string, unknown> | null>;
39
+ /** A pack's mirror as the pack builds it (`<vendor>MirrorHtml`, `build<Vendor>MirrorClient`,
40
+ * `<vendor>MirrorStyles`), found the way a World finds the pack; null when the pack has none. The
41
+ * client builds at serve under Bun and is served prebuilt under Node, by the pack's own builder. */
42
+ export type PackMirror = {
43
+ html: () => string;
44
+ client: () => Promise<string>;
45
+ styles?: () => Promise<string>; /** the account and a credential its screens sign in with (`<vendor>MirrorSignIn`): the account found in the twin's
46
+ * state at `root`, the credential minted for it through the twin's own doors (`twin`), never a stored one read back */
47
+ signIn?: (as: string, ctx: MirrorSignInContext) => Promise<{
48
+ account: string;
49
+ token: string;
50
+ } | null> | {
51
+ account: string;
52
+ token: string;
53
+ } | null;
54
+ };
55
+ /** What a pack's `<vendor>MirrorSignIn` is given: the twin's state root (to find the account) and the twin's own doors
56
+ * (to mint a credential for it, as the World's seed does through `/_twin/…`). */
57
+ export type MirrorSignInContext = {
58
+ root: string;
59
+ twin: (path: string, init?: RequestInit) => Promise<Response>;
60
+ };
61
+ export declare function packMirror(vendor: string, from: string): Promise<PackMirror | null>;
39
62
  /** The real state system's adapters for a vendor: registered with the pack (protocol 2), else the
40
63
  * pack's anchored exports (protocol 1: `perform<Name>Action`, `sync<Name>FromRemote`). */
41
64
  export type IngestAdapter = (request: Request, ctx: {
package/dist/src/root.js CHANGED
@@ -1,11 +1,3 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
1
  // A TWIN'S ROOT (docs/concepts/the-model.md): the vendor's real account
10
2
  // behind a twin on a shared world. `volter twin <vendor> root <url> --deploy <policy>` records it,
11
3
  // key-free, in `.volter/world.json` (committed) and, at boot, in the twin's state dir (core's
@@ -22,6 +14,7 @@ import { authStrategyFor, getActiveWorldStore, getPack, sealingKey, buildRemoteE
22
14
  import { loadWorldConfig, writeWorldConfig } from "./configs.js";
23
15
  import { changesetReadiness, collectObservations, foldObservations, loadChecks, openRootCredential, performEntries, rootCustody, protocolStanding } from '@volter/world-core';
24
16
  import { findWorldChangeset, listWorldChangesets } from "./changeset.js";
17
+ import { importModule } from "./import-module.js";
25
18
  import { statusWorld } from "./runtime.js";
26
19
  import { findInstalledPackage, twinPackageName } from "./catalog.js";
27
20
  // ── the user's sealing key ──────────────────────────────────────────────────────────────────
@@ -125,7 +118,7 @@ async function packModule(vendor, from) {
125
118
  if (registered)
126
119
  return { pack: registered };
127
120
  const entry = packEntryPath(vendor, from);
128
- return entry ? (await import(__rewriteRelativeImportExtension(entry))) : null;
121
+ return entry ? await importModule(entry) : null;
129
122
  }
130
123
  /** The pack's module itself, installed beside the World or in this checkout: its every export (a
131
124
  * mirror's shell and client), where the registry holds only the descriptor. */
@@ -144,7 +137,26 @@ export async function packMirrorExports(vendor, from) {
144
137
  return null;
145
138
  const dir = dirname(entry);
146
139
  const file = existsSync(dir) ? readdirSync(dir).find((f) => /-mirror-ui\.(ts|js)$/.test(f) && !f.includes('.test.')) : undefined;
147
- return (await import(__rewriteRelativeImportExtension(file ? join(dir, file) : entry)));
140
+ return importModule(file ? join(dir, file) : entry);
141
+ }
142
+ export async function packMirror(vendor, from) {
143
+ const installed = findInstalledPackage(from, twinPackageName(vendor));
144
+ const dir = installed ?? resolve(import.meta.dir, '..', '..', 'twin', vendor);
145
+ // the pack's mirror module, as the hosted build reads it (its index need not re-export every builder)
146
+ const src = join(dir, 'src');
147
+ const file = existsSync(src) ? readdirSync(src).find((f) => f.endsWith('-mirror-ui.ts')) : undefined;
148
+ const entry = file ? join(src, file) : installed ? packEntry(installed) : join(src, 'index.ts');
149
+ if (!existsSync(entry))
150
+ return null;
151
+ const mod = await importModule(entry);
152
+ const pick = (re) => { const k = Object.keys(mod).find((key) => re.test(key) && typeof mod[key] === 'function'); return k ? mod[k] : undefined; };
153
+ const html = pick(/^\w+MirrorHtml$/);
154
+ const client = pick(/^build\w+MirrorClient$/);
155
+ const styles = pick(/^\w+MirrorStyles$/);
156
+ const signIn = pick(/^\w+MirrorSignIn$/);
157
+ if (!html || !client)
158
+ return null;
159
+ return { html: html, client: client, ...(styles ? { styles: styles } : {}), ...(signIn ? { signIn: signIn } : {}) };
148
160
  }
149
161
  export async function adaptersFor(vendor, from) {
150
162
  const registered = stateSystemFor(vendor);
@@ -7,7 +7,7 @@ import { siblingScript } from "./sibling.js";
7
7
  * The task process is outside the caller's POSIX process group and owns boot through teardown. */
8
8
  export function superviseWorldRun(configId, command, options) {
9
9
  return new Promise((done, reject) => {
10
- const child = spawn(process.execPath, [siblingScript(import.meta.url, 'run-task-worker')], {
10
+ const child = spawn(process.execPath, [siblingScript(import.meta.url, 'run-task-worker')], { windowsHide: true,
11
11
  cwd: process.cwd(), env: process.env, detached: process.platform !== 'win32',
12
12
  stdio: ['inherit', 'inherit', 'inherit', 'ipc'], serialization: 'json',
13
13
  });
@@ -122,6 +122,7 @@ export declare function pruneWorlds(options?: {
122
122
  entries: WorldCleanupEntry[];
123
123
  };
124
124
  export declare function parseCloudflareQuickPublicUrl(candidate: string): string | undefined;
125
+ export declare function ownCommand(command: string[]): string[];
125
126
  export declare function shareWorld(name: string, options?: ShareWorldOptions): Promise<WorldInstance>;
126
127
  export declare function shareWorldServices(name: string, options?: ShareWorldServicesOptions): Promise<WorldInstance>;
127
128
  /** TWIN-62: `unshare` must give tunnels the same contract plain `downWorld` already gives owned
@@ -17,7 +17,7 @@ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
17
17
  import { packFacts } from "./pack-facts.js";
18
18
  import { describeServiceEnd, readServiceEnd, serviceOutput } from "./service-exit.js";
19
19
  import { Socket } from 'node:net';
20
- import { getActiveWorldStore, withFileLock, stateDirName, PROTOCOL_VERSION, PROTOCOL_MAJOR, WORLD_BOOT_PATH, WORLD_ENV_NAMES_ENV } from '@volter/world-core';
20
+ import { getActiveWorldStore, withFileLock, stateDirName, PROTOCOL_VERSION, PROTOCOL_MAJOR, WORLD_BOOT_PATH, WORLD_DATA_ENV, WORLD_ENV_NAMES_ENV } from '@volter/world-core';
21
21
  import { loadWorldConfig } from "./configs.js";
22
22
  import { createWorldNetworkPolicy, WORLD_NETWORK_POLICY_ENV } from '@volter/world-core/network-policy';
23
23
  import { resolveServicePackage } from "./catalog.js";
@@ -65,7 +65,7 @@ function spawnRecorded(serviceId, command, args, cwd, env, log) {
65
65
  recorderEnv.VOLTER_WORLD_SERVICE_NODE_OPTIONS = recorderEnv.NODE_OPTIONS;
66
66
  delete recorderEnv.NODE_OPTIONS;
67
67
  }
68
- const child = spawn(process.execPath, [siblingScript(import.meta.url, 'service-recorder'), command, ...args], {
68
+ const child = spawn(process.execPath, [siblingScript(import.meta.url, 'service-recorder'), command, ...args], { windowsHide: true,
69
69
  cwd,
70
70
  env: recorderEnv,
71
71
  detached: true,
@@ -614,7 +614,7 @@ function processStarts(pids) {
614
614
  }
615
615
  return starts;
616
616
  }
617
- const result = spawnSync('ps', ['-o', 'pid=,lstart=', '-p', pids.join(',')], { encoding: 'utf8', timeout: 1000, killSignal: 'SIGKILL', env: { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } });
617
+ const result = spawnSync('ps', ['-o', 'pid=,lstart=', '-p', pids.join(',')], { windowsHide: true, encoding: 'utf8', timeout: 1000, killSignal: 'SIGKILL', env: { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } });
618
618
  for (const line of (result.stdout ?? '').split('\n')) {
619
619
  const match = /^\s*(\d+)\s+(.+?)\s*$/.exec(line);
620
620
  if (match)
@@ -762,13 +762,13 @@ function getByJsonPath(value, jsonPath) {
762
762
  return current;
763
763
  }
764
764
  async function runExternalCommand(command, cwd, serviceId, phase, env = process.env, abort, timeoutMs = 60_000) {
765
- const [bin, ...args] = command;
765
+ const [bin, ...args] = ownCommand(command);
766
766
  if (!bin)
767
767
  throw new Error(`External service "${serviceId}": ${phase} command is empty`);
768
768
  if (!commandExists(bin))
769
769
  throw new Error(`External service "${serviceId}": \`${bin}\` not found on PATH (the ${phase} command's tool is not installed)`);
770
770
  abort?.throwIfAborted();
771
- const child = spawn(bin, args, { cwd, env, detached: commandProcessGroup, stdio: ['ignore', 'pipe', 'pipe'] });
771
+ const child = spawn(bin, args, { windowsHide: true, cwd, env, detached: commandProcessGroup, stdio: ['ignore', 'pipe', 'pipe'] });
772
772
  const lifetime = commandLifetime(child, abort, true);
773
773
  let stdout = '';
774
774
  let stderr = '';
@@ -803,7 +803,7 @@ async function runExternalCommand(command, cwd, serviceId, phase, env = process.
803
803
  * (instead of buffering through spawnSync's 1MB cap, which a large DB restore would overflow). Returns
804
804
  * the combined output so `discover` can read it. Throws with a bounded tail on nonzero exit. */
805
805
  async function runExternalUp(command, cwd, serviceId, env, logPath, abort) {
806
- const [bin, ...args] = command;
806
+ const [bin, ...args] = ownCommand(command);
807
807
  if (!bin)
808
808
  throw new Error(`External service "${serviceId}": up command is empty`);
809
809
  if (!commandExists(bin)) {
@@ -815,7 +815,7 @@ async function runExternalUp(command, cwd, serviceId, env, logPath, abort) {
815
815
  const chunks = [];
816
816
  try {
817
817
  abort?.throwIfAborted();
818
- const child = spawn(bin, args, { cwd, env, detached: commandProcessGroup, stdio: ['ignore', 'pipe', 'pipe'] });
818
+ const child = spawn(bin, args, { windowsHide: true, cwd, env, detached: commandProcessGroup, stdio: ['ignore', 'pipe', 'pipe'] });
819
819
  const lifetime = commandLifetime(child, abort, true);
820
820
  let failure;
821
821
  child.on('error', error => { failure = error; });
@@ -1453,7 +1453,8 @@ export async function upWorld(configId, options = {}, abort) {
1453
1453
  VOLTER_WORLD_MODE: mode,
1454
1454
  VOLTER_WORLD_CONFIG: loaded.path,
1455
1455
  VOLTER_WORLD_INSTANCE: instanceFile,
1456
- VOLTER_WORLD_DATA: data,
1456
+ // every service's root is `<data>/<service id>`; the kernel finds another pack's store across them (A3, ownerStoreRoots)
1457
+ [WORLD_DATA_ENV]: data,
1457
1458
  // the live env file this `up` writes: what a service that runs apps elsewhere (world-machine's guest) carries there
1458
1459
  VOLTER_WORLD_ENV_FILE: envFile,
1459
1460
  // THE WORLD CLOCK: every service stamps from this file via the kernel's worldNow();
@@ -2220,11 +2221,26 @@ function sleep(ms) {
2220
2221
  }
2221
2222
  function commandExists(command) {
2222
2223
  // Pass the live env so PATH reflects the current process (some runtimes snapshot env otherwise).
2223
- const result = spawnSync('which', [command], { stdio: 'ignore', env: process.env });
2224
- return result.status === 0;
2224
+ return OWN_COMMANDS[command] !== undefined || onPath(command);
2225
+ }
2226
+ /** A lifecycle command naming this runtime's own bin (`volter-world-infra`, which `init` names in every managed
2227
+ * infrastructure's lifecycle) runs this runtime's own script with the executable running it, when nothing on PATH
2228
+ * answers the name: a bare `volter-world down` (no runner that put node_modules/.bin on PATH) failed "not found on
2229
+ * PATH" and left the infrastructure up. Only the name is mapped — no directory joins PATH — and a same-named
2230
+ * command on PATH still wins, as before. */
2231
+ const OWN_COMMANDS = { 'volter-world-infra': 'infra-cli' };
2232
+ export function ownCommand(command) {
2233
+ const [bin, ...args] = command;
2234
+ const stem = bin === undefined ? undefined : OWN_COMMANDS[bin];
2235
+ if (stem === undefined || onPath(bin))
2236
+ return command;
2237
+ return [process.execPath, siblingScript(import.meta.url, stem), ...args];
2238
+ }
2239
+ function onPath(command) {
2240
+ return spawnSync('which', [command], { windowsHide: true, stdio: 'ignore', env: process.env }).status === 0;
2225
2241
  }
2226
2242
  function resolveWithPublicDns(hostname) {
2227
- const result = spawnSync('dig', ['+short', '@1.1.1.1', hostname, 'A'], {
2243
+ const result = spawnSync('dig', ['+short', '@1.1.1.1', hostname, 'A'], { windowsHide: true,
2228
2244
  encoding: 'utf8',
2229
2245
  });
2230
2246
  if (result.status !== 0)
@@ -2260,7 +2276,7 @@ async function verifyPublicUrl(publicUrl, path, timeoutMs) {
2260
2276
  || (isIP(normalizedHostname) === 4 && normalizedHostname.split('.')[0] === '127');
2261
2277
  if (local) {
2262
2278
  const target = publicHealthUrl(publicUrl, path);
2263
- const result = spawnSync('curl', ['--noproxy', '*', '-sS', '-o', '-', '-w', '\n%{http_code}', '--max-time', String(Math.max(1, Math.ceil(timeoutMs / 1000))), target], { encoding: 'utf8' });
2279
+ const result = spawnSync('curl', ['--noproxy', '*', '-sS', '-o', '-', '-w', '\n%{http_code}', '--max-time', String(Math.max(1, Math.ceil(timeoutMs / 1000))), target], { windowsHide: true, encoding: 'utf8' });
2264
2280
  const separator = result.stdout.lastIndexOf('\n');
2265
2281
  const body = separator >= 0 ? result.stdout.slice(0, separator).trim() : result.stdout.trim();
2266
2282
  const status = Number(separator >= 0 ? result.stdout.slice(separator + 1).trim() : '0');
@@ -2291,7 +2307,7 @@ async function verifyPublicUrl(publicUrl, path, timeoutMs) {
2291
2307
  '--resolve',
2292
2308
  `${hostname}:443:${resolvedIp}`,
2293
2309
  target,
2294
- ], { encoding: 'utf8' });
2310
+ ], { windowsHide: true, encoding: 'utf8' });
2295
2311
  if (result.status === 0) {
2296
2312
  const output = result.stdout;
2297
2313
  const separator = output.lastIndexOf('\n');
@@ -2354,7 +2370,7 @@ async function shareWorldReserved(name, options) {
2354
2370
  // Each spawned tunnel owns one log generation. Truncating before spawn prevents a repeated
2355
2371
  // share from accepting the previous process's URL handshake as the new process's endpoint.
2356
2372
  const out = openPrivateLog(log, 'w');
2357
- const child = spawn(tunnel.command, tunnel.args, {
2373
+ const child = spawn(tunnel.command, tunnel.args, { windowsHide: true,
2358
2374
  cwd: root,
2359
2375
  env: process.env,
2360
2376
  detached: true,
@@ -2523,7 +2539,8 @@ async function probeExternalOnce(ready, cwd, env, logPath) {
2523
2539
  if (!commandExists(ready.command)) {
2524
2540
  return { ok: false, message: `readiness command \`${ready.command}\` not found on PATH` };
2525
2541
  }
2526
- const result = spawnSync(ready.command, ready.args ?? [], {
2542
+ const [probeBin, ...probeArgs] = ownCommand([ready.command, ...(ready.args ?? [])]);
2543
+ const result = spawnSync(probeBin, probeArgs, { windowsHide: true,
2527
2544
  cwd, encoding: 'utf8', env, timeout: DOCTOR_EXTERNAL_PROBE_TIMEOUT_MS, maxBuffer: 64 * 1024 * 1024,
2528
2545
  });
2529
2546
  if (result.status === 0)
@@ -2897,7 +2914,7 @@ export function runWithWorldEnv(name, command, root = process.cwd(), options = {
2897
2914
  throw new Error('Missing command after --');
2898
2915
  const attached = worldAttachedCommandEnv(name, root, options.verbose);
2899
2916
  return new Promise((resolveExit) => {
2900
- const child = spawn(command[0], command.slice(1), {
2917
+ const child = spawn(command[0], command.slice(1), { windowsHide: true,
2901
2918
  cwd: resolve(options.cwd ?? root),
2902
2919
  env: attached.env,
2903
2920
  stdio: 'inherit',
@@ -2981,7 +2998,7 @@ export async function attachWorld(name, command, root = process.cwd(), options =
2981
2998
  let retired = true;
2982
2999
  try {
2983
3000
  return await new Promise((done, reject) => {
2984
- const child = spawn(command[0], command.slice(1), { cwd: resolve(options.cwd ?? root), env: attached.env, stdio: 'inherit', detached: commandProcessGroup });
3001
+ const child = spawn(command[0], command.slice(1), { windowsHide: true, cwd: resolve(options.cwd ?? root), env: attached.env, stdio: 'inherit', detached: commandProcessGroup });
2985
3002
  const lifetime = commandLifetime(child);
2986
3003
  let failure;
2987
3004
  record.consumerPid = child.pid;
@@ -3082,7 +3099,7 @@ async function runWithWorldEnvLogged(name, command, root, options = {}) {
3082
3099
  let spawnError;
3083
3100
  let child;
3084
3101
  try {
3085
- child = spawn(command[0], command.slice(1), {
3102
+ child = spawn(command[0], command.slice(1), { windowsHide: true,
3086
3103
  cwd: resolve(options.cwd ?? root),
3087
3104
  env: attached.env,
3088
3105
  stdio: ['inherit', 'pipe', 'pipe'],
@@ -3189,7 +3206,7 @@ export async function shellWorld(name, root = process.cwd()) {
3189
3206
  process.stderr.write(`world '${name}' active in a subshell — vendor calls hit the twins (env-only; install openssl for ambient https redirect). type 'exit' to leave.\n`);
3190
3207
  }
3191
3208
  try {
3192
- const result = spawnSync(shell, ['-i'], { cwd, env, stdio: 'inherit' });
3209
+ const result = spawnSync(shell, ['-i'], { windowsHide: true, cwd, env, stdio: 'inherit' });
3193
3210
  return result.status ?? 0;
3194
3211
  }
3195
3212
  finally {
@@ -3297,7 +3314,7 @@ function startProxyDaemonOwned(name, root, caCertPath, envFile, boot, caller = p
3297
3314
  mkdirSync(dirname(proxyLogPath(root, name)), { recursive: true, mode: 0o700 });
3298
3315
  const proxyOut = openPrivateLog(proxyLogPath(root, name), 'a');
3299
3316
  try {
3300
- child = spawn(process.execPath, [proxyDaemonEntry(), name, '--root', root, ...(envFile ? [`--env-file=${envFile}`] : [])], {
3317
+ child = spawn(process.execPath, [proxyDaemonEntry(), name, '--root', root, ...(envFile ? [`--env-file=${envFile}`] : [])], { windowsHide: true,
3301
3318
  cwd: root, env: process.env, detached: true, stdio: ['ignore', proxyOut, proxyOut],
3302
3319
  });
3303
3320
  }
@@ -190,6 +190,11 @@ export type WorldServiceConfig = {
190
190
  * to loopback services directly while presenting a disguised world to the app/user.
191
191
  */
192
192
  controlPlane?: boolean;
193
+ /** The account a vendor's own screens (its mirror) open signed in as: its handle, username or email, which the pack
194
+ * resolves against the accounts this World holds (`<vendor>MirrorSignIn`). Nothing secret is written here. */
195
+ signIn?: {
196
+ as: string;
197
+ };
193
198
  injectEnv?: string;
194
199
  /**
195
200
  * Additional env vars exported into the generated world env after this service starts.
@@ -358,7 +358,7 @@ export function assertWorldSelection(value, path) {
358
358
  function serviceV2ToLegacy(value, path) {
359
359
  if (!record(value))
360
360
  throw new Error(`World service must be an object in ${path}`);
361
- assertKeys(value, ['id', 'type', 'description', 'source', 'execution', 'endpoint', 'bindings', 'root'], `Service ${String(value.id ?? '')}`, path, true);
361
+ assertKeys(value, ['id', 'type', 'description', 'source', 'execution', 'endpoint', 'bindings', 'root', 'signIn'], `Service ${String(value.id ?? '')}`, path, true);
362
362
  const source = value.source === undefined ? {} : value.source;
363
363
  const execution = value.execution === undefined ? {} : value.execution;
364
364
  const endpoint = value.endpoint === undefined ? {} : value.endpoint;
@@ -379,6 +379,13 @@ function serviceV2ToLegacy(value, path) {
379
379
  throw new Error(`World service must define string id in ${path}`);
380
380
  if (value.description !== undefined && typeof value.description !== 'string')
381
381
  throw new Error(`Service "${value.id}" description must be a string in ${path}`);
382
+ if (value.signIn !== undefined) {
383
+ if (!record(value.signIn))
384
+ throw new Error(`Service "${value.id}" signIn must be an object in ${path}`);
385
+ assertKeys(value.signIn, ['as'], `Service "${value.id}" signIn`, path);
386
+ if (typeof value.signIn.as !== 'string' || value.signIn.as.trim() === '')
387
+ throw new Error(`Service "${value.id}" signIn.as must name an account (a handle, username or email) in ${path}`);
388
+ }
382
389
  if (value.description !== undefined && value['//'] !== undefined)
383
390
  throw new Error(`Service "${value.id}" must not set both description and // in ${path}`);
384
391
  if (execution.cwd !== undefined && typeof execution.cwd !== 'string')
@@ -437,6 +444,7 @@ function serviceV2ToLegacy(value, path) {
437
444
  ...(bindings.injectEnvTemplates === undefined ? {} : { injectEnvTemplates: bindings.injectEnvTemplates }),
438
445
  ...(bindings.cliRedirect === undefined ? {} : { cliRedirect: bindings.cliRedirect }),
439
446
  ...(value.root === undefined ? {} : { root: value.root }),
447
+ ...(value.signIn === undefined ? {} : { signIn: { as: value.signIn.as } }),
440
448
  ...(external === undefined ? {} : { external }),
441
449
  };
442
450
  if (type !== 'external' && bindings.discover !== undefined)
@@ -573,6 +581,7 @@ function serviceToV2(service) {
573
581
  ...(Object.keys(endpoint).length ? { endpoint } : {}),
574
582
  ...(Object.keys(bindings).length ? { bindings } : {}),
575
583
  ...(service.root === undefined ? {} : { root: service.root }),
584
+ ...(service.signIn === undefined ? {} : { signIn: service.signIn }),
576
585
  };
577
586
  }
578
587
  /** Serialize normalized intent as the committed format-2 manifest. */