@volter/world-runtime 3.0.162 → 3.0.164

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 (140) hide show
  1. package/dist/src/ancestry.js +2 -2
  2. package/dist/src/app-url.d.ts +5 -1
  3. package/dist/src/app-url.js +14 -1
  4. package/dist/src/branch.js +28 -8
  5. package/dist/src/browser-files.d.ts +5 -0
  6. package/dist/src/browser-files.js +14 -0
  7. package/dist/src/browsers.d.ts +2 -3
  8. package/dist/src/browsers.js +4 -4
  9. package/dist/src/catalog.d.ts +2 -1
  10. package/dist/src/catalog.js +3 -2
  11. package/dist/src/changeset.js +1 -1
  12. package/dist/src/cli-commands.js +71 -18
  13. package/dist/src/cli-load-clock.d.ts +1 -0
  14. package/dist/src/cli-load-clock.js +2 -0
  15. package/dist/src/cli.js +5 -1
  16. package/dist/src/clickhouse-backing.d.ts +2 -0
  17. package/dist/src/clickhouse-backing.js +145 -0
  18. package/dist/src/command-lifetime.d.mts +1 -1
  19. package/dist/src/command-lifetime.mjs +5 -2
  20. package/dist/src/configs.js +1 -1
  21. package/dist/src/consumers.js +1 -1
  22. package/dist/src/covers.d.ts +4 -2
  23. package/dist/src/covers.js +8 -4
  24. package/dist/src/go-mysql-binding.d.ts +19 -0
  25. package/dist/src/go-mysql-binding.js +51 -0
  26. package/dist/src/guest-processes.d.mts +1 -1
  27. package/dist/src/guest-processes.mjs +22 -14
  28. package/dist/src/host-cli.js +8 -1
  29. package/dist/src/host-ready.d.ts +16 -2
  30. package/dist/src/host-ready.js +66 -39
  31. package/dist/src/host-worker.js +7 -1
  32. package/dist/src/host.d.ts +26 -0
  33. package/dist/src/host.js +93 -3
  34. package/dist/src/infra-definition.d.ts +35 -0
  35. package/dist/src/infra-definition.js +146 -0
  36. package/dist/src/infra.d.ts +3 -0
  37. package/dist/src/infra.js +375 -105
  38. package/dist/src/init.d.ts +46 -0
  39. package/dist/src/init.js +445 -18
  40. package/dist/src/lifecycle-record.d.ts +5 -1
  41. package/dist/src/lifecycle-record.js +27 -4
  42. package/dist/src/local-branches.js +1 -1
  43. package/dist/src/mariadb-bootstrap.d.ts +32 -0
  44. package/dist/src/mariadb-bootstrap.js +90 -0
  45. package/dist/src/mariadb-supplier.d.ts +23 -0
  46. package/dist/src/mariadb-supplier.js +215 -0
  47. package/dist/src/mongodb-backing.d.ts +4 -0
  48. package/dist/src/mongodb-backing.js +71 -1
  49. package/dist/src/native-mariadb-backing.d.ts +13 -0
  50. package/dist/src/native-mariadb-backing.js +512 -0
  51. package/dist/src/native-postgres-backing.d.ts +4 -10
  52. package/dist/src/native-postgres-backing.js +428 -43
  53. package/dist/src/native-server-inputs.d.ts +16 -0
  54. package/dist/src/native-server-inputs.js +204 -0
  55. package/dist/src/origin.js +3 -3
  56. package/dist/src/original-inputs.d.ts +14 -0
  57. package/dist/src/original-inputs.js +84 -0
  58. package/dist/src/pack-facts.d.ts +9 -1
  59. package/dist/src/pglite-backing.d.ts +41 -17
  60. package/dist/src/pglite-backing.js +213 -79
  61. package/dist/src/pglite-host.mjs +482 -10
  62. package/dist/src/process-groups.d.mts +6 -1
  63. package/dist/src/process-groups.d.ts +2 -1
  64. package/dist/src/process-groups.js +1 -1
  65. package/dist/src/process-groups.mjs +82 -15
  66. package/dist/src/process-signal.d.mts +2 -2
  67. package/dist/src/process-signal.mjs +29 -9
  68. package/dist/src/project-inspect.d.ts +12 -0
  69. package/dist/src/project-inspect.js +41 -4
  70. package/dist/src/proxy-env.d.ts +34 -0
  71. package/dist/src/proxy-env.js +114 -0
  72. package/dist/src/redirect-proxy.d.ts +1 -31
  73. package/dist/src/redirect-proxy.js +3 -112
  74. package/dist/src/redis-backing.d.ts +6 -4
  75. package/dist/src/redis-backing.js +20 -168
  76. package/dist/src/runtime.d.ts +1 -0
  77. package/dist/src/runtime.js +503 -218
  78. package/dist/src/schema.d.ts +1 -0
  79. package/dist/src/schema.js +5 -3
  80. package/dist/src/served-world.js +15 -10
  81. package/dist/src/service-exit.d.ts +3 -2
  82. package/dist/src/service-exit.js +7 -5
  83. package/dist/src/twin-backing.d.ts +47 -0
  84. package/dist/src/twin-backing.js +343 -0
  85. package/dist/src/up-cli.js +56 -3
  86. package/package.json +4 -4
  87. package/src/ancestry.ts +2 -2
  88. package/src/app-url.ts +9 -2
  89. package/src/branch.ts +26 -8
  90. package/src/browser-files.ts +15 -0
  91. package/src/browsers.ts +4 -4
  92. package/src/catalog.ts +4 -3
  93. package/src/changeset.ts +1 -1
  94. package/src/cli-commands.ts +85 -18
  95. package/src/cli-load-clock.ts +2 -0
  96. package/src/cli.ts +5 -1
  97. package/src/clickhouse-backing.ts +70 -0
  98. package/src/command-lifetime.d.mts +1 -1
  99. package/src/command-lifetime.mjs +5 -2
  100. package/src/configs.ts +1 -1
  101. package/src/consumers.ts +1 -1
  102. package/src/covers.ts +9 -4
  103. package/src/go-mysql-binding.ts +49 -0
  104. package/src/guest-processes.d.mts +1 -1
  105. package/src/guest-processes.mjs +22 -14
  106. package/src/host-cli.ts +7 -1
  107. package/src/host-ready.ts +62 -31
  108. package/src/host-worker.ts +6 -2
  109. package/src/host.ts +88 -6
  110. package/src/infra-definition.ts +135 -0
  111. package/src/infra.ts +250 -45
  112. package/src/init.ts +416 -17
  113. package/src/lifecycle-record.ts +18 -4
  114. package/src/local-branches.ts +1 -1
  115. package/src/mariadb-bootstrap.ts +107 -0
  116. package/src/mariadb-supplier.ts +199 -0
  117. package/src/mongodb-backing.ts +59 -1
  118. package/src/native-mariadb-backing.ts +418 -0
  119. package/src/native-postgres-backing.ts +353 -41
  120. package/src/native-server-inputs.ts +169 -0
  121. package/src/origin.ts +3 -3
  122. package/src/original-inputs.ts +72 -0
  123. package/src/pack-facts.ts +1 -1
  124. package/src/pglite-backing.ts +192 -80
  125. package/src/pglite-host.mjs +482 -10
  126. package/src/process-groups.d.mts +6 -1
  127. package/src/process-groups.mjs +82 -15
  128. package/src/process-groups.ts +2 -1
  129. package/src/process-signal.d.mts +2 -2
  130. package/src/process-signal.mjs +29 -9
  131. package/src/project-inspect.ts +37 -4
  132. package/src/proxy-env.ts +128 -0
  133. package/src/redirect-proxy.ts +3 -125
  134. package/src/redis-backing.ts +19 -136
  135. package/src/runtime.ts +342 -107
  136. package/src/schema.ts +5 -4
  137. package/src/served-world.ts +14 -10
  138. package/src/service-exit.ts +5 -3
  139. package/src/twin-backing.ts +300 -0
  140. package/src/up-cli.ts +52 -3
@@ -1,18 +1,17 @@
1
1
  import { errorMessage } from "./error-message.js";
2
- import { materializeBrowserFiles, clearBrowserFiles } from "./browsers.js";
2
+ import { materializeWorldBrowserFiles } from "./browser-files.js";
3
3
  import { claimEnvOutput, publishEnvOutput } from "./env-output.js";
4
4
  import { offSignal } from "./signals.js";
5
5
  import { parallelStartup } from "./startup.js";
6
6
  import { waitForHostReady } from "./host-ready.js";
7
7
  import { assertWorldStateRemovable, removeWorldState } from "./ancestry.js";
8
8
  import { packCli, packEntry } from "./catalog.js";
9
- import { vendorOfPackModule } from '@volter/world-core/runtime';
9
+ import { vendorOfPackModule } from '@volter/world-core/runtime-state';
10
10
  import { spawn, spawnSync } from 'node:child_process';
11
11
  import { get as httpGet } from 'node:http';
12
12
  import { siblingScript } from "./sibling.js";
13
13
  import { commandLifetime, commandProcessGroup, CommandRetirementError } from "./command-lifetime.js";
14
- import { superviseWorldRun } from "./run-task.js";
15
- import { processStarts, survivingOwnedGroups } from "./process-groups.js";
14
+ import { processStarts, survivingOwnedGroups, setWorldEventsFile, worldEvent } from "./process-groups.js";
16
15
  import { spawnIdentity, signalOwnedProcess, signalChild, signalHeldChild, processBirth, childIdentity } from './process-signal.mjs';
17
16
  import { readGuestProcesses, releaseGuestProcesses } from './guest-processes.mjs';
18
17
  import { randomUUID, createHash } from 'node:crypto';
@@ -25,10 +24,10 @@ import { basename, delimiter, dirname, join, relative, resolve, sep } from 'node
25
24
  import { packFacts } from "./pack-facts.js";
26
25
  import { assertOwnedRealPath, ownedPath } from "./owned-path.js";
27
26
  import { isEnvironmentName } from "./schema.js";
28
- import { infraConnections, parseInfraDefinition } from "./pglite-backing.js";
27
+ import { infraConnections, parseInfraDefinition } from "./infra-definition.js";
29
28
  import { describeServiceEnd, readServiceEnd } from "./service-exit.js";
30
29
  import { Socket } from 'node:net';
31
- import { beginPlaceholderPull, endPlaceholderPull, placeholderPullActive, getActiveWorldStore, withFileLock, stateDirName, PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding, WORLD_BOOT_PATH, WORLD_DATA_ENV, WORLD_ENV_NAMES_ENV } from '@volter/world-core/runtime';
30
+ import { beginPlaceholderPull, endPlaceholderPull, placeholderPullActive, getActiveWorldStore, removeCarriedLocks, withFileLock, stateDirName, PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding, WORLD_BOOT_PATH, WORLD_DATA_ENV, WORLD_ENV_NAMES_ENV } from '@volter/world-core/runtime-state';
32
31
  import { loadWorldConfig } from "./configs.js";
33
32
  import { createWorldNetworkPolicy, parseWorldNetworkPolicy, normalizeWorldNetwork, intersectWorldNetwork, WORLD_NETWORK_POLICY_ENV } from '@volter/world-core/network-policy';
34
33
  import { overlayPackFactsFile, resolveServicePackage } from "./catalog.js";
@@ -36,10 +35,10 @@ import { fakeEnvValue } from "./fixture-env.js";
36
35
  import { APP_URL_ENV, ISSUE_ENV_PREFIX, MINT_ENV } from "./schema.js";
37
36
  import { readAppUrl } from "./app-url.js";
38
37
  import { inertInjectEnvWarnings } from "./inject-map.js";
