@nimbus-sh/worker 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/NOTICE.md +5 -0
  2. package/README.md +10 -0
  3. package/dist/_shared/session-router.d.ts +7 -0
  4. package/dist/_shared/session-router.d.ts.map +1 -1
  5. package/dist/_shared/session-router.js +7 -0
  6. package/dist/bindings/body.d.ts +6 -0
  7. package/dist/bindings/body.d.ts.map +1 -0
  8. package/dist/bindings/body.js +37 -0
  9. package/dist/bindings/kv.d.ts +0 -2
  10. package/dist/bindings/kv.d.ts.map +1 -1
  11. package/dist/bindings/kv.js +3 -42
  12. package/dist/bindings/r2.d.ts +0 -2
  13. package/dist/bindings/r2.d.ts.map +1 -1
  14. package/dist/bindings/r2.js +4 -44
  15. package/dist/esbuild-cli-artifact.generated.d.ts +18 -0
  16. package/dist/esbuild-cli-artifact.generated.d.ts.map +1 -0
  17. package/dist/esbuild-cli-artifact.generated.js +17 -0
  18. package/dist/facets/cirrus-real.d.ts.map +1 -1
  19. package/dist/facets/cirrus-real.js +9 -4
  20. package/dist/facets/compose.d.ts +11 -7
  21. package/dist/facets/compose.d.ts.map +1 -1
  22. package/dist/facets/compose.js +8 -38
  23. package/dist/facets/data-plan.d.ts +79 -0
  24. package/dist/facets/data-plan.d.ts.map +1 -0
  25. package/dist/facets/data-plan.js +477 -0
  26. package/dist/facets/esbuild-transform.d.ts +36 -10
  27. package/dist/facets/esbuild-transform.d.ts.map +1 -1
  28. package/dist/facets/esbuild-transform.js +216 -20
  29. package/dist/facets/exec-telemetry.d.ts +7 -3
  30. package/dist/facets/exec-telemetry.d.ts.map +1 -1
  31. package/dist/facets/manager.d.ts +179 -78
  32. package/dist/facets/manager.d.ts.map +1 -1
  33. package/dist/facets/manager.js +1221 -649
  34. package/dist/facets/opencode-staging.d.ts +0 -2
  35. package/dist/facets/opencode-staging.d.ts.map +1 -1
  36. package/dist/facets/opencode-staging.js +5 -11
  37. package/dist/facets/process.d.ts +4 -0
  38. package/dist/facets/process.d.ts.map +1 -1
  39. package/dist/facets/process.js +3 -0
  40. package/dist/facets/read-profile.d.ts +149 -0
  41. package/dist/facets/read-profile.d.ts.map +1 -0
  42. package/dist/facets/read-profile.js +483 -0
  43. package/dist/facets/resident-identity.d.ts +0 -1
  44. package/dist/facets/resident-identity.d.ts.map +1 -1
  45. package/dist/facets/resident-identity.js +0 -3
  46. package/dist/facets/wasm-swap-registry.d.ts +34 -25
  47. package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
  48. package/dist/facets/wasm-swap-registry.js +62 -56
  49. package/dist/facets/ws-terminal.d.ts +4 -4
  50. package/dist/facets/ws-terminal.d.ts.map +1 -1
  51. package/dist/facets/ws-terminal.js +4 -5
  52. package/dist/git/commands.d.ts +16 -9
  53. package/dist/git/commands.d.ts.map +1 -1
  54. package/dist/git/commands.js +1818 -188
  55. package/dist/git/network-facet.d.ts +10 -2
  56. package/dist/git/network-facet.d.ts.map +1 -1
  57. package/dist/git/network-facet.js +62 -16
  58. package/dist/git/unified-diff.d.ts +74 -0
  59. package/dist/git/unified-diff.d.ts.map +1 -0
  60. package/dist/git/unified-diff.js +676 -0
  61. package/dist/git-bundle.generated.d.ts +7 -11
  62. package/dist/git-bundle.generated.d.ts.map +1 -1
  63. package/dist/git-bundle.generated.js +7 -11
  64. package/dist/hosted/commands.d.ts.map +1 -1
  65. package/dist/hosted/commands.js +62 -243
  66. package/dist/hosted/runtime.d.ts +19 -13
  67. package/dist/hosted/runtime.d.ts.map +1 -1
  68. package/dist/hosted/runtime.js +71 -14
  69. package/dist/hosted/services.d.ts +6 -7
  70. package/dist/hosted/services.d.ts.map +1 -1
  71. package/dist/hosted/services.js +10 -14
  72. package/dist/hosted/session.d.ts +165 -0
  73. package/dist/hosted/session.d.ts.map +1 -0
  74. package/dist/hosted/session.js +132 -0
  75. package/dist/loaders/generated-workers.d.ts +5 -2
  76. package/dist/loaders/generated-workers.d.ts.map +1 -1
  77. package/dist/loaders/generated-workers.js +6 -3
  78. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
  79. package/dist/loaders/npm-resolve-preamble.js +0 -27
  80. package/dist/node-shims-artifact.generated.d.ts +13 -4
  81. package/dist/node-shims-artifact.generated.d.ts.map +1 -1
  82. package/dist/node-shims-artifact.generated.js +16 -7
  83. package/dist/npm/bin-links.d.ts +3 -1
  84. package/dist/npm/bin-links.d.ts.map +1 -1
  85. package/dist/npm/bin-links.js +32 -8
  86. package/dist/npm/cache.d.ts +5 -0
  87. package/dist/npm/cache.d.ts.map +1 -1
  88. package/dist/npm/cache.js +13 -1
  89. package/dist/npm/install-batch-facet.d.ts +2 -2
  90. package/dist/npm/install-batch-facet.js +3 -3
  91. package/dist/npm/installer.d.ts +17 -1
  92. package/dist/npm/installer.d.ts.map +1 -1
  93. package/dist/npm/installer.js +159 -20
  94. package/dist/npm/package-lock.d.ts +32 -0
  95. package/dist/npm/package-lock.d.ts.map +1 -0
  96. package/dist/npm/package-lock.js +65 -0
  97. package/dist/npm/pre-bundle-facet.d.ts +1 -30
  98. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  99. package/dist/npm/pre-bundle-facet.js +3 -5
  100. package/dist/npm/r2-cache.d.ts +2 -0
  101. package/dist/npm/r2-cache.d.ts.map +1 -1
  102. package/dist/npm/r2-cache.js +2 -0
  103. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  104. package/dist/npm/resolve-one-facet.js +14 -3
  105. package/dist/npm/resolver.d.ts +0 -7
  106. package/dist/npm/resolver.d.ts.map +1 -1
  107. package/dist/npm/resolver.js +0 -22
  108. package/dist/opencode-artifact.generated.js +2 -2
  109. package/dist/replica/routing.d.ts +2 -38
  110. package/dist/replica/routing.d.ts.map +1 -1
  111. package/dist/replica/routing.js +3 -55
  112. package/dist/router/index.d.ts.map +1 -1
  113. package/dist/router/index.js +15 -2
  114. package/dist/router/public-directory.d.ts +18 -0
  115. package/dist/router/public-directory.d.ts.map +1 -1
  116. package/dist/router/public-directory.js +1 -1
  117. package/dist/router/remote-api.d.ts +3 -1
  118. package/dist/router/remote-api.d.ts.map +1 -1
  119. package/dist/router/remote-api.js +42 -79
  120. package/dist/runtime/bash-repl.js +2 -2
  121. package/dist/runtime/bun-repl.d.ts.map +1 -1
  122. package/dist/runtime/bun-repl.js +2 -13
  123. package/dist/runtime/esbuild-wasm-bytes.d.ts +17 -5
  124. package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
  125. package/dist/runtime/esbuild-wasm-bytes.js +43 -94
  126. package/dist/runtime/git-bundle-artifact.d.ts +17 -0
  127. package/dist/runtime/git-bundle-artifact.d.ts.map +1 -0
  128. package/dist/runtime/git-bundle-artifact.js +25 -0
  129. package/dist/runtime/node-repl.d.ts.map +1 -1
  130. package/dist/runtime/node-repl.js +2 -13
  131. package/dist/runtime/node-shims-artifact.d.ts +22 -23
  132. package/dist/runtime/node-shims-artifact.d.ts.map +1 -1
  133. package/dist/runtime/node-shims-artifact.js +48 -78
  134. package/dist/runtime/node-shims.d.ts.map +1 -1
  135. package/dist/runtime/node-shims.js +1567 -864
  136. package/dist/runtime/opencode-artifact.d.ts.map +1 -1
  137. package/dist/runtime/opencode-artifact.js +17 -53
  138. package/dist/runtime/opencode-facet-runner.d.ts +3 -6
  139. package/dist/runtime/opencode-facet-runner.d.ts.map +1 -1
  140. package/dist/runtime/opencode-facet-runner.js +45 -10
  141. package/dist/runtime/opentui-wasm-bytes.d.ts.map +1 -1
  142. package/dist/runtime/opentui-wasm-bytes.js +16 -49
  143. package/dist/runtime/package-manager.d.ts +0 -2
  144. package/dist/runtime/package-manager.d.ts.map +1 -1
  145. package/dist/runtime/package-manager.js +0 -3
  146. package/dist/runtime/python-repl.d.ts.map +1 -1
  147. package/dist/runtime/python-repl.js +2 -2
  148. package/dist/runtime/repl-session.d.ts +10 -0
  149. package/dist/runtime/repl-session.d.ts.map +1 -1
  150. package/dist/runtime/repl-session.js +7 -0
  151. package/dist/runtime/ruby-repl.d.ts.map +1 -1
  152. package/dist/runtime/ruby-repl.js +2 -2
  153. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  154. package/dist/runtime/ruby-resident.js +36 -35
  155. package/dist/runtime/runtime-catalog.d.ts +10 -3
  156. package/dist/runtime/runtime-catalog.d.ts.map +1 -1
  157. package/dist/runtime/runtime-catalog.js +125 -15
  158. package/dist/runtime/sqlite-wasm-bytes.d.ts +4 -4
  159. package/dist/runtime/sqlite-wasm-bytes.d.ts.map +1 -1
  160. package/dist/runtime/sqlite-wasm-bytes.js +21 -57
  161. package/dist/runtime/staged-source.d.ts +67 -0
  162. package/dist/runtime/staged-source.d.ts.map +1 -0
  163. package/dist/runtime/staged-source.js +113 -0
  164. package/dist/runtime/wasi/preamble.d.ts.map +1 -1
  165. package/dist/runtime/wasi/preamble.js +0 -24
  166. package/dist/runtime-catalog.generated.js +1 -1
  167. package/dist/session/agent.d.ts +14 -2
  168. package/dist/session/agent.d.ts.map +1 -1
  169. package/dist/session/agent.js +57 -19
  170. package/dist/session/ai.d.ts +0 -11
  171. package/dist/session/ai.d.ts.map +1 -1
  172. package/dist/session/ai.js +0 -4
  173. package/dist/session/fs-watch.d.ts +0 -11
  174. package/dist/session/fs-watch.d.ts.map +1 -1
  175. package/dist/session/fs-watch.js +0 -21
  176. package/dist/session/hibernation.d.ts +34 -11
  177. package/dist/session/hibernation.d.ts.map +1 -1
  178. package/dist/session/hibernation.js +46 -29
  179. package/dist/session/init-phases.d.ts +1 -2
  180. package/dist/session/init-phases.d.ts.map +1 -1
  181. package/dist/session/init-phases.js +1 -2
  182. package/dist/session/init.d.ts.map +1 -1
  183. package/dist/session/init.js +7 -20
  184. package/dist/session/keys.d.ts +0 -3
  185. package/dist/session/keys.d.ts.map +1 -1
  186. package/dist/session/keys.js +0 -3
  187. package/dist/session/legacy-reset.d.ts +11 -0
  188. package/dist/session/legacy-reset.d.ts.map +1 -0
  189. package/dist/session/legacy-reset.js +16 -0
  190. package/dist/session/nimbus-session.d.ts +41 -15
  191. package/dist/session/nimbus-session.d.ts.map +1 -1
  192. package/dist/session/nimbus-session.js +64 -70
  193. package/dist/session/npm-install-port.d.ts.map +1 -1
  194. package/dist/session/npm-install-port.js +1 -0
  195. package/dist/session/programmatic.d.ts +13 -18
  196. package/dist/session/programmatic.d.ts.map +1 -1
  197. package/dist/session/programmatic.js +53 -39
  198. package/dist/session/replica-routes.d.ts +2 -3
  199. package/dist/session/replica-routes.d.ts.map +1 -1
  200. package/dist/session/replica-routes.js +3 -10
  201. package/dist/session/routes.d.ts.map +1 -1
  202. package/dist/session/routes.js +7 -10
  203. package/dist/session/rpc.d.ts +104 -20
  204. package/dist/session/rpc.d.ts.map +1 -1
  205. package/dist/session/rpc.js +184 -37
  206. package/dist/session/start-real-vite.js +2 -2
  207. package/dist/session/state-store.d.ts +1 -17
  208. package/dist/session/state-store.d.ts.map +1 -1
  209. package/dist/session/state-store.js +3 -53
  210. package/dist/session/supervisor-op.d.ts +7 -0
  211. package/dist/session/supervisor-op.d.ts.map +1 -1
  212. package/dist/session/supervisor-op.js +6 -1
  213. package/dist/session/supervisor-rpc.d.ts +104 -15
  214. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  215. package/dist/session/supervisor-rpc.js +188 -71
  216. package/dist/session/vite-command.d.ts.map +1 -1
  217. package/dist/session/vite-command.js +17 -14
  218. package/dist/session/ws.d.ts.map +1 -1
  219. package/dist/session/ws.js +20 -8
  220. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  221. package/dist/shell/npm-bin-entrypoints.js +30 -5
  222. package/dist/vfs/facet-resident-limits.d.ts +33 -0
  223. package/dist/vfs/facet-resident-limits.d.ts.map +1 -0
  224. package/dist/vfs/facet-resident-limits.js +33 -0
  225. package/dist/vfs/facet-resident-store.d.ts +2 -10
  226. package/dist/vfs/facet-resident-store.d.ts.map +1 -1
  227. package/dist/vfs/facet-resident-store.js +1594 -219
  228. package/dist/workspace-host.d.ts +2 -0
  229. package/dist/workspace-host.d.ts.map +1 -1
  230. package/dist/workspace-host.js +2 -0
  231. package/dist/wrangler/nimbus-wrangler.d.ts +0 -2
  232. package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
  233. package/dist/wrangler/nimbus-wrangler.js +5 -22
  234. package/package.json +8 -7
  235. package/public/_assets/opencode/1.16.2/index-attach.js +2 -2
  236. package/public/_assets/runtime/esbuild-cli-c4fda380d52befc1.js +6892 -0
  237. package/public/_assets/runtime/git-de3e60c9f3ff006a.js +103 -0
  238. package/public/_assets/runtime/{node-shims-7b4dd7a76d95d21e.js → node-shims-06565b121555c194.js} +2089 -861
  239. package/public/_assets/runtime/resident-store-6ffb1939c53d982f.js +2264 -0
  240. package/public/_assets/runtime/vfs-write-ledger-e501e8fc793bc2c1.js +889 -0
  241. package/scripts/build-opencode-attach-entry.mjs +51 -1
  242. package/scripts/bundle-facet-workers.mjs +114 -3
  243. package/scripts/bundle-git.mjs +88 -27
  244. package/scripts/bundle-node-shims.mjs +72 -46
  245. package/scripts/bundle-runtime.mjs +18 -421
  246. package/scripts/cf-git-patch.mjs +137 -0
  247. package/scripts/patch-install-deps.mjs +49 -96
  248. package/scripts/runtime-specs.mjs +435 -0
  249. package/vendor/git-http.generated.d.ts +82 -0
  250. package/vendor/git-upstream.generated.d.ts +4378 -0
  251. package/vendor/git.LICENSE +7 -0
  252. package/vendor/git.generated.d.mts +21 -0
  253. package/vendor/git.generated.mjs +103 -0
  254. package/dist/replica/suspension.d.ts +0 -36
  255. package/dist/replica/suspension.d.ts.map +0 -1
  256. package/dist/replica/suspension.js +0 -49
  257. package/dist/runtime/static-server.d.ts +0 -3
  258. package/dist/runtime/static-server.d.ts.map +0 -1
  259. package/dist/runtime/static-server.js +0 -133
  260. package/scripts/clean-dist.mjs +0 -7
@@ -15,29 +15,38 @@
15
15
  * Long-running processes use a dynamic Worker entrypoint that stays
16
16
  * registered in ProcessTable and PortRegistry until exit or kill.
17
17
  */
18
- import { fetchNodeShimsCode } from '../runtime/node-shims-artifact.js';
18
+ import { MK_COMPILED_FN_SOURCE } from '@nimbus-sh/core/_shared/compiled-fn.js';
19
+ import { exitCodeForSignal } from '@nimbus-sh/core/substrate/lifo/shell/signals.js';
20
+ import { fetchNodeFacetSources } from '../runtime/node-shims-artifact.js';
19
21
  import { generateSqliteFacetPreamble } from '../runtime/sqlite-shim.js';
20
22
  import { getRealNodeImportsCode } from '@nimbus-sh/core/_shared/real-node-imports.js';
21
- import { FACET_RESIDENT_STORE_SOURCE } from '../vfs/facet-resident-store.js';
22
23
  import { VFS_CURSOR_SEED_SOURCE, serializeFacetVfsCursor, } from '@nimbus-sh/core/_shared/facet-vfs-cursor.js';
23
- import { VFS_WRITE_LEDGER_SOURCE } from '@nimbus-sh/core/_shared/vfs-write-ledger.js';
24
- import { ExecutionFs } from '@nimbus-sh/core/shell/execution-fs.js';
24
+ import { typescriptLoader } from '@nimbus-sh/core/_shared/typescript-specifiers.js';
25
25
  import { stripLeadingSlashes, vfsPathExtension } from '@nimbus-sh/core/vfs/path.js';
26
26
  import { clearPortCapability, listPortReservations, readPortReservation, readPortReservationByOwner, releasePortReservation, restoreReservedPortCapability, } from '../session/port-capability.js';
27
27
  import { deriveResidentOwner } from './resident-identity.js';
28
28
  import { z } from 'zod/v4';
29
29
  import { RESIDENT_OWNER_KEY_PREFIX, DURABLE_IMAGES_KEY_PREFIX } from '../session/keys.js';
30
30
  import { PORT_CAPABILITY_KEY_PREFIX } from '../session/keys.js';
31
- import { unbindPublicPortCapability } from '../router/public-directory.js';
32
- import { prefetchForRequire, ClosureBoundExceededError } from '@nimbus-sh/core/runtime/require-resolver.js';
33
- import { hasTopLevelModuleSyntax } from '@nimbus-sh/core/runtime/javascript-ast.js';
31
+ import { sessionIdentity, unbindPublicPortCapability } from '../router/public-directory.js';
32
+ import { prefetchForRequire, requireFsOverBridge, ClosureBoundExceededError } from '@nimbus-sh/core/runtime/require-resolver.js';
33
+ import { hasTopLevelModuleSyntax, parseJavaScriptModule } from '@nimbus-sh/core/runtime/javascript-ast.js';
34
+ import { findStaticFsReferences } from '@nimbus-sh/core/runtime/static-fs-refs.js';
35
+ import { planFacetData } from './data-plan.js';
36
+ import { principalTag, profilePrincipal, ReadProfile, verifiedEvidence, } from './read-profile.js';
37
+ /** What the shared read profile may add to one launch: an eighth of its module map's bytes. */
38
+ const READ_PROFILE_LAUNCH_BYTES = Math.floor(VFS_BUNDLE_MAX_BYTES / 8);
39
+ import { NpmCache } from '../npm/cache.js';
34
40
  import { bindImportMetaResolve, importMetaDefines } from '@nimbus-sh/core/runtime/import-meta-transform.js';
41
+ import { mayHaveDynamicImport } from '@nimbus-sh/core/runtime/dynamic-import-rewrite.js';
35
42
  import { recordFailure, getLastRpcFrame, getLastFacetId } from '@nimbus-sh/platform/oom-discriminator.js';
36
43
  import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
37
44
  import { TurnBudget, PacedWork, turnChunkMaxBytes, withResolvers } from '@nimbus-sh/fabric/turn-budget.js';
38
45
  import { onColdStart } from '@nimbus-sh/fabric/generation.js';
39
46
  import { FencedWork, FENCED_WORK_KEY_PREFIX, } from '@nimbus-sh/fabric/fenced-work.js';
40
- import { EsbuildService, rewriteBundledEsmToCjs, rewriteProvidedCommonJsModules, } from '@nimbus-sh/core/runtime/esbuild-service.js';
47
+ import { rewriteBundledEsmToCjs, rewriteProvidedCommonJsModules, } from '@nimbus-sh/core/runtime/esbuild-service.js';
48
+ import { errorText } from '@nimbus-sh/core/_shared/error-text.js';
49
+ import { DEFAULT_CJS_CONDITIONS, DEFAULT_ESM_CONDITIONS, resolvePackageEntry, parseResolvablePackageJson, } from '@nimbus-sh/core/_shared/exports-resolver.js';
41
50
  import { isExecDiagEnabled, recordExecTelemetry } from './exec-telemetry.js';
42
51
  import { disposeRpcResource, disposeRpcResources } from '@nimbus-sh/platform/rpc-dispose.js';
43
52
  import { sqliteWasmModuleEntry } from './opencode-staging.js';
@@ -49,13 +58,26 @@ import { persistDurableWorkerImage, purgeDurableWorkerImages, } from './durable-
49
58
  import { SQLITE_WASM_MODULE_NAME, } from '../runtime/opencode-facet-runner.js';
50
59
  import { parsePortFromArgv, resolveLongRunningPort } from '@nimbus-sh/core/runtime/long-running-handle.js';
51
60
  import { DEFAULT_FACET_BUNDLE_PROFILE, } from '@nimbus-sh/core/runtime/bundle-profile.js';
52
- import { CF_COMPAT_DATE, VFS_BUNDLE_MAX_FILES, VFS_BUNDLE_MAX_BYTES, CWD_SNAPSHOT_MAX_FILE_BYTES, BUNDLE_MAX_ENCODED_BYTES, PREFETCH_CACHE_MAX_BYTES, ESM_TRANSFORM_CACHE_MAX_BYTES, } from '@nimbus-sh/core/constants.js';
61
+ import { CF_COMPAT_DATE, VFS_BUNDLE_MAX_FILES, VFS_BUNDLE_MAX_BYTES, CWD_SNAPSHOT_MAX_FILE_BYTES, BUNDLE_MAX_ENCODED_BYTES, PREFETCH_CACHE_MAX_BYTES, ESM_TRANSFORM_CACHE_MAX_BYTES, FS_LIST_PAGE_LIMIT, } from '@nimbus-sh/core/constants.js';
62
+ import { LEDGER_ROW_BYTES } from '@nimbus-sh/core/runtime/storage-ledger.js';
63
+ import { ONE_SHOT_STORE_MEMORY_BYTES, RESIDENT_CHUNK_BYTES } from '../vfs/facet-resident-limits.js';
53
64
  import { MAX_RPC_SAFE_PAYLOAD_BYTES } from '@nimbus-sh/platform/limits.js';
54
- import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
65
+ import { CRED_KERNEL, isNativeBinPath } from '@nimbus-sh/core/runtime/os-contracts.js';
55
66
  import { acquireSupervisorAllocation } from '@nimbus-sh/platform/heavy-alloc-coord.js';