39
- import { activeVendorMap, ensureCa, hostHasRawEgress, opensslAvailable, proxyEnvFor, startRedirectProxy, tearDownCa } from "./redirect-proxy.js";
38
+ import { activeVendorMap, ensureCa, hostHasRawEgress, opensslAvailable, proxyEnvFor, tearDownCa } from "./proxy-env.js";
40
39
  import { CaTrustError, sessionTrustEnv } from "./ca-trust.js";
41
40
  import { checkedOwner, CONSUMER_HEARTBEAT_MS, createConsumer, refreshConsumer, releaseConsumer, removeConsumerRecord, worldConsumers, consumerProcessState } from "./consumers.js";
42
- import { assertLifecycleStartable, beginWorldLifecycle, handoffWorldLifecycle, recordWorldLifecycleEvent, releaseWorldLifecycle, retainWorldLifecycleForCleanup, hasWorldLifecycle, isWorldLifecycleRunning, lifecycleLogPath, releaseAbandonedWorldLifecycle, exitedWorldLifecyclePids } from "./lifecycle-record.js";
41
+ import { assertLifecycleStartable, beginWorldLifecycle, handoffWorldLifecycle, recordWorldLifecycleEvent, releaseWorldLifecycle, retainWorldLifecycleForCleanup, hasWorldLifecycle, isWorldLifecycleRunning, describeWorldLifecycle, lifecycleLogPath, releaseAbandonedWorldLifecycle, exitedWorldLifecyclePids } from "./lifecycle-record.js";
43
42
  // Births captured at actual spawn; retries never remeasure a retained PID.
44
43
  const spawnedIdentities = new Map();
45
44
  const spawnedChildren = new Map();
@@ -130,13 +129,17 @@ async function confirmOwnPort(port, boot, ended, twin, what, log) {
130
129
  if (answered && typeof answered.boot === 'string') {
131
130
  if (answered.boot === boot.bootId)
132
131
  return;
133
- throw new Error(`${what}: its port is answered by a twin of another World boot (${answered.boot}), not this one; a leftover World holds the port. Stop it (\`volter-world list\`, then \`down\`) and retry.\nLog: ${log}`);
132
+ throw foreignBoot(what, answered.boot, log);
134
133
  }
135
134
  // Whether the service has already ended, read now: one turn of the event loop, not a wait for it to fail.
136
135
  const endedNow = ended ? await Promise.race([ended.then(() => true), new Promise((resolveNow) => setImmediate(() => resolveNow(false)))]) : false;
137
136
  if (endedNow)
138
137
  throw new Error(`${what} ended right after its port first answered, so another process probably holds the port: the service ${endedAccount(log)}\nLog: ${log}`);
139
138
  }
139
+ /** A port answered by a twin of another World boot: a leftover World holds it. */
140
+ function foreignBoot(what, answered, log) {
141
+ return new Error(`${what}: its port is answered by a twin of another World boot (${answered}), not this one; a leftover World holds the port. Stop it (\`volter-world list\`, then \`down\`) and retry.\nLog: ${log}`);
142
+ }
140
143
  /** What the server on a loopback port says its World boot is (WORLD_BOOT_PATH), or null. A direct loopback request:
141
144
  * never through a proxy an activated shell's environment names. */
142
145
  function worldBootOf(port, abort) {
@@ -806,17 +809,17 @@ async function waitForTcp(port, timeoutMs = 15_000, abort) {
806
809
  }
807
810
  throw new Error(`Service on port ${port} did not become reachable`);
808
811
  }