67
+ import { supervisorBindingProps } from '@nimbus-sh/fabric/supervisor-props.js';
56
68
  import { wasmImageDigest } from './wasm-image-digest.js';
57
69
  import { prefetchBundleStart, prefetchBundleEnd, setPrefetchCacheBytes, setTransformCacheBytes, } from '@nimbus-sh/platform/diag-counters.js';
58
- const ISOLATED_ESM_TRANSFORM_MIN_BYTES = 512 * 1024;
70
+ const launchAdapters = new WeakMap();
71
+ function filesOf(fs) {
72
+ let files = launchAdapters.get(fs);
73
+ if (files === undefined) {
74
+ files = requireFsOverBridge(fs);
75
+ launchAdapters.set(fs, files);
76
+ }
77
+ return files;
78
+ }
79
+ /** A bundled ESM file this large is rewritten to CJS without esbuild when its shape allows. */
80
+ const BUNDLED_ESM_REWRITE_MIN_BYTES = 512 * 1024;
59
81
  /**
60
82
  * Detect & restore a Uint8Array that's been JSON-mangled to a
61
83
  * {"0":n,"1":n,...} object during the result-envelope round-trip.
@@ -281,6 +303,14 @@ const ENTRYPOINT_TIMER_TRACKER = `
281
303
  g.setInterval = function(fn, ms, ...a){ const id = si(fn, ms, ...a); iv.add(id); g.__nimbusPendingTimers++; return id; };
282
304
  g.clearInterval = function(id){ if (iv.delete(id)) g.__nimbusPendingTimers--; return ci(id); };
283
305
  }
306
+ // process.exit: the program's pending timers and intervals never fire again.
307
+ g.__nimbusStopProgramTimers = function(){
308
+ for (const id of one) ct(id);
309
+ one.clear();
310
+ if (typeof ci === "function") for (const id of iv) ci(id);
311
+ iv.clear();
312
+ g.__nimbusPendingTimers = 0;
313
+ };
284
314
  })(globalThis);
285
315
  `;
286
316
  /**
@@ -304,6 +334,11 @@ const ENTRYPOINT_TIMER_TRACKER = `
304
334
  */
305
335
  const RESIDENCY_MISS_REPORT = `
306
336
  const __NIMBUS_RESIDENCY_NAMED_MAX = 20;
337
+ // Everything the next launch of this entry must stage: unanswered reads and
338
+ // modules whose text arrived after boot, too late to compile.
339
+ function __nimbusStagingMisses() {
340
+ return [...(globalThis.__nimbusVfsResidencyMisses || []), ...(globalThis.__nimbusModuleMisses || [])];
341
+ }
307
342
  function __nimbusResidencyMissReport() {
308
343
  const __missed = globalThis.__nimbusVfsResidencyMisses;
309
344
  if (!__missed || __missed.size === 0) return "";
@@ -370,11 +405,9 @@ const SQLITE_FACET_IMPORT = `import __nimbusSqliteWasmModule from "${SQLITE_WASM
370
405
  /**
371
406
  * Generate one-shot runtime code with a plain fetch handler.
372
407
  */
373
- export async function generateEntrypointCode(userCode, vfsState, usesSqlite, shims, wasmImports = []) {
408
+ export async function generateEntrypointCode(userCode, vfsState, usesSqlite, sources, wasmImports = []) {
374
409
  const safeCode = JSON.stringify(rewriteProvidedCommonJsModules(userCode));
375
410
  const bundleSource = await facetVfsBundleSourceFor(vfsState);
376
- const safeManifest = vfsState.serializedManifest ?? JSON.stringify(vfsState.manifest);
377
- const safeMetadata = vfsState.serializedMetadata ?? JSON.stringify(vfsState.metadata);
378
411
  return {
379
412
  code: `
380
413
  ${bundleSource.imports}
@@ -384,7 +417,7 @@ ${facetWasmImportsSource(wasmImports)}
384
417
  const USER_CODE = ${safeCode};
385
418
  const __NimbusHostResponse = globalThis.Response;
386
419
 
387
- ${FACET_COMPILE_HELPER}
420
+ ${MK_COMPILED_FN_SOURCE}
388
421
 
389
422
  let __compiledFn = null;
390
423
  let __entryCompileFailure = null;
@@ -394,15 +427,13 @@ try {
394
427
  __entryCompileFailure = (__e && __e.stack) || (__e && __e.message) || String(__e);
395
428
  }
396
429
 
397
- // VFS bundle + manifest + pre-compiled modules — all at module level (startup time).
430
+ // The module bundle and its pre-compiled modules, at module level (startup time).
398
431
  const __MODULE_VFS_BUNDLE = ${bundleSource.expression};
399
- const __MODULE_VFS_MANIFEST = ${safeManifest};
400
- const __MODULE_VFS_METADATA = ${safeMetadata};
401
432
  const __compiledModules = new Map();
402
433
  const __compileFailures = new Map();
403
434
  // Precompile JS modules AND extensionless CJS entries (bin scripts and
404
435
  // shims). workerd forbids new Function at request time, so anything not
405
- // precompiled here surfaces the misleading "file was not pre-bundled".
436
+ // precompiled here surfaces the misleading "not in this launch's module map".
406
437
  ${BUNDLE_PRECOMPILE_LOOP}
407
438
 
408
439
  class __ProcessExit extends Error {
@@ -412,7 +443,7 @@ class __ProcessExit extends Error {
412
443
  export default {
413
444
  async fetch(request, workerEnv) {
414
445
  const args = await request.json();
415
- const { argv, env, cwd: _cwd, filename, dirname, stdin, captureOutput, cred, diag: __diag, vfsCursor } = args;
446
+ const { argv, env, cwd: _cwd, filename, dirname, stdin, captureOutput, cred, diag: __diag, vfsCursor, dataPlan } = args;
416
447
  // Per invocation, not per module: this body is cached on
417
448
  // hash(code + bundle + manifest) and reused by any session whose snapshot
418
449
  // hashes the same, and epochs are per supervisor incarnation.
@@ -423,10 +454,30 @@ ${VFS_CURSOR_SEED_SOURCE}
423
454
  // ends it early.
424
455
  const __entryBudgetMs = Infinity;
425
456
  let __drainPasses = 0;
426
- const __vfsBundle = __MODULE_VFS_BUNDLE;
427
- const __vfsManifest = __MODULE_VFS_MANIFEST;
428
- const __vfsMetadata = __MODULE_VFS_METADATA;
429
457
  const __supervisor = workerEnv?.SUPERVISOR || null;
458
+ // The same store, namespace and data plan a resident boots on, backed by
459
+ // this run's heap (runOnce hosts no SQLite; vfs/facet-resident-store.ts).
460
+ // Declared inside the request, beside the shims, so a loader that reuses
461
+ // this isolate for another run starts it from an empty store.
462
+ ${sources.residentStore}
463
+ __residentBindInMemory(${ONE_SHOT_STORE_MEMORY_BYTES});
464
+ __residentSetStorage(undefined, __supervisor);
465
+ __nsSetCred(cred);
466
+ __residentSetPlan(dataPlan);
467
+ __residentSetPushRoots([_cwd || "/home/user", "/tmp"]);
468
+ // The module bundle stays at module scope: a reused isolate adopts it
469
+ // again on its next run, and its text cells are shared, not copied.
470
+ const __residentBooted = await __residentBoot(() => __MODULE_VFS_BUNDLE, __MODULE_VFS_CURSOR, __supervisor);
471
+ if (__residentBooted.cursor) {
472
+ globalThis.__nimbusVfsCursor = { epoch: __residentBooted.cursor.epoch, rev: __residentBooted.cursor.rev };
473
+ }
474
+ // No user code runs before the namespace answers: it is the view every
475
+ // synchronous stat, exists and readdir reads (CUTOVER #13).
476
+ const __namespaceFailure = await __residentRequireNamespace(__supervisor, __residentBooted.failure);
477
+ if (__namespaceFailure) {
478
+ return __NimbusHostResponse.json({ exitCode: 1, stdout: "", stderr: __namespaceFailure + "\\n", vfsWrites: {}, residencyMisses: [] });
479
+ }
480
+ const __vfsBundle = __nimbusResidentBundle;
430
481
  const __pendingIO = [];
431
482
  // Fix 6 orphan counters (same as NodeProcess.run) — count RPC writes
432
483
  // that get dropped during isolate teardown so reportExit can report them.
@@ -453,24 +504,24 @@ ${VFS_CURSOR_SEED_SOURCE}
453
504
  let stdout = "", stderr = "";
454
505
  let exitCode = 0;
455
506
  const __nimbusDeferProcessExitReport = true;
456
- ${VFS_WRITE_LEDGER_SOURCE}
507
+ ${sources.ledger}
457
508
  const __vfsDirs = {};
458
509
 
459
510
  ${ENTRYPOINT_TIMER_TRACKER}
460
- ${shims}
511
+ ${sources.shims}
461
512
 
462
513
  ${ENTRYPOINT_EVENT_LOOP}
463
514
  ${RESIDENCY_MISS_REPORT}
464
515
 
465
516
  // Override console AND process.stdout/stderr for live SUPERVISOR streaming
466
517
  if (__supervisor && !captureOutput) {
467
- __consoleMod.log = (...a) => { const s = __utilMod.format(...a) + "\\n"; stdout += s; __queueRpcWrite("stdout", __nimbusOutEnc.encode(s)); };
468
- __consoleMod.error = (...a) => { const s = __utilMod.format(...a) + "\\n"; stderr += s; __queueRpcWrite("stderr", __nimbusOutEnc.encode(s)); };
518
+ __consoleMod.log = (...a) => { if (__nimbusProgramStopped) return; const s = __utilMod.format(...a) + "\\n"; stdout += s; __queueRpcWrite("stdout", __nimbusOutEnc.encode(s)); };
519
+ __consoleMod.error = (...a) => { if (__nimbusProgramStopped) return; const s = __utilMod.format(...a) + "\\n"; stderr += s; __queueRpcWrite("stderr", __nimbusOutEnc.encode(s)); };
469
520
  __consoleMod.warn = __consoleMod.error;
470
521
  __consoleMod.info = __consoleMod.log;
471
522
  __consoleMod.debug = __consoleMod.log;
472
- __processMod.stdout.write = (d, enc, cb) => { if (typeof enc === "function") cb = enc; const b = __nimbusOutBytes(d, enc); stdout += __nimbusOutText("stdout", b); __queueRpcWrite("stdout", b); if (typeof cb === "function") queueMicrotask(cb); return true; };
473
- __processMod.stderr.write = (d, enc, cb) => { if (typeof enc === "function") cb = enc; const b = __nimbusOutBytes(d, enc); stderr += __nimbusOutText("stderr", b); __queueRpcWrite("stderr", b); if (typeof cb === "function") queueMicrotask(cb); return true; };
523
+ __processMod.stdout.write = (d, enc, cb) => { if (typeof enc === "function") cb = enc; if (__nimbusProgramStopped) return true; const b = __nimbusOutBytes(d, enc); stdout += __nimbusOutText("stdout", b); __queueRpcWrite("stdout", b); if (typeof cb === "function") queueMicrotask(cb); return true; };
524
+ __processMod.stderr.write = (d, enc, cb) => { if (typeof enc === "function") cb = enc; if (__nimbusProgramStopped) return true; const b = __nimbusOutBytes(d, enc); stderr += __nimbusOutText("stderr", b); __queueRpcWrite("stderr", b); if (typeof cb === "function") queueMicrotask(cb); return true; };
474
525
  }
475
526
 
476
527
  try { globalThis.console = __consoleMod; } catch {}
@@ -514,6 +565,12 @@ ${RESIDENCY_MISS_REPORT}
514
565
  }
515
566
  }
516
567
  }
568
+ // A program that ended without process.exit still gets its 'exit' event.
569
+ if (__nimbusProcessExitCode === null) {
570
+ __nimbusEmitExit(exitCode);
571
+ if (__nimbusProcessExitCode !== null) exitCode = __nimbusProcessExitCode;
572
+ __nimbusProgramStopped = true;
573
+ }
517
574
 
518
575
  async function __drainPendingIO(maxPasses = 12) {
519
576
  let __settledIO = 0;
@@ -584,8 +641,11 @@ ${RESIDENCY_MISS_REPORT}
584
641
  // Unconditional, unlike diag: the supervisor stages these paths into
585
642
  // the next bundle for the same entry, so withholding them behind a
586
643
  // debug flag would leave the miss to repeat forever.
587
- residencyMisses: [...(globalThis.__nimbusVfsResidencyMisses || [])],
588
- ...(__diag ? { diag: { drainPasses: __drainPasses, rpcWrites: __rpcWriteCount, fsRpcReads: globalThis.__nimbusFsRpcReads || 0 } } : {}),
644
+ residencyMisses: __nimbusStagingMisses(),
645
+ ...(__diag ? { diag: {
646
+ drainPasses: __drainPasses, rpcWrites: __rpcWriteCount, fsRpcReads: globalThis.__nimbusFsRpcReads || 0,
647
+ namespaceRefusals: (globalThis.__nimbusVfsCoherence && globalThis.__nimbusVfsCoherence.namespaceRefusals) || 0,
648
+ } } : {}),
589
649
  });
590
650
  }
591
651
  };
@@ -655,7 +715,7 @@ function facetWasmImportsSource(wasmImports) {
655
715
  return `${lines.join('\n')}\nglobalThis.__nimbusPrecompiledWasm = new Map([${entries.join(', ')}]);`
656
716
  + `\nglobalThis.__nimbusPrecompiledWasmByDigest = new Map([${byDigest.join(', ')}]);`;
657
717
  }
658
- export async function generateLongRunningNodeCode(userCode, vfsState, opts, usesSqlite, shims, pacer) {
718
+ export async function generateLongRunningNodeCode(userCode, vfsState, opts, usesSqlite, sources, pacer) {
659
719
  const safeCode = JSON.stringify(rewriteProvidedCommonJsModules(userCode));
660
720
  const safeArgs = JSON.stringify({
661
721
  argv: opts.argv || [],
@@ -668,8 +728,6 @@ export async function generateLongRunningNodeCode(userCode, vfsState, opts, uses
668
728
  cred: opts.cred,
669
729
  });
670
730
  const bundleSource = await facetVfsBundleSourceFor(vfsState, pacer);
671
- const safeManifest = vfsState.serializedManifest ?? JSON.stringify(vfsState.manifest);
672
- const safeMetadata = vfsState.serializedMetadata ?? JSON.stringify(vfsState.metadata);
673
731
  return {
674
732
  code: `
675
733
  ${bundleSource.imports}
@@ -681,7 +739,7 @@ const USER_CODE = ${safeCode};
681
739
  const __NIMBUS_ARGS = ${safeArgs};
682
740
  const __NimbusHostResponse = globalThis.Response;
683
741
 
684
- ${FACET_COMPILE_HELPER}
742
+ ${MK_COMPILED_FN_SOURCE}
685
743
 
686
744
  let __compiledFn = null;
687
745
  let __entryCompileFailure = null;
@@ -696,8 +754,6 @@ try {
696
754
  // and this object are the same bytes twice, and for pi that is the largest
697
755
  // single allocation in the facet before the program starts.
698
756
  let __MODULE_VFS_BUNDLE = ${bundleSource.expression};
699
- const __MODULE_VFS_MANIFEST = ${safeManifest};
700
- const __MODULE_VFS_METADATA = ${safeMetadata};
701
757
  const __compiledModules = new Map();
702
758
  const __compileFailures = new Map();
703
759
  // Module evaluation is the ONLY place workerd allows a string to become code —
@@ -706,7 +762,7 @@ const __compileFailures = new Map();
706
762
  // here, off the module map. That is why code cannot come from the store.
707
763
  ${BUNDLE_PRECOMPILE_LOOP}
708
764
 
709
- ${FACET_RESIDENT_STORE_SOURCE}
765
+ ${sources.residentStore}
710
766
 
711
767
  class __ProcessExit extends Error {
712
768
  constructor(code) { super("process.exit(" + code + ")"); this.code = code; }
@@ -717,6 +773,15 @@ let __nimbusStarting = null;
717
773
  let __nimbusRuntime = null;
718
774
  let __nimbusAttachedLifecycle = null;
719
775
 
776
+ // The platform's timer, captured at module evaluation, before the shims wrap
777
+ // setTimeout in the resumption barrier. The flush below yields turns for its
778
+ // own bookkeeping; that is not a resumption of the program, and through the
779
+ // wrapped timer it paid a hidden ACQUIRE per yield — the one an inbound
780
+ // request relied on without saying so, and a second one after the response
781
+ // on every request. The request's barrier is taken explicitly, in the shared
782
+ // dispatch (__nimbusServeHttp), exactly once.
783
+ const __nimbusPlatformSetTimeout = setTimeout;
784
+
720
785
  async function __nimbusFlushRuntime() {
721
786
  const rt = __nimbusRuntime;
722
787
  if (!rt) return;
@@ -736,7 +801,9 @@ async function __nimbusFlushRuntime() {
736
801
  rt.drainVfsMutations(),
737
802
  ]);
738
803
  for (let pass = 0; pass < 12; pass++) {
739
- await new Promise((resolve) => setTimeout(resolve, 0));
804
+ const turn = Promise.withResolvers();
805
+ __nimbusPlatformSetTimeout(turn.resolve, 0);
806
+ await turn.promise;
740
807
  if (rt.pendingIO.length <= rt.settledIO) break;
741
808
  const slice = rt.pendingIO.slice(rt.settledIO);
742
809
  rt.settledIO = rt.pendingIO.length;
@@ -765,8 +832,6 @@ async function __nimbusEnsureStarted(workerEnv, workerCtx, __startArgs) {
765
832
  // time. Same reason argv/env/pid want to move here.
766
833
  const __MODULE_VFS_CURSOR = (__startArgs && __startArgs.vfsCursor) || null;
767
834
  ${VFS_CURSOR_SEED_SOURCE}
768
- const __vfsManifest = __MODULE_VFS_MANIFEST;
769
- const __vfsMetadata = __MODULE_VFS_METADATA;
770
835
  const __supervisor = workerEnv?.SUPERVISOR || null;
771
836
  // The resident set lives in this facet's own SQLite rather than its heap.
772
837
  // A synchronous read cannot block and no JS stack here can be suspended, so
@@ -774,41 +839,56 @@ ${VFS_CURSOR_SEED_SOURCE}
774
839
  // \`ctx.storage.sql.exec\` returns a Cursor, not a Promise. See
775
840
  // vfs/facet-resident-store.ts.
776
841
  __residentBind(workerCtx);
777
- const __residentAdopted = __residentAdoptModuleBundle(__MODULE_VFS_BUNDLE, __MODULE_VFS_CURSOR);
778
- // The store now holds these bytes; the parsed object is the duplicate.
779
- __MODULE_VFS_BUNDLE = null;
780
- // One cursor, not two. The seed above publishes the cursor this SPAWN
781
- // staged at, which is right for a cold slot and stale for a warm one — the
782
- // store's persisted cursor describes what the rows actually are, and it is
783
- // the only one that survived the last incarnation.
784
- if (__residentAdopted) {
785
- globalThis.__nimbusVfsCursor = { epoch: __residentAdopted.epoch, rev: __residentAdopted.rev };
786
- }
787
- // Bring the store to the authority's current state. This is what makes a
788
- // first synchronous read of an untouched file succeed, and it is the ONLY
789
- // blocking step: the waiting is done once, here, before the program's
790
- // first instruction, so that no synchronous read after it ever has to
791
- // wait.
842
+ __residentSetStorage(__startArgs && __startArgs.storage, __supervisor);
843
+ __nsSetCred(cred);
844
+ __residentSetPlan(__startArgs && __startArgs.dataPlan);
845
+ // The shared read profile's files this launch staged; each is struck off
846
+ // when read, and what is left at exit is reported unread (read-profile.ts).
847
+ globalThis.__nimbusProfileStaged = new Set((__startArgs && __startArgs.profileStaged) || []);
848
+ __residentSetPushRoots([_cwd || "/home/user", "/tmp"]);
849
+ // Bring the store to the authority's current state before the program's
850
+ // first instruction. This is what makes a first synchronous read of an
851
+ // untouched file succeed, and it is the ONLY blocking step: the waiting is
852
+ // done once, here, so that no synchronous read after it ever has to wait.
792
853
  //
793
- // Cheap on a warm slot, and no longer merely by assumption: rows the
794
- // absolute listing proves current are kept, so the pass fetches what
795
- // changed rather than trusting that nothing did. Run whether or not the
796
- // adopt produced a cursor — a facet whose snapshot carried none holds an
797
- // empty store, and filling it here is what stops its first ACQUIRE from
798
- // asking about a null epoch and being answered with a poison.
799
- if (__supervisor) {
854
+ // A cold store adopts this spawn's snapshot and fills the rest. A store
855
+ // that outlived its process — a durable application's, relaunched or
856
+ // re-driven — is cheap to bring current: rows the absolute listing proves
857
+ // current are kept, and only what changed is fetched. It is SERVED only
858
+ // once that listing has vouched for it: rows it cannot vouch for are a
859
+ // previous process's, of unknown age, so the kept store is emptied and
860
+ // this launch boots as a cold one does. See __residentBoot.
861
+ //
862
+ // The thunk hands the parsed bundle over and drops this scope's reference,
863
+ // so a cold boot frees it the moment it is adopted rather than holding it
864
+ // through the fill. A kept store that reconciles never takes it.
865
+ const __residentBootStarted = Date.now();
866
+ const __residentBooted = await __residentBoot(
867
+ () => { const __bundle = __MODULE_VFS_BUNDLE; __MODULE_VFS_BUNDLE = null; return __bundle; },
868
+ __MODULE_VFS_CURSOR,
869
+ __supervisor,
870
+ );
871
+ if (__startArgs && __startArgs.diag && __supervisor) {
872
+ // NIMBUS_DEBUG only: what this process holds, for per-process storage measurement.
800
873
  try {
801
- const __synced = await __residentSynchronizeFromSupervisor(__supervisor);
802
- if (__synced.cursor) {
803
- globalThis.__nimbusVfsCursor = { epoch: __synced.cursor.epoch, rev: __synced.cursor.rev };
804
- }
805
- } catch (__e) {
806
- // A failed pass is a smaller resident set, not a dead process: every
807
- // path it did not reach reads exactly as it would have without this
808
- // store. Surfacing beats a silent capability loss.
809
- try { globalThis.__nimbusResidentFillError = (__e && __e.message) || String(__e); } catch {}
810
- }
874
+ const __stats = __residentStats();
875
+ await __supervisor.stderr(new TextEncoder().encode("[nimbus-debug] resident boot " + JSON.stringify({
876
+ ms: Date.now() - __residentBootStarted, ...__stats, failure: __residentBooted.failure || null,
877
+ }) + "\\n"));
878
+ } catch {}
811
879
  }
880
+ __MODULE_VFS_BUNDLE = null;
881
+ // One cursor, not two. The seed above publishes the cursor this SPAWN
882
+ // staged at, which is right for a cold store and stale for a kept one —
883
+ // the cursor the boot returns describes what the rows actually are.
884
+ if (__residentBooted.cursor) {
885
+ globalThis.__nimbusVfsCursor = { epoch: __residentBooted.cursor.epoch, rev: __residentBooted.cursor.rev };
886
+ }
887
+ // No user code runs before the namespace answers: it is the view every
888
+ // synchronous stat, exists and readdir reads (CUTOVER #13). A launch whose
889
+ // namespace cannot be listed fails here, naming why.
890
+ const __namespaceFailure = await __residentRequireNamespace(__supervisor, __residentBooted.failure);
891
+ if (__namespaceFailure) throw new Error(__namespaceFailure);
812
892
  const __vfsBundle = __nimbusResidentBundle;
813
893
  const __pendingIO = [];
814
894
  let __rpcDrops = 0;
@@ -834,23 +914,23 @@ ${VFS_CURSOR_SEED_SOURCE}
834
914
  let stdout = "", stderr = "";
835
915
  let exitCode = 0;
836
916
  const __nimbusDeferProcessExitReport = true;
837
- ${VFS_WRITE_LEDGER_SOURCE}
917
+ ${sources.ledger}
838
918
  const __vfsDirs = {};
839
919
 
840
920
  ${ENTRYPOINT_TIMER_TRACKER}
841
- ${shims}
921
+ ${sources.shims}
842
922
 
843
923
  ${ENTRYPOINT_EVENT_LOOP}
844
924
  ${RESIDENCY_MISS_REPORT}
845
925
 
846
926
  if (__supervisor && !captureOutput) {
847
- __consoleMod.log = (...a) => { const s = __utilMod.format(...a) + "\\n"; stdout += s; __queueRpcWrite("stdout", __nimbusOutEnc.encode(s)); };
848
- __consoleMod.error = (...a) => { const s = __utilMod.format(...a) + "\\n"; stderr += s; __queueRpcWrite("stderr", __nimbusOutEnc.encode(s)); };
927
+ __consoleMod.log = (...a) => { if (__nimbusProgramStopped) return; const s = __utilMod.format(...a) + "\\n"; stdout += s; __queueRpcWrite("stdout", __nimbusOutEnc.encode(s)); };
928
+ __consoleMod.error = (...a) => { if (__nimbusProgramStopped) return; const s = __utilMod.format(...a) + "\\n"; stderr += s; __queueRpcWrite("stderr", __nimbusOutEnc.encode(s)); };
849
929
  __consoleMod.warn = __consoleMod.error;
850
930
  __consoleMod.info = __consoleMod.log;
851
931
  __consoleMod.debug = __consoleMod.log;
852
- __processMod.stdout.write = (d, enc, cb) => { if (typeof enc === "function") cb = enc; const b = __nimbusOutBytes(d, enc); stdout += __nimbusOutText("stdout", b); __queueRpcWrite("stdout", b); if (typeof cb === "function") queueMicrotask(cb); return true; };
853
- __processMod.stderr.write = (d, enc, cb) => { if (typeof enc === "function") cb = enc; const b = __nimbusOutBytes(d, enc); stderr += __nimbusOutText("stderr", b); __queueRpcWrite("stderr", b); if (typeof cb === "function") queueMicrotask(cb); return true; };
932
+ __processMod.stdout.write = (d, enc, cb) => { if (typeof enc === "function") cb = enc; if (__nimbusProgramStopped) return true; const b = __nimbusOutBytes(d, enc); stdout += __nimbusOutText("stdout", b); __queueRpcWrite("stdout", b); if (typeof cb === "function") queueMicrotask(cb); return true; };
933
+ __processMod.stderr.write = (d, enc, cb) => { if (typeof enc === "function") cb = enc; if (__nimbusProgramStopped) return true; const b = __nimbusOutBytes(d, enc); stderr += __nimbusOutText("stderr", b); __queueRpcWrite("stderr", b); if (typeof cb === "function") queueMicrotask(cb); return true; };
854
934
  }
855
935
 
856
936
  try { globalThis.console = __consoleMod; } catch {}
@@ -935,7 +1015,7 @@ ${RESIDENCY_MISS_REPORT}
935
1015
  if (Number(code ?? 0) === 0) code = 1;
936
1016
  try { await __supervisor.stderr(__nimbusOutEnc.encode(__residencyReport)); } catch {}
937
1017
  }
938
- await __supervisor.reportExit(code, reason || "");
1018
+ await __supervisor.reportExit(code, reason || "", __nimbusStagingMisses(), [...(globalThis.__nimbusProfileStaged || [])]);
939
1019
  __nimbusProcessExitReported = true;
940
1020
  };
941
1021
  const __nimbusReportLifecycleFailure = async (e) => {
@@ -967,6 +1047,11 @@ ${RESIDENCY_MISS_REPORT}
967
1047
  } else {
968
1048
  finalCode = Number(await __nimbusProcessExitPromise);
969
1049
  }
1050
+ if (__nimbusProcessExitCode === null) {
1051
+ __nimbusEmitExit(finalCode);
1052
+ if (__nimbusProcessExitCode !== null) finalCode = __nimbusProcessExitCode;
1053
+ __nimbusProgramStopped = true;
1054
+ }
970
1055
  await __nimbusFlushRuntime();
971
1056
  await __nimbusReportFinalExit(finalCode, "");
972
1057
  })().catch(async (e) => {
@@ -994,9 +1079,7 @@ ${RESIDENCY_MISS_REPORT}
994
1079
  try { await __supervisor.stderr(__nimbusOutEnc.encode(tail)); } catch {}
995
1080
  }
996
1081
  if (exitCode !== 0) {
997
- if (__supervisor && !__nimbusProcessExitReported) {
998
- await __supervisor.reportExit(exitCode, stderr || ("exit " + exitCode + "\\n"));
999
- }
1082
+ await __nimbusReportFinalExit(exitCode, stderr || ("exit " + exitCode + "\\n"));
1000
1083
  throw new Error(stderr || ("long-running node startup exited " + exitCode));
1001
1084
  }
1002
1085
  __nimbusStarted = true;
@@ -1032,7 +1115,13 @@ export class NimbusProcess extends DurableObject {
1032
1115
  if (startArgs) __nimbusStartArgs = startArgs;
1033
1116
  await __nimbusEnsureStarted(this.env, this.ctx, __nimbusStartArgs);
1034
1117
  if (__nimbusAttachedLifecycle) await __nimbusAttachedLifecycle;
1035
- return { ok: true };
1118
+ // What this facet's database holds now, and the cap its store keeps
1119
+ // under, for the session's storage ledger (N18).
1120
+ let databaseSize;
1121
+ try { databaseSize = this.ctx.storage.sql.databaseSize; } catch {}
1122
+ let storageCap = null;
1123
+ try { storageCap = __residentStorageCap(); } catch {}
1124
+ return { ok: true, databaseSize, storageCap };
1036
1125
  }
1037
1126
  async fetch(req) { return __nimbusDispatchHttp(req, this.env, this.ctx); }
1038
1127
  async handleHttpRequest(req) { return __nimbusDispatchHttp(req, this.env, this.ctx); }
@@ -1041,13 +1130,33 @@ export class NimbusProcess extends DurableObject {
1041
1130
  modules: bundleSource.modules,
1042
1131
  };
1043
1132
  }
1133
+ /**
1134
+ * What the facet store charges itself to hold one cell (facet-resident-store's
1135
+ * __residentCellCost, which this must match): its bytes (a text cell's UTF-8
1136
+ * length) plus 1%, a row per chunk and head, and a page of slack.
1137
+ */
1138
+ /** What a module map costs the facet's store that adopts it (N18). A denial is a head row only. */
1139
+ function moduleMapStorageBytes(bundle) {
1140
+ let bytes = 0;
1141
+ for (const cell of Object.values(bundle)) {
1142
+ bytes += typeof cell === 'string' || cell instanceof Uint8Array ? residentCellCost(cell) : 2 * LEDGER_ROW_BYTES;
1143
+ }
1144
+ return bytes;
1145
+ }
1146
+ function residentCellCost(cell) {
1147
+ const isText = typeof cell === 'string';
1148
+ const size = isText ? cell.length : cell.byteLength;
1149
+ const limit = isText ? Math.floor(RESIDENT_CHUNK_BYTES / 3) : RESIDENT_CHUNK_BYTES;
1150
+ const chunks = Math.max(1, Math.ceil(size / limit));
1151
+ const bytes = isText ? new TextEncoder().encode(cell).byteLength : size;
1152
+ return Math.ceil(bytes * 1.01) + (chunks + 1) * LEDGER_ROW_BYTES + 4096;
1153
+ }
1044
1154
  /**
1045
1155
  * Drop the raw forms of everything that has been serialized, in place.
1046
1156
  *
1047
- * `bundleSource`, `serializedManifest` and `serializedMetadata` are total
1048
- * encodings of `bundle`, `manifest` and `metadata` — no caller can distinguish
1049
- * a state carrying both from one carrying only the serialized halves, because
1050
- * both facet generators read the serialized halves and nothing else does.
1157
+ * `bundleSource` is a total encoding of `bundle`: no caller can distinguish
1158
+ * a state carrying both from one carrying only the serialized half, because
1159
+ * both facet generators read the serialized half and nothing else does.
1051
1160
  * Holding both doubles the cost of a cached entry for its whole lifetime, and
1052
1161
  * that lifetime spans execs.
1053
1162
  *
@@ -1060,16 +1169,12 @@ export class NimbusProcess extends DurableObject {
1060
1169
  export function releaseSerializedSources(vfsState) {
1061
1170
  if (vfsState.bundleSource)
1062
1171
  vfsState.bundle = {};
1063
- if (vfsState.serializedManifest !== undefined)
1064
- vfsState.manifest = {};
1065
- if (vfsState.serializedMetadata !== undefined)
1066
- vfsState.metadata = {};
1067
1172
  }
1068
1173
  /**
1069
1174
  * Drop the serialized forms once a module map has been generated from them.
1070
1175
  *
1071
- * The generated source is a total encoding of all three: every byte of the
1072
- * bundle expression, the manifest and the metadata is inside it. Holding them
1176
+ * The generated source is a total encoding of it: every byte of the bundle
1177
+ * expression is inside it. Holding them
1073
1178
  * afterwards keeps a second copy of the largest thing this DO builds alive for
1074
1179
  * as long as the facet runs — for pi, 22.7 MB across the ~20 s window in which
1075
1180
  * the isolate was being reset. For the one-shot path that window is the run;
@@ -1086,8 +1191,6 @@ export function releaseSerializedSources(vfsState) {
1086
1191
  */
1087
1192
  export function releaseGeneratedSources(vfsState) {
1088
1193
  vfsState.bundleSource = undefined;
1089
- vfsState.serializedManifest = undefined;
1090
- vfsState.serializedMetadata = undefined;
1091
1194
  vfsState.generatedSourcesReleased = true;
1092
1195
  }
1093
1196
  /**
@@ -1121,7 +1224,9 @@ async function facetVfsBundleSourceFor(vfsState, pacer) {
1121
1224
  * pre-fix readFileString contract).
1122
1225
  */
1123
1226
  async function _readBundleCell(vfs, path) {
1124
- const bytes = await vfs.readFile(path);
1227
+ const bytes = await filesOf(vfs).readBytes(path);
1228
+ if (bytes === null)
1229
+ throw Object.assign(new Error(`ENOENT: ${path}`), { code: 'ENOENT', path });
1125
1230
  try {
1126
1231
  return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
1127
1232
  }
@@ -1134,6 +1239,12 @@ async function _readBundleCell(vfs, path) {
1134
1239
  * Strings counted as char-length (a slight under-count for non-ASCII
1135
1240
  * but matches the pre-fix behaviour); Uint8Array counted as byteLength.
1136
1241
  */
1242
+ /** A cell's raw bytes: UTF-8 for text, as the file is on disk. */
1243
+ function _bundleCellRawBytes(cell) {
1244
+ if (cell instanceof Uint8Array)
1245
+ return cell.byteLength;
1246
+ return typeof cell === 'string' ? _encodedSourceBytes(cell) : 0;
1247
+ }
1137
1248
  function _bundleCellLength(cell) {
1138
1249
  return typeof cell === 'string' ? cell.length : cell.byteLength;
1139
1250
  }
@@ -1149,13 +1260,10 @@ function _bundleCellLength(cell) {
1149
1260
  * BIN_PACKAGE_SPECULATIVE_MAX_FILE_BYTES); omit it for budget-only checks.
1150
1261
  */
1151
1262
  async function _bundleAdmits(vfs, path, budgetState, perFileCeiling) {
1152
- let size;
1153
- try {
1154
- size = (await vfs.lstat(path)).size;
1155
- }
1156
- catch {
1263
+ const stat = await filesOf(vfs).lstat(path).catch(() => null);
1264
+ if (stat === null)
1157
1265
  return false;
1158
- }
1266
+ const size = stat.size;
1159
1267
  if (perFileCeiling !== undefined && size > perFileCeiling)
1160
1268
  return false;
1161
1269
  return budgetState.totalBytes + size <= VFS_BUNDLE_MAX_BYTES;
@@ -1189,8 +1297,6 @@ function retainedVfsStateBytes(state) {
1189
1297
  for (const moduleSource of Object.values(source.modules))
1190
1298
  bytes += moduleSource.length;
1191
1299
  }
1192
- bytes += state.serializedManifest?.length ?? 0;
1193
- bytes += state.serializedMetadata?.length ?? 0;
1194
1300
  return bytes;
1195
1301
  }
1196
1302
  /**
@@ -1270,8 +1376,8 @@ function _jsonEncodedBytes(value) {
1270
1376
  }
1271
1377
  return bytes;
1272
1378
  }
1273
- /** `{"bundle":` + `,"manifest":` + `}` — the frame around the two members. */
1274
- const ENCODED_PAYLOAD_FRAME_BYTES = 23;
1379
+ /** `{"bundle":` + `}`: the frame around the one member. */
1380
+ const ENCODED_PAYLOAD_FRAME_BYTES = 11;
1275
1381
  /**
1276
1382
  * Running UTF-8 byte length of `JSON.stringify({ bundle, manifest })`,
1277
1383
  * accumulated one cell at a time.
@@ -1287,10 +1393,9 @@ const ENCODED_PAYLOAD_FRAME_BYTES = 23;
1287
1393
  * the total is a sum of independent per-cell terms: exact, incremental, and
1288
1394
  * never holding more than one cell's worth of scratch.
1289
1395
  */
1290
- export function encodedBundleSize(bundle, manifest) {
1396
+ export function encodedBundleSize(bundle) {
1291
1397
  const cells = new Map();
1292
1398
  let cellSum = 0;
1293
- const manifestBytes = _jsonEncodedBytes(manifest);
1294
1399
  const self = {
1295
1400
  add(path, cell) {
1296
1401
  if (cells.has(path))
@@ -1308,7 +1413,7 @@ export function encodedBundleSize(bundle, manifest) {
1308
1413
  },
1309
1414
  get bytes() {
1310
1415
  const separators = Math.max(0, cells.size - 1);
1311
- return ENCODED_PAYLOAD_FRAME_BYTES + 2 + cellSum + separators + manifestBytes;
1416
+ return ENCODED_PAYLOAD_FRAME_BYTES + 2 + cellSum + separators;
1312
1417
  },
1313
1418
  };
1314
1419
  for (const [path, cell] of Object.entries(bundle))
@@ -1471,7 +1576,13 @@ function _inlineBundleSourceBytes(bundle) {
1471
1576
  * the merge expression concatenates those fragments back to the original
1472
1577
  * string or Uint8Array before module precompilation begins.
1473
1578
  */
1474
- export async function buildFacetVfsBundleSource(bundle, forceSideModules = false, pacer) {
1579
+ export async function buildFacetVfsBundleSource(bundle, forceSideModules = false, pacer, { consume = false } = {}) {
1580
+ // \`consume\`: the caller releases the raw cells right after (a launch's
1581
+ // build), so each is taken as its side module is serialized and the map is
1582
+ // never held twice. Side modules always: an inline expression is copied
1583
+ // again into the runner's own source.
1584
+ if (consume)
1585
+ forceSideModules = true;
1475
1586
  // Size the inline form before building it. A bundle that will be split has
1476
1587
  // no use for the whole-bundle expression, and building one to read its
1477
1588
  // length off cost a second full copy of the largest string this DO makes.
@@ -1522,46 +1633,55 @@ export async function buildFacetVfsBundleSource(bundle, forceSideModules = false
1522
1633
  }
1523
1634
  return pieces;
1524
1635
  }
1525
- const chunks = [];
1526
- let chunk = {};
1527
- let estimatedBytes = 0;
1528
- function flushChunk() {
1529
- if (Object.keys(chunk).length === 0)
1530
- return;
1531
- chunks.push(chunk);
1532
- chunk = {};
1533
- estimatedBytes = 0;
1534
- }
1535
- for (const [path, cell] of Object.entries(bundle)) {
1536
- for (const [piecePath, pieceCell] of splitCell(path, cell)) {
1537
- const pieceBytes = sourceBytes(piecePath, pieceCell);
1538
- if (piecePath in chunk
1539
- || (estimatedBytes > 0 && estimatedBytes + pieceBytes > maxModuleBytes)) {
1540
- flushChunk();
1541
- }
1542
- chunk[piecePath] = pieceCell;
1543
- estimatedBytes += pieceBytes;
1544
- }
1545
- }
1546
- flushChunk();
1547
1636
  const modules = {};
1548
1637
  const imports = [];
1549
1638
  const aliases = [];
1550
- for (let index = 0; index < chunks.length; index++) {
1639
+ let chunk = {};
1640
+ let chunkPaths = [];
1641
+ let estimatedBytes = 0;
1642
+ // A chunk becomes its side module the moment it is full, so the cells it
1643
+ // carries and their serialization are never both held for the whole map:
1644
+ // consumed, a cell leaves the bundle as its module is written.
1645
+ async function flushChunk() {
1646
+ // Pieces of a split cell fill chunks before the cell's path is complete,
1647
+ // so emptiness is the chunk's own, not its finished paths'.
1648
+ if (estimatedBytes === 0)
1649
+ return;
1650
+ const index = aliases.length;
1551
1651
  const moduleName = `${FACET_VFS_MODULE_PREFIX}${index}.js`;
1552
1652
  const alias = `__nimbusVfsBundle${index}`;
1553
- const source = _facetBundleModuleSource(chunks[index]);
1653
+ const source = _facetBundleModuleSource(chunk);
1554
1654
  if (_encodedSourceBytes(source) > BUNDLE_MAX_ENCODED_BYTES) {
1555
1655
  throw new Error(`Nimbus: generated VFS side module exceeds encoded limit: ${moduleName}`);
1556
1656
  }
1557
1657
  modules[moduleName] = source;
1558
1658
  imports.push(`import ${alias} from "${moduleName}";`);
1559
1659
  aliases.push(alias);
1660
+ if (consume)
1661
+ for (const path of chunkPaths)
1662
+ delete bundle[path];
1663
+ chunk = {};
1664
+ chunkPaths = [];
1665
+ estimatedBytes = 0;
1560
1666
  // Each side module is an independent serialization of its own cells, so
1561
1667
  // the turn may end between any two of them.
1562
1668
  if (pacer)
1563
1669
  await pacer.spend(source.length);
1564
1670
  }
1671
+ for (const path of Object.keys(bundle)) {
1672
+ const cell = bundle[path];
1673
+ for (const [piecePath, pieceCell] of splitCell(path, cell)) {
1674
+ const pieceBytes = sourceBytes(piecePath, pieceCell);
1675
+ if (piecePath in chunk
1676
+ || (estimatedBytes > 0 && estimatedBytes + pieceBytes > maxModuleBytes)) {
1677
+ await flushChunk();
1678
+ }
1679
+ chunk[piecePath] = pieceCell;
1680
+ estimatedBytes += pieceBytes;
1681
+ }
1682
+ chunkPaths.push(path);
1683
+ }
1684
+ await flushChunk();
1565
1685
  const expression = `(function(__parts){const __out={};for(const __part of __parts){` +
1566
1686
  `for(const [__k,__v] of Object.entries(__part)){const __prev=__out[__k];` +
1567
1687
  `if(__prev===undefined){__out[__k]=__v;}` +
@@ -1616,174 +1736,47 @@ function _fnv1a(s) {
1616
1736
  return h.toString(16);
1617
1737
  }
1618
1738
  const MANIFEST_MAX_DEPTH = 12;
1619
- /**
1620
- * Build the manifest pass — uncapped path→child-names map. UNCHANGED
1621
- * from W2.5b; this is the W2.5b root-cause fix and continues to keep
1622
- * fs.readdirSync / fs.statSync honest regardless of which subset of
1623
- * file CONTENT we ship.
1624
- */
1625
- async function buildManifest(vfs, cwd, scriptPath) {
1626
- const manifest = {};
1627
- async function walk(dirPath, depth = 0) {
1628
- if (depth > MANIFEST_MAX_DEPTH)
1629
- return;
1630
- const stripped = dirPath.replace(/^\/+/, '');
1631
- if (stripped in manifest)
1632
- return;
1633
- let entries;
1634
- try {
1635
- entries = (await vfs.readdir(stripped));
1636
- }
1637
- catch {
1638
- return;
1639
- }
1640
- manifest[stripped] = entries.map((e) => e.name);
1641
- for (const entry of entries) {
1642
- if (entry.type === 'directory') {
1643
- const childPath = stripped ? stripped + '/' + entry.name : entry.name;
1644
- (await walk(childPath, depth + 1));
1645
- }
1646
- }
1647
- }
1648
- const cwdStripped = cwd.replace(/^\/+/, '');
1649
- // ── The path from the root down to the working directory ──────────────
1650
- //
1651
- // One level each, before the deep walks, so that every directory on that
1652
- // chain is ENUMERATED rather than merely mentioned. It is what makes a
1653
- // synchronous "not there" honest for the shape programs probe most: node's
1654
- // resolver walks upward asking for `<dir>/node_modules` at every level, and
1655
- // a config lookup asks for a dotfile directory in $HOME. Those paths mostly
1656
- // do not exist, and answering that requires having listed the directory
1657
- // they would be in — otherwise the only honest answer is a refusal, and the
1658
- // resolver pays a round trip per rung to hear it.
1659
- //
1660
- // Cheap by construction: one readdir per component, names only, and the
1661
- // recursion below each stops immediately.
1662
- {
1663
- const segments = cwdStripped ? cwdStripped.split('/') : [];
1664
- for (let i = 0; i < segments.length; i++) {
1665
- (await walk(segments.slice(0, i).join('/'), MANIFEST_MAX_DEPTH));
1666
- }
1667
- }
1668
- (await walk(cwdStripped, 0));
1669
- const nmDir = cwdStripped + '/node_modules';
1670
- if ((await vfs.exists(nmDir)) && (await vfs.isDirectory(nmDir))) {
1671
- (await walk(nmDir, 0));
1672
- }
1673
- // ── Bin-target package + hoisted dependency roots ─────────────────────
1674
- //
1675
- // When the entry script lives in a node_modules outside cwd (npx-cache
1676
- // packages, globally-installed bins, etc.), buildManifest's cwd walk
1677
- // misses the package's sibling files. The bin's index.js gets
1678
- // require-walked + greedy-added to the BUNDLE, but the MANIFEST
1679
- // (which is the source of truth for `fs.readdirSync`) was empty for
1680
- // those paths — so `readdirSync('/tmp/.npx-cache/.../template-X')`
1681
- // returned [] and `create-vite` scaffolded zero files.
1682
- //
1683
- // Walk the innermost `node_modules/<pkg>/` of `scriptPath` so its
1684
- // entire package tree is enumerable via readdir. Also walk that
1685
- // `node_modules` directory itself: global npm bins resolve hoisted
1686
- // transitive dependencies as siblings of the bin's own package.
1687
- // Bounded by MANIFEST_MAX_DEPTH; same depth budget as the cwd walk.
1688
- if (scriptPath) {
1689
- const sp = scriptPath.replace(/^\/+/, '');
1690
- const segs = sp.split('/');
1691
- let nmIdx = -1;
1692
- for (let i = segs.length - 1; i >= 0; i--) {
1693
- if (segs[i] === 'node_modules') {
1694
- nmIdx = i;
1695
- break;
1696
- }
1697
- }
1698
- if (nmIdx >= 0) {
1699
- const isScoped = segs[nmIdx + 1]?.startsWith('@');
1700
- const pkgEnd = isScoped ? nmIdx + 3 : nmIdx + 2;
1701
- if (pkgEnd <= segs.length) {
1702
- const pkgRoot = segs.slice(0, pkgEnd).join('/');
1703
- if ((await vfs.exists(pkgRoot)) && (await vfs.isDirectory(pkgRoot))) {
1704
- (await walk(pkgRoot, 0));
1705
- }
1706
- }
1707
- const nodeModulesRoot = segs.slice(0, nmIdx + 1).join('/');
1708
- if ((await vfs.exists(nodeModulesRoot)) && (await vfs.isDirectory(nodeModulesRoot))) {
1709
- (await walk(nodeModulesRoot, 0));
1710
- }
1711
- }
1712
- }
1713
- return manifest;
1714
- }
1715
- async function buildVfsMetadata(vfs, manifest, bundle) {
1716
- const paths = new Set(Object.keys(bundle).filter((path) => compiledCellPath(path) === null));
1717
- for (const [directory, children] of Object.entries(manifest)) {
1718
- paths.add(directory);
1719
- for (const child of children) {
1720
- paths.add(directory ? `${directory}/${child}` : child);
1721
- }
1722
- }
1723
- const metadata = {};
1724
- for (const path of paths) {
1725
- try {
1726
- const stat = await vfs.authority.stat(path, { followSymlinks: false });
1727
- if (!stat)
1728
- continue;
1729
- metadata[path] = {
1730
- type: stat.type,
1731
- size: stat.size,
1732
- mode: stat.mode,
1733
- uid: stat.uid,
1734
- gid: stat.gid,
1735
- };
1736
- }
1737
- catch {
1738
- // The credentialed lookup is authoritative; inaccessible ancestors do
1739
- // not reveal whether a leaf exists.
1740
- }
1741
- }
1742
- return metadata;
1743
- }
1744
- async function addUnreadableDenialCells(vfs, bundle, metadata) {
1745
- for (const [path, stat] of Object.entries(metadata)) {
1746
- if (stat.type === 'directory' || path in bundle)
1747
- continue;
1748
- try {
1749
- (await vfs.access(path, 0o4));
1750
- }
1751
- catch (error) {
1752
- if (typeof error === 'object'
1753
- && error !== null
1754
- && 'code' in error
1755
- && error.code === 'EACCES') {
1756
- bundle[path] = { error: 'EACCES' };
1757
- }
1758
- }
1759
- }
1760
- }
1761
- /**
1762
- * Greedy-oversample every installed package's main entry. The static
1763
- * prefetch via require-resolver covers the require() chain literally
1764
- * present in source; greedy oversampling adds a safety net for dynamic
1765
- * patterns the regex misses (jest/`bindings`/`import-local` style
1766
- * computed-path requires). Bounded to package.json + 1 main-entry file
1767
- * per package — sub-agent §Q3 quantified the worst-case cumulative
1768
- * budget impact (~322 KiB for fastify, ~1.7 MiB for ts-jest).
1769
- *
1770
- * `requiredPaths` is the static require closure. A package the closure
1771
- * already reached — but reached only through a SUBPATH — has its main entry
1772
- * skipped; see `mainIsSpeculative`.
1773
- */
1774
- // can verify the hash-chunk + shared/ oversample directly. Pre-X.5-C this
1775
- // was a file-local helper. Adding the named export is a pure surface
1776
- // addition — no callers other than buildPrefetchBundle (same file) and
1777
- // the new probe.
1778
- export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPaths = new Set()) {
1739
+ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPaths = new Set(), options = {}) {
1779
1740
  let added = 0;
1741
+ const groups = [];
1742
+ const bound = Math.min(options.maxBundleBytes ?? VFS_BUNDLE_MAX_BYTES, VFS_BUNDLE_MAX_BYTES);
1743
+ const independent = new Set(Object.keys(bundle));
1744
+ let rawBytes = 0;
1745
+ for (const cell of Object.values(bundle))
1746
+ rawBytes += _bundleCellRawBytes(cell);
1747
+ if (rawBytes >= bound)
1748
+ return { added, groups };
1780
1749
  const cwdStripped = cwd.replace(/^\/+/, '');
1781
1750
  const nmDir = cwdStripped + '/node_modules';
1782
- if (!((await vfs.exists(nmDir)) && (await vfs.isDirectory(nmDir))))
1783
- return { added };
1751
+ if (!((await filesOf(vfs).exists(nmDir)) && (await filesOf(vfs).isDirectory(nmDir))))
1752
+ return { added, groups };
1784
1753
  const exts = ['', '.js', '.cjs', '.mjs', '/index.js', '/index.cjs'];
1785
1754
  async function addOne(path) {
1786
1755
  const stripped = path.replace(/^\/+/, '');
1756
+ if ((isBundleModuleCandidate(stripped) || stripped.endsWith('.cjs')) && !isNativeBinPath(stripped)) {
1757
+ if (independent.has(stripped))
1758
+ return false;
1759
+ if (bundle[stripped] === undefined && !(await _bundleAdmits(vfs, stripped, budgetState, BIN_PACKAGE_SPECULATIVE_MAX_FILE_BYTES)))
1760
+ return false;
1761
+ const closure = await prefetchForRequire(requireFsOverBridge(vfs), '', stripped.slice(0, stripped.lastIndexOf('/')), '/' + stripped, bound, options.pacer?.spend.bind(options.pacer), {
1762
+ purpose: 'dependency-closure', held: bundle,
1763
+ maxAdditionalBytes: Math.max(0, bound - rawBytes),
1764
+ maxAdditionalFiles: Math.max(0, VFS_BUNDLE_MAX_FILES - budgetState.fileCount),
1765
+ });
1766
+ if ('kind' in closure || closure.bundle[stripped] === undefined)
1767
+ return false;
1768
+ for (const [member, cell] of Object.entries(closure.bundle)) {
1769
+ if (bundle[member] !== undefined)
1770
+ continue;
1771
+ bundle[member] = cell;
1772
+ rawBytes += _bundleCellRawBytes(cell);
1773
+ budgetState.totalBytes += _bundleCellLength(cell);
1774
+ budgetState.fileCount++;
1775
+ added++;
1776
+ }
1777
+ groups.push({ root: stripped, members: new Set(Object.keys(closure.bundle)) });
1778
+ return true;
1779
+ }
1787
1780
  if (stripped in bundle)
1788
1781
  return false;
1789
1782
  if (budgetState.fileCount >= VFS_BUNDLE_MAX_FILES)
@@ -1791,7 +1784,7 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
1791
1784
  if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
1792
1785
  return false;
1793
1786
  try {
1794
- if (!(await vfs.exists(stripped)) || (await vfs.isDirectory(stripped)))
1787
+ if (!(await filesOf(vfs).exists(stripped)) || (await filesOf(vfs).isDirectory(stripped)))
1795
1788
  return false;
1796
1789
  // This is a guess at what a program might require, and the same
1797
1790
  // per-file ceiling the entry-package walk applies bounds it: a
@@ -1799,8 +1792,8 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
1799
1792
  // `lib/typescript.js` beside the `lib/tsc.js` that actually runs), and
1800
1793
  // one guess must not spend a third of the budget — and a third of the
1801
1794
  // supervisor's headroom — on every invocation that never reads it.
1802
- // Anything the program really requires arrives through the closure,
1803
- // which is uncapped.
1795
+ // Literal dependencies use the closure's byte allowance, not this
1796
+ // speculative-root per-file ceiling.
1804
1797
  if (!(await _bundleAdmits(vfs, stripped, budgetState, BIN_PACKAGE_SPECULATIVE_MAX_FILE_BYTES)))
1805
1798
  return false;
1806
1799
  // A guess must not be a native binary. Nothing in a Workers isolate can
@@ -1810,14 +1803,19 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
1810
1803
  // Measured: freeing 1.3 MiB of rollup let `@napi-rs/lzma-linux-x64-gnu`'s
1811
1804
  // 1,445,448-byte `.node` in, which the size guard had been evicting.
1812
1805
  // Only the guess is filtered; a path the closure requires is untouched.
1813
- if (stripped.endsWith('.node') || stripped.endsWith('.exe'))
1806
+ if (isNativeBinPath(stripped))
1814
1807
  return false;
1815
1808
  // hardening-r5: preserve binary content as Uint8Array.
1809
+ const stat = await filesOf(vfs).stat(stripped);
1810
+ if (stat === null || rawBytes + stat.size > bound)
1811
+ return false;
1816
1812
  const content = (await _readBundleCell(vfs, stripped));
1817
1813
  const cellLen = _bundleCellLength(content);
1818
- if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
1814
+ const raw = _bundleCellRawBytes(content);
1815
+ if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES || rawBytes + raw > bound)
1819
1816
  return false;
1820
1817
  bundle[stripped] = content;
1818
+ rawBytes += raw;
1821
1819
  budgetState.totalBytes += cellLen;
1822
1820
  budgetState.fileCount++;
1823
1821
  added++;
@@ -1827,26 +1825,6 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
1827
1825
  return false;
1828
1826
  }
1829
1827
  }
1830
- // X.5-C Fix #2 helper: walk a (possibly nested) exports value and
1831
- // collect every string-leaf path. unbuild-shaped packages like pathe
1832
- // nest two deep — `exports."."`.{require,import}.{types,default} —
1833
- // and the previous one-level loop only caught the inner string leaves
1834
- // when default was at the top, missing the unbuild shape entirely.
1835
- function collectExportLeaves(node, out) {
1836
- if (typeof node === 'string') {
1837
- out.add(node);
1838
- return;
1839
- }
1840
- if (!node || typeof node !== 'object')
1841
- return;
1842
- // Order matters for the "most likely usable" leaf: prefer require
1843
- // (most CJS-friendly), then default, then node, then import. We add
1844
- // ALL of them to the candidate set — addPkgEntry will probe each.
1845
- for (const k of ['require', 'node', 'default', 'import']) {
1846
- if (k in node)
1847
- collectExportLeaves(node[k], out);
1848
- }
1849
- }
1850
1828
  /**
1851
1829
  * Whether guessing at `pkgDir`'s main entry is still a guess.
1852
1830
  *
@@ -1878,47 +1856,52 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
1878
1856
  return !reached;
1879
1857
  }
1880
1858
  async function addPkgEntry(pkgDir) {
1881
- (await addOne(pkgDir + '/package.json'));
1859
+ const manifest = (pkgDir + '/package.json').replace(/^\/+/, '');
1860
+ if (bundle[manifest] === undefined && !(await addOne(manifest)))
1861
+ return;
1882
1862
  // A package the closure reached keeps exactly what it reached; only an
1883
1863
  // unreached one gets the guess below.
1884
1864
  if (!mainIsSpeculative(pkgDir))
1885
1865
  return;
1886
- let meta;
1866
+ // Malformed JSON, or a package whose entry fields the resolver cannot
1867
+ // read (`main: 7`, `exports: true`) drops that field and keeps the rest;
1868
+ // unparseable JSON is a package with no declared entry. Either way the
1869
+ // next package is unaffected.
1870
+ let pkg;
1887
1871
  try {
1888
- meta = JSON.parse((await vfs.readFileString(pkgDir + '/package.json')));
1872
+ const text = bundle[manifest];
1873
+ pkg = typeof text === 'string' ? parseResolvablePackageJson(text) : null;
1889
1874
  }
1890
1875
  catch {
1891
- meta = null;
1892
- }
1893
- const candidates = new Set();
1894
- if (meta) {
1895
- if (typeof meta.main === 'string')
1896
- candidates.add(meta.main);
1897
- if (typeof meta.module === 'string')
1898
- candidates.add(meta.module);
1899
- const exp = meta.exports;
1900
- if (typeof exp === 'string')
1901
- candidates.add(exp);
1902
- else if (exp && typeof exp === 'object') {
1903
- const dot = exp['.'];
1904
- // X.5-C Fix #2: walk nested condition trees recursively. Without
1905
- // this, packages with two-level exports (pathe, magic-string,
1906
- // most unbuild-emitted libs) miss their actual entry leaf and
1907
- // greedyAddMainEntries falls back to /index.js probing — which
1908
- // doesn't exist for those packages.
1909
- collectExportLeaves(dot, candidates);
1910
- }
1911
- }
1912
- if (candidates.size === 0)
1913
- candidates.add('index.js');
1876
+ pkg = null;
1877
+ }
1878
+ // The root entry a bare require of this package resolves to, in the
1879
+ // order the runtime resolves it (require-resolver.ts resolvePkgSubpathEx):
1880
+ // exports under the CJS conditions, then the ESM ones, then legacy main,
1881
+ // then an index probe, each tried until one lands. A denied root
1882
+ // (`"." : null`) resolves to nothing and falls through the same way. A
1883
+ // hand-rolled walk of `exports["."]` read nothing from a root conditional
1884
+ // map (on-change@6: `{ types, default }`, no ".", no main) and guessed
1885
+ // `index.js`, which does not exist.
1886
+ const candidates = [];
1887
+ if (pkg) {
1888
+ let entry = resolvePackageEntry(pkg, '.', DEFAULT_CJS_CONDITIONS);
1889
+ if (entry === null && pkg.exports != null)
1890
+ entry = resolvePackageEntry(pkg, '.', DEFAULT_ESM_CONDITIONS);
1891
+ if (entry !== null)
1892
+ candidates.push(entry);
1893
+ if (pkg.main !== undefined)
1894
+ candidates.push(pkg.main);
1895
+ }
1896
+ candidates.push('index.js');
1914
1897
  for (const rel of candidates) {
1915
1898
  const norm = rel.replace(/^\.\//, '');
1916
1899
  const base = pkgDir + '/' + norm;
1917
1900
  let landed = false;
1918
1901
  const tries = /\.[a-z]+$/.test(norm) ? [base] : exts.map((e) => base + e);
1919
1902
  for (const candidate of tries) {
1920
- if ((await vfs.exists(candidate.replace(/^\/+/, ''))) &&
1921
- !(await vfs.isDirectory(candidate.replace(/^\/+/, '')))) {
1903
+ if ((await filesOf(vfs).exists(candidate.replace(/^\/+/, ''))) &&
1904
+ !(await filesOf(vfs).isDirectory(candidate.replace(/^\/+/, '')))) {
1922
1905
  if ((await addOne(candidate))) {
1923
1906
  landed = true;
1924
1907
  break;
@@ -1936,8 +1919,8 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
1936
1919
  // The static walker cannot discover computed hash-chunk imports, so
1937
1920
  // the greedy oversample is the safety net for their reachability.
1938
1921
  const entryDir = base.replace(/\/[^/]+$/, '');
1939
- try {
1940
- const sibs = (await vfs.readdir(entryDir));
1922
+ {
1923
+ const sibs = await (async () => vfs.readdir(entryDir))().catch(() => []);
1941
1924
  for (const sib of sibs) {
1942
1925
  if (sib.type !== 'file')
1943
1926
  continue;
@@ -1965,8 +1948,8 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
1965
1948
  // typical shared/ dir returns 1-5 files.
1966
1949
  const sharedDir = entryDir + '/shared';
1967
1950
  const sharedStripped = sharedDir.replace(/^\/+/, '');
1968
- if ((await vfs.exists(sharedStripped)) && (await vfs.isDirectory(sharedStripped))) {
1969
- for (const sh of (await vfs.readdir(sharedDir))) {
1951
+ if (await (async () => filesOf(vfs).isDirectory(sharedStripped))().catch(() => false)) {
1952
+ for (const sh of await (async () => vfs.readdir(sharedDir))().catch(() => [])) {
1970
1953
  if (sh.type !== 'file')
1971
1954
  continue;
1972
1955
  if (!/\.(cjs|mjs|js)$/.test(sh.name))
@@ -1975,22 +1958,20 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
1975
1958
  }
1976
1959
  }
1977
1960
  }
1978
- catch { /* unreadable dir — drop sibling oversample, entry
1979
- file is enough */
1980
- }
1981
1961
  break;
1982
1962
  }
1983
1963
  }
1984
1964
  }
1985
1965
  for (const pkgDir of (await speculativePackageDirs(vfs, cwdStripped, bundle)))
1986
1966
  (await addPkgEntry(pkgDir));
1987
- return { added };
1967
+ return { added, groups };
1988
1968
  }
1989
1969
  /**
1990
- * The packages a computed `require(name)` inside the program can plausibly
1991
- * name: the project root's own runtime `dependencies`, plus every package
1992
- * ONE `dependencies` hop from a package that already owns a file in the
1993
- * static closure. Never devDependencies, never a second hop.
1970
+ * The packages a name resolved at runtime — a computed `require(name)`, or a
1971
+ * resolver call like exsolve's `resolveModulePath(name, { from: rootDir })` —
1972
+ * can plausibly reach: every package ONE `dependencies` hop from a package
1973
+ * the project itself declares or that owns a file in the static closure, plus
1974
+ * those roots themselves. Never devDependencies, never a second hop.
1994
1975
  *
1995
1976
  * Unbounded, the greedy oversample read every installed package's main:
1996
1977
  * for `node -e "import('got')"` in got's repo — a one-file static closure —
@@ -2002,11 +1983,26 @@ export async function greedyAddMainEntries(vfs, cwd, bundle, budgetState, requir
2002
1983
  * tree. Computed requires almost always target a declared runtime
2003
1984
  * dependency of the package doing the requiring, so the bound is one hop
2004
1985
  * over `dependencies` only. Directories, sorted for a stable bundle.
1986
+ *
1987
+ * The project's own dependencies are hop ROOTS and not merely members,
1988
+ * because a bin runs inside the project and resolves from the project root,
1989
+ * where what it names is its host framework's runtime peer rather than
1990
+ * anything its own package declares. Measured on staging, `nuxt dev` on a
1991
+ * `nuxi init` project: npm points `node_modules/.bin/nuxt` at
1992
+ * `@nuxt/cli/bin/nuxi.mjs`, so `@nuxt/cli` owns the entry; `@nuxt/kit` is a
1993
+ * devDependency of `@nuxt/cli` and a dependency of `nuxt`, which the project
1994
+ * declares. Admitting `nuxt` without hopping from it left
1995
+ * `@nuxt/kit/package.json` unstaged, `readFileSync` raised EAGAIN inside
1996
+ * exsolve — which swallows every error — and the CLI reported
1997
+ * `Cannot resolve module "@nuxt/kit" (from: /home/user/nuxt-probe/mvp/)`.
1998
+ * One hop from each project dependency is what the project's own node_modules
1999
+ * was hoisted for; it is the same edge kind and the same single level the
2000
+ * owner hop already spends.
2005
2001
  */
2006
2002
  export async function speculativePackageDirs(vfs, cwdStripped, bundle) {
2007
2003
  const runtimeDeps = async (pkgJsonPath) => {
2008
2004
  try {
2009
- const meta = JSON.parse((await vfs.readFileString(pkgJsonPath)));
2005
+ const meta = JSON.parse((await filesOf(vfs).readFileString(pkgJsonPath)));
2010
2006
  const names = new Set();
2011
2007
  for (const field of ['dependencies', 'optionalDependencies']) {
2012
2008
  const deps = meta?.[field];
@@ -2035,7 +2031,7 @@ export async function speculativePackageDirs(vfs, cwdStripped, bundle) {
2035
2031
  let dir = fromDir;
2036
2032
  for (;;) {
2037
2033
  const candidate = dir + '/node_modules/' + name;
2038
- if ((await vfs.exists(candidate + '/package.json')))
2034
+ if ((await filesOf(vfs).exists(candidate + '/package.json')))
2039
2035
  return candidate;
2040
2036
  const idx = dir.lastIndexOf('/');
2041
2037
  if (idx <= 0)
@@ -2044,14 +2040,24 @@ export async function speculativePackageDirs(vfs, cwdStripped, bundle) {
2044
2040
  }
2045
2041
  };
2046
2042
  const reached = new Set();
2043
+ const hopped = new Set();
2044
+ // Returns what this hop landed on, so a root can be hopped from without
2045
+ // reading its manifest twice and without the caller tracking the edges.
2047
2046
  const hop = async (fromDir) => {
2047
+ if (hopped.has(fromDir))
2048
+ return [];
2049
+ hopped.add(fromDir);
2050
+ const landed = [];
2048
2051
  for (const name of (await runtimeDeps(fromDir + '/package.json'))) {
2049
2052
  const dir = (await resolveDir(name, fromDir));
2050
- if (dir !== null)
2051
- reached.add(dir);
2053
+ if (dir === null)
2054
+ continue;
2055
+ reached.add(dir);
2056
+ landed.push(dir);
2052
2057
  }
2058
+ return landed;
2053
2059
  };
2054
- (await hop(cwdStripped));
2060
+ const projectDeps = (await hop(cwdStripped));
2055
2061
  const owners = new Set();
2056
2062
  for (const path of Object.keys(bundle)) {
2057
2063
  const owner = ownerOf(path);
@@ -2062,6 +2068,12 @@ export async function speculativePackageDirs(vfs, cwdStripped, bundle) {
2062
2068
  reached.add(owner);
2063
2069
  (await hop(owner));
2064
2070
  }
2071
+ // The second kind of root: what the project declares. A bin resolving from
2072
+ // the project root reaches these packages' dependencies, and nothing else
2073
+ // in this function would — the project's dependency owns no staged file
2074
+ // when the bin that runs belongs to a sibling package.
2075
+ for (const dir of projectDeps)
2076
+ (await hop(dir));
2065
2077
  return [...reached].sort();
2066
2078
  }
2067
2079
  /**
@@ -2130,7 +2142,7 @@ export async function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
2130
2142
  if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
2131
2143
  return false;
2132
2144
  try {
2133
- if (!(await vfs.exists(stripped)) || (await vfs.isDirectory(stripped)))
2145
+ if (!(await filesOf(vfs).exists(stripped)) || (await filesOf(vfs).isDirectory(stripped)))
2134
2146
  return false;
2135
2147
  if (!(await _bundleAdmits(vfs, stripped, budgetState)))
2136
2148
  return false;
@@ -2226,7 +2238,7 @@ export async function addStaticReadFileAssets(vfs, cwd, bundle, budgetState) {
2226
2238
  * (0, path_1.resolve)(__dirname, '../../../.ts-jest-digest'), 'utf8');
2227
2239
  *
2228
2240
  * The install pipeline writes `.ts-jest-digest` to VFS correctly
2229
- * (manifest pass at buildManifest enumerates it). But the runtime
2241
+ * (the namespace lists it). But the runtime
2230
2242
  * fs shim's readFileSync (`src/node-shims.ts:202-215`) consults
2231
2243
  * `__vfsBundle` only, and none of the existing bundle-population
2232
2244
  * passes — `prefetchForRequire` (require-graph), `greedyAddMainEntries`
@@ -2305,7 +2317,7 @@ export async function addStaticReadFileDotfilesAndCompiled(vfs, cwd, bundle, bud
2305
2317
  if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
2306
2318
  return false;
2307
2319
  try {
2308
- if (!(await vfs.exists(stripped)) || (await vfs.isDirectory(stripped)))
2320
+ if (!(await filesOf(vfs).exists(stripped)) || (await filesOf(vfs).isDirectory(stripped)))
2309
2321
  return false;
2310
2322
  if (!(await _bundleAdmits(vfs, stripped, budgetState)))
2311
2323
  return false;
@@ -2494,13 +2506,10 @@ export async function addBinTargetSiblings(vfs, scriptPath, bundle, budgetState,
2494
2506
  wasmPaths.push(child);
2495
2507
  if (bundle[child] !== undefined)
2496
2508
  continue;
2497
- let size;
2498
- try {
2499
- size = (await vfs.lstat(child)).size;
2500
- }
2501
- catch {
2509
+ const stat = await filesOf(vfs).lstat(child).catch(() => null);
2510
+ if (stat === null)
2502
2511
  continue;
2503
- }
2512
+ const size = stat.size;
2504
2513
  if (size > BIN_PACKAGE_SPECULATIVE_MAX_FILE_BYTES)
2505
2514
  continue;
2506
2515
  candidates.push({ path: child, size });
@@ -2566,13 +2575,9 @@ export async function collectClosureWasmImages(vfs, bundle, unstagedPaths) {
2566
2575
  for (const path of unstagedPaths) {
2567
2576
  if (byPath.has(path))
2568
2577
  continue;
2569
- let bytes;
2570
- try {
2571
- bytes = await vfs.readFile(path);
2572
- }
2573
- catch {
2578
+ const bytes = await filesOf(vfs).readBytes(path).catch(() => null);
2579
+ if (bytes === null)
2574
2580
  continue;
2575
- }
2576
2581
  byPath.set(path, { vfsPath: '/' + stripLeadingSlashes(path), digest: wasmImageDigest(bytes) });
2577
2582
  }
2578
2583
  return [...byPath.values()];
@@ -2592,21 +2597,29 @@ export async function collectClosureWasmImages(vfs, bundle, unstagedPaths) {
2592
2597
  * Admitted smallest-first for the same reason as the entry-package walk: the
2593
2598
  * budget is shared, so ordering by size maximizes the number of misses a
2594
2599
  * fixed number of bytes repairs.
2600
+ *
2601
+ * `room` is what the snapshot bound leaves after what is already committed
2602
+ * (the static closure, and for learned entries this session's own misses), in
2603
+ * raw bytes: evidence fills that room and never pushes the snapshot past the
2604
+ * bound, so it can never be the reason a launch fails. `bytes` is what this
2605
+ * call staged, in the same unit.
2595
2606
  */
2596
- export async function addObservedReads(vfs, observed, bundle, requiredPaths, budgetState) {
2607
+ export async function addObservedReads(vfs, observed, bundle, requiredPaths, budgetState, room = Infinity, pacer) {
2597
2608
  if (!observed || observed.size === 0)
2598
- return { added: 0 };
2609
+ return { added: 0, bytes: 0 };
2610
+ let bytes = 0;
2599
2611
  const candidates = [];
2600
2612
  for (const path of observed) {
2601
- if (path === '' || bundle[path] !== undefined)
2613
+ if (path === '')
2602
2614
  continue;
2603
- let stat;
2604
- try {
2605
- stat = (await vfs.lstat(path));
2606
- }
2607
- catch {
2615
+ // Already staged, but evictable: the evidence is what makes it required.
2616
+ if (bundle[path] !== undefined) {
2617
+ requiredPaths.add(path);
2608
2618
  continue;
2609
2619
  }
2620
+ const stat = await filesOf(vfs).lstat(path).catch(() => null);
2621
+ if (stat === null)
2622
+ continue;
2610
2623
  if (stat.type === 'directory')
2611
2624
  continue;
2612
2625
  candidates.push({ path, size: stat.size });
@@ -2628,13 +2641,48 @@ export async function addObservedReads(vfs, observed, bundle, requiredPaths, bud
2628
2641
  const cellLen = _bundleCellLength(content);
2629
2642
  if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
2630
2643
  continue;
2644
+ const raw = _bundleCellRawBytes(content);
2645
+ if (bytes + raw > room)
2646
+ continue;
2631
2647
  bundle[candidate.path] = content;
2632
2648
  requiredPaths.add(candidate.path);
2649
+ bytes += raw;
2633
2650
  budgetState.totalBytes += cellLen;
2634
2651
  budgetState.fileCount++;
2635
2652
  added++;
2636
2653
  }
2637
- return { added };
2654
+ // A module brings its static imports: learned one miss per launch, nuxt's
2655
+ // on-change alone would have cost a relaunch for each of its files.
2656
+ for (const path of observed) {
2657
+ if (!/\.[cm]?js$/.test(path) || bundle[path] === undefined)
2658
+ continue;
2659
+ const closure = await prefetchForRequire(requireFsOverBridge(vfs), '', path.slice(0, path.lastIndexOf('/')), '/' + path, undefined, pacer?.spend.bind(pacer));
2660
+ if ('kind' in closure)
2661
+ continue;
2662
+ for (const [dep, content] of Object.entries(closure.bundle)) {
2663
+ if (closure.speculative.has(dep))
2664
+ continue;
2665
+ if (bundle[dep] !== undefined) {
2666
+ requiredPaths.add(dep);
2667
+ continue;
2668
+ }
2669
+ const cellLen = _bundleCellLength(content);
2670
+ if (budgetState.fileCount >= VFS_BUNDLE_MAX_FILES)
2671
+ break;
2672
+ if (budgetState.totalBytes + cellLen > VFS_BUNDLE_MAX_BYTES)
2673
+ continue;
2674
+ const raw = _bundleCellRawBytes(content);
2675
+ if (bytes + raw > room)
2676
+ continue;
2677
+ bundle[dep] = content;
2678
+ requiredPaths.add(dep);
2679
+ bytes += raw;
2680
+ budgetState.totalBytes += cellLen;
2681
+ budgetState.fileCount++;
2682
+ added++;
2683
+ }
2684
+ }
2685
+ return { added, bytes };
2638
2686
  }
2639
2687
  const RUNTIME_PACKAGE_EXCLUDED_ROOT_DIRS = new Set([
2640
2688
  'docs',
@@ -2677,6 +2725,31 @@ const RUNTIME_PACKAGE_EXCLUDED_FILE_SUFFIXES = [
2677
2725
  '.mov',
2678
2726
  '.webm',
2679
2727
  ];
2728
+ /**
2729
+ * Whether the facet's require path could ever load this cell AS A MODULE.
2730
+ *
2731
+ * Not a judgement about whether the file is useful — an evicted cell is a
2732
+ * real loss either way, since the synchronous read it exists for raises
2733
+ * EAGAIN. It is a judgement about what the loss can BREAK. A cell the loader
2734
+ * can resolve is one some other module may be importing, and losing it takes
2735
+ * every importer down with it; a cell it can never resolve is only ever read
2736
+ * as data, by whoever asked for that path specifically.
2737
+ *
2738
+ * Declaration files are the clean case, and the reason this is a suffix test
2739
+ * rather than a content one: `foo.d.ts` is consumed by a type checker and is
2740
+ * never the target of a `require`, so shedding one cannot orphan a module.
2741
+ * That does not contradict the list above keeping them admissible — a `.d.ts`
2742
+ * IS a legitimate runtime read for the one package that reads its own (tsc
2743
+ * and `lib.*.d.ts`). This decides only what goes FIRST once a bound has
2744
+ * already been breached and something has to.
2745
+ */
2746
+ function _isLoadableModuleCell(path) {
2747
+ if (isTypescriptDeclarationFile(path))
2748
+ return false;
2749
+ const ext = vfsPathExtension(path);
2750
+ return ext === '.js' || ext === '.mjs' || ext === '.cjs' || ext === '.json'
2751
+ || ext === '.wasm' || ext === '' || bundleTypescriptLoader(path) !== null;
2752
+ }
2680
2753
  /**
2681
2754
  * Per-file ceiling for the speculative passes over installed packages: the
2682
2755
  * entry-package walk (`addBinTargetSiblings`) and the main-entry oversample
@@ -2828,7 +2901,7 @@ async function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
2828
2901
  if (budgetState.totalBytes >= VFS_BUNDLE_MAX_BYTES)
2829
2902
  return;
2830
2903
  try {
2831
- if (!(await vfs.exists(stripped)) || (await vfs.isDirectory(stripped)))
2904
+ if (!(await filesOf(vfs).exists(stripped)) || (await filesOf(vfs).isDirectory(stripped)))
2832
2905
  return;
2833
2906
  if (!(await _bundleAdmits(vfs, stripped, budgetState)))
2834
2907
  return;
@@ -2881,8 +2954,19 @@ async function addEntryAbsPathReads(vfs, entryCode, bundle, budgetState) {
2881
2954
  }
2882
2955
  return { added };
2883
2956
  }
2884
- function looksLikeEsm(src) {
2885
- return hasTopLevelModuleSyntax(src);
2957
+ function looksLikeEsm(path, src) {
2958
+ if (!hasTopLevelModuleSyntax(src))
2959
+ return false;
2960
+ if (vfsPathExtension(path) !== '')
2961
+ return true;
2962
+ // No extension: a bin script, or data such as a LICENSE whose prose says "import". Only a parse tells them apart.
2963
+ try {
2964
+ parseJavaScriptModule(src);
2965
+ return true;
2966
+ }
2967
+ catch {
2968
+ return false;
2969
+ }
2886
2970
  }
2887
2971
  /**
2888
2972
  * W3.5 Fix B — module-level cache for ESM→CJS transform results, keyed
@@ -2966,14 +3050,21 @@ function _markBundleEsmAsFailed(bundle, reason) {
2966
3050
  // A TypeScript source is never runnable as staged, so it always needs
2967
3051
  // the emit it cannot get; a JavaScript file only if it is ESM.
2968
3052
  const typescript = bundleTypescriptLoader(path) !== null;
2969
- if (!typescript && !looksLikeEsm(src))
3053
+ if (!typescript && !looksLikeEsm(path, src))
2970
3054
  continue;
2971
- const escapedReason = JSON.stringify(`esbuild transform failed for ${path}: ${reason}`);
2972
- bundle[typescript ? compiledCellKey(path) : path] =
2973
- '// framework-fixes-F4 diagnostic shim — esbuild rejected the ESM transform\n' +
2974
- '(function () { throw new Error(' + escapedReason + '); })();\n';
3055
+ bundle[typescript ? compiledCellKey(path) : path] = esbuildDiagnosticShim(path, reason);
2975
3056
  }
2976
3057
  }
3058
+ /**
3059
+ * Parseable CommonJS standing in for a module esbuild could not transform: it
3060
+ * throws the esbuild reason when required, so the failure surfaces at the
3061
+ * `require` with its cause rather than as a bare "Cannot use import statement".
3062
+ */
3063
+ function esbuildDiagnosticShim(path, reason) {
3064
+ const escapedReason = JSON.stringify(`esbuild transform failed for ${path}: ${reason.replace(/\n/g, ' ')}`);
3065
+ return '// framework-fixes-F4 diagnostic shim — esbuild rejected the ESM transform\n' +
3066
+ '(function () { throw new Error(' + escapedReason + '); })();\n';
3067
+ }
2977
3068
  /**
2978
3069
  * The set of bundle entries the facet's startup pre-compile loop turns into
2979
3070
  * functions, minus the ones already in the right format. Everything the loop
@@ -2984,7 +3075,8 @@ function _markBundleEsmAsFailed(bundle, reason) {
2984
3075
  * Extensionless entries are in the set for the same reason the pre-compile
2985
3076
  * loop takes them — that is the shape of nearly every npm `bin` script.
2986
3077
  * `.json` is data and `.cjs` is CommonJS by definition; neither needs the
2987
- * transform. Content, not the path, decides from here: `looksLikeEsm` parses.
3078
+ * transform. Content decides from here: `looksLikeEsm` sniffs module syntax,
3079
+ * and parses an extensionless file, which may be data rather than a script.
2988
3080
  */
2989
3081
  export function isBundleModuleCandidate(path) {
2990
3082
  const ext = vfsPathExtension(path);
@@ -2992,7 +3084,8 @@ export function isBundleModuleCandidate(path) {
2992
3084
  }
2993
3085
  /**
2994
3086
  * The esbuild loader for a TypeScript source in the bundle, or null when the
2995
- * path does not name one.
3087
+ * path does not name one. Which extensions are TypeScript is
3088
+ * `typescriptLoader`'s table, the one a runtime's entry script is decided by.
2996
3089
  *
2997
3090
  * A resolved `.ts` file reaches the facet as TypeScript, and TypeScript is not
2998
3091
  * JavaScript: `new Function` on a type annotation is a SyntaxError whether or
@@ -3011,12 +3104,7 @@ export function isBundleModuleCandidate(path) {
3011
3104
  * gone. So a declaration file is left exactly as it was staged.
3012
3105
  */
3013
3106
  export function bundleTypescriptLoader(path) {
3014
- if (isTypescriptDeclarationFile(path))
3015
- return null;
3016
- const ext = vfsPathExtension(path);
3017
- if (ext === '.tsx')
3018
- return 'tsx';
3019
- return ext === '.ts' || ext === '.mts' || ext === '.cts' ? 'ts' : null;
3107
+ return isTypescriptDeclarationFile(path) ? null : typescriptLoader(path);
3020
3108
  }
3021
3109
  /** `name.d.ts` / `name.d.mts` / `name.d.cts`, by TypeScript's own rule. */
3022
3110
  export function isTypescriptDeclarationFile(path) {
@@ -3052,35 +3140,6 @@ export function compiledCellKey(path) {
3052
3140
  export function compiledCellPath(key) {
3053
3141
  return key.startsWith(COMPILED_CELL_KEY_PREFIX) ? key.slice(COMPILED_CELL_KEY_PREFIX.length) : null;
3054
3142
  }
3055
- /**
3056
- * The compile helper both generated facets define: one CommonJS cell to one
3057
- * function, at module evaluation, the only moment workerd lets a string
3058
- * become code. One definition so the two facets cannot drift on what a cell
3059
- * may contain — the long-running facet once lacked the shebang strip, and a
3060
- * required module that carried one compiled in a short command and failed
3061
- * in the same tool's attached process.
3062
- */
3063
- export const FACET_COMPILE_HELPER = `
3064
- function __mkCompiledFn(code) {
3065
- // Node strips a leading shebang from every module before evaluation, not
3066
- // only the entry: a required module bundled with its "#!/usr/bin/env node"
3067
- // line is a SyntaxError under new Function.
3068
- if (typeof code === "string" && code.charCodeAt(0) === 35 && code.charCodeAt(1) === 33) {
3069
- const __nl = code.indexOf("\\n");
3070
- code = __nl >= 0 ? code.slice(__nl + 1) : "";
3071
- }
3072
- function renameIfDeclared(name) {
3073
- const re = new RegExp("(?:^|\\\\n|;)\\\\s*(?:const|let|var|function|class)\\\\s+" + name + "(?![$\\\\w])", "m");
3074
- return re.test(code) ? name + "__nimbus_unused" : name;
3075
- }
3076
- const baseParams = [
3077
- "exports", renameIfDeclared("require"), "module",
3078
- renameIfDeclared("__filename"),
3079
- renameIfDeclared("__dirname"),
3080
- ];
3081
- return new Function(...baseParams, code);
3082
- }
3083
- `;
3084
3143
  /**
3085
3144
  * The pre-compile loop both generated facets run at module evaluation, the
3086
3145
  * only moment workerd lets a string become code. One definition so the two
@@ -3114,19 +3173,22 @@ for (const [__p, __c] of Object.entries(__MODULE_VFS_BUNDLE)) {
3114
3173
  `;
3115
3174
  /**
3116
3175
  * Transform every ESM-shaped file in the bundle to CJS via esbuild.
3117
- * Mutates `bundle` in place. Errors are swallowed (the file is left as
3118
- * ESM source); the facet's pre-compile loop will record the SyntaxError
3119
- * into __compileFailures and __loadModule will surface it (Fix C).
3176
+ * Mutates `bundle` in place. A module esbuild rejects becomes a diagnostic
3177
+ * shim that throws the reason when required (`esbuildDiagnosticShim`).
3120
3178
  *
3121
- * Candidate set is `isBundleModuleCandidate`; within it, files with no
3122
- * top-level import/export are already CJS-shaped and left alone.
3179
+ * ESM/TypeScript candidates are `isBundleModuleCandidate`. CommonJS cells,
3180
+ * including .cjs, also visit the rewrite-only path for dynamic import().
3123
3181
  *
3124
3182
  * A JavaScript cell is rewritten in place. A TypeScript source keeps its
3125
3183
  * bytes and gets a compiled cell beside it — see `compiledCellKey`.
3126
3184
  *
3185
+ * When the service transforms in another isolate every cell goes in one
3186
+ * `transformMany`, so a launch costs one round trip and this isolate never
3187
+ * grows esbuild's heap. Transforms that run here are paced like any pass.
3188
+ *
3127
3189
  * Returns the count of files transformed (for diagnostics).
3128
3190
  */
3129
- async function transformEsmInBundle(bundle, esbuild, pacer, isolatedTransform) {
3191
+ async function transformEsmInBundle(bundle, esbuild, pacer) {
3130
3192
  let transformed = 0;
3131
3193
  let failed = 0;
3132
3194
  // Snapshot the keys first — esbuild calls await; never iterate-and-mutate.
@@ -3134,104 +3196,140 @@ async function transformEsmInBundle(bundle, esbuild, pacer, isolatedTransform) {
3134
3196
  for (const path of Object.keys(bundle)) {
3135
3197
  if (compiledCellPath(path) !== null)
3136
3198
  continue;
3137
- if (!isBundleModuleCandidate(path))
3199
+ if (!isBundleModuleCandidate(path) && !path.endsWith('.cjs'))
3138
3200
  continue;
3139
3201
  const src = bundle[path];
3140
3202
  // hardening-r5: binary cells are not ESM. Skip — looksLikeEsm +
3141
3203
  // esbuild.transform expect strings.
3142
3204
  if (typeof src !== 'string')
3143
3205
  continue;
3144
- if (bundleTypescriptLoader(path) === null) {
3145
- const esm = looksLikeEsm(src);
3146
- if (!esm)
3206
+ if (path.endsWith('.cjs')) {
3207
+ if (!mayHaveDynamicImport(src))
3147
3208
  continue;
3148
3209
  }
3210
+ else if (bundleTypescriptLoader(path) === null && !looksLikeEsm(path, src) && !mayHaveDynamicImport(src))
3211
+ continue;
3149
3212
  candidates.push(path);
3150
3213
  }
3214
+ const settle = (cell, outcome) => {
3215
+ if ('error' in outcome) {
3216
+ // Infrastructure failure is not source code. Abort before the bundle
3217
+ // or immutable loader image can cache a throw-on-require diagnostic.
3218
+ if (outcome.transient)
3219
+ throw new Error(`esbuild transform unavailable for ${cell.path}: ${outcome.error}`);
3220
+ // esbuild's verdict on this source is cached with it; a host that could
3221
+ // not run the transform this time has no verdict to cache.
3222
+ const shim = esbuildDiagnosticShim(cell.path, outcome.error);
3223
+ bundle[cell.target] = shim;
3224
+ __esmTransformCacheSet(cell.key, shim);
3225
+ failed++;
3226
+ return;
3227
+ }
3228
+ const code = bindImportMetaResolve(outcome.code, cell.absUrl);
3229
+ bundle[cell.target] = code;
3230
+ __esmTransformCacheSet(cell.key, code);
3231
+ transformed++;
3232
+ };
3233
+ const transformCells = async (cells) => {
3234
+ let outcomes;
3235
+ try {
3236
+ outcomes = await esbuild.transformMany(cells.map((cell) => cell.request));
3237
+ }
3238
+ catch (e) {
3239
+ // Publishing a whole-bundle diagnostic would poison the next launch
3240
+ // even when the per-cell transform cache correctly rejected it.
3241
+ throw new Error(`esbuild transform service unavailable: ${errorText(e)}`, { cause: e });
3242
+ }
3243
+ cells.forEach((cell, i) => settle(cell, outcomes[i]));
3244
+ };
3245
+ const batch = [];
3151
3246
  for (const path of candidates) {
3152
3247
  const original = bundle[path];
3153
3248
  if (typeof original !== 'string')
3154
3249
  continue;
3155
- const src = bundleTypescriptLoader(path) === null ? rewriteProvidedCommonJsModules(original) : original;
3250
+ const loader = bundleTypescriptLoader(path);
3156
3251
  // A TypeScript source keeps its bytes; its emit lands beside it.
3157
- const target = bundleTypescriptLoader(path) === null ? path : compiledCellKey(path);
3158
- // esbuild-wasm runs inside this isolate, so a transform is computation
3159
- // like any other pass — the await above it yields nothing on its own.
3160
- if (pacer)
3161
- await pacer.spend(src.length);
3162
- // `import.meta.url` substitution mirrors the sibling fix at
3163
- // src/runtime/runtime-registry.ts:383-389 (framework-gaps-fix P5).
3164
- // Without `define`, esbuild's CJS transform reduces `import.meta.url`
3165
- // to undefined (single-pass) or preserves it literally — only to
3166
- // SyntaxError at `new Function(...)` parse time (two-pass via
3167
- // EsbuildService.transform's async-IIFE wrap → "Cannot use
3168
- // 'import.meta' outside a module"). The substitution value is the
3169
- // real `file:///<absolute-path>` URL — exactly what real Node returns
3170
- // for an ESM module at that path.
3171
- //
3172
- // Note: cache key now incorporates the path because the transformed
3173
- // output is path-specific (the URL literal is baked in). Two files
3174
- // with identical source but different paths would otherwise share a
3175
- // cache entry and the second file would get the first file's URL.
3252
+ const target = loader === null ? path : compiledCellKey(path);
3253
+ // Source is transformed once per path; import.meta reads metadata from
3254
+ // each evaluation's module object, including its query and fragment.
3255
+ // The source URL still keys transforms and supplies the static parent
3256
+ // for rewritten dynamic imports and diagnostics.
3176
3257
  const absUrl = 'file:///' + path.replace(/^\/+/, '');
3177
- const key = __cacheKey(src + '\0' + absUrl);
3258
+ // Keyed on the staged bytes, so a cell the pre-pass fails has a key too.
3259
+ const key = __cacheKey(original + '\0' + absUrl);
3178
3260
  const cached = __esmTransformCacheGet(key);
3179
3261
  if (cached !== undefined) {
3180
3262
  bundle[target] = cached;
3181
3263
  transformed++;
3182
3264
  continue;
3183
3265
  }
3266
+ // Every cell's dynamic import() is the process's: the transform keeps
3267
+ // them, and the facet rewrites each to the process's ESM loader.
3268
+ const moduleMetadata = !path.endsWith('.cjs') && (loader !== null || looksLikeEsm(path, original));
3269
+ const cellFor = (code, rewriteOnly = false) => ({
3270
+ path,
3271
+ target,
3272
+ key,
3273
+ absUrl,
3274
+ request: {
3275
+ code,
3276
+ options: rewriteOnly
3277
+ ? { rewriteOnly: true, dynamicImportParent: absUrl, moduleMetadata }
3278
+ : { loader: loader ?? 'js', format: 'cjs', target: 'esnext', define: importMetaDefines(absUrl, true), dynamicImportParent: absUrl, moduleMetadata },
3279
+ },
3280
+ });
3281
+ let src;
3184
3282
  try {
3185
- const transformOptions = {
3186
- loader: bundleTypescriptLoader(path) ?? 'js',
3187
- format: 'cjs',
3188
- target: 'esnext',
3189
- define: importMetaDefines(absUrl),
3190
- };
3191
- const bounded = transformOptions.loader === 'js' && src.length >= ISOLATED_ESM_TRANSFORM_MIN_BYTES
3192
- ? rewriteBundledEsmToCjs(src, absUrl)
3193
- : null;
3194
- const t = bounded ?? (isolatedTransform && src.length >= ISOLATED_ESM_TRANSFORM_MIN_BYTES
3195
- ? await isolatedTransform(src, transformOptions)
3196
- : await esbuild.transform(src, transformOptions));
3197
- const code = bindImportMetaResolve(t.code, absUrl);
3198
- bundle[target] = code;
3199
- __esmTransformCacheSet(key, code);
3200
- transformed++;
3283
+ src = loader === null ? rewriteProvidedCommonJsModules(original) : original;
3201
3284
  }
3202
3285
  catch (e) {
3203
- // framework-fixes-F4 (2026-05-12): replace the source with a
3204
- // JS-valid module that throws an INFORMATIVE Error when require'd.
3205
- //
3206
- // Pre-fix this catch swallowed the failure silently. The pre-
3207
- // compile loop then re-saw the ESM source and emitted the
3208
- // SyntaxError "Cannot use import statement outside a module" into
3209
- // __compileFailures. __loadModule surfaced that as the user error
3210
- // — but it told the user nothing about WHY esbuild rejected the
3211
- // file (was it TLA at module scope? a TypeScript file mistyped
3212
- // as .js? an unsupported syntax? our esbuild service not init?).
3213
- //
3214
- // Post-fix the replacement source is parseable CJS that, when
3215
- // require()'d, throws a real Error carrying the esbuild reason.
3216
- // The pre-compile loop succeeds, the file lands in
3217
- // __compiledModules, and the failure surfaces at require time
3218
- // with FULL diagnostic context. Net effect for users: the same
3219
- // upstream tool still fails, but they now see WHY.
3220
- // Probe: tests/behavioral/npm-create/new/create-astro-diagnostic.mjs
3221
- // asserts the user-visible error contains "esbuild transform" so
3222
- // future diagnostic regressions are caught.
3223
- const reason = (e && e.message) ? String(e.message).replace(/\n/g, ' ') : String(e);
3224
- const escapedReason = JSON.stringify(`esbuild transform failed for ${path}: ${reason}`);
3225
- // Build a valid CJS module that throws on load. Use IIFE to keep
3226
- // module-scope flat (no TLA, no top-level await). Esbuild-cache
3227
- // this so repeated submits don't re-run the same transform.
3228
- const diagnosticSrc = '// framework-fixes-F4 diagnostic shim — esbuild rejected the ESM transform\n' +
3229
- '(function () { throw new Error(' + escapedReason + '); })();\n';
3230
- bundle[target] = diagnosticSrc;
3231
- __esmTransformCacheSet(key, diagnosticSrc);
3232
- failed++;
3286
+ // The pre-pass cannot read this cell: a verdict on it alone, like esbuild's.
3287
+ settle(cellFor(original), { error: errorText(e) });
3288
+ continue;
3233
3289
  }
3290
+ // CommonJS already: only its dynamic import() calls change.
3291
+ const cell = path.endsWith('.cjs') || (loader === null && !looksLikeEsm(path, src)) ? cellFor(src, true) : cellFor(src);
3292
+ if (cell.request.options?.rewriteOnly !== true && loader === null && src.length >= BUNDLED_ESM_REWRITE_MIN_BYTES) {
3293
+ // The bounded rewrite is computation in this isolate, however large.
3294
+ if (pacer)
3295
+ await pacer.spend(src.length);
3296
+ let rewritten;
3297
+ try {
3298
+ rewritten = rewriteBundledEsmToCjs(src, absUrl, true);
3299
+ }
3300
+ catch (e) {
3301
+ rewritten = { error: errorText(e) };
3302
+ }
3303
+ if (rewritten && 'error' in rewritten) {
3304
+ settle(cell, rewritten);
3305
+ continue;
3306
+ }
3307
+ if (rewritten) {
3308
+ // Its declarations are CommonJS now; what import() calls remain go to
3309
+ // the facet like any cell's.
3310
+ if (!mayHaveDynamicImport(rewritten.code) && !rewritten.code.includes('import.meta')) {
3311
+ settle(cell, rewritten);
3312
+ continue;
3313
+ }
3314
+ const rest = cellFor(rewritten.code, true);
3315
+ if (esbuild.transformsInIsolate) {
3316
+ await transformCells([rest]);
3317
+ continue;
3318
+ }
3319
+ batch.push(rest);
3320
+ continue;
3321
+ }
3322
+ }
3323
+ if (esbuild.transformsInIsolate) {
3324
+ if (pacer)
3325
+ await pacer.spend(src.length);
3326
+ await transformCells([cell]);
3327
+ continue;
3328
+ }
3329
+ batch.push(cell);
3234
3330
  }
3331
+ if (batch.length > 0)
3332
+ await transformCells(batch);
3235
3333
  return { transformed, failed };
3236
3334
  }
3237
3335
  /**
@@ -3250,7 +3348,7 @@ async function transformEsmInBundle(bundle, esbuild, pacer, isolatedTransform) {
3250
3348
  * behaviour for code paths that don't have esbuild handy).
3251
3349
  *
3252
3350
  */
3253
- export async function buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bundleProfile = DEFAULT_FACET_BUNDLE_PROFILE, observedReads, pacer, isolatedTransform, maxBundleBytes) {
3351
+ export async function buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bundleProfile = DEFAULT_FACET_BUNDLE_PROFILE, observedReads, pacer, maxBundleBytes, learnedFor) {
3254
3352
  // This build accumulates raw VFS contents in the supervisor heap, and did it
3255
3353
  // with nothing watching: the estimator read 9.4 MiB while these bytes were
3256
3354
  // resetting the DO three times. Take the budget the enrichment passes are
@@ -3260,20 +3358,20 @@ export async function buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbui
3260
3358
  const lease = await acquireSupervisorAllocation(VFS_BUNDLE_MAX_BYTES);
3261
3359
  prefetchBundleStart(VFS_BUNDLE_MAX_BYTES);
3262
3360
  try {
3263
- return await _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bundleProfile, observedReads, pacer, isolatedTransform, maxBundleBytes);
3361
+ return await _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bundleProfile, observedReads, pacer, maxBundleBytes, learnedFor);
3264
3362
  }
3265
3363
  finally {
3266
3364
  prefetchBundleEnd(VFS_BUNDLE_MAX_BYTES);
3267
3365
  lease.release();
3268
3366
  }
3269
3367
  }
3270
- async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bundleProfile = DEFAULT_FACET_BUNDLE_PROFILE, observedReads, pacer, isolatedTransform, maxBundleBytes = VFS_BUNDLE_MAX_BYTES) {
3368
+ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bundleProfile = DEFAULT_FACET_BUNDLE_PROFILE, observedReads, pacer, maxBundleBytes = VFS_BUNDLE_MAX_BYTES, learnedFor) {
3271
3369
  // Read the cursor BEFORE the walk: a mutation that lands while the bundle
3272
3370
  // is being assembled must be reported as invalidated, not silently missed.
3273
- const admitted = await vfs.authority.acquire(null, 0);
3371
+ const admitted = await vfs.acquire(null, 0);
3274
3372
  const cursor = { epoch: admitted.epoch, rev: admitted.rev };
3275
3373
  // 1. Static reachable-set walk from entry.
3276
- const prefetch = (await prefetchForRequire(vfs, entryCode || '', cwd, scriptPath, maxBundleBytes));
3374
+ const prefetch = (await prefetchForRequire(requireFsOverBridge(vfs), entryCode || '', cwd, scriptPath, maxBundleBytes, pacer?.spend.bind(pacer)));
3277
3375
  if ('kind' in prefetch) {
3278
3376
  // A required closure larger than the bound can never launch as a
3279
3377
  // snapshot. Surface it as the process's own failure rather than a
@@ -3281,7 +3379,16 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
3281
3379
  throw new ClosureBoundExceededError(prefetch);
3282
3380
  }
3283
3381
  const bundle = { ...prefetch.bundle };
3284
- const requiredPaths = new Set(Object.keys(prefetch.bundle));
3382
+ const closurePaths = new Set(Object.keys(prefetch.bundle).filter((path) => !prefetch.speculative.has(path)));
3383
+ // Evidence is kept apart from the closure: an observed subpath is not the
3384
+ // closure choosing that corner of a package, and only the closure can fail
3385
+ // a launch. The room evidence may fill is what the closure leaves under the
3386
+ // bound, this session's own misses first, then what other sessions learned.
3387
+ const observedPaths = new Set();
3388
+ const learnedPaths = new Set();
3389
+ let room = maxBundleBytes;
3390
+ for (const path of closurePaths)
3391
+ room -= _bundleCellRawBytes(bundle[path]);
3285
3392
  let truncated = false;
3286
3393
  const budgetState = { totalBytes: 0, fileCount: 0 };
3287
3394
  // Each enrichment pass below re-scans the bundle accumulated so far, so a
@@ -3303,14 +3410,23 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
3303
3410
  // admitted first, ahead of every guess, and they join the required set
3304
3411
  // rather than the evictable one, because a file evicted here misses
3305
3412
  // again on the next run and the loop never closes.
3306
- const observedAdd = (await addObservedReads(vfs, observedReads, bundle, requiredPaths, budgetState));
3307
- void observedAdd;
3413
+ // Other sessions' misses in the packages this closure can load
3414
+ // (read-profile.ts) are evidence of the same kind: a learned module joins
3415
+ // the module map with its imports, as this session's own misses do.
3416
+ const learned = learnedFor ? await learnedFor(Object.keys(bundle)).catch(() => []) : [];
3417
+ const own = await addObservedReads(vfs, observedReads, bundle, observedPaths, budgetState, Math.max(0, room), pacer);
3418
+ room -= own.bytes;
3419
+ const learnedOnly = new Set(learned.filter((path) => !observedReads?.has(path) && !observedPaths.has(path)));
3420
+ await addObservedReads(vfs, learnedOnly, bundle, learnedPaths, budgetState, Math.max(0, room), pacer);
3421
+ for (const path of observedPaths)
3422
+ learnedPaths.delete(path);
3308
3423
  await paceAfterPass();
3309
3424
  // 2. Greedy oversample — every installed pkg's pkg.json + main.
3310
3425
  // Catches dynamic-require / `bindings()` / plugin-loader cases the
3311
3426
  // regex prefetch misses. Its budget is independent from the complete
3312
3427
  // static require closure, which is correctness-critical.
3313
- const greedy = (await greedyAddMainEntries(vfs, cwd, bundle, budgetState, requiredPaths));
3428
+ const independentBeforeGroups = new Set(Object.keys(bundle));
3429
+ const greedy = (await greedyAddMainEntries(vfs, cwd, bundle, budgetState, closurePaths, { maxBundleBytes, pacer }));
3314
3430
  await paceAfterPass();
3315
3431
  // 2.25 X.5-Z3: static-readFileSync asset prefetch. Scans every
3316
3432
  // bundle .js/.mjs/.cjs source for the canonical jsdom shape:
@@ -3385,31 +3501,17 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
3385
3501
  // ESM files (e.g. tldts/dist/es6/index.js, @remix-run/react/dist/esm,
3386
3502
  // @tailwindcss/vite, react-remove-scroll, astro) silently fail
3387
3503
  // `new Function` at facet startup and surface as the misleading
3388
- // "file was not pre-bundled" at request time.
3504
+ // "not in this launch's module map" at request time.
3389
3505
  if (esbuild) {
3390
- try {
3391
- await transformEsmInBundle(bundle, esbuild, pacer, isolatedTransform);
3392
- // Recount bytes after the transform — CJS rebuilds can be larger
3393
- // OR smaller than the ESM source. We don't try to thread totalBytes
3394
- // through the transform because the eviction loop below recomputes
3395
- // the encoded size from scratch anyway.
3396
- }
3397
- catch (e) {
3398
- // framework-fixes-F4 (2026-05-12): the prior catch was silent.
3399
- // Replace every detected-ESM source with the SAME diagnostic
3400
- // shim transformEsmInBundle's per-file catch installs, so the
3401
- // user sees "esbuild transform failed (service-level): <reason>"
3402
- // instead of a bare "Cannot use import statement..." parse
3403
- // error with no context.
3404
- const reason = (e && e.message) ? String(e.message).replace(/\n/g, ' ') : String(e);
3405
- _markBundleEsmAsFailed(bundle, `esbuild service unavailable: ${reason}`);
3406
- }
3506
+ // Transient failures propagate through the launch failure path before
3507
+ // serialization/cache/LOADER publication. Per-source verdicts still use
3508
+ // the lazy diagnostic cells installed by transformEsmInBundle.
3509
+ await transformEsmInBundle(bundle, esbuild, pacer);
3407
3510
  }
3408
3511
  else {
3409
- // framework-fixes-F4 (2026-05-12): no esbuild service available at
3410
- // all (lazy-init failed or never wired). Same diagnostic-shim
3411
- // treatment so users see WHY the ESM file couldn't be transformed.
3412
- _markBundleEsmAsFailed(bundle, 'esbuild service not initialized (likely lazy-init failure)');
3512
+ // No esbuild service was given: the ESM cells stage as diagnostics that
3513
+ // say so, rather than as source `new Function` rejects without a reason.
3514
+ _markBundleEsmAsFailed(bundle, 'no esbuild service was given to this launch');
3413
3515
  }
3414
3516
  for (const path of Object.keys(bundle)) {
3415
3517
  if (compiledCellPath(path) === null && (!isBundleModuleCandidate(path) || bundleTypescriptLoader(path) !== null))
@@ -3418,61 +3520,165 @@ async function _buildPrefetchBundle(vfs, scriptPath, cwd, entryCode, esbuild, bu
3418
3520
  if (typeof source !== 'string')
3419
3521
  continue;
3420
3522
  await pacer?.spend(source.length);
3421
- bundle[path] = rewriteProvidedCommonJsModules(source);
3523
+ try {
3524
+ bundle[path] = rewriteProvidedCommonJsModules(source);
3525
+ }
3526
+ catch {
3527
+ // Unparseable, so not a module and no bundled records to bind: data such as a LICENSE.
3528
+ }
3422
3529
  }
3423
3530
  await paceAfterPass();
3424
- // 3. Manifest pass — UNCHANGED from W2.5b. Decouples directory shape
3425
- // from content cap so fs.readdirSync remains honest even if the
3426
- // content for a given file was capped out.
3427
- await paceAfterPass();
3428
- const manifest = (await buildManifest(vfs, cwd, scriptPath));
3429
- await paceAfterPass();
3430
- // 4. JSON-encoded-size guard, measured in UTF-8 bytes (not UTF-16 code
3431
- // units) because that is what workerd charges against the per-module
3432
- // text-size budget. Only OPTIONAL enrichment is evictable, largest
3433
- // first; the manifest and the static require closure stay.
3531
+ // 4. The snapshot's size guard, in the unit the session DO's memory was
3532
+ // measured in and the require walk counts: raw bytes, a cell's UTF-8
3533
+ // length (VFS_BUNDLE_MAX_BYTES, platform/limits.ts, unless the caller
3534
+ // set its own). The per-module text limit is met by side modules and is
3535
+ // not a reason to evict. Only
3536
+ // OPTIONAL enrichment is evictable; the static require closure stays, and
3537
+ // a closure past the bound by itself fails the launch by name.
3434
3538
  //
3435
3539
  // Evicting an enrichment file is a real loss — the sync fs reads it
3436
3540
  // exists for cannot fall back to the supervisor — so the paths that
3437
3541
  // went are named rather than silently dropped.
3438
- const size = encodedBundleSize(bundle, manifest);
3439
- if (size.bytes > BUNDLE_MAX_ENCODED_BYTES) {
3542
+ const size = encodedBundleSize(bundle);
3543
+ const rawBytes = new Map();
3544
+ let rawTotal = 0;
3545
+ for (const [path, cell] of Object.entries(bundle)) {
3546
+ const bytes = _bundleCellRawBytes(cell);
3547
+ rawBytes.set(path, bytes);
3548
+ rawTotal += bytes;
3549
+ }
3550
+ if (rawTotal > maxBundleBytes) {
3551
+ // Only the walk's closure can fail a launch, and the path it names is the
3552
+ // one whose bytes (with its compiled cell) crossed the bound, in walk order.
3553
+ let closureBytes = 0;
3554
+ let crossedAt = null;
3555
+ for (const path of closurePaths) {
3556
+ closureBytes += (rawBytes.get(path) ?? 0) + (rawBytes.get(compiledCellKey(path)) ?? 0);
3557
+ if (crossedAt === null && closureBytes > maxBundleBytes)
3558
+ crossedAt = path;
3559
+ }
3560
+ if (crossedAt !== null) {
3561
+ throw new ClosureBoundExceededError({
3562
+ kind: 'closure-exceeds-bound',
3563
+ entry: scriptPath ?? 'entry code',
3564
+ bytesSeen: closureBytes,
3565
+ bound: maxBundleBytes,
3566
+ lastPath: crossedAt,
3567
+ });
3568
+ }
3440
3569
  // A compiled cell goes with its source: required when the source is.
3441
- const evictable = Object.keys(bundle)
3442
- .filter((path) => !requiredPaths.has(compiledCellPath(path) ?? path))
3443
- .sort((a, b) => _bundleCellLength(bundle[b]) - _bundleCellLength(bundle[a]));
3444
- const evicted = [];
3445
- for (const k of evictable) {
3446
- if (size.bytes <= BUNDLE_MAX_ENCODED_BYTES)
3570
+ //
3571
+ // Order matters as much as the bound. Largest-first alone ranks a cell by
3572
+ // what it costs and never by what losing it costs: an admitted module and
3573
+ // the sibling it imports are both "optional", and shedding the sibling
3574
+ // leaves a module in the bundle that cannot load. On `astro dev` the
3575
+ // snapshot breached its bound by 591 files, and among the largest were
3576
+ // modules while 3,291 declaration files (never a require target) stayed.
3577
+ //
3578
+ // Unloadable cells first, then enrichment largest-first, then dynamic-import
3579
+ // subtrees last-discovered first, so a CLI's own `import()` deferral is shed last.
3580
+ const speculativeRank = new Map();
3581
+ for (const path of prefetch.speculative)
3582
+ speculativeRank.set(path, speculativeRank.size);
3583
+ const rankOf = (path) => speculativeRank.get(compiledCellPath(path) ?? path) ?? -1;
3584
+ // Evidence goes after all enrichment: what others learned before this
3585
+ // session's own misses, each tier last-admitted first.
3586
+ const tierOf = (path) => {
3587
+ const source = compiledCellPath(path) ?? path;
3588
+ return closurePaths.has(source) ? 3 : observedPaths.has(source) ? 2 : learnedPaths.has(source) ? 1 : 0;
3589
+ };
3590
+ const members = new Set();
3591
+ for (const group of greedy.groups)
3592
+ for (const path of group.members)
3593
+ members.add(path);
3594
+ const sources = new Set(Object.keys(bundle).map(path => compiledCellPath(path) ?? path));
3595
+ const independent = new Set([...sources].filter(path => !members.has(path) || independentBeforeGroups.has(path)));
3596
+ const keptGroups = new Set(greedy.groups);
3597
+ const weight = (path) => (rawBytes.get(path) ?? 0) + (rawBytes.get(compiledCellKey(path)) ?? 0);
3598
+ const unitBytes = new Map();
3599
+ for (const group of greedy.groups) {
3600
+ let total = 0;
3601
+ for (const member of group.members)
3602
+ if (!independent.has(member))
3603
+ total += weight(member);
3604
+ unitBytes.set(group.root, total);
3605
+ if (pacer)
3606
+ await pacer.spend(group.members.size * 256);
3607
+ }
3608
+ const lastAdmittedFirst = (tier) => [...tier].reverse().filter(path => independent.has(path));
3609
+ const enrichment = [...independent, ...greedy.groups.map(group => group.root)]
3610
+ .filter(path => tierOf(path) === 0)
3611
+ .sort((a, b) => {
3612
+ const loadable = (_isLoadableModuleCell(a) ? 1 : 0) - (_isLoadableModuleCell(b) ? 1 : 0);
3613
+ if (loadable !== 0)
3614
+ return loadable;
3615
+ const rankA = rankOf(a);
3616
+ const rankB = rankOf(b);
3617
+ if (rankA !== rankB)
3618
+ return rankA < 0 ? -1 : rankB < 0 ? 1 : rankB - rankA;
3619
+ return (unitBytes.get(b) ?? weight(b)) - (unitBytes.get(a) ?? weight(a));
3620
+ });
3621
+ const evictable = [...enrichment, ...lastAdmittedFirst(learnedPaths), ...lastAdmittedFirst(observedPaths)];
3622
+ async function retainedPaths() {
3623
+ const retained = new Set(independent);
3624
+ for (const group of keptGroups) {
3625
+ let work = 0;
3626
+ for (const path of group.members) {
3627
+ retained.add(path);
3628
+ work += path.length + 256;
3629
+ }
3630
+ if (pacer)
3631
+ await pacer.spend(work);
3632
+ }
3633
+ for (const source of [...retained]) {
3634
+ const compiled = compiledCellKey(source);
3635
+ if (bundle[compiled] !== undefined)
3636
+ retained.add(compiled);
3637
+ }
3638
+ return retained;
3639
+ }
3640
+ let retained = await retainedPaths();
3641
+ for (const path of evictable) {
3642
+ if (rawTotal <= maxBundleBytes)
3447
3643
  break;
3448
- evicted.push([k, _bundleCellLength(bundle[k])]);
3644
+ if (independent.delete(path)) {
3645
+ for (const group of keptGroups)
3646
+ if (group.members.has(path))
3647
+ keptGroups.delete(group);
3648
+ }
3649
+ else {
3650
+ for (const group of keptGroups)
3651
+ if (group.root === path)
3652
+ keptGroups.delete(group);
3653
+ }
3654
+ retained = await retainedPaths();
3655
+ rawTotal = 0;
3656
+ for (const path of retained)
3657
+ rawTotal += rawBytes.get(path) ?? 0;
3658
+ }
3659
+ const evicted = [];
3660
+ for (const k of Object.keys(bundle)) {
3661
+ if (retained.has(k))
3662
+ continue;
3663
+ evicted.push([k, rawBytes.get(k) ?? 0]);
3449
3664
  delete bundle[k];
3450
3665
  size.remove(k);
3451
3666
  }
3452
3667
  if (evicted.length > 0) {
3453
3668
  truncated = true;
3454
- console.warn(`[facet-manager] prefetch snapshot exceeded ${BUNDLE_MAX_ENCODED_BYTES} encoded `
3455
- + `bytes; evicted ${evicted.length} optional file(s). They still exist and `
3669
+ console.warn(`[facet-manager] prefetch snapshot exceeded ${maxBundleBytes} bytes; `
3670
+ + `evicted ${evicted.length} optional file(s). They still exist and `
3456
3671
  + `async reads still return them; synchronous reads raise EAGAIN: `
3457
3672
  + `${describeBundleCells(evicted)}`);
3458
3673
  }
3459
3674
  }
3460
3675
  const fileCount = Object.keys(bundle).length;
3461
- const metadata = (await buildVfsMetadata(vfs, manifest, bundle));
3462
- (await addUnreadableDenialCells(vfs, bundle, metadata));
3463
- await paceAfterPass();
3464
- // Denial cells land after the eviction pass, so account for them before
3465
- // deciding where the bundle has to live.
3466
- for (const [path, cell] of Object.entries(bundle))
3467
- size.add(path, cell);
3468
3676
  const bundleSideModulesRequired = size.bytes > BUNDLE_MAX_ENCODED_BYTES;
3469
3677
  // Suppress lint: `greedy.added` is observed only via diagnostics.
3470
3678
  void greedy;
3471
3679
  const wasmImages = (await collectClosureWasmImages(vfs, bundle, binSiblingAdd.wasmPaths));
3472
3680
  return {
3473
3681
  bundle,
3474
- manifest,
3475
- metadata,
3476
3682
  cursor,
3477
3683
  reachableCount: fileCount,
3478
3684
  truncated,
@@ -3554,24 +3760,27 @@ export class FacetManager {
3554
3760
  // spawn created as an OS-child of the attach TUI. When the attach process
3555
3761
  // exits (reported / killed), its serve facet is torn down with it.
3556
3762
  _pairedServeFacet = new Map();
3763
+ // The bundle a resident pid booted from, so the misses it reports at exit
3764
+ // stage into the next launch of the same entry, as a one-shot's do.
3765
+ residentBundleKeys = new Map();
3766
+ /** Per resident pid: the shared read profile's entries its launch staged, settled at its exit. */
3767
+ residentProfileOffers = new Map();
3557
3768
  /**
3558
- * W3.5 Fix B: lazily-created EsbuildService for the ESM→CJS pre-pass
3559
- * over the prefetch bundle. Created on first exec where vfs is set;
3560
- * shared across subsequent execs (warm wasm). Optional setter
3561
- * `setEsbuildService` lets NimbusSession share its existing instance
3562
- * to avoid double-init.
3769
+ * The esbuild the bundle's ESM→CJS pass transforms with. composeFacetManager
3770
+ * sets it: the host's own, or one whose transforms run in the session's
3771
+ * esbuild facet. Never one of this isolate: esbuild-wasm's heap only grows.
3563
3772
  */
3564
3773
  esbuild = null;
3565
3774
  /**
3566
3775
  * Prefetch-bundle cache. buildPrefetchBundle does a full VFS reachable-set
3567
3776
  * walk + greedy oversample + esbuild ESM→CJS pass on EVERY foreground
3568
3777
  * exec — dominant wall-clock on large node_modules. This memoizes the
3569
- * result (including the serialized facet bundle + manifest) keyed on
3778
+ * result (including the serialized facet bundle) keyed on
3570
3779
  * (bundleProfile, cwd, scriptPath, entryCode identity).
3571
3780
  *
3572
3781
  * Correctness watermark: the GLOBAL SqliteVFS revision. buildPrefetchBundle
3573
3782
  * reads from paths that can lie anywhere in the VFS (addEntryAbsPathReads
3574
- * pulls absolute-path literals like /tmp/x; buildManifest walks from '/'),
3783
+ * pulls absolute-path literals like /tmp/x),
3575
3784
  * so a cwd-scoped subtree revision cannot guarantee invalidation. The
3576
3785
  * global revision bumps on ANY write, so the cache invalidates on every
3577
3786
  * mutation that could change any file the bundle reads — provably
@@ -3592,6 +3801,19 @@ export class FacetManager {
3592
3801
  * already deliver for the case that matters: running the command again.
3593
3802
  */
3594
3803
  residencyProfiles = new Map();
3804
+ /**
3805
+ * Misses shared across sessions per installed package (read-profile.ts),
3806
+ * kept in the npm tarball cache bucket (NPM_TARBALL_CACHE) beside the
3807
+ * tarballs. Unbound, a miss is learned for this session only
3808
+ * (residencyProfiles).
3809
+ */
3810
+ readProfile = null;
3811
+ /** Read-profile changes dropped after losing every write race. */
3812
+ readProfileConflicts = 0;
3813
+ /** Per module path: its static references at a revision (see _closureStaticRefs). */
3814
+ staticRefsMemo = new Map();
3815
+ /** Modules whose references are remembered: a few programs' closures. */
3816
+ static STATIC_REFS_MEMO_MAX = 16_384;
3595
3817
  /**
3596
3818
  * What the prefetch cache holds right now, for /api/_diag/memory: each
3597
3819
  * entry's key, the revision it was built at, and its retained bytes, next
@@ -3645,6 +3867,12 @@ export class FacetManager {
3645
3867
  ? Reflect.get(env, 'NIMBUS_DEBUG')
3646
3868
  : undefined;
3647
3869
  this.debugEnabled = debugVar === '1' || debugVar === 'true';
3870
+ const profiles = ((typeof env === 'object' || typeof env === 'function') && env !== null)
3871
+ ? Reflect.get(env, 'NPM_TARBALL_CACHE')
3872
+ : undefined;
3873
+ this.readProfile = profiles && typeof profiles === 'object'
3874
+ ? new ReadProfile(profiles, Date.now, () => { this.readProfileConflicts++; })
3875
+ : null;
3648
3876
  this.launchJournal = new FencedWork(ctx.storage, {
3649
3877
  generationBase: () => this.processes.pidBase,
3650
3878
  waitUntil: (promise) => this.ctx.waitUntil(promise),
@@ -3669,8 +3897,31 @@ export class FacetManager {
3669
3897
  // Rooted on waitUntil: the hook fires synchronously inside whatever turn
3670
3898
  // ended the process, and the delete must not be a floating promise there.
3671
3899
  this.processes.setOnTerminal((pid) => {
3900
+ this.residentBundleKeys.delete(pid);
3901
+ this.residentProfileOffers.delete(pid);
3672
3902
  this.ctx.waitUntil(this.trackLaunchTask(this._onResidentTerminal(pid)));
3673
3903
  });
3904
+ this.processes.setDefaultSignalAction((pid, code, signal) => this._endBySignal(pid, code, signal));
3905
+ }
3906
+ /**
3907
+ * A signal's default action: the process ends with 128+signo whether its
3908
+ * facet is still being built (the launch stops at its next ownership gate)
3909
+ * or already booted (its resources are released like a kill).
3910
+ */
3911
+ _endBySignal(pid, code, signal) {
3912
+ if (this.processes.get(pid)?.state !== 'running')
3913
+ return;
3914
+ this.portRegistry.unregisterByPid(pid);
3915
+ this.releaseProcessRpcResources(pid);
3916
+ this.revokeProcessVfsWriters(pid);
3917
+ this.processes.exit(pid, code);
3918
+ this.processes.markExit(pid, code, signal);
3919
+ this.processes.closeInput(pid);
3920
+ try {
3921
+ this.hooks.onExternalExit?.(pid, code, signal);
3922
+ }
3923
+ catch { }
3924
+ this._teardownPairedServeFacet(pid);
3674
3925
  }
3675
3926
  /**
3676
3927
  * The process is over. Every end-of-life passes through here: a clean
@@ -3809,12 +4060,42 @@ export class FacetManager {
3809
4060
  }
3810
4061
  return vfs;
3811
4062
  }
4063
+ /** Give the bundle's ESM→CJS pass the host's esbuild, as composeFacetManager does. */
4064
+ setEsbuildService(esbuild) { this.esbuild = esbuild; }
3812
4065
  /**
3813
- * W3.5 Fix B: hand the FacetManager a pre-warmed EsbuildService for
3814
- * the ESM→CJS bundle pre-pass. NimbusSession already lazy-creates one
3815
- * for the user-shell `node` runtime; sharing avoids paying init twice.
4066
+ * The entry script as the facet compiles it: each dynamic `import()` routed
4067
+ * to the process's ESM loader, with the entry's own URL as the parent (Node
4068
+ * names `-e` code `<cwd>/[eval]` and stdin `<cwd>/[stdin]`). The parse runs
4069
+ * in the esbuild facet like every cell's, and is cached by content. The
4070
+ * module-map walk reads the script as written, before this.
3816
4071
  */
3817
- setEsbuildService(esbuild) { this.esbuild = esbuild; }
4072
+ async _entryDynamicImports(code, filename, cwd) {
4073
+ if (!mayHaveDynamicImport(code))
4074
+ return code;
4075
+ if (this.esbuild === null)
4076
+ throw new Error('entry dynamic import requires the transform service');
4077
+ const base = cwd.replace(/\/+$/, '') || '/';
4078
+ const path = filename === undefined || filename === '<eval>'
4079
+ ? `${base}/[eval]`
4080
+ : filename === '[stdin]' ? `${base}/[stdin]` : filename;
4081
+ const parentUrl = 'file:///' + path.replace(/^\/+/, '');
4082
+ const key = __cacheKey(code + '\0' + parentUrl + '\0entry');
4083
+ const cached = __esmTransformCacheGet(key);
4084
+ if (cached !== undefined)
4085
+ return cached;
4086
+ const [outcome] = await this.esbuild.transformMany([
4087
+ { code, options: { rewriteOnly: true, dynamicImportParent: parentUrl } },
4088
+ ]);
4089
+ // A failed entry rewrite must not reach a Worker Loader as native host
4090
+ // import(), or become an immutable cached image. Preserve the reported
4091
+ // reason for permanent source errors as well as transient failures.
4092
+ if (outcome === undefined)
4093
+ throw new Error('entry transform service returned no outcome');
4094
+ if ('error' in outcome)
4095
+ throw new Error(`entry dynamic import transform failed: ${outcome.error}`);
4096
+ __esmTransformCacheSet(key, outcome.code);
4097
+ return outcome.code;
4098
+ }
3818
4099
  /**
3819
4100
  * The pacer every launch is built under: the session's alarm-driven turn
3820
4101
  * pump, the deployment's chunk bound, and the one check a suspended launch
@@ -3872,36 +4153,211 @@ export class FacetManager {
3872
4153
  return modules;
3873
4154
  if (!this.filesystem)
3874
4155
  throw new Error('Process filesystem authority is not initialized');
3875
- const vfs = new ExecutionFs(this.filesystem.bind({ pid: entry.pid, cred: entry.cred }));
4156
+ const vfs = this.filesystem.bind({ pid: entry.pid, cred: entry.cred });
3876
4157
  for (const image of wasmImports) {
3877
- let bytes;
3878
- try {
3879
- bytes = (await vfs.readFile(stripLeadingSlashes(image.vfsPath)));
3880
- }
3881
- catch {
4158
+ const bytes = await filesOf(vfs).readBytes(stripLeadingSlashes(image.vfsPath)).catch(() => null);
4159
+ if (bytes === null)
3882
4160
  continue;
3883
- }
3884
4161
  modules[image.moduleName] = {
3885
4162
  wasm: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength),
3886
4163
  };
3887
4164
  }
3888
4165
  return modules;
3889
4166
  }
4167
+ /**
4168
+ * Which contents a resident process holds from its first instruction,
4169
+ * beyond its module map: data-plan.ts over this process's view of the
4170
+ * namespace. A path the plan leaves out is still named and stat-able; a
4171
+ * synchronous read of it is the one honest miss.
4172
+ */
4173
+ async _planResidentData(entry, vfsState, cwd, home, pacer) {
4174
+ if (!this.vfs || !this.filesystem)
4175
+ return { paths: [], storageBytes: 0 };
4176
+ const vfs = this.filesystem.bind({ pid: entry.pid, cred: entry.cred });
4177
+ const started = Date.now();
4178
+ const trace = (what) => {
4179
+ if (!this.debugEnabled)
4180
+ return;
4181
+ this.processes.appendOutput(entry.pid, 'stderr', `[nimbus-debug] data plan: ${what} at ${Date.now() - started} ms\n`);
4182
+ };
4183
+ trace('static references');
4184
+ const refs = await this._closureStaticRefs(vfs, vfsState.bundlePaths ?? [], pacer);
4185
+ trace(`${refs.length} modules name paths; learned reads`);
4186
+ const learned = await this._learnedReads(vfsState, entry.cred);
4187
+ trace(`${learned.length} learned; listing`);
4188
+ let pages = 0;
4189
+ let names = 0;
4190
+ const plan = await planFacetData({
4191
+ list: async (after) => {
4192
+ const page = await vfs.list(after, FS_LIST_PAGE_LIMIT);
4193
+ names += page.entries.length;
4194
+ trace(`page ${++pages}: ${page.entries.length} entries`);
4195
+ return { entries: page.entries, next: page.next };
4196
+ },
4197
+ readText: async (path) => {
4198
+ try {
4199
+ return await filesOf(vfs).readFileString(path);
4200
+ }
4201
+ catch {
4202
+ return null;
4203
+ }
4204
+ },
4205
+ // A missing or unreadable component is not a link: the lookup ends there.
4206
+ readlink: async (path) => {
4207
+ try {
4208
+ return await vfs.readlink(path);
4209
+ }
4210
+ catch {
4211
+ return null;
4212
+ }
4213
+ },
4214
+ stat: async (path) => {
4215
+ const st = await filesOf(vfs).stat(path).catch(() => null);
4216
+ return st && { kind: st.type, size: st.size };
4217
+ },
4218
+ }, {
4219
+ cwd,
4220
+ home: home || '/home/user',
4221
+ closure: vfsState.bundlePaths ?? [],
4222
+ refs,
4223
+ learned,
4224
+ spend: (units) => pacer.spend(units),
4225
+ });
4226
+ trace(`planned ${plan.paths.length}`);
4227
+ // What the facet's store will hold (N18), in the cost its store charges
4228
+ // itself (facet-resident-store's __residentCellCost): the planned bytes on
4229
+ // overflow pages (1% over), a head and chunk rows per file, a namespace
4230
+ // row per name, and slack for the last write's page.
4231
+ const rows = plan.paths.length * 2 + Math.ceil(plan.bytes / RESIDENT_CHUNK_BYTES) + names;
4232
+ // And the module map, which the store adopts at boot: the process's code,
4233
+ // costed when the map was built (its cells are released once serialized).
4234
+ const moduleBytes = vfsState.moduleStorageBytes ?? moduleMapStorageBytes(vfsState.bundle);
4235
+ const storageBytes = Math.ceil(plan.bytes * 1.01) + rows * LEDGER_ROW_BYTES + moduleBytes + 65_536;
4236
+ return { paths: plan.paths, storageBytes };
4237
+ }
4238
+ /**
4239
+ * Paths earlier launches of the same build missed in this session. Other
4240
+ * sessions' misses (the shared read profile) join the module map instead,
4241
+ * in _buildProcessBundle, where a learned module brings its imports.
4242
+ */
4243
+ async _learnedReads(vfsState, _cred) {
4244
+ return vfsState.bundleKey ? [...(this.residencyProfiles.get(vfsState.bundleKey) ?? [])] : [];
4245
+ }
4246
+ /**
4247
+ * The installed packages a closure can load: every package the lockfiles
4248
+ * pin whose node_modules directory is on the resolution path of the cwd or
4249
+ * of a closure file. A first miss is, by definition, in a package the
4250
+ * closure did not already load, so its package's root is found this way.
4251
+ */
4252
+ _profileRoots(closure, cwd) {
4253
+ const sql = this.ctx.storage.sql;
4254
+ if (!sql)
4255
+ return [];
4256
+ const integrities = new NpmCache(sql).installedIntegrities();
4257
+ if (integrities.size === 0)
4258
+ return [];
4259
+ const dirs = new Set();
4260
+ const ancestors = (dir) => {
4261
+ for (let d = dir;;) {
4262
+ const nm = d === '' ? 'node_modules' : `${d}/node_modules`;
4263
+ if (dirs.has(nm))
4264
+ return;
4265
+ dirs.add(nm);
4266
+ if (d === '')
4267
+ return;
4268
+ d = d.slice(0, Math.max(0, d.lastIndexOf('/')));
4269
+ }
4270
+ };
4271
+ ancestors(cwd.replace(/^\/+|\/+$/g, ''));
4272
+ for (const path of closure) {
4273
+ const k = path.replace(/^\/+/, '');
4274
+ ancestors(k.slice(0, Math.max(0, k.lastIndexOf('/'))));
4275
+ }
4276
+ return [...integrities.keys()].filter((root) => {
4277
+ const i = root.lastIndexOf('/node_modules/');
4278
+ const parent = i === -1 ? (root.startsWith('node_modules/') ? 'node_modules' : null) : root.slice(0, i + '/node_modules'.length);
4279
+ return parent !== null && dirs.has(parent);
4280
+ });
4281
+ }
4282
+ /**
4283
+ * A package directory's identity for the shared read profile: the tarball
4284
+ * integrity the session's lockfiles pin, or, for a package no lockfile
4285
+ * pins (a link, a git or file dependency), the content key of its
4286
+ * package.json as this credential reads it.
4287
+ */
4288
+ _packageIdentity(cred) {
4289
+ const sql = this.ctx.storage.sql;
4290
+ const integrities = sql ? new NpmCache(sql).installedIntegrities() : new Map();
4291
+ const view = this.vfs?.as(cred) ?? null;
4292
+ return (root) => {
4293
+ const integrity = integrities.get(root);
4294
+ if (integrity !== undefined)
4295
+ return integrity;
4296
+ if (view === null)
4297
+ return null;
4298
+ try {
4299
+ return `pkgjson:${view.contentKey(root + '/package.json')}`;
4300
+ }
4301
+ catch {
4302
+ return null;
4303
+ }
4304
+ };
4305
+ }
4306
+ /**
4307
+ * What the closure's JavaScript names by a foldable path (static-fs-refs.ts),
4308
+ * read from the VFS as written rather than from the module map, whose ESM
4309
+ * cells were rewritten and lost their import.meta. Each module is parsed once
4310
+ * per revision of it, in this session.
4311
+ */
4312
+ async _closureStaticRefs(vfs, paths, pacer) {
4313
+ const out = [];
4314
+ for (const path of paths) {
4315
+ if (!/\.(?:c|m)?js$/.test(path))
4316
+ continue;
4317
+ let rev;
4318
+ try {
4319
+ rev = await vfs.revision(path);
4320
+ }
4321
+ catch {
4322
+ continue;
4323
+ }
4324
+ const memo = this.staticRefsMemo.get(path);
4325
+ let refs;
4326
+ if (memo && memo.rev === rev) {
4327
+ refs = memo.refs;
4328
+ }
4329
+ else {
4330
+ let source;
4331
+ try {
4332
+ source = await filesOf(vfs).readFileString(path);
4333
+ }
4334
+ catch {
4335
+ continue;
4336
+ }
4337
+ await pacer.spend(source.length);
4338
+ refs = findStaticFsReferences(source, '/' + path);
4339
+ this.staticRefsMemo.delete(path);
4340
+ this.staticRefsMemo.set(path, { rev, refs });
4341
+ for (const oldest of this.staticRefsMemo.keys()) {
4342
+ if (this.staticRefsMemo.size <= FacetManager.STATIC_REFS_MEMO_MAX)
4343
+ break;
4344
+ this.staticRefsMemo.delete(oldest);
4345
+ }
4346
+ }
4347
+ if (refs.exact.length + refs.listed.length + refs.patterns.length + refs.cwdRelative.length + refs.resolves.length > 0) {
4348
+ out.push(refs);
4349
+ }
4350
+ }
4351
+ return out;
4352
+ }
3890
4353
  async _buildProcessBundle(entry, spec, pacer) {
3891
4354
  if (!this.vfs) {
3892
- return { bundle: {}, manifest: {}, metadata: {}, reachableCount: 0, truncated: false };
3893
- }
3894
- // W3.5 Fix B: thread an EsbuildService into buildPrefetchBundle so ESM
3895
- // source files (e.g. tldts/dist/es6/index.js, @remix-run/react,
3896
- // @tailwindcss/vite, react-remove-scroll, astro) get transformed to CJS
3897
- // before they hit the facet's `new Function` pre-compile loop. Lazy-create
3898
- // one if NimbusSession didn't share its own.
3899
- if (!this.esbuild)
3900
- this.esbuild = new EsbuildService(this.vfs.as(CRED_KERNEL));
4355
+ return { bundle: {}, reachableCount: 0, truncated: false };
4356
+ }
3901
4357
  this.imageStore.ensureDir();
3902
4358
  if (!this.filesystem)
3903
4359
  throw new Error('Process filesystem authority is not initialized');
3904
- const vfs = new ExecutionFs(this.filesystem.bind({ pid: entry.pid, cred: entry.cred }));
4360
+ const vfs = this.filesystem.bind({ pid: entry.pid, cred: entry.cred });
3905
4361
  const { cred } = entry;
3906
4362
  const profile = spec.bundleProfile ?? DEFAULT_FACET_BUNDLE_PROFILE;
3907
4363
  const credKey = `${cred.uid}:${cred.gid}:${cred.groups.join(',')}`;
@@ -3930,16 +4386,41 @@ export class FacetManager {
3930
4386
  this.prefetchBundleCache.set(key, cached);
3931
4387
  return { ...cached.vfsState, cacheHit: true, cacheRetained: true };
3932
4388
  }
3933
- const vfsState = await buildPrefetchBundle(vfs, spec.scriptPath, spec.cwd, spec.entryCode, this.esbuild, profile, this.residencyProfiles.get(key), pacer, this.hooks.transformLargeEsm);
4389
+ const readProfile = this.readProfile;
4390
+ const offered = [];
4391
+ const learnedFor = readProfile === null ? undefined : async (closure) => {
4392
+ const roots = this._profileRoots(closure, spec.cwd);
4393
+ if (roots.length === 0)
4394
+ return [];
4395
+ offered.push(...await readProfile.lookup(roots, this._packageIdentity(cred), READ_PROFILE_LAUNCH_BYTES));
4396
+ return offered.map((entry) => entry.path);
4397
+ };
4398
+ const vfsState = await buildPrefetchBundle(vfs, spec.scriptPath, spec.cwd, spec.entryCode, this.esbuild ?? undefined, profile, this.residencyProfiles.get(key), pacer, undefined, learnedFor);
4399
+ if (offered.length > 0) {
4400
+ const staged = [];
4401
+ const unresolved = [];
4402
+ for (const entry of offered) {
4403
+ if (vfsState.bundle[entry.path] !== undefined) {
4404
+ staged.push(entry);
4405
+ continue;
4406
+ }
4407
+ // Not staged: over the budget, or nothing a regular file answers (then it is pruned).
4408
+ const stat = await Promise.resolve(vfs.stat('/' + entry.path, { followSymlinks: false })).catch(() => null);
4409
+ if (stat === null || stat.type !== 'file')
4410
+ unresolved.push(entry.path);
4411
+ }
4412
+ vfsState.profileOffer = { staged, unresolved };
4413
+ }
3934
4414
  vfsState.bundleKey = key;
3935
- vfsState.bundleSource = await buildFacetVfsBundleSource(vfsState.bundle, vfsState.bundleSideModulesRequired, pacer);
3936
- vfsState.serializedManifest = JSON.stringify(vfsState.manifest);
3937
- vfsState.serializedMetadata = JSON.stringify(vfsState.metadata);
3938
- // Two more serializations of the same weight as the passes before them.
3939
- await pacer.spend(vfsState.serializedManifest.length + vfsState.serializedMetadata.length);
3940
- // The only consumer of the raw cells past this point is a single boolean,
3941
- // so answer it now rather than hold ~17 MB (pi) to answer it later.
4415
+ vfsState.bundlePaths = Object.keys(vfsState.bundle);
4416
+ // The only consumer of the raw cells past serialization is a single
4417
+ // boolean, so it is answered first; the serialization then consumes them.
3942
4418
  vfsState.usesNodeSqlite = bundleUsesNodeSqlite(spec.entryCode, vfsState.bundle);
4419
+ // And what the module map costs the facet's store, which adopts it at boot
4420
+ // (N18): taken now, while the cells exist, for every launch this state
4421
+ // serves (a cache hit included).
4422
+ vfsState.moduleStorageBytes = moduleMapStorageBytes(vfsState.bundle);
4423
+ vfsState.bundleSource = await buildFacetVfsBundleSource(vfsState.bundle, vfsState.bundleSideModulesRequired, pacer, { consume: true });
3943
4424
  vfsState.cacheHit = false;
3944
4425
  // Serialization is total: bundleSource/serializedManifest/serializedMetadata
3945
4426
  // carry every byte the raw cells and objects do, and both generators read
@@ -4075,7 +4556,43 @@ export class FacetManager {
4075
4556
  hasResidentProcess(pid) {
4076
4557
  return this.processRpcResources.has(pid);
4077
4558
  }
4078
- noteProcessReportedExit(pid, exitCode) {
4559
+ noteProcessReportedExit(pid, exitCode, residencyMisses, evidence) {
4560
+ // Filed before the exit marks the table: the terminal hook forgets the key.
4561
+ this._recordResidencyMisses(this.residentBundleKeys.get(pid), residencyMisses);
4562
+ const exiting = this.processes.get(pid);
4563
+ const offer = this.residentProfileOffers.get(pid);
4564
+ this.residentProfileOffers.delete(pid);
4565
+ if (this.readProfile && exiting) {
4566
+ // The shared profile learns only what this supervisor served the process
4567
+ // after a miss, as the process's own credential sees it (read-profile.ts).
4568
+ const profile = this.readProfile;
4569
+ const identity = this._packageIdentity(exiting.cred);
4570
+ const view = this.vfs?.as(exiting.cred) ?? null;
4571
+ const served = evidence?.served ?? new Set();
4572
+ // Observers and vouchers are principals the router verified, read off
4573
+ // this session's Durable Object name; an anonymous session only reads.
4574
+ const principal = profilePrincipal(sessionIdentity({ ctx: this.ctx })?.tenantSegment);
4575
+ this.ctx.waitUntil((async () => {
4576
+ const tag = principal === null ? null : await principalTag(principal);
4577
+ if (tag !== null && view !== null && residencyMisses && residencyMisses.length > 0) {
4578
+ const found = await verifiedEvidence(residencyMisses, served, async (path) => {
4579
+ try {
4580
+ const st = view.lstat(path.replace(/^\/+/, ''));
4581
+ return { type: st.type, size: st.size };
4582
+ }
4583
+ catch {
4584
+ return null;
4585
+ }
4586
+ });
4587
+ if (found.length > 0)
4588
+ await profile.observe(found, tag, identity);
4589
+ }
4590
+ if (tag !== null && offer !== undefined) {
4591
+ const unread = evidence?.profileUnread ? new Set(evidence.profileUnread) : null;
4592
+ await profile.settle(offer.staged, unread, served, new Set(offer.unresolved), tag);
4593
+ }
4594
+ })().catch(() => undefined));
4595
+ }
4079
4596
  this.portRegistry.unregisterByPid(pid);
4080
4597
  this.processes.exit(pid, exitCode);
4081
4598
  const tracked = this.processRpcResources.get(pid);
@@ -4181,7 +4698,7 @@ export class FacetManager {
4181
4698
  }
4182
4699
  const bundleMs = diagOn ? Date.now() - __bundleStart : 0;
4183
4700
  const diagSink = diagOn
4184
- ? { loadMs: 0, runMs: 0, moduleMapBytes: 0, bundleBytes: 0, manifestBytes: 0, metadataBytes: 0 }
4701
+ ? { loadMs: 0, runMs: 0, moduleMapBytes: 0, bundleBytes: 0 }
4185
4702
  : undefined;
4186
4703
  const abortController = new AbortController();
4187
4704
  // Ctrl-C / kill on a user program has to end the in-flight run, not just
@@ -4192,7 +4709,11 @@ export class FacetManager {
4192
4709
  const onShellAbort = () => abortController.abort();
4193
4710
  opts.signal?.addEventListener('abort', onShellAbort, { once: true });
4194
4711
  try {
4195
- const result = await this._execViaLoader(code, opts, entry, vfsState, abortController.signal, diagSink);
4712
+ // A one-shot holds its module map and what it was seen to read (both in
4713
+ // the bundle), and plans nothing beyond them: a data plan is chosen from
4714
+ // a listing of the namespace, and a one-shot takes no listing of its own
4715
+ // (§2.8: the principal's image is where that comes from).
4716
+ const result = await this._execViaLoader(code, opts, entry, vfsState, [], abortController.signal, diagSink);
4196
4717
  this._flushVfsWrites(result, entry.pid);
4197
4718
  this._recordResidencyMisses(vfsState.bundleKey, result.residencyMisses);
4198
4719
  this.processes.exit(entry.pid, result.exitCode);
@@ -4208,8 +4729,7 @@ export class FacetManager {
4208
4729
  drainPasses: result.diag?.drainPasses ?? 0,
4209
4730
  moduleMapBytes: diagSink.moduleMapBytes,
4210
4731
  bundleBytes: diagSink.bundleBytes,
4211
- manifestBytes: diagSink.manifestBytes,
4212
- metadataBytes: diagSink.metadataBytes,
4732
+ namespaceRefusals: result.diag?.namespaceRefusals ?? 0,
4213
4733
  rpcWrites: result.diag?.rpcWrites ?? 0,
4214
4734
  fsRpcReads: result.diag?.fsRpcReads ?? 0,
4215
4735
  cacheHit: vfsState.cacheHit ?? false,
@@ -4280,13 +4800,13 @@ export class FacetManager {
4280
4800
  }
4281
4801
  }
4282
4802
  // ── One-shot dynamic Worker entrypoint ────────────────────────────────
4283
- async _execViaLoader(code, opts, entry, vfsState, signal, diagSink) {
4803
+ async _execViaLoader(code, opts, entry, vfsState, dataPlan, signal, diagSink) {
4284
4804
  // Answered by _buildProcessBundle while the raw cells were still in
4285
4805
  // hand; re-deriving it here is what forced them to be retained.
4286
4806
  const usesSqlite = vfsState.usesNodeSqlite ?? bundleUsesNodeSqlite(code, vfsState.bundle);
4287
- const [sqliteModules, shims] = await Promise.all([
4807
+ const [sqliteModules, sources] = await Promise.all([
4288
4808
  this.sqliteModuleEntry(usesSqlite),
4289
- fetchNodeShimsCode(this.env),
4809
+ fetchNodeFacetSources(this.env),
4290
4810
  ]);
4291
4811
  const writerId = crypto.randomUUID();
4292
4812
  let writerActivated = false;
@@ -4302,6 +4822,7 @@ export class FacetManager {
4302
4822
  captureOutput: !!opts.captureOutput,
4303
4823
  cred: { ...entry.cred, groups: [...entry.cred.groups] },
4304
4824
  vfsCursor: vfsState.cursor,
4825
+ dataPlan,
4305
4826
  ...(diagSink ? { diag: true } : {}),
4306
4827
  });
4307
4828
  try {
@@ -4322,7 +4843,8 @@ export class FacetManager {
4322
4843
  // the map, and compiled by the loader like the sqlite sidecar.
4323
4844
  const wasmImports = facetWasmImports([], vfsState.wasmImages ?? []);
4324
4845
  const wasmModules = (await this._wasmModulesByValue(entry, wasmImports));
4325
- const generatedWorker = await generateEntrypointCode(code, vfsState, usesSqlite, shims, wasmImports);
4846
+ const entryCode = await this._entryDynamicImports(code, opts.filename, opts.cwd || '/home/user');
4847
+ const generatedWorker = await generateEntrypointCode(entryCode, vfsState, usesSqlite, sources, wasmImports);
4326
4848
  if (diagSink) {
4327
4849
  diagSink.moduleMapBytes = _encodedSourceBytes(generatedWorker.code);
4328
4850
  for (const source of Object.values(generatedWorker.modules)) {
@@ -4337,8 +4859,6 @@ export class FacetManager {
4337
4859
  for (const source of Object.values(vfsState.bundleSource?.modules ?? {})) {
4338
4860
  diagSink.bundleBytes += _encodedSourceBytes(source);
4339
4861
  }
4340
- diagSink.manifestBytes = _encodedSourceBytes(vfsState.serializedManifest ?? '');
4341
- diagSink.metadataBytes = _encodedSourceBytes(vfsState.serializedMetadata ?? '');
4342
4862
  }
4343
4863
  if (!vfsState.cacheRetained)
4344
4864
  releaseGeneratedSources(vfsState);
@@ -4405,7 +4925,7 @@ export class FacetManager {
4405
4925
  // the Worker-Loader cache-miss path (with SUPERVISOR bound to THIS call's
4406
4926
  // context, which stays open for the whole run), never in this DO.
4407
4927
  const writerId = crypto.randomUUID();
4408
- const supervisor = { doId: this.ctx.id.toString(), pid: staged.pid, writerId };
4928
+ const supervisor = { ...supervisorBindingProps(this.ctx, staged.pid), writerId };
4409
4929
  const ctxExports = getNimbusCtxExports();
4410
4930
  let entrypoint;
4411
4931
  let writerActivated = false;
@@ -4486,10 +5006,10 @@ export class FacetManager {
4486
5006
  // other writes flush live through the SUPERVISOR RPC bridge.
4487
5007
  if (this.vfs)
4488
5008
  this.imageStore.ensureDir();
4489
- const processVfs = this.filesystem ? new ExecutionFs(this.filesystem.bind({ pid: entry.pid, cred: entry.cred })) : null;
5009
+ const processVfs = this.filesystem ? this.filesystem.bind({ pid: entry.pid, cred: entry.cred }) : null;
4490
5010
  const vfsState = processVfs
4491
- ? await buildPrefetchBundle(processVfs, undefined, opts.cwd, '', this.esbuild || undefined, DEFAULT_FACET_BUNDLE_PROFILE, undefined, undefined, this.hooks.transformLargeEsm)
4492
- : { bundle: {}, manifest: {}, metadata: {}, reachableCount: 0, truncated: false };
5011
+ ? await buildPrefetchBundle(processVfs, undefined, opts.cwd, '', this.esbuild || undefined, DEFAULT_FACET_BUNDLE_PROFILE)
5012
+ : { bundle: {}, reachableCount: 0, truncated: false };
4493
5013
  const vfsBundle = _serializeBundleForFacet(vfsState.bundle);
4494
5014
  assertStagedBundleFitsRpcPayload(vfsBundle, vfsState.bundle);
4495
5015
  const stageSpec = {
@@ -4500,8 +5020,6 @@ export class FacetManager {
4500
5020
  cwd: opts.cwd,
4501
5021
  stdin: opts.stdin ?? '',
4502
5022
  vfsBundle,
4503
- vfsManifest: JSON.stringify(vfsState.manifest),
4504
- vfsMetadata: JSON.stringify(vfsState.metadata),
4505
5023
  vfsCursor: serializeFacetVfsCursor(vfsState.cursor),
4506
5024
  };
4507
5025
  return { pid, command, stageSpec };
@@ -4669,7 +5187,8 @@ export class FacetManager {
4669
5187
  // The handle's route target resolves the RUNNING facet wherever it is
4670
5188
  // hosted; binding it for the pid before the port is announced is what
4671
5189
  // lets the shim's listen()→SUPERVISOR.registerPort back-fill.
4672
- this.portRegistry.bindFacetStub(pid, handle.routeTarget);
5190
+ // The opencode runner dispatches through node-shims' __nimbusServeHttp, which strips the ACQUIRE.
5191
+ this.portRegistry.bindFacetStub(pid, handle.routeTarget, { deliversAcquire: true });
4673
5192
  this.trackProcessRpcResources(pid, [handle], { releaseOnReportExit: false });
4674
5193
  resourcesTracked = true;
4675
5194
  this.ctx.waitUntil(handle.done
@@ -4807,10 +5326,6 @@ export class FacetManager {
4807
5326
  await Promise.allSettled([...this.launchTasks]);
4808
5327
  await Promise.all(this.rowAmendments.values());
4809
5328
  }
4810
- /** Whether any launch is suspended waiting for a turn. */
4811
- get hasPendingLaunchTurns() {
4812
- return this.launchPump.hasPending;
4813
- }
4814
5329
  /** Allocate a free loopback port for a resident server facet (from 4096 up). */
4815
5330
  _allocateLoopbackPort() {
4816
5331
  for (let port = 4096; port < 4096 + 4096; port++) {
@@ -5197,7 +5712,14 @@ export class FacetManager {
5197
5712
  async _residentLaunchBody(entry, code, command, cwd, opts, pacer, durableFacetName, launchEnv) {
5198
5713
  const diagOn = isExecDiagEnabled();
5199
5714
  const __bundleStart = diagOn ? Date.now() : 0;
5715
+ if (this.debugEnabled)
5716
+ this.processes.appendOutput(entry.pid, 'stderr', '[nimbus-debug] launch: building the module map\n');
5200
5717
  const vfsState = await this._buildProcessBundle(entry, { scriptPath: opts.filename, cwd, entryCode: code, bundleProfile: opts.bundleProfile }, pacer);
5718
+ const planStart = Date.now();
5719
+ const { paths: dataPlan, storageBytes } = await this._planResidentData(entry, vfsState, cwd, opts.env?.HOME, pacer);
5720
+ if (this.debugEnabled) {
5721
+ this.processes.appendOutput(entry.pid, 'stderr', `[nimbus-debug] data plan: ${dataPlan.length} paths in ${Date.now() - planStart} ms (${pacer.chunks} turns so far)\n`);
5722
+ }
5201
5723
  const bundleMs = diagOn ? Date.now() - __bundleStart : 0;
5202
5724
  // The launch-time overlay (`$PORT`, `$NIMBUS_APP` under a reservation)
5203
5725
  // rides on top of the recipe's env and is never journalled: it is
@@ -5218,13 +5740,13 @@ export class FacetManager {
5218
5740
  : spawnEnv;
5219
5741
  // Answered by _buildProcessBundle while the raw cells were still in hand.
5220
5742
  const usesSqlite = vfsState.usesNodeSqlite ?? bundleUsesNodeSqlite(code, vfsState.bundle);
5221
- const [sqliteModules, shims] = await Promise.all([
5743
+ const [sqliteModules, sources] = await Promise.all([
5222
5744
  this.sqliteModuleEntry(usesSqlite),
5223
- fetchNodeShimsCode(this.env),
5745
+ fetchNodeFacetSources(this.env),
5224
5746
  ]);
5225
5747
  // Each image is read by path when the facet loads, never by value here.
5226
5748
  const wasmImports = facetWasmImports([], vfsState.wasmImages ?? []);
5227
- let generatedWorker = await generateLongRunningNodeCode(code, vfsState, { ...opts, env: processEnv, cred: entry.cred, wasmImports }, usesSqlite, shims, pacer);
5749
+ let generatedWorker = await generateLongRunningNodeCode(await this._entryDynamicImports(code, opts.filename, cwd), vfsState, { ...opts, env: processEnv, cred: entry.cred, wasmImports }, usesSqlite, sources, pacer);
5228
5750
  // Sized here, while the map is still in hand. Reading these after the load
5229
5751
  // would itself be what keeps the map alive, and the whole point of the
5230
5752
  // scoping below is that nothing does.
@@ -5236,10 +5758,13 @@ export class FacetManager {
5236
5758
  }
5237
5759
  moduleMapBytes = _encodedSourceBytes(generatedWorker.code) + bundleBytes;
5238
5760
  }
5239
- const manifestBytes = diagOn ? _encodedSourceBytes(vfsState.serializedManifest ?? '') : 0;
5240
- const metadataBytes = diagOn ? _encodedSourceBytes(vfsState.serializedMetadata ?? '') : 0;
5241
5761
  const cacheHit = vfsState.cacheHit ?? false;
5242
5762
  const vfsCursor = vfsState.cursor;
5763
+ if (vfsState.bundleKey)
5764
+ this.residentBundleKeys.set(entry.pid, vfsState.bundleKey);
5765
+ const profileOffer = vfsState.profileOffer;
5766
+ if (profileOffer !== undefined)
5767
+ this.residentProfileOffers.set(entry.pid, profileOffer);
5243
5768
  // The map is generated; the state's only remaining job is its cursor.
5244
5769
  // Released here, before the boot — releasing afterwards keeps the copy
5245
5770
  // alive for exactly the window that was resetting the isolate. A state
@@ -5261,6 +5786,8 @@ export class FacetManager {
5261
5786
  };
5262
5787
  generatedWorker = undefined;
5263
5788
  const vfsTextModules = await this.imageStore.materialize(entry.pid, drainSources(sources), pacer);
5789
+ if (this.debugEnabled)
5790
+ this.processes.appendOutput(entry.pid, 'stderr', '[nimbus-debug] launch: images stored, starting the facet\n');
5264
5791
  // Last gate before the facet exists. A launch now spans many turns, so
5265
5792
  // a kill can land anywhere inside it; booting a process the table has
5266
5793
  // already exited would leave a facet nothing owns, running against a
@@ -5270,7 +5797,12 @@ export class FacetManager {
5270
5797
  // The attached-TTY runner holds startProcess open for the process's
5271
5798
  // life; the server/watch runner returns once it is up.
5272
5799
  startContract: opts.attachedTty ? 'lifetime' : 'boot',
5273
- startArgs: { vfsCursor },
5800
+ startArgs: {
5801
+ vfsCursor, dataPlan,
5802
+ ...(profileOffer !== undefined && profileOffer.staged.length > 0 ? { profileStaged: profileOffer.staged.map((e) => e.path) } : {}),
5803
+ ...(this.debugEnabled ? { diag: true } : {}),
5804
+ },
5805
+ storageBytes,
5274
5806
  // A resident whose declared port is reserved binds the owner's
5275
5807
  // durable slot — the same store a durable worker spawn takes — so the
5276
5808
  // reservation's durability reaches this process's storage too.
@@ -5290,6 +5822,11 @@ export class FacetManager {
5290
5822
  },
5291
5823
  },
5292
5824
  });
5825
+ // Ended while its facet was being created (a signal's default action).
5826
+ if (this.processes.get(entry.pid)?.state !== 'running') {
5827
+ handle.kill();
5828
+ return;
5829
+ }
5293
5830
  if (diagOn) {
5294
5831
  recordExecTelemetry({
5295
5832
  command,
@@ -5306,8 +5843,7 @@ export class FacetManager {
5306
5843
  // when it stayed inline it rides inside worker.js and shows up only
5307
5844
  // in the total.
5308
5845
  bundleBytes,
5309
- manifestBytes,
5310
- metadataBytes,
5846
+ namespaceRefusals: 0,
5311
5847
  rpcWrites: 0,
5312
5848
  fsRpcReads: 0,
5313
5849
  cacheHit,
@@ -5318,7 +5854,8 @@ export class FacetManager {
5318
5854
  }
5319
5855
  this.trackProcessRpcResources(entry.pid, [handle], { releaseOnReportExit: !opts.attachedTty });
5320
5856
  resourcesTracked = true;
5321
- this.portRegistry.bindFacetStub(entry.pid, handle.routeTarget);
5857
+ // A node-shims resident: its __nimbusServeHttp takes the delivered ACQUIRE off each request.
5858
+ this.portRegistry.bindFacetStub(entry.pid, handle.routeTarget, { deliversAcquire: true });
5322
5859
  if (opts.attachedTty) {
5323
5860
  this.ctx.waitUntil(handle.done
5324
5861
  .catch((e) => {
@@ -5437,6 +5974,7 @@ export class FacetManager {
5437
5974
  let record;
5438
5975
  let durableFacetName;
5439
5976
  let launchEnv;
5977
+ const foreground = opts.foreground ? this._holdForeground(entry.pid, opts.foreground) : null;
5440
5978
  try {
5441
5979
  if (opts.durable) {
5442
5980
  // A durable spawn on a declared port starts only when a reservation the
@@ -5554,7 +6092,7 @@ export class FacetManager {
5554
6092
  this.trackProcessRpcResources(entry.pid, [handle]);
5555
6093
  resourcesTracked = true;
5556
6094
  this.portRegistry.bindFacetStub(entry.pid, handle.routeTarget);
5557
- const boot = await handle.booted();
6095
+ const boot = foreground ? await Promise.race([handle.booted(), foreground.interrupted]) : await handle.booted();
5558
6096
  if (record && this.launchJournal.has(entry.pid)) {
5559
6097
  // Booted and running: the launch proved itself, so the resident
5560
6098
  // starts its running life with a fresh re-drive budget.
@@ -5597,9 +6135,38 @@ export class FacetManager {
5597
6135
  this.releaseProcessRpcResources(entry.pid);
5598
6136
  else
5599
6137
  handle?.kill();
5600
- this._failLaunch(entry.pid, 'long-running worker boot failed: ' + errorMessage(e));
6138
+ // An interrupt already killed the process; it did not fail.
6139
+ if (!opts.foreground?.signal.aborted)
6140
+ this._failLaunch(entry.pid, 'long-running worker boot failed: ' + errorMessage(e));
5601
6141
  throw e;
5602
6142
  }
6143
+ finally {
6144
+ foreground?.release();
6145
+ }
6146
+ }
6147
+ _holdForeground(pid, launch) {
6148
+ this.processes.setForeground(pid, true);
6149
+ const unsubscribe = this.processes.subscribeLogs(pid, (chunk) => launch.write(chunk.stream, chunk.data));
6150
+ let onAbort = () => { };
6151
+ const interrupted = new Promise((_, reject) => {
6152
+ onAbort = () => {
6153
+ this.kill(pid);
6154
+ reject(new DOMException('the launching command was interrupted', 'AbortError'));
6155
+ };
6156
+ });
6157
+ interrupted.catch(() => { });
6158
+ if (launch.signal.aborted)
6159
+ onAbort();
6160
+ else
6161
+ launch.signal.addEventListener('abort', onAbort, { once: true });
6162
+ return {
6163
+ interrupted,
6164
+ release: () => {
6165
+ launch.signal.removeEventListener('abort', onAbort);
6166
+ unsubscribe();
6167
+ this.processes.setForeground(pid, false);
6168
+ },
6169
+ };
5603
6170
  }
5604
6171
  /**
5605
6172
  * A resident process announcing it bound `port`.
@@ -5840,18 +6407,23 @@ export class FacetManager {
5840
6407
  catch { }
5841
6408
  }
5842
6409
  }
5843
- /** Kill a running process by PID. */
5844
- kill(pid) {
6410
+ /**
6411
+ * Kill a running process by PID. Given the signal that ends it (a name
6412
+ * without `SIG`), it exits with that signal's status, 128+signo, and its
6413
+ * exit names `SIG<name>`; without one it is SIGKILL's 137, `killed`.
6414
+ */
6415
+ kill(pid, signal) {
5845
6416
  const entry = this.processes.get(pid);
5846
6417
  if (!entry || entry.state !== 'running')
5847
6418
  return false;
6419
+ const code = signal === undefined ? 137 : exitCodeForSignal(signal);
5848
6420
  this.portRegistry.unregisterByPid(pid);
5849
6421
  this.releaseProcessRpcResources(pid);
5850
6422
  this.revokeProcessVfsWriters(pid);
5851
- const result = this.processes.kill(pid);
6423
+ const result = this.processes.kill(pid, code);
5852
6424
  if (result) {
5853
6425
  try {
5854
- this.hooks.onExternalExit?.(pid, 137, 'killed');
6426
+ this.hooks.onExternalExit?.(pid, code, signal === undefined ? 'killed' : `SIG${signal}`);
5855
6427
  }
5856
6428
  catch { }
5857
6429
  }
@@ -6011,5 +6583,5 @@ export class FacetManager {
6011
6583
  }
6012
6584
  return this.portRegistry.has(port);
6013
6585
  }
6014
- get stats() { return this.processes.stats; }
6586
+ get stats() { return { ...this.processes.stats, readProfileConflicts: this.readProfileConflicts }; }
6015
6587
  }