809
- function readPids(path) {
812
+ function readPids(path, receipts = readGuestProcesses(dirname(path))) {
810
813
  const held = getActiveWorldStore().read(path);
811
- const guests = readGuestProcesses(dirname(path)).map(receipt => receipt.record.pid);
814
+ const guests = receipts.map(receipt => receipt.record.pid);
812
815
  return [...new Set([...(held ?? '')
813
816
  .split(/\r?\n/)
814
817
  .map((line) => Number(line.trim()))
815
818
  .filter((pid) => Number.isInteger(pid) && pid > 0), ...guests])];
816
819
  }
817
- function livePids(pids) {
820
+ function livePids(pids, listing) {
818
821
  // A recorded leader may have exited while its detached descendants still run.
819
- return survivingOwnedGroups(pids);
822
+ return survivingOwnedGroups(pids, listing);
820
823
  }
821
824
  function tunnelPids(instance) {
822
825
  return Object.values(instance.services)
@@ -853,7 +856,7 @@ function writeOwnedPids(root, name, pids, fresh = [], births = heldStarts()) {
853
856
  function identityPath(root, name) {
854
857
  return join(instanceDir(root, name), 'pids.identity');
855
858
  }
856
- function readIdentities(root, name) {
859
+ function readIdentities(root, name, receipts = readGuestProcesses(instanceDir(root, name)), onConflict) {
857
860
  let known = {};
858
861
  try {
859
862
  const value = JSON.parse(getActiveWorldStore().read(identityPath(root, name)) ?? '{}');
@@ -862,13 +865,18 @@ function readIdentities(root, name) {
862
865
  }
863
866
  catch { /* Guest receipts below remain independently authoritative. */ }
864
867
  const births = new Map();
865
- for (const { record } of readGuestProcesses(instanceDir(root, name))) {
866
- if (births.has(record.pid) && births.get(record.pid) !== record.start)
867
- throw new Error('Conflicting guest spawn identities; ownership retained');
868
+ const conflicts = new Set();
869
+ for (const { record } of receipts) {
870
+ if (births.has(record.pid) && births.get(record.pid) !== record.start) {
871
+ if (!onConflict)
872
+ throw new Error('Conflicting guest spawn identities; ownership retained');
873
+ conflicts.add(record.pid);
874
+ onConflict(record.pid);
875
+ }
868
876
  births.set(record.pid, record.start);
869
877
  }
870
878
  for (const [pid, start] of births) {
871
- if (start)
879
+ if (start && !conflicts.has(pid))
872
880
  known[pid] = start;
873
881
  else
874
882
  delete known[pid];
@@ -1084,9 +1092,9 @@ async function runExternalCommand(command, cwd, serviceId, phase, env = process.
1084
1092
  /** Run an external service's `up` command, STREAMING its stdout+stderr to the service log as it runs
1085
1093
  * (instead of buffering through spawnSync's 1MB cap, which a large DB restore would overflow). Returns
1086
1094
  * the combined output so `discover` can read it. Throws with a bounded tail on nonzero exit. */
1087
- async function runExternalUp(command, cwd, serviceId, env, logPath, abort, progress) {
1095
+ async function runExternalUp(command, cwd, serviceId, env, logPath, abort, progress, verified) {
1088
1096
  if (ownInfrastructureUp(command))
1089
- return runOwnInfrastructureUp(command, serviceId, env, logPath, abort, progress);
1097
+ return runOwnInfrastructureUp(command, serviceId, env, logPath, abort, progress, verified);
1090
1098
  const [bin, ...args] = ownCommand(command);
1091
1099
  if (!bin)
1092
1100
  throw new Error(`External service "${serviceId}": up command is empty`);
@@ -1176,13 +1184,14 @@ async function runOwnInfrastructureStatus(serviceId, phase, env, abort, timeoutM
1176
1184
  abort?.removeEventListener('abort', onAbort);
1177
1185
  }
1178
1186
  }
1179
- async function runOwnInfrastructureUp(command, serviceId, env, logPath, abort, progress) {
1187
+ async function runOwnInfrastructureUp(command, serviceId, env, logPath, abort, progress, verified) {
1180
1188
  writeFileSync(logPath, `$ ${command.join(' ')}\n`, { mode: 0o600 });
1181
1189
  chmodSync(logPath, 0o600);
1182
1190
  const out = openPrivateLog(logPath, 'a');
1183
1191
  const chunks = [];
1184
1192
  try {
1185
1193
  abort?.throwIfAborted();
1194
+ const importing = Date.now();
1186
1195
  const { runInfrastructure } = await import("./infra.js");
1187
1196
  // Its phase lines (backing chosen, host spawned, serving) are the boot's progress too.
1188
1197
  const write = (text) => {
@@ -1192,7 +1201,9 @@ async function runOwnInfrastructureUp(command, serviceId, env, logPath, abort, p
1192
1201
  if (line.startsWith('managed infrastructure'))
1193
1202
  progress?.(`${serviceId}: ${line}`);
1194
1203
  };
1195
- const code = await runInfrastructure(command[1], env, { out: write, err: write }, abort);
1204
+ // Its module's load, said with the line's own time (S2, Twenty's World up): what of the start before the backing's line was this.
1205
+ write(`managed infrastructure: its module loaded in ${Date.now() - importing} ms\n`);
1206
+ const code = await runInfrastructure(command[1], env, { out: write, err: write, ...(verified ? { verified: (status) => { verified.status = status; } } : {}) }, abort);
1196
1207
  abort?.throwIfAborted();
1197
1208
  if (code !== 0) {
1198
1209
  const detail = chunks.join('').trim().split('\n').slice(-40).join('\n');
@@ -1216,7 +1227,7 @@ function resolveReadyProbe(ready, svc) {
1216
1227
  .split('${port}').join(String(svc.port));
1217
1228
  return { ...ready, httpUrl };
1218
1229
  }
1219
- async function awaitReadiness(ready, cwd, serviceId, env = process.env, abort) {
1230
+ async function awaitReadiness(ready, cwd, serviceId, env = process.env, abort, passing) {
1220
1231
  if (!ready)
1221
1232
  return 'no readiness declared';
1222
1233
  const timeoutMs = ready.timeoutMs ?? 60_000;
@@ -1232,7 +1243,9 @@ async function awaitReadiness(ready, cwd, serviceId, env = process.env, abort) {
1232
1243
  throw new Error(`Service "${serviceId}": readiness command \`${ready.command}\` not found on PATH`);
1233
1244
  }
1234
1245
  try {
1235
- await runExternalCommand([ready.command, ...(ready.args ?? [])], cwd, serviceId, 'readiness', env, abort, Math.max(1, timeoutMs - (Date.now() - started)));
1246
+ const output = await runExternalCommand([ready.command, ...(ready.args ?? [])], cwd, serviceId, 'readiness', env, abort, Math.max(1, timeoutMs - (Date.now() - started)));
1247
+ if (passing)
1248
+ Object.assign(passing, output);
1236
1249
  return passed(`\`${[ready.command, ...(ready.args ?? [])].join(' ')}\` exited 0`);
1237
1250
  }
1238
1251
  catch (error) {
@@ -1289,15 +1302,39 @@ async function startExternalService(service, paths, worldEnv, stripEnv, boot) {
1289
1302
  // Publish the declared teardown before an external command can create state.
1290
1303
  boot?.external({ id: service.id, type: 'external', command: [...external.up], pid: 0, log, env: {},
1291
1304
  external: { down: [...external.down], cwd, discoveredEnv: { ...(service.env ?? {}) } } });
1292
- const upOutput = await runExternalUp(external.up, cwd, service.id, env, log, boot?.signal, boot?.progress);
1305
+ const verified = {};
1306
+ const upOutput = await runExternalUp(external.up, cwd, service.id, env, log, boot?.signal, boot?.progress, verified);
1293
1307
  boot?.progress(`${service.id}: up returned`);
1294
1308
  // 2. readiness — Docker-backed externals take seconds to boot.
1295
- const readiness = await awaitReadiness(external.readyWhen, cwd, service.id, env, boot?.signal);
1309
+ const passing = {};
1310
+ // The runtime's own infrastructure, every service twin-served or its PostgreSQL native (no MongoDB native), proves in
1311
+ // its `up` what its `status` would, status's proofs asked again at hand-over though not its choice of backing (infra.ts
1312
+ // handOver: twin-backing.ts twinBackingHeld, each port
1313
+ // served by the child it started; native-postgres-backing.ts nativePostgresStatus, the postmaster's ownership, live
1314
+ // group, identity and answering port; Cal's status run took 70 ms, c670b965 run 2, on the mini, in a browser tab; S2
1315
+ // R46). Its up hands over the status it would print, so the probe,
1316
+ // when it is that same `status`, is not run again (LibreChat: the status run took 206 ms before it connected, e946aa4c
1317
+ // lap 060250 run 2, on the mini, in a browser tab; S2 R45). Any other probe, or a backing up does not prove, is
1318
+ // probed as before.
1319
+ const probed = external.readyWhen?.command === undefined ? undefined : [external.readyWhen.command, ...(external.readyWhen.args ?? [])];
1320
+ const upProved = verified.status !== undefined && probed !== undefined && ownInfrastructureStatus(probed);
1321
+ if (upProved) {
1322
+ passing.stdout = verified.status;
1323
+ passing.stderr = '';
1324
+ }
1325
+ const readiness = upProved ? 'proved by its own up, as its status would' : await awaitReadiness(external.readyWhen, cwd, service.id, env, boot?.signal, passing);
1296
1326
  boot?.progress(`${service.id}: ready (${readiness})`);
1297
1327
  // 3. status — discover connection info (optional; may reuse up output instead).
1298
1328
  let statusOutput = '';
1299
1329
  if (external.status) {
1300
- const statusResult = await runExternalCommand(external.status, cwd, service.id, 'status', env, boot?.signal);
1330
+ // The runtime's own infrastructure probes its readiness with the very status command it then reads (init declares
1331
+ // both as `volter-world-infra status`): the probe that just passed is that read, taken a moment ago in this
1332
+ // process, so it is not run again (S2 R17: LibreChat's second run of it, 190 ms of up after its infrastructure
1333
+ // was ready). Any other status command, or a probe that is not the same command, is run as before.
1334
+ const probe = external.readyWhen?.command === undefined ? undefined : [external.readyWhen.command, ...(external.readyWhen.args ?? [])];
1335
+ const same = probe !== undefined && passing.stdout !== undefined && ownInfrastructureStatus(external.status)
1336
+ && probe.length === external.status.length && probe.every((part, index) => part === external.status[index]);
1337
+ const statusResult = same ? { stdout: passing.stdout, stderr: passing.stderr ?? '' } : await runExternalCommand(external.status, cwd, service.id, 'status', env, boot?.signal);
1301
1338
  statusOutput = statusResult.stdout || statusResult.stderr;
1302
1339
  appendFileSync(log, `\n$ ${external.status.join(' ')}\n${statusOutput}`);
1303
1340
  boot?.progress(`${service.id}: status read`);
@@ -1640,9 +1677,12 @@ const ISSUE_TIMEOUT_MS = 15_000;
1640
1677
  /** A twin's issued credential (its `issues`, copied from its pack's credentialDoor by init): once it is up, the door is
1641
1678
  * asked for one and each pending name of that service is set to it. A twin that answers no credential, or has no
1642
1679
  * door, leaves the names unset and says so, rather than handing the application a fixture the twin would refuse. */
1643
- async function issueCredentials(service, started, env, pending, unissued, controlRoot) {
1680
+ async function issueCredentials(service, started, env, pending, unissued, controlRoot, times) {
1644
1681
  const direct = new Set([...pending].filter(([, id]) => id === `${service.id}@direct`).map(([name]) => name));
1645
1682
  const wanted = [...pending].filter(([name, id]) => id === service.id || direct.has(name)).map(([name]) => name);
1683
+ if (times) {
1684
+ times.names = wanted.length;
1685
+ }
1646
1686
  if (wanted.length === 0)
1647
1687
  return;
1648
1688
  for (const name of wanted)
@@ -1653,6 +1693,8 @@ async function issueCredentials(service, started, env, pending, unissued, contro
1653
1693
  door = packFacts()[service.id]?.credentialDoor ?? door;
1654
1694
  }
1655
1695
  catch { /* no artifact: the saved copy */ }
1696
+ if (times)
1697
+ times.door = performance.now();
1656
1698
  try {
1657
1699
  if (!door)
1658
1700
  throw new Error('its service names no credential door');
@@ -1664,130 +1706,148 @@ async function issueCredentials(service, started, env, pending, unissued, contro
1664
1706
  beginPlaceholderPull(service.id, controlRoot);
1665
1707
  let answer;
1666
1708
  try {
1709
+ if (times)
1710
+ times.sent = performance.now();
1667
1711
  const res = await fetch(new URL(door.path, started.url), { method: 'POST', headers: { 'content-type': 'application/json', 'x-volter-world-activity': 'provisioning' }, body: JSON.stringify(door.body), signal: AbortSignal.timeout(ISSUE_TIMEOUT_MS) });
1712
+ if (times)
1713
+ times.headers = performance.now();
1714
+ // What the door answered is said with its status: a twin's own error is the only reading of why it issued
1715
+ // nothing, and it is otherwise in a log of the twin's host that the caller of `up` may never see.
1668
1716
  if (!res.ok)
1669
- throw new Error(`answered ${res.status}`);
1717
+ throw new Error(`answered ${res.status}: ${(await res.text().catch(() => '')).replace(/\s+/g, ' ').slice(0, 400) || 'no body'}`);
1670
1718
  answer = (await res.json());
1719
+ if (times)
1720
+ times.answered = performance.now();
1671
1721
  }
1672
1722
  finally {
1673
1723
  if (!enclosingSeed)
1674
1724
  endPlaceholderPull(service.id, controlRoot);
1675
1725
  }
1726
+ // EACH NAME IS FILLED ON ITS OWN. A door answers several things (a key, a sign-in's files); a part it could not
1727
+ // make, or a name whose part is not in the answer, leaves THAT name unset and said, and the others are given.
1728
+ // One throw here used to unset every name of the service, the key among them.
1676
1729
  for (const name of wanted) {
1677
- const spec = door.fill[name] ?? '';
1678
- if (spec.startsWith('directory:') || spec.startsWith('file:')) {
1679
- if (direct.has(name))
1680
- throw new Error(`cannot give ${name}'s filesystem credential a direct URL`);
1681
- if (!isEnvironmentName(name) || !/^[a-z0-9][a-z0-9._-]*$/.test(service.id))
1682
- throw new Error('Invalid credential destination name');
1683
- if (!env.VOLTER_WORLD_INSTANCE)
1684
- throw new Error('World credential custody is unknown');
1685
- const instance = realpathSync(dirname(env.VOLTER_WORLD_INSTANCE));
1686
- const custody = ownedPath(instance, 'credentials');
1687
- const artifact = `${service.id}-${name.toLowerCase()}`;
1688
- const isDirectory = spec.startsWith('directory:');
1689
- const field = spec.slice(spec.indexOf(':') + 1), value = answer[field];
1690
- if (value === undefined || value === null)
1691
- throw new Error(`answered no ${field}`);
1692
- if (isDirectory && (typeof value !== 'object' || Array.isArray(value) || Object.keys(value).length === 0))
1693
- throw new Error('Invalid credential directory');
1694
- const entries = isDirectory ? Object.entries(value) : [[`${artifact}.json`, value]];
1695
- const files = entries.map(([file, contents]) => {
1696
- if (isDirectory && (!file || file.includes('\\') || file.split('/').some(part => !/^[A-Za-z0-9._-]+$/.test(part) || part === '.' || part === '..')))
1697
- throw new Error('Invalid World-owned path');
1698
- return { path: ownedPath(custody, isDirectory ? `${artifact}/${file}` : file), contents };
1699
- });
1700
- const directories = new Set([custody]);
1701
- for (const { path } of files) {
1702
- let parent = dirname(path);
1703
- while (parent !== custody) {
1704
- directories.add(parent);
1705
- parent = dirname(parent);
1730
+ try {
1731
+ const spec = door.fill[name] ?? '';
1732
+ if (spec.startsWith('directory:') || spec.startsWith('file:')) {
1733
+ if (direct.has(name))
1734
+ throw new Error(`cannot give ${name}'s filesystem credential a direct URL`);
1735
+ if (!isEnvironmentName(name) || !/^[a-z0-9][a-z0-9._-]*$/.test(service.id))
1736
+ throw new Error('Invalid credential destination name');
1737
+ if (!env.VOLTER_WORLD_INSTANCE)
1738
+ throw new Error('World credential custody is unknown');
1739
+ const instance = realpathSync(dirname(env.VOLTER_WORLD_INSTANCE));
1740
+ const custody = ownedPath(instance, 'credentials');
1741
+ const artifact = `${service.id}-${name.toLowerCase()}`;
1742
+ const isDirectory = spec.startsWith('directory:');
1743
+ const field = spec.slice(spec.indexOf(':') + 1), value = answer[field];
1744
+ if (value === undefined || value === null)
1745
+ throw new Error(`answered no ${field}`);
1746
+ if (isDirectory && (typeof value !== 'object' || Array.isArray(value) || Object.keys(value).length === 0))
1747
+ throw new Error('Invalid credential directory');
1748
+ const entries = isDirectory ? Object.entries(value) : [[`${artifact}.json`, value]];
1749
+ const files = entries.map(([file, contents]) => {
1750
+ if (isDirectory && (!file || file.includes('\\') || file.split('/').some(part => !/^[A-Za-z0-9._-]+$/.test(part) || part === '.' || part === '..')))
1751
+ throw new Error('Invalid World-owned path');
1752
+ return { path: ownedPath(custody, isDirectory ? `${artifact}/${file}` : file), contents };
1753
+ });
1754
+ const directories = new Set([custody]);
1755
+ for (const { path } of files) {
1756
+ let parent = dirname(path);
1757
+ while (parent !== custody) {
1758
+ directories.add(parent);
1759
+ parent = dirname(parent);
1760
+ }
1761
+ const stat = lstatSync(path, { throwIfNoEntry: false });
1762
+ if (stat && (!stat.isFile() || stat.nlink !== 1))
1763
+ throw new Error('Invalid World-owned path');
1706
1764
  }
1707
- const stat = lstatSync(path, { throwIfNoEntry: false });
1708
- if (stat && (!stat.isFile() || stat.nlink !== 1))
1709
- throw new Error('Invalid World-owned path');
1710
- }
1711
- // Validate every destination and parent before creating or changing any custody artifact.
1712
- for (const dir of directories) {
1713
- ownedPath(instance, relative(instance, dir));
1714
- const stat = lstatSync(dir, { throwIfNoEntry: false });
1715
- if (stat && !stat.isDirectory())
1716
- throw new Error('Invalid World-owned path');
1717
- }
1718
- for (const dir of [...directories].sort((a, b) => a.length - b.length)) {
1719
- if (!existsSync(dir))
1720
- mkdirSync(dir, { mode: 0o700 });
1721
- assertOwnedRealPath(custody, dir);
1722
- chmodSync(dir, 0o700);
1723
- }
1724
- for (const { path, contents } of files) {
1725
- ownedPath(custody, relative(custody, path));
1726
- assertOwnedRealPath(custody, dirname(path));
1727
- const fd = openSync(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_NOFOLLOW, 0o600);
1728
- let opened;
1729
- try {
1730
- const stat = fstatSync(fd);
1731
- if (!stat.isFile() || stat.nlink !== 1)
1765
+ // Validate every destination and parent before creating or changing any custody artifact.
1766
+ for (const dir of directories) {
1767
+ ownedPath(instance, relative(instance, dir));
1768
+ const stat = lstatSync(dir, { throwIfNoEntry: false });
1769
+ if (stat && !stat.isDirectory())
1732
1770
  throw new Error('Invalid World-owned path');
1733
- opened = stat;
1734
- fchmodSync(fd, 0o600);
1735
- assertOwnedRealPath(custody, dirname(path));
1736
- ftruncateSync(fd, 0);
1737
- writeFileSync(fd, typeof contents === 'string' ? contents : `${JSON.stringify(contents, null, 2)}\n`);
1738
1771
  }
1739
- finally {
1740
- closeSync(fd);
1741
- let realFile;
1772
+ for (const dir of [...directories].sort((a, b) => a.length - b.length)) {
1773
+ if (!existsSync(dir))
1774
+ mkdirSync(dir, { mode: 0o700 });
1775
+ assertOwnedRealPath(custody, dir);
1776
+ chmodSync(dir, 0o700);
1777
+ }
1778
+ for (const { path, contents } of files) {
1779
+ ownedPath(custody, relative(custody, path));
1780
+ assertOwnedRealPath(custody, dirname(path));
1781
+ const fd = openSync(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_NOFOLLOW, 0o600);
1782
+ let opened;
1742
1783
  try {
1743
- realFile = realpathSync(path);
1744
- const current = statSync(realFile);
1745
- if (!opened || current.dev !== opened.dev || current.ino !== opened.ino)
1784
+ const stat = fstatSync(fd);
1785
+ if (!stat.isFile() || stat.nlink !== 1)
1746
1786
  throw new Error('Invalid World-owned path');
1747
- assertOwnedRealPath(custody, realFile);
1787
+ opened = stat;
1788
+ fchmodSync(fd, 0o600);
1789
+ assertOwnedRealPath(custody, dirname(path));
1790
+ ftruncateSync(fd, 0);
1791
+ writeFileSync(fd, typeof contents === 'string' ? contents : `${JSON.stringify(contents, null, 2)}\n`);
1748
1792
  }
1749
- catch (error) {
1750
- // Remove only the file whose descriptor we opened; a raced replacement is not ours to delete.
1751
- const current = realFile ? lstatSync(realFile, { throwIfNoEntry: false }) : undefined;
1752
- if (opened && current?.dev === opened.dev && current.ino === opened.ino)
1753
- rmSync(realFile, { force: true });
1754
- throw error;
1793
+ finally {
1794
+ closeSync(fd);
1795
+ let realFile;
1796
+ try {
1797
+ realFile = realpathSync(path);
1798
+ const current = statSync(realFile);
1799
+ if (!opened || current.dev !== opened.dev || current.ino !== opened.ino)
1800
+ throw new Error('Invalid World-owned path');
1801
+ assertOwnedRealPath(custody, realFile);
1802
+ }
1803
+ catch (error) {
1804
+ // Remove only the file whose descriptor we opened; a raced replacement is not ours to delete.
1805
+ const current = realFile ? lstatSync(realFile, { throwIfNoEntry: false }) : undefined;
1806
+ if (opened && current?.dev === opened.dev && current.ino === opened.ino)
1807
+ rmSync(realFile, { force: true });
1808
+ throw error;
1809
+ }
1755
1810
  }
1756
1811
  }
1812
+ env[name] = isDirectory ? ownedPath(custody, artifact) : files[0].path;
1813
+ continue;
1757
1814
  }
1758
- env[name] = isDirectory ? ownedPath(custody, artifact) : files[0].path;
1759
- continue;
1760
- }
1761
- // `side:<side>:<field>`: a URL the twin issues for one of its side ports (a native wire beside its HTTP API, the
1762
- // PlanetScale twin's MySQL), its authority the side's listener as this boot placed it
1763
- if (spec.startsWith('side:')) {
1764
- const [, side, field] = spec.split(':');
1765
- const value = answer[field ?? ''];
1815
+ // `side:<side>:<field>`: a URL the twin issues for one of its side ports (a native wire beside its HTTP API, the
1816
+ // PlanetScale twin's MySQL), its authority the side's listener as this boot placed it
1817
+ if (spec.startsWith('side:')) {
1818
+ const [, side, field] = spec.split(':');
1819
+ const value = answer[field ?? ''];
1820
+ if (typeof value !== 'string')
1821
+ throw new Error(`answered no ${field}`);
1822
+ const at = side ? started.sides?.[side]?.url : undefined;
1823
+ if (!at)
1824
+ throw new Error(`it serves no ${side} side port`);
1825
+ const issued = new URL(value);
1826
+ issued.host = new URL(at).host;
1827
+ env[name] = issued.toString();
1828
+ continue;
1829
+ }
1830
+ const field = spec;
1831
+ const value = answer[field];
1766
1832
  if (typeof value !== 'string')
1767
1833
  throw new Error(`answered no ${field}`);
1768
- const at = side ? started.sides?.[side]?.url : undefined;
1769
- if (!at)
1770
- throw new Error(`it serves no ${side} side port`);
1771
- const issued = new URL(value);
1772
- issued.host = new URL(at).host;
1773
- env[name] = issued.toString();
1774
- continue;
1834
+ if (direct.has(name)) {
1835
+ const issued = new URL(value);
1836
+ if (!['http:', 'https:'].includes(issued.protocol))
1837
+ throw new Error(`cannot route ${name}'s non-HTTP URL directly`);
1838
+ const endpoint = new URL(started.url);
1839
+ issued.protocol = endpoint.protocol;
1840
+ issued.host = endpoint.host;
1841
+ env[name] = issued.toString();
1842
+ }
1843
+ else
1844
+ env[name] = value;
1775
1845
  }
1776
- const field = spec;
1777
- const value = answer[field];
1778
- if (typeof value !== 'string')
1779
- throw new Error(`answered no ${field}`);
1780
- if (direct.has(name)) {
1781
- const issued = new URL(value);
1782
- if (!['http:', 'https:'].includes(issued.protocol))
1783
- throw new Error(`cannot route ${name}'s non-HTTP URL directly`);
1784
- const endpoint = new URL(started.url);
1785
- issued.protocol = endpoint.protocol;
1786
- issued.host = endpoint.host;
1787
- env[name] = issued.toString();
1846
+ catch (error) {
1847
+ process.stderr.write(`volter-world: ${service.id} did not issue ${name} (${errorMessage(error)}${typeof answer.codex_unavailable === 'string' ? `; the twin says: ${answer.codex_unavailable.slice(0, 300)}` : ''}); left unset\n`);
1848
+ delete env[name];
1849
+ unissued.add(name);
1788
1850
  }
1789
- else
1790
- env[name] = value;
1791
1851
  }
1792
1852
  if (started.url) {
1793
1853
  const endpoint = new URL(started.url);
@@ -1803,6 +1863,8 @@ async function issueCredentials(service, started, env, pending, unissued, contro
1803
1863
  unissued.add(name);
1804
1864
  }
1805
1865
  }
1866
+ if (times)
1867
+ times.filled = performance.now();
1806
1868
  }
1807
1869
  function materializeConfig(config, root) {
1808
1870
  const services = config.services.map((service) => {
@@ -1837,6 +1899,23 @@ function versionSatisfies(actual, pin) {
1837
1899
  return am === pm && (an ?? 0) >= (pn ?? 0);
1838
1900
  return actual === pin;
1839
1901
  }
1902
+ /** Where the host's start went, from its receipt, for the receipt's progress line (S2 R38): when its own modules were
1903
+ * loaded, when its twins' factories returned (the slowest named) and when it published, each after its spawn, and how
1904
+ * long after publication the receipt was read. A host that says none of it adds nothing. */
1905
+ function hostStartAccount(times, spawnedAt, readAt) {
1906
+ const after = (time) => `+${Math.max(0, time - spawnedAt)} ms`;
1907
+ const parts = [];
1908
+ if (times.at !== undefined)
1909
+ parts.push(`${Math.max(0, readAt - times.at)} ms after the host published it`);
1910
+ if (times.began !== undefined)
1911
+ parts.push(`the host's own modules loaded ${after(times.began)} after its spawn`);
1912
+ const started = Object.entries(times.started ?? {}).sort((a, b) => a[1] - b[1]);
1913
+ if (started.length)
1914
+ parts.push(`its twins returned ${after(started[0][1])} to ${after(started.at(-1)[1])} (slowest ${started.at(-1)[0]})`);
1915
+ if (times.at !== undefined && (times.began !== undefined || started.length))
1916
+ parts.push(`published ${after(times.at)}`);
1917
+ return parts.length ? `, ${parts.join('; ')}` : '';
1918
+ }
1840
1919
  /**
1841
1920
  * Boot every `colocate`-declaring service inside ONE `volter-world-host` child (see src/host.ts —
1842
1921
  * 'colocated' maps to the host's 'shared' mode, 'worker' to one Worker thread per twin). The
@@ -1856,21 +1935,33 @@ async function startColocatedServices(config, colocated, isolation, worldEnv, pa
1856
1935
  const readyFile = join(paths.instance, `host-ready-${boot.bootId}.json`);
1857
1936
  const args = [hostCli, '--isolation', isolation === 'colocated' ? 'shared' : 'worker', '--ready-file', readyFile];
1858
1937
  const allocated = [];
1859
- for (const service of colocated) {
1860
- const port = service.port === undefined || service.port === 'auto' ? await allocatePort(allocatedPorts) : service.port;
1938
+ // Every twin's port and side ports are probed together, not one after another: a probe is a listen and a close
1939
+ // on IPv4 and IPv6, ~25 ms each in a tab, and Dub's fourteen twins and their sides held sixteen of them in turn
1940
+ // (S2 R16: 406 ms of its 1,871 ms up before the host was spawned). Concurrent probes hold distinct ports while
1941
+ // open, and `allocatedPorts` is checked and added to synchronously after each probe, so no port is given twice;
1942
+ // a declared port is in it before any automatic one is chosen, as before. Which twin gets which automatic port
1943
+ // is not part of any contract.
1944
+ // Relative module paths resolve from the world root (import() inside the host would
1945
+ // otherwise resolve them against the runtime's own source directory).
1946
+ const moduleOf = (service) => {
1947
+ const declared = service.colocate.module;
1948
+ return declared.startsWith('.') ? resolve(paths.root, declared) : declared;
1949
+ };
1950
+ const probed = await Promise.all(colocated.map(async (service) => ({
1951
+ port: service.port === undefined || service.port === 'auto' ? await allocatePort(allocatedPorts) : service.port,
1952
+ sides: await sidePortsFor(vendorOfModule(moduleOf(service), service.id), [], allocatedPorts),
1953
+ })));
1954
+ for (const [index, service] of colocated.entries()) {
1955
+ const { port, sides } = probed[index];
1861
1956
  const serviceDataDir = ownedPath(paths.data, service.id);
1862
1957
  mkdirSync(serviceDataDir, { recursive: true });
1863
- // Relative module paths resolve from the world root (import() inside the host would
1864
- // otherwise resolve them against the runtime's own source directory).
1865
- const declared = service.colocate.module;
1866
- const module = declared.startsWith('.') ? resolve(paths.root, declared) : declared;
1958
+ const module = moduleOf(service);
1867
1959
  // R17 — the lockfile check, BEFORE the host boots: what this module's own package.json says
1868
1960
  // it is, against the pin the world config carries; and R16 — the protocol standing the
1869
1961
  // catalog recorded for the package (pack-facts), refused two majors back, warned one back.
1870
1962
  const mounted = lockfileCheck(service, module);
1871
1963
  const scenario = service.colocate.scenarioPath;
1872
1964
  const binding = managedDatabaseFor(vendorOfModule(module, service.id), worldEnv.VOLTER_WORLD_CONFIG, config.env ?? {});
1873
- const sides = await sidePortsFor(vendorOfModule(module, service.id), [], allocatedPorts);
1874
1965
  args.push('--spec', [service.id, module, service.colocate.export, String(port), serviceDataDir, ...(scenario ? [`scenario=${scenario.startsWith('/') ? scenario : resolve(paths.root, scenario)}`] : []), ...(binding !== undefined ? [`database=${binding.url}`] : []), ...sides.map((side) => `side.${side.option}=${side.port}`)].join('|'));
1875
1966
  allocated.push({ service, port, mounted, sides });
1876
1967
  }
@@ -1886,6 +1977,7 @@ async function startColocatedServices(config, colocated, isolation, worldEnv, pa
1886
1977
  delete env[key];
1887
1978
  dropUnissued(env);
1888
1979
  env.VOLTER_WORLD_BOOT_ID = boot.bootId;
1980
+ const spawnedAt = Date.now();
1889
1981
  // Under the same exit recorder as a process service, so the host's end (and every twin it holds) is in host.log.
1890
1982
  const { child, ended, identity } = spawnRecorded('host', process.execPath, args, paths.root, env, log, boot);
1891
1983
  child.once('error', error => boot.fail(error));
@@ -1922,7 +2014,7 @@ async function startColocatedServices(config, colocated, isolation, worldEnv, pa
1922
2014
  });
1923
2015
  const ready = (async () => {
1924
2016
  const hostTimeoutMs = allocated.reduce((timeout, { service }) => Math.max(timeout, typeof service.ready === 'object' ? service.ready.timeoutMs ?? 15_000 : 15_000), 15_000);
1925
- await untilReadyOrEnded((abort) => waitForHostReady(readyFile, boot.bootId, new Map(allocated.map(({ service, port }) => [service.id, port])), hostTimeoutMs, abort, () => { try {
2017
+ const hostTimes = await untilReadyOrEnded((abort) => waitForHostReady(readyFile, boot.bootId, new Map(allocated.map(({ service, port }) => [service.id, port])), hostTimeoutMs, abort, () => { try {
1926
2018
  return statSync(log).size;
1927
2019
  }
1928
2020
  catch {
@@ -1936,8 +2028,35 @@ async function startColocatedServices(config, colocated, isolation, worldEnv, pa
1936
2028
  catch { /* best effort cleanup */ }
1937
2029
  throw new Error(`Co-located host failed to complete startup (isolation=${isolation}): ${errorMessage(error)}\nLog: ${log}`);
1938
2030
  });
1939
- boot.progress('co-located host: its all-started receipt is read');
2031
+ boot.progress(`co-located host: its all-started receipt is read${hostStartAccount(hostTimes, spawnedAt, Date.now())}`);
2032
+ // The host's receipt (this boot, every requested port) is published only
2033
+ // after every factory bound its own port; a port another process held fails
2034
+ // its bind and leaves no receipt. The receipt is the host's ownership proof
2035
+ // for each port (ADR 0014). A port that answers with another boot's identity
2036
+ // is still refused.
2037
+ // So each port is asked its boot at once: this boot's answer is the port's reachability and its ownership in one
2038
+ // connection. Another boot's answer is refused at once, as confirmation refuses it; a port that answers no
2039
+ // boot is waited for and confirmed as before.
2040
+ // A twin the host's receipt names a byte stream (host.ts servesByteStream) is not asked: it would answer the request
2041
+ // in its own protocol, after its own greeting work (Rallly's SMTP relay answered 57 ms after the request, holding
2042
+ // host-ready: e946aa4c run 3, on the mini, World 3.0.2197; S2 R38), and then be asked again by confirmation. Its
2043
+ // admission is its TCP answer and its host still running, as a port with no boot identity's is: another holder of
2044
+ // its port fails its bind, so no receipt.
2045
+ const streams = new Set(hostTimes.streams ?? []);
1940
2046
  await parallelStartup(allocated, async ({ service, port }) => {
2047
+ const byteStream = streams.has(service.id);
2048
+ const answered = byteStream ? null : await worldBootOf(port, boot.signal);
2049
+ if (answered && typeof answered.boot === 'string') {
2050
+ if (answered.boot === boot.bootId)
2051
+ return;
2052
+ if (undeadPids([child.pid]).length)
2053
+ recordWorldStop([log], 'world.service.stop', 'up (port not its own)', 'SIGTERM');
2054
+ try {
2055
+ await signalHeldChild(child, 'SIGTERM', true, identity);
2056
+ }
2057
+ catch { /* best effort cleanup */ }
2058
+ throw foreignBoot(`Co-located twin "${service.id}"`, answered.boot, log);
2059
+ }
1941
2060
  const bindTimeoutMs = typeof service.ready === 'object' && service.ready.timeoutMs !== undefined ? Math.max(service.ready.timeoutMs, 15_000) : 15_000;
1942
2061
  await untilReadyOrEnded((abort) => waitForTcp(port, bindTimeoutMs, abort), ended, boot, 'the co-located host', log).catch(async (error) => {
1943
2062
  if (undeadPids([child.pid]).length)
@@ -1948,14 +2067,7 @@ async function startColocatedServices(config, colocated, isolation, worldEnv, pa
1948
2067
  catch { /* best effort cleanup */ }
1949
2068
  throw new Error(`Co-located twin "${service.id}" failed to start (host isolation=${isolation}): ${errorMessage(error)}\nLog: ${log}`);
1950
2069
  });
1951
- });
1952
- // The host's receipt (this boot, every requested port) is published only
1953
- // after every factory bound its own port; a port another process held fails
1954
- // its bind and leaves no receipt. The receipt is the host's ownership proof
1955
- // for each port (ADR 0014). A port that answers with another boot's identity
1956
- // is still refused.
1957
- await parallelStartup(allocated, async ({ service, port }) => {
1958
- await confirmOwnPort(port, boot, ended, true, `Co-located twin "${service.id}"`, log).catch(async (error) => {
2070
+ await confirmOwnPort(port, boot, ended, !byteStream, `Co-located twin "${service.id}"`, log).catch(async (error) => {
1959
2071
  if (undeadPids([child.pid]).length)
1960
2072
  recordWorldStop([log], 'world.service.stop', 'up (port not its own)', 'SIGTERM');
1961
2073
  try {
@@ -1975,9 +2087,51 @@ async function startColocatedServices(config, colocated, isolation, worldEnv, pa
1975
2087
  ready.catch(() => undefined);
1976
2088
  return { services, ready };
1977
2089
  }
2090
+ /** How late this process's event loop ran while `up` worked (S2 R45): a 10 ms tick, unref'd, on the monotonic clock.
2091
+ * A tick 2 ms or more late adds its lateness to the total (less is timer jitter, not counted); the longest is kept; a
2092
+ * stall is a tick 10 ms or more late, and the three longest are kept with when, after up began, each began (to within
2093
+ * the 10 ms tick). LibreChat's up showed three windows of 206-263 ms with no work of the runtime's in its trace
2094
+ * (e946aa4c lap 060250 run 2, on the mini, in a browser tab): this says whether its loop was stalled in them or working. */
2095
+ function eventLoopLag() {
2096
+ const TICK_MS = 10;
2097
+ const JITTER_MS = 2;
2098
+ const started = performance.now();
2099
+ let expected = started + TICK_MS;
2100
+ let total = 0;
2101
+ let longest = 0;
2102
+ const stalls = [];
2103
+ const timer = setInterval(() => {
2104
+ const now = performance.now();
2105
+ const late = now - expected;
2106
+ expected = now + TICK_MS;
2107
+ if (late < JITTER_MS)
2108
+ return;
2109
+ total += late;
2110
+ longest = Math.max(longest, late);
2111
+ if (late < TICK_MS)
2112
+ return;
2113
+ stalls.push({ at: now - late - started, ms: late });
2114
+ stalls.sort((a, b) => b.ms - a.ms);
2115
+ stalls.length = Math.min(stalls.length, 3);
2116
+ }, TICK_MS);
2117
+ timer.unref?.();
2118
+ return {
2119
+ account: () => `runtime event loop ${Math.round(total)} ms late in all (ticks 2 ms or more late), longest ${Math.round(longest)} ms${stalls.length ? `; stalls ${stalls.map((stall) => `${Math.round(stall.ms)} ms at +${(stall.at / 1000).toFixed(2)} s`).join(', ')} (starts ±10 ms)` : ''}`,
2120
+ stop: () => clearInterval(timer),
2121
+ };
2122
+ }
1978
2123
  export async function upWorld(configId, options = {}, abort) {
1979
- abort?.throwIfAborted();
1980
2124
  const upStarted = Date.now();
2125
+ const lag = eventLoopLag();
2126
+ try {
2127
+ return await upWorldRecorded(configId, options, abort, upStarted, lag);
2128
+ }
2129
+ finally {
2130
+ lag.stop();
2131
+ }
2132
+ }
2133
+ async function upWorldRecorded(configId, options, abort, upStarted, lag) {
2134
+ abort?.throwIfAborted();
1981
2135
  const root = resolve(options.root ?? process.cwd());
1982
2136
  const found = loadWorldConfig(configId, root);
1983
2137
  // the facts of the World's installed packs, over the kernel's, for this process and (through the instance file) its own
@@ -1985,7 +2139,10 @@ export async function upWorld(configId, options = {}, abort) {
1985
2139
  for (const file of packFactsPaths)
1986
2140
  overlayPackFactsFile(file);
1987
2141
  // and this process's injector host rules, which `up`'s inert-injectEnv warning reads, know them too
1988
- createRequire(import.meta.url)('@volter/world-core/pack-facts').extendPackFacts(packFactsPaths);
2142
+ // An empty overlay changes no facts. Leave built-in host rules at their first use
2143
+ // instead of loading and rebuilding a second facts/host table on every empty up.
2144
+ if (packFactsPaths.length)
2145
+ createRequire(import.meta.url)('@volter/world-core/pack-facts').extendPackFacts(packFactsPaths);
1989
2146
  const loaded = { path: found.path, config: materializeConfig(found.config, root) };
1990
2147
  const name = options.name ?? loaded.config.id;
1991
2148
  const mode = options.mode ?? 'local';
@@ -2071,6 +2228,8 @@ export async function upWorld(configId, options = {}, abort) {
2071
2228
  // the lockfile adds the cross-process exclusion, and an in-process lock holder always releases
2072
2229
  // before its first await, so withFileLock's synchronous contender wait can never deadlock the
2073
2230
  // event loop against a holder in the same process.
2231
+ // The claim's start: a kept state's lock file older than it is its last life's (removeCarriedLocks, S2 K4).
2232
+ const claimStartedAt = Date.now();
2074
2233
  try {
2075
2234
  withFileLock(instanceLockFile(root, name), () => {
2076
2235
  assertProxyOwnershipResolved(root, name);
@@ -2160,6 +2319,9 @@ export async function upWorld(configId, options = {}, abort) {
2160
2319
  for (const entry of existsSync(instance) ? readdirSync(instance) : [])
2161
2320
  if (!KEEP.includes(entry))
2162
2321
  rmSync(join(instance, entry), { recursive: true, force: true });
2322
+ // The kept state's lock files with no readable holder record are its last life's, and no one's: removed now,
2323
+ // under this boot's claim, before any process of it waits on one (S2 K4; world-store.ts removeCarriedLocks).
2324
+ removeCarriedLocks(KEEP.map(entry => join(instance, entry)).filter(path => existsSync(path)), { stateDir: stateDirName(), before: claimStartedAt });
2163
2325
  }
2164
2326
  else
2165
2327
  removeWorldState(root, instance, () => rmSync(instance, { recursive: true, force: true }));
@@ -2315,11 +2477,52 @@ export async function upWorld(configId, options = {}, abort) {
2315
2477
  await host.ready;
2316
2478
  if (colocated.length)
2317
2479
  boot.progress('co-located host ready: receipt read, every port admitted as its own');
2318
- for (const started of host.services) {
2480
+ // Every twin is asked for its credentials at once, and each ask's time is said: thirteen twins took 4.7 s
2481
+ // of one World's up and fourteen took 0.2 s of another's, and the one line after them said only that all had.
2482
+ // Asked in turn they were the tail of up (S2 R17: LibreChat's three, 426 ms, after its infrastructure was
2483
+ // ready). Each ask fills a copy of the environment as it stood before any ask; the copies' changes are then
2484
+ // applied in declaration order, so the environment, its file and up's return are what asking in turn made:
2485
+ // a twin's names are its own (`pending` is split by service before the first await), and no ask reads
2486
+ // another's answer. One difference: where one ask fails, the others still run to their end and write their
2487
+ // custody files, where asking in turn stopped at the first failure; up fails either way.
2488
+ const before = { ...env };
2489
+ // Each ask's own steps, from one origin (S2, the twins' credentials: Twenty's thirteen took 388-500 ms each and
2490
+ // Dub's fourteen 72-90 ms): queued (its turn of the map), its door read, sent, the status, the body, every name
2491
+ // filled. Asks sent together and answered together late say the host or the tab was busy; one long between its
2492
+ // own sent and status says that twin's door.
2493
+ const origin = performance.now();
2494
+ const steps = [];
2495
+ const asks = await Promise.all(host.services.map(async (started) => {
2319
2496
  const declared = colocated.find((s) => s.id === started.id);
2320
- if (declared)
2321
- await issueCredentials(declared, started, env, issuing, unissued, ownedPath(data, started.id));
2497
+ if (!declared)
2498
+ return undefined;
2499
+ const began = performance.now();
2500
+ const times = { ...(declared.package ? { twin: declared.package } : {}) };
2501
+ steps.push({ id: started.id, queued: began, times });
2502
+ const own = { ...before };
2503
+ await issueCredentials(declared, started, own, issuing, unissued, ownedPath(data, started.id), times);
2504
+ return { id: started.id, ms: Math.round(performance.now() - began), own };
2505
+ }));
2506
+ const at = (time) => time === undefined ? '-' : `+${Math.round(time - origin)}`;
2507
+ const named = steps.filter((step) => (step.times.names ?? 0) > 0);
2508
+ if (named.length)
2509
+ boot.progress(`co-located twins: each credential ask's steps, ms from the asks' start (queued/door/sent/status/body/filled): ${named
2510
+ .sort((left, right) => (right.times.filled ?? 0) - (left.times.filled ?? 0))
2511
+ .map(({ id, queued, times }) => `${id}${times.twin ? ` (${times.twin})` : ''} ${at(queued)}/${at(times.door)}/${at(times.sent)}/${at(times.headers)}/${at(times.answered)}/${at(times.filled)}`).join('; ')}`);
2512
+ const asked = [];
2513
+ for (const ask of asks) {
2514
+ if (!ask)
2515
+ continue;
2516
+ for (const [key, value] of Object.entries(ask.own))
2517
+ if (before[key] !== value)
2518
+ env[key] = value;
2519
+ for (const key of Object.keys(before))
2520
+ if (!Object.hasOwn(ask.own, key))
2521
+ delete env[key];
2522
+ asked.push({ id: ask.id, ms: ask.ms });
2322
2523
  }
2524
+ if (asked.length)
2525
+ boot.progress(`co-located twins: each asked for its credentials at once, slowest first: ${[...asked].sort((left, right) => right.ms - left.ms).slice(0, 6).map((each) => `${each.id} ${each.ms} ms`).join(', ')}; ${asked.length} asked, ${Math.max(...asked.map((each) => each.ms))} ms the longest`);
2323
2526
  writeProxyEnv(root, name, env);
2324
2527
  if (colocated.length)
2325
2528
  boot.progress('co-located twins: credentials issued, proxy environment written');
@@ -2345,11 +2548,13 @@ export async function upWorld(configId, options = {}, abort) {
2345
2548
  // possible) or the proxy daemon fails/times out, refuse rather than let unmodified CLIs bypass
2346
2549
  // the twins undetected.
2347
2550
  const attemptProxy = () => {
2348
- if (proxyForServices || (Object.keys(activeVendorMap(env)).length === 0 && env[WORLD_NETWORK_POLICY_ENV] === undefined))
2551
+ if (proxyForServices)
2349
2552
  return;
2350
2553
  // a host with no raw sockets (a browser tab) has nothing for the proxy to intercept; the injector holds the policy
2351
2554
  if (!hostHasRawEgress())
2352
2555
  return;
2556
+ if (Object.keys(activeVendorMap(env)).length === 0 && env[WORLD_NETWORK_POLICY_ENV] === undefined)
2557
+ return;
2353
2558
  if (!opensslAvailable()) {
2354
2559
  if (mode === 'sealed' || loaded.config.network !== undefined) {
2355
2560
  throw new Error(`world "${name}" requires its network policy proxy, but openssl is unavailable; refusing to start. Install openssl.`);
@@ -2426,12 +2631,12 @@ export async function upWorld(configId, options = {}, abort) {
2426
2631
  delete worldInstance.previousEnvOutput;
2427
2632
  saveWorldInstance(worldInstance);
2428
2633
  boot.progress(`environment written: ${envFile}`);
2429
- materializeBrowserFiles(data, root, name, loaded.config.id);
2634
+ await materializeWorldBrowserFiles(data, root, name, loaded.config.id);
2430
2635
  writePidsFromInstance(worldInstance, Object.values(worldInstance.services).map((service) => service.pid).filter((pid) => pid > 0));
2431
2636
  saveWorldInstance(worldInstance);
2432
2637
  const servicePids = [...new Set(Object.values(services).map((service) => service.pid).filter((pid) => pid > 0))];
2433
2638
  handoffWorldLifecycle(root, name, servicePids);
2434
- boot.progress('returning: the World is up');
2639
+ boot.progress(`returning: the World is up; ${lag.account()}`);
2435
2640
  if (mode === 'share') {
2436
2641
  try {
2437
2642
  return await shareWorldServices(name, { root, ...(options.share ?? {}) });
@@ -2710,6 +2915,7 @@ export async function downWorld(name, root = process.cwd(), options = {}) {
2710
2915
  assertSafeWorldName(name);
2711
2916
  const resolvedRoot = resolve(root);
2712
2917
  const token = randomUUID();
2918
+ const ownershipErrors = [];
2713
2919
  // Like boot's claim: a short synchronous lock protects an async lifecycle transition.
2714
2920
  // Do not hold a filesystem lock while external shutdown or the grace period waits.
2715
2921
  withFileLock(instanceLockFile(resolvedRoot, name), () => withFileLock(`${sharingClaimFile(resolvedRoot, name)}.lock`, () => {
@@ -2726,8 +2932,12 @@ export async function downWorld(name, root = process.cwd(), options = {}) {
2726
2932
  throw new Error(`World ownership mismatch: ${name}`);
2727
2933
  }
2728
2934
  }
2729
- assertProxyOwnershipResolved(resolvedRoot, name);
2730
- const prior = existsSync(instanceFile(resolvedRoot, name)) ? readWorldInstance(name, resolvedRoot, true) : undefined;
2935
+ // Unresolved startup evidence prevents finalization, not retirement of known children.
2936
+ let prior;
2937
+ try {
2938
+ prior = readWorldInstance(name, resolvedRoot, true);
2939
+ }
2940
+ catch { /* diagnosed after owned children retire */ }
2731
2941
  if (prior?.resources && !prior.lifecycle)
2732
2942
  throw new Error(`World "${name}" belongs to a legacy runtime; use its pinned runtime for teardown before migration`);
2733
2943
  if (options.purge)
@@ -2746,11 +2956,20 @@ export async function downWorld(name, root = process.cwd(), options = {}) {
2746
2956
  throw new Error(`World "${name}" is being shared or has an unreadable sharing claim; retry after sharing finishes`);
2747
2957
  rmSync(sharePath); // explicit down may reclaim a provably stale sharing claim
2748
2958
  }
2749
- retainWorldLifecycleForCleanup(resolvedRoot, name);
2959
+ retainWorldLifecycleForCleanup(resolvedRoot, name, error => { ownershipErrors.push(`Lifecycle ownership unreadable; retained: ${errorMessage(error)}`); });
2750
2960
  writeTextAtomic(stoppingFile(resolvedRoot, name), JSON.stringify({ pid: process.pid, at: new Date().toISOString(), token }));
2751
2961
  }));
2752
2962
  try {
2753
- return await downWorldClaimed(name, resolvedRoot, options);
2963
+ // The down's own signals and its whole time are said in the World's log (process-groups.mjs, worldEvent).
2964
+ const downAsked = Date.now();
2965
+ setWorldEventsFile(worldEventsPath(join(instanceDir(resolvedRoot, name), 'logs')));
2966
+ try {
2967
+ return await downWorldClaimed(name, resolvedRoot, options, ownershipErrors);
2968
+ }
2969
+ finally {
2970
+ worldEvent({ event: 'world.down', world: name, ms: Date.now() - downAsked });
2971
+ setWorldEventsFile(undefined);
2972
+ }
2754
2973
  }
2755
2974
  finally {
2756
2975
  withFileLock(instanceLockFile(resolvedRoot, name), () => {
@@ -2760,16 +2979,26 @@ export async function downWorld(name, root = process.cwd(), options = {}) {
2760
2979
  });
2761
2980
  }
2762
2981
  }
2763
- async function downWorldClaimed(name, root, options) {
2982
+ async function downWorldClaimed(name, root, options, externalErrors = []) {
2983
+ const clearBrowserFiles = options.purge ? (await import("./browsers.js")).clearBrowserFiles : undefined;
2764
2984
  assertSafeWorldName(name);
2765
2985
  const resolvedRoot = resolve(root);
2766
2986
  const dir = instanceDir(resolvedRoot, name);
2767
2987
  const pidsFile = join(dir, 'pids');
2768
- const before = existsSync(instanceFile(resolvedRoot, name)) ? readFileSync(instanceFile(resolvedRoot, name), 'utf8') : undefined;
2988
+ let before;
2989
+ try {
2990
+ before = getActiveWorldStore().read(instanceFile(resolvedRoot, name)) ?? undefined;
2991
+ }
2992
+ catch (error) {
2993
+ externalErrors.push(`World instance unreadable; retained: ${errorMessage(error)}`);
2994
+ }
2769
2995
  // A retry must not retain an earlier success receipt while teardown is in progress.
2770
2996
  rmSync(join(dir, 'stopped.json'), { force: true });
2771
- // Tear down the ambient-redirect proxy + its session CA FIRST, so the trusted CA never outlives
2772
- // the world (it was only ever trusted via per-shell env, never system-wide).
2997
+ const guestError = (error) => {
2998
+ const message = errorMessage(error);
2999
+ if (!externalErrors.includes(message))
3000
+ externalErrors.push(message);
3001
+ };
2773
3002
  let instance;
2774
3003
  try {
2775
3004
  instance = readWorldInstance(name, resolvedRoot);
@@ -2779,51 +3008,76 @@ async function downWorldClaimed(name, root, options) {
2779
3008
  catch {
2780
3009
  instance = undefined;
2781
3010
  }
2782
- readGuestProcesses(dir, instance?.instance); // missing World metadata never erases guest ownership
2783
- // Nothing to tear down when the metadata is gone and every process the lifecycle recorded has exited.
2784
- if (!instance && before === undefined && releaseAbandonedWorldLifecycle(resolvedRoot, name)) {
2785
- return { name, stopped: [], escalated: [], externalStopped: [], externalErrors: [] };
3011
+ // Read independent process records even when a guest receipt or instance is unreadable.
3012
+ const readGuests = () => {
3013
+ try {
3014
+ return readGuestProcesses(dir, instance?.instance, guestError);
3015
+ }
3016
+ catch (error) {
3017
+ guestError(error);
3018
+ return [];
3019
+ }
3020
+ };
3021
+ const guests = readGuests();
3022
+ let proxyBefore = null;
3023
+ try {
3024
+ proxyBefore = getActiveWorldStore().read(proxyStateFile(resolvedRoot, name));
2786
3025
  }
2787
- if (!instance && (before !== undefined || hasWorldLifecycle(resolvedRoot, name))) {
2788
- return { name, stopped: [], escalated: [], externalStopped: [],
2789
- externalErrors: ['World instance or external teardown metadata missing or unreadable; cleanup refused; lifecycle evidence retained'] };
3026
+ catch (error) {
3027
+ externalErrors.push(`Proxy process record unreadable; retained: ${errorMessage(error)}`);
2790
3028
  }
2791
3029
  const proxyState = readProxyState(resolvedRoot, name);
2792
- const proxyPid = proxyState?.pid;
2793
- await stopWorldProxy(resolvedRoot, name, spawnedChildren);
3030
+ const proxyPid = proxyState && Number.isInteger(proxyState.pid) && proxyState.pid > 0 ? proxyState.pid : undefined;
3031
+ if (proxyBefore !== null && (!proxyPid || (proxyState?.birth !== undefined && (typeof proxyState.birth !== 'string' || !proxyState.birth)))) {
3032
+ externalErrors.push('Proxy process record unreadable; ownership retained');
3033
+ }
2794
3034
  const foregroundPid = instance?.lastRun?.state !== 'completed' ? instance?.lastRun?.consumerPid : undefined;
2795
3035
  const holderPid = instance?.lifecycle?.holderPid ?? instance?.resources?.holderPid;
2796
- const recordedPids = [...new Set([...readPids(pidsFile), ...(foregroundPid ? [foregroundPid] : []), ...(proxyPid ? [proxyPid] : [])])].filter(pid => pid !== holderPid);
2797
- const identities = readIdentities(resolvedRoot, name);
2798
- if (proxyState?.birth)
2799
- identities[proxyState.pid] = proxyState.birth;
3036
+ let filePids = [];
3037
+ try {
3038
+ filePids = readPids(pidsFile, guests);
3039
+ }
3040
+ catch (error) {
3041
+ externalErrors.push(`World PID record unreadable; retained: ${errorMessage(error)}`);
3042
+ }
3043
+ const recordedPids = [...new Set([...(proxyPid ? [proxyPid] : []), ...filePids, ...(foregroundPid ? [foregroundPid] : []), ...(holderPid ? [holderPid] : [])])].filter(pid => Number.isInteger(pid) && pid > 0);
3044
+ const conflicts = new Set();
3045
+ const conflictingIdentity = (pid) => {
3046
+ if (conflicts.has(pid))
3047
+ return;
3048
+ conflicts.add(pid);
3049
+ externalErrors.push(`Conflicting spawn identities for pid ${pid}; not signalled; ownership retained`);
3050
+ };
3051
+ const identities = readIdentities(resolvedRoot, name, guests, conflictingIdentity);
3052
+ if (proxyPid && typeof proxyState?.birth === 'string' && proxyState.birth) {
3053
+ if (identities[proxyPid] && identities[proxyPid] !== proxyState.birth)
3054
+ conflictingIdentity(proxyPid);
3055
+ else
3056
+ identities[proxyPid] = proxyState.birth;
3057
+ }
2800
3058
  const foreign = (await Promise.all(recordedPids.map(async (pid) => {
2801
3059
  const now = identities[pid] ? await processBirth(pid) : undefined;
2802
3060
  return now && now !== identities[pid] ? { pid, recorded: identities[pid], now } : undefined;
2803
3061
  }))).filter((entry) => !!entry);
2804
- const pids = recordedPids.filter((pid) => !foreign.some((entry) => entry.pid === pid));
3062
+ const pids = recordedPids.filter((pid) => pid !== holderPid && !conflicts.has(pid) && !foreign.some((entry) => entry.pid === pid));
2805
3063
  const processServices = Object.values(instance?.services ?? {}).filter((service) => service.type !== 'external' && service.log);
2806
3064
  // Only a service still alive is stopped by this `down`; a dead one's end was not the World's.
2807
3065
  const stillAlive = undeadPids(processServices.map((service) => service.pid).filter((pid) => pids.includes(pid)));
2808
3066
  recordWorldStop(processServices.filter((service) => stillAlive.includes(service.pid)).map((service) => service.log), 'world.service.stop', 'down', 'SIGTERM');
2809
3067
  const stopped = await signalPids(pids, 'SIGTERM', identities, spawnedChildren);
2810
- // Stop self-managed externals via their declared `down` command. A missing instance is
2811
- // safe only without retained lifecycle ownership, checked before any signals above.
3068
+ // Missing external metadata remains a finalization error after known children retire.
2812
3069
  let externalStopped = [];
2813
- let externalErrors = [];
2814
3070
  let unavailable = [];
2815
- try {
2816
- if (!instance)
2817
- throw new Error('World instance unavailable');
2818
- const result = await teardownExternalServices(instance);
2819
- externalStopped = result.stopped;
2820
- externalErrors = result.errors;
2821
- unavailable = result.unavailable;
2822
- }
2823
- catch {
2824
- // no instance.json (never fully came up) — only owned processes needed stopping.
2825
- if (before !== undefined)
2826
- externalErrors.push('World instance or external teardown metadata unreadable; cleanup refused');
3071
+ if (instance) {
3072
+ try {
3073
+ const result = await teardownExternalServices(instance);
3074
+ externalStopped = result.stopped;
3075
+ externalErrors.push(...result.errors);
3076
+ unavailable = result.unavailable;
3077
+ }
3078
+ catch (error) {
3079
+ externalErrors.push(errorMessage(error));
3080
+ }
2827
3081
  }
2828
3082
  // Escalate AFTER the external teardown so externals never wait on the grace, and
2829
3083
  // poll the full pids list rather than `stopped`: a pid whose SIGTERM errored is almost always
@@ -2832,7 +3086,10 @@ async function downWorldClaimed(name, root, options) {
2832
3086
  recordWorldStop(processServices.filter((service) => escalated.includes(service.pid)).map((service) => service.log), 'world.service.killed', 'down', 'SIGKILL');
2833
3087
  // A launch which had already entered its spawn seam may publish while TERM is delivered.
2834
3088
  // Once services/agents retire, join every late receipt before certifying this generation stopped.
2835
- const lateGuests = readGuestProcesses(dir, instance?.instance).filter(receipt => !pids.includes(receipt.record.pid));
3089
+ const settlingGuests = readGuests();
3090
+ readIdentities(resolvedRoot, name, settlingGuests, conflictingIdentity);
3091
+ const lateGuests = settlingGuests.filter(({ record }) => !conflicts.has(record.pid)
3092
+ && (!recordedPids.includes(record.pid) || identities[record.pid] !== record.start));
2836
3093
  try {
2837
3094
  await retireOwnedProcessGroups(lateGuests.map(receipt => receipt.record), options.graceMs ?? DOWN_GRACE_MS_DEFAULT);
2838
3095
  }
@@ -2840,7 +3097,7 @@ async function downWorldClaimed(name, root, options) {
2840
3097
  externalErrors.push(errorMessage(error));
2841
3098
  }
2842
3099
  pids.push(...lateGuests.map(receipt => receipt.record.pid));
2843
- if (holderPid && !externalErrors.length && !undeadPids(pids).length) {
3100
+ if (holderPid && recordedPids.includes(holderPid) && !conflicts.has(holderPid) && !foreign.some(entry => entry.pid === holderPid)) {
2844
3101
  pids.push(holderPid);
2845
3102
  stopped.push(...await signalPids([holderPid], 'SIGTERM', identities, spawnedChildren));
2846
3103
  escalated.push(...await killSurvivorsAfterGrace([holderPid], options.graceMs ?? DOWN_GRACE_MS_DEFAULT, identities, spawnedChildren));
@@ -2851,21 +3108,43 @@ async function downWorldClaimed(name, root, options) {
2851
3108
  const notSignalled = foreign.map((entry) => `pid ${entry.pid} now names another process (started ${entry.now}; the World's started ${entry.recorded}); not signalled`);
2852
3109
  const result = { name, stopped, escalated, externalStopped, externalErrors, ...(notSignalled.length ? { notSignalled } : {}) };
2853
3110
  const path = instanceFile(resolvedRoot, name);
2854
- const current = existsSync(path) ? readFileSync(path, 'utf8') : undefined;
3111
+ let current;
3112
+ try {
3113
+ current = getActiveWorldStore().read(path) ?? undefined;
3114
+ }
3115
+ catch (error) {
3116
+ externalErrors.push(`World generation unreadable; finalization refused: ${errorMessage(error)}`);
3117
+ return result;
3118
+ }
2855
3119
  if (current !== before || existsSync(bootingFile(resolvedRoot, name))) {
2856
3120
  externalErrors.push('World generation changed or is booting; finalization refused');
2857
3121
  return result;
2858
3122
  }
2859
- if (existsSync(proxyStateFile(resolvedRoot, name)) || existsSync(sharingClaimFile(resolvedRoot, name))) {
3123
+ let currentProxy;
3124
+ try {
3125
+ currentProxy = getActiveWorldStore().read(proxyStateFile(resolvedRoot, name));
3126
+ }
3127
+ catch (error) {
3128
+ externalErrors.push(`Proxy record unreadable; finalization refused: ${errorMessage(error)}`);
3129
+ return result;
3130
+ }
3131
+ if ((currentProxy !== null && currentProxy !== proxyBefore) || existsSync(sharingClaimFile(resolvedRoot, name))) {
2860
3132
  externalErrors.push('Proxy or sharing lifecycle changed during teardown; cleanup refused');
2861
3133
  return result;
2862
3134
  }
2863
- const guestReceipts = readGuestProcesses(dir, instance?.instance);
3135
+ if (existsSync(proxyStartingFile(resolvedRoot, name))) {
3136
+ externalErrors.push(`World "${name}" has unresolved proxy startup ownership; recorded children retired where identity verified; lifecycle and instance retained. Inspect: ${proxyStartingFile(resolvedRoot, name)}`);
3137
+ }
3138
+ const guestReceipts = readGuests();
2864
3139
  const surviving = undeadPids([...pids, ...guestReceipts.map(receipt => receipt.record.pid)]);
2865
3140
  if (surviving.length)
2866
3141
  externalErrors.push(`World processes still alive or uninspectable after teardown: ${surviving.join(', ')}; cleanup refused`);
2867
3142
  if (guestReceipts.some(receipt => foreign.some(entry => entry.pid === receipt.record.pid)))
2868
3143
  externalErrors.push('Guest spawn identity changed; receipt retained and cleanup refused');
3144
+ if (!instance && (before !== undefined || hasWorldLifecycle(resolvedRoot, name))
3145
+ && !(before === undefined && !externalErrors.length && releaseAbandonedWorldLifecycle(resolvedRoot, name))) {
3146
+ externalErrors.push('World instance or external teardown metadata missing or unreadable; cleanup refused; lifecycle evidence retained');
3147
+ }
2869
3148
  let externalAbandoned;
2870
3149
  if (options.purge && instance && externalErrors.length && externalErrors.length === unavailable.length && !foreign.length) {
2871
3150
  const inventory = exitedWorldLifecyclePids(resolvedRoot, name, instance.createdAt);
@@ -2880,6 +3159,8 @@ async function downWorldClaimed(name, root, options) {
2880
3159
  }
2881
3160
  if (externalErrors.length)
2882
3161
  return result;
3162
+ // Keep proxy state and session trust until its recorded processes have exited.
3163
+ clearWorldProxyFiles(resolvedRoot, name);
2883
3164
  releaseGuestProcesses(guestReceipts);
2884
3165
  rmSync(pidsFile, { force: true });
2885
3166
  if (before) {
@@ -2899,7 +3180,9 @@ async function downWorldClaimed(name, root, options) {
2899
3180
  }
2900
3181
  export function statusWorld(name, root = process.cwd(), options = {}) {
2901
3182
  const worldInstance = readWorldInstance(name, resolve(root), options.readOnly);
2902
- const live = ownedPids(resolve(root), name, livePids(readPids(worldInstance.pidsFile)));
3183
+ // One status, one reading of the process table: its questions below are about the same moment.
3184
+ const listing = {};
3185
+ const live = ownedPids(resolve(root), name, livePids(readPids(worldInstance.pidsFile), listing));
2903
3186
  const externalOnly = Object.values(worldInstance.services).every(service => service.type === 'external');
2904
3187
  const recorded = externalOnly && !existsSync(stoppingFile(root, name)) && !existsSync(join(instanceDir(root, name), 'stopped.json'))
2905
3188
  && isWorldLifecycleRunning(root, name, worldInstance.createdAt);
@@ -2907,9 +3190,9 @@ export function statusWorld(name, root = process.cwd(), options = {}) {
2907
3190
  // read; a service's own state is judged by its process group, so a dead service reads as dead beside a live daemon.
2908
3191
  const owned = Object.values(worldInstance.services).filter((service) => service.type !== 'external' && service.pid);
2909
3192
  const ownPids = ownedPids(resolve(root), name, owned.map((service) => service.pid));
2910
- const leaders = livePids(ownPids);
3193
+ const leaders = livePids(ownPids, listing);
2911
3194
  // `light` (a listing of every World) skips the process-table scan: a lingering group then reads as stopped.
2912
- const groups = ownPids.length && !options.light ? survivingOwnedGroups(ownPids) : [];
3195
+ const groups = ownPids.length && !options.light ? survivingOwnedGroups(ownPids, listing) : [];
2913
3196
  const serviceStates = {};
2914
3197
  for (const service of Object.values(worldInstance.services)) {
2915
3198
  serviceStates[service.id] = service.type === 'external' ? 'external'
@@ -3771,7 +4054,7 @@ export async function runWorld(configId, command, options = {}) {
3771
4054
  if (command.length === 0)
3772
4055
  throw new Error('Missing command after --');
3773
4056
  if (!options.keep)
3774
- return superviseWorldRun(configId, command, options);
4057
+ return (await import("./run-task.js")).superviseWorldRun(configId, command, options);
3775
4058
  return runWorldOwned(configId, command, options);
3776
4059
  }
3777
4060
  /** Internal task entry. The lifetime owner exists before admission/startup. Cancellation during
@@ -3878,13 +4161,13 @@ export function listWorlds(root = process.cwd()) {
3878
4161
  })
3879
4162
  .sort((a, b) => a.name.localeCompare(b.name));
3880
4163
  }
3881
- function worldAttachedCommandEnv(name, root, verbose = false) {
3882
- const status = statusWorld(name, root);
4164
+ function worldAttachedCommandEnv(name, root, verbose = false, instance) {
4165
+ const status = instance ?? statusWorld(name, root);
3883
4166
  const sealed = status.env.VOLTER_WORLD_MODE === 'sealed' || status.env[WORLD_NETWORK_POLICY_ENV] !== undefined;
3884
4167
  const baseEnv = { ...admittedEnv(stripEnvOf(status.env)), ...status.env };
3885
4168
  let proxyEnv = {};
3886
4169
  try {
3887
- const proxy = ensureWorldProxy(name, root, [admittedEnv(stripEnvOf(status.env)), status.env]);
4170
+ const proxy = ensureWorldProxyForCommand(name, root, [admittedEnv(stripEnvOf(status.env)), status.env], instance);
3888
4171
  if (proxy?.url) {
3889
4172
  proxyEnv = proxy.env;
3890
4173
  // a host with no raw sockets (a browser tab) runs no proxy: the injector holds the policy there, as at `up`
@@ -3996,7 +4279,7 @@ export async function attachWorld(name, command, root = process.cwd(), options =
3996
4279
  assertSafeWorldName(name);
3997
4280
  const resolvedRoot = resolve(root);
3998
4281
  checkedOwner(options.owner);
3999
- const attached = worldAttachedCommandEnv(name, resolvedRoot, options.verbose);
4282
+ const attached = worldAttachedCommandEnv(name, resolvedRoot, options.verbose, options.instance);
4000
4283
  const record = withFileLock(instanceLockFile(resolvedRoot, name), () => {
4001
4284
  if (existsSync(stoppingFile(resolvedRoot, name)) || existsSync(bootingFile(resolvedRoot, name))) {
4002
4285
  throw new Error(`World "${name}" is changing lifecycle; retry after it finishes`);
@@ -4004,7 +4287,7 @@ export async function attachWorld(name, command, root = process.cwd(), options =
4004
4287
  const current = statusWorld(name, resolvedRoot);
4005
4288
  // Which of the two it was, with what was read: "stopped" and "another instance" are different repairs.
4006
4289
  if (!current.running)
4007
- throw new Error(`World "${name}" is not running while attaching: none of its recorded processes is live and its own (recorded pids ${readPids(current.pidsFile).join(', ') || 'none'}; live and its own: none)`);
4290
+ throw new Error(`World "${name}" is not running while attaching: none of its recorded processes is live and its own (recorded pids ${readPids(current.pidsFile).join(', ') || 'none'}; live and its own: none); ${describeWorldLifecycle(resolvedRoot, name, current.createdAt)})`);
4008
4291
  if (current.createdAt !== attached.instance.createdAt)
4009
4292
  throw new Error(`World "${name}" changed while attaching: its instance was created ${attached.instance.createdAt} when the command's environment was read and ${current.createdAt} now`);
4010
4293
  // Attaching to a World whose services died is allowed (a command may not need them), but never silent.
@@ -4016,7 +4299,7 @@ export async function attachWorld(name, command, root = process.cwd(), options =
4016
4299
  process.stderr.write(`World "${name}": service ${id} is not running${end ? `: it ${describeServiceEnd(end)}` : ''}${service?.log ? `\n log: ${service.log}` : ''}\n`);
4017
4300
  }
4018
4301
  return createConsumer(resolvedRoot, name, current.createdAt, 'attach', options.owner ?? current.lifecycle?.owner ?? current.resources?.owner, { completionRequired: true });
4019
- });
4302
+ }, { operation: 'instance attach' });
4020
4303
  let timer;
4021
4304
  let retired = true;
4022
4305
  try {
@@ -4214,6 +4497,7 @@ export async function shellWorld(name, root = process.cwd()) {
4214
4497
  }
4215
4498
  else if (opensslAvailable()) {
4216
4499
  try {
4500
+ const { startRedirectProxy } = await import("./redirect-proxy.js");
4217
4501
  proxy = await startRedirectProxy({ env, trustEnv: [admittedEnv(stripEnvOf(env)), env], tlsDir: tlsDir(resolvedRoot, name) });
4218
4502
  Object.assign(env, proxy.proxyEnv());
4219
4503
  process.stderr.write(`world '${name}' active in a subshell — vendor calls hit the twins (ambient proxy ${proxy.url}). type 'exit' to leave.\n`);
@@ -4257,6 +4541,9 @@ function proxyDaemonDeadlineMs() {
4257
4541
  * reuses a live daemon. Returns its proxy env, or null if openssl is missing (env-only fallback). The
4258
4542
  * daemon is recorded in `proxy.json` and stopped by `down`. */
4259
4543
  export function ensureWorldProxy(name, root = process.cwd(), caller = process.env) {
4544
+ return ensureWorldProxyForCommand(name, root, caller);
4545
+ }
4546
+ function ensureWorldProxyForCommand(name, root, caller, instance) {
4260
4547
  assertSafeWorldName(name);
4261
4548
  const resolvedRoot = resolve(root);
4262
4549
  return withFileLock(instanceLockFile(resolvedRoot, name), () => {
@@ -4265,7 +4552,7 @@ export function ensureWorldProxy(name, root = process.cwd(), caller = process.en
4265
4552
  throw new Error(`World "${name}" is being stopped`);
4266
4553
  if (liveBootingClaim(bootingFile(resolvedRoot, name)))
4267
4554
  throw new Error(`World "${name}" is being booted`);
4268
- if (!statusWorld(name, resolvedRoot, { readOnly: true }).running)
4555
+ if (!instance && !statusWorld(name, resolvedRoot, { readOnly: true }).running)
4269
4556
  throw new Error(`World "${name}" is not running`);
4270
4557
  return ensureWorldProxyUnlocked(name, resolvedRoot, caller);
4271
4558
  });
@@ -4483,6 +4770,7 @@ export async function runProxyDaemon(name, root = process.cwd(), envFile) {
4483
4770
  throw new Error(`World "${name}" is not running`);
4484
4771
  env = { ...admittedEnv(stripEnvOf(status.env)), ...status.env };
4485
4772
  }
4773
+ const { startRedirectProxy } = await import("./redirect-proxy.js");
4486
4774
  const proxy = await startRedirectProxy({ env, envLoader, tlsDir: tlsDir(resolvedRoot, name), note: (line) => process.stderr.write(`${line}\n`) });
4487
4775
  const identity = await spawnIdentity(process.pid);
4488
4776
  if (!identity.start) {
@@ -4516,8 +4804,8 @@ export async function runProxyDaemon(name, root = process.cwd(), envFile) {
4516
4804
  process.on('SIGTERM', stop);
4517
4805
  process.on('SIGINT', stop);
4518
4806
  }
4519
- /** Stop a world's ambient-redirect proxy daemon (if any) and drop its session CA. Best-effort —
4520
- * called from `downWorld` so the trusted CA never outlives the world. */
4807
+ /** Failed boot's proxy retirement; normal down retires it with the other recorded children
4808
+ * and clears session trust only after confirming exit. */
4521
4809
  async function stopWorldProxy(root, name, handles) {
4522
4810
  const state = readProxyState(root, name);
4523
4811
  if (state && isAlive(state.pid)) {
@@ -4532,14 +4820,11 @@ async function stopWorldProxy(root, name, handles) {
4532
4820
  await signalOwnedProcess(identity, 'SIGTERM');
4533
4821
  }
4534
4822
  }
4535
- try {
4536
- rmSync(proxyStateFile(root, name), { force: true });
4537
- }
4538
- catch { /* ignore */ }
4539
- try {
4540
- rmSync(proxyEnvFile(root, name), { force: true });
4541
- }
4542
- catch { /* ignore */ }
4823
+ clearWorldProxyFiles(root, name);
4824
+ }
4825
+ function clearWorldProxyFiles(root, name) {
4826
+ rmSync(proxyStateFile(root, name), { force: true });
4827
+ rmSync(proxyEnvFile(root, name), { force: true });
4543
4828
  tearDownCa(tlsDir(root, name));
4544
4829
  }
4545
4830
  /**