@nimbus-sh/worker 0.9.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 (286) 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/esbuild-wasm-bundle.generated.d.ts +24 -24
  19. package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -1
  20. package/dist/esbuild-wasm-bundle.generated.js +24 -24
  21. package/dist/facets/cirrus-real.d.ts.map +1 -1
  22. package/dist/facets/cirrus-real.js +11 -6
  23. package/dist/facets/compose.d.ts +11 -7
  24. package/dist/facets/compose.d.ts.map +1 -1
  25. package/dist/facets/compose.js +8 -31
  26. package/dist/facets/data-plan.d.ts +79 -0
  27. package/dist/facets/data-plan.d.ts.map +1 -0
  28. package/dist/facets/data-plan.js +477 -0
  29. package/dist/facets/esbuild-bundle-pool.d.ts +1 -1
  30. package/dist/facets/esbuild-bundle-pool.js +3 -3
  31. package/dist/facets/esbuild-transform.d.ts +38 -8
  32. package/dist/facets/esbuild-transform.d.ts.map +1 -1
  33. package/dist/facets/esbuild-transform.js +219 -19
  34. package/dist/facets/exec-telemetry.d.ts +7 -3
  35. package/dist/facets/exec-telemetry.d.ts.map +1 -1
  36. package/dist/facets/manager.d.ts +198 -69
  37. package/dist/facets/manager.d.ts.map +1 -1
  38. package/dist/facets/manager.js +1276 -652
  39. package/dist/facets/opencode-staging.d.ts +0 -2
  40. package/dist/facets/opencode-staging.d.ts.map +1 -1
  41. package/dist/facets/opencode-staging.js +5 -11
  42. package/dist/facets/process.d.ts +4 -0
  43. package/dist/facets/process.d.ts.map +1 -1
  44. package/dist/facets/process.js +3 -0
  45. package/dist/facets/read-profile.d.ts +149 -0
  46. package/dist/facets/read-profile.d.ts.map +1 -0
  47. package/dist/facets/read-profile.js +483 -0
  48. package/dist/facets/real-vite-hmr.d.ts +2 -0
  49. package/dist/facets/real-vite-hmr.d.ts.map +1 -1
  50. package/dist/facets/real-vite-hmr.js +3 -2
  51. package/dist/facets/resident-identity.d.ts +0 -1
  52. package/dist/facets/resident-identity.d.ts.map +1 -1
  53. package/dist/facets/resident-identity.js +0 -3
  54. package/dist/facets/wasm-swap-registry.d.ts +34 -25
  55. package/dist/facets/wasm-swap-registry.d.ts.map +1 -1
  56. package/dist/facets/wasm-swap-registry.js +62 -56
  57. package/dist/facets/ws-terminal.d.ts +4 -4
  58. package/dist/facets/ws-terminal.d.ts.map +1 -1
  59. package/dist/facets/ws-terminal.js +4 -5
  60. package/dist/git/commands.d.ts +33 -10
  61. package/dist/git/commands.d.ts.map +1 -1
  62. package/dist/git/commands.js +1891 -197
  63. package/dist/git/network-facet.d.ts +12 -2
  64. package/dist/git/network-facet.d.ts.map +1 -1
  65. package/dist/git/network-facet.js +69 -18
  66. package/dist/git/unified-diff.d.ts +74 -0
  67. package/dist/git/unified-diff.d.ts.map +1 -0
  68. package/dist/git/unified-diff.js +676 -0
  69. package/dist/git-bundle.generated.d.ts +7 -11
  70. package/dist/git-bundle.generated.d.ts.map +1 -1
  71. package/dist/git-bundle.generated.js +7 -11
  72. package/dist/hosted/commands.d.ts.map +1 -1
  73. package/dist/hosted/commands.js +64 -245
  74. package/dist/hosted/runtime.d.ts +22 -15
  75. package/dist/hosted/runtime.d.ts.map +1 -1
  76. package/dist/hosted/runtime.js +72 -14
  77. package/dist/hosted/services.d.ts +8 -1
  78. package/dist/hosted/services.d.ts.map +1 -1
  79. package/dist/hosted/services.js +12 -13
  80. package/dist/hosted/session.d.ts +165 -0
  81. package/dist/hosted/session.d.ts.map +1 -0
  82. package/dist/hosted/session.js +132 -0
  83. package/dist/loaders/generated-workers.d.ts +6 -3
  84. package/dist/loaders/generated-workers.d.ts.map +1 -1
  85. package/dist/loaders/generated-workers.js +8 -5
  86. package/dist/loaders/npm-resolve-preamble.d.ts.map +1 -1
  87. package/dist/loaders/npm-resolve-preamble.js +0 -27
  88. package/dist/loaders/pre-bundle-preamble.d.ts +6 -3
  89. package/dist/loaders/pre-bundle-preamble.d.ts.map +1 -1
  90. package/dist/loaders/pre-bundle-preamble.js +13 -8
  91. package/dist/node-shims-artifact.generated.d.ts +13 -4
  92. package/dist/node-shims-artifact.generated.d.ts.map +1 -1
  93. package/dist/node-shims-artifact.generated.js +16 -7
  94. package/dist/npm/bin-links.d.ts +3 -1
  95. package/dist/npm/bin-links.d.ts.map +1 -1
  96. package/dist/npm/bin-links.js +32 -8
  97. package/dist/npm/cache.d.ts +11 -1
  98. package/dist/npm/cache.d.ts.map +1 -1
  99. package/dist/npm/cache.js +21 -7
  100. package/dist/npm/install-batch-facet.d.ts +4 -2
  101. package/dist/npm/install-batch-facet.d.ts.map +1 -1
  102. package/dist/npm/install-batch-facet.js +32 -18
  103. package/dist/npm/installer.d.ts +23 -3
  104. package/dist/npm/installer.d.ts.map +1 -1
  105. package/dist/npm/installer.js +330 -131
  106. package/dist/npm/npx-install.d.ts +3 -1
  107. package/dist/npm/npx-install.d.ts.map +1 -1
  108. package/dist/npm/npx-install.js +4 -1
  109. package/dist/npm/package-lock.d.ts +32 -0
  110. package/dist/npm/package-lock.d.ts.map +1 -0
  111. package/dist/npm/package-lock.js +65 -0
  112. package/dist/npm/placement.d.ts +16 -0
  113. package/dist/npm/placement.d.ts.map +1 -0
  114. package/dist/npm/placement.js +33 -0
  115. package/dist/npm/pre-bundle-facet.d.ts +1 -30
  116. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  117. package/dist/npm/pre-bundle-facet.js +3 -5
  118. package/dist/npm/r2-cache.d.ts +12 -6
  119. package/dist/npm/r2-cache.d.ts.map +1 -1
  120. package/dist/npm/r2-cache.js +34 -16
  121. package/dist/npm/resolve-one-facet.d.ts +3 -0
  122. package/dist/npm/resolve-one-facet.d.ts.map +1 -1
  123. package/dist/npm/resolve-one-facet.js +15 -3
  124. package/dist/npm/resolver.d.ts +11 -13
  125. package/dist/npm/resolver.d.ts.map +1 -1
  126. package/dist/npm/resolver.js +12 -25
  127. package/dist/opencode-artifact.generated.js +2 -2
  128. package/dist/replica/routing.d.ts +2 -38
  129. package/dist/replica/routing.d.ts.map +1 -1
  130. package/dist/replica/routing.js +3 -55
  131. package/dist/router/index.d.ts.map +1 -1
  132. package/dist/router/index.js +15 -2
  133. package/dist/router/public-directory.d.ts +18 -0
  134. package/dist/router/public-directory.d.ts.map +1 -1
  135. package/dist/router/public-directory.js +1 -1
  136. package/dist/router/remote-api.d.ts +3 -1
  137. package/dist/router/remote-api.d.ts.map +1 -1
  138. package/dist/router/remote-api.js +42 -79
  139. package/dist/runtime/bash-repl.js +2 -2
  140. package/dist/runtime/bun-repl.d.ts.map +1 -1
  141. package/dist/runtime/bun-repl.js +2 -13
  142. package/dist/runtime/cpython-resident.d.ts.map +1 -1
  143. package/dist/runtime/cpython-resident.js +2 -10
  144. package/dist/runtime/esbuild-wasm-bytes.d.ts +37 -18
  145. package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
  146. package/dist/runtime/esbuild-wasm-bytes.js +66 -92
  147. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  148. package/dist/runtime/facet-loader-host.js +0 -7
  149. package/dist/runtime/git-bundle-artifact.d.ts +17 -0
  150. package/dist/runtime/git-bundle-artifact.d.ts.map +1 -0
  151. package/dist/runtime/git-bundle-artifact.js +25 -0
  152. package/dist/runtime/node-repl.d.ts.map +1 -1
  153. package/dist/runtime/node-repl.js +2 -13
  154. package/dist/runtime/node-shims-artifact.d.ts +22 -23
  155. package/dist/runtime/node-shims-artifact.d.ts.map +1 -1
  156. package/dist/runtime/node-shims-artifact.js +48 -78
  157. package/dist/runtime/node-shims.d.ts.map +1 -1
  158. package/dist/runtime/node-shims.js +2358 -996
  159. package/dist/runtime/opencode-artifact.d.ts.map +1 -1
  160. package/dist/runtime/opencode-artifact.js +17 -53
  161. package/dist/runtime/opencode-facet-runner.d.ts +3 -6
  162. package/dist/runtime/opencode-facet-runner.d.ts.map +1 -1
  163. package/dist/runtime/opencode-facet-runner.js +45 -10
  164. package/dist/runtime/opentui-wasm-bytes.d.ts.map +1 -1
  165. package/dist/runtime/opentui-wasm-bytes.js +16 -49
  166. package/dist/runtime/package-manager.d.ts +0 -2
  167. package/dist/runtime/package-manager.d.ts.map +1 -1
  168. package/dist/runtime/package-manager.js +0 -3
  169. package/dist/runtime/python-repl.d.ts.map +1 -1
  170. package/dist/runtime/python-repl.js +4 -35
  171. package/dist/runtime/repl-session.d.ts +10 -0
  172. package/dist/runtime/repl-session.d.ts.map +1 -1
  173. package/dist/runtime/repl-session.js +7 -0
  174. package/dist/runtime/ruby-repl.d.ts.map +1 -1
  175. package/dist/runtime/ruby-repl.js +2 -2
  176. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  177. package/dist/runtime/ruby-resident.js +41 -51
  178. package/dist/runtime/runtime-catalog.d.ts +10 -3
  179. package/dist/runtime/runtime-catalog.d.ts.map +1 -1
  180. package/dist/runtime/runtime-catalog.js +125 -15
  181. package/dist/runtime/sqlite-wasm-bytes.d.ts +4 -4
  182. package/dist/runtime/sqlite-wasm-bytes.d.ts.map +1 -1
  183. package/dist/runtime/sqlite-wasm-bytes.js +21 -57
  184. package/dist/runtime/staged-source.d.ts +67 -0
  185. package/dist/runtime/staged-source.d.ts.map +1 -0
  186. package/dist/runtime/staged-source.js +113 -0
  187. package/dist/runtime/wasi/preamble.d.ts +0 -22
  188. package/dist/runtime/wasi/preamble.d.ts.map +1 -1
  189. package/dist/runtime/wasi/preamble.js +98 -1425
  190. package/dist/runtime-catalog.generated.js +1 -1
  191. package/dist/session/agent.d.ts +14 -2
  192. package/dist/session/agent.d.ts.map +1 -1
  193. package/dist/session/agent.js +57 -19
  194. package/dist/session/ai.d.ts +0 -11
  195. package/dist/session/ai.d.ts.map +1 -1
  196. package/dist/session/ai.js +0 -4
  197. package/dist/session/fs-watch.d.ts +0 -11
  198. package/dist/session/fs-watch.d.ts.map +1 -1
  199. package/dist/session/fs-watch.js +0 -21
  200. package/dist/session/hibernation.d.ts +68 -2
  201. package/dist/session/hibernation.d.ts.map +1 -1
  202. package/dist/session/hibernation.js +95 -1
  203. package/dist/session/init-phases.d.ts +1 -2
  204. package/dist/session/init-phases.d.ts.map +1 -1
  205. package/dist/session/init-phases.js +1 -2
  206. package/dist/session/init.d.ts.map +1 -1
  207. package/dist/session/init.js +7 -20
  208. package/dist/session/keys.d.ts +0 -3
  209. package/dist/session/keys.d.ts.map +1 -1
  210. package/dist/session/keys.js +0 -3
  211. package/dist/session/legacy-reset.d.ts +11 -0
  212. package/dist/session/legacy-reset.d.ts.map +1 -0
  213. package/dist/session/legacy-reset.js +16 -0
  214. package/dist/session/nimbus-session.d.ts +50 -16
  215. package/dist/session/nimbus-session.d.ts.map +1 -1
  216. package/dist/session/nimbus-session.js +77 -71
  217. package/dist/session/npm-install-port.d.ts.map +1 -1
  218. package/dist/session/npm-install-port.js +2 -0
  219. package/dist/session/programmatic.d.ts +13 -18
  220. package/dist/session/programmatic.d.ts.map +1 -1
  221. package/dist/session/programmatic.js +53 -39
  222. package/dist/session/replica-routes.d.ts +2 -3
  223. package/dist/session/replica-routes.d.ts.map +1 -1
  224. package/dist/session/replica-routes.js +3 -10
  225. package/dist/session/routes.d.ts.map +1 -1
  226. package/dist/session/routes.js +11 -12
  227. package/dist/session/rpc.d.ts +108 -21
  228. package/dist/session/rpc.d.ts.map +1 -1
  229. package/dist/session/rpc.js +194 -39
  230. package/dist/session/start-real-vite.js +2 -2
  231. package/dist/session/state-store.d.ts +1 -17
  232. package/dist/session/state-store.d.ts.map +1 -1
  233. package/dist/session/state-store.js +3 -53
  234. package/dist/session/supervisor-op.d.ts +7 -0
  235. package/dist/session/supervisor-op.d.ts.map +1 -1
  236. package/dist/session/supervisor-op.js +6 -1
  237. package/dist/session/supervisor-rpc.d.ts +116 -19
  238. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  239. package/dist/session/supervisor-rpc.js +194 -84
  240. package/dist/session/vite-command.d.ts.map +1 -1
  241. package/dist/session/vite-command.js +17 -14
  242. package/dist/session/ws.d.ts.map +1 -1
  243. package/dist/session/ws.js +20 -8
  244. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  245. package/dist/shell/npm-bin-entrypoints.js +30 -5
  246. package/dist/vfs/facet-resident-limits.d.ts +33 -0
  247. package/dist/vfs/facet-resident-limits.d.ts.map +1 -0
  248. package/dist/vfs/facet-resident-limits.js +33 -0
  249. package/dist/vfs/facet-resident-store.d.ts +2 -10
  250. package/dist/vfs/facet-resident-store.d.ts.map +1 -1
  251. package/dist/vfs/facet-resident-store.js +1594 -219
  252. package/dist/workspace-host.d.ts +3 -1
  253. package/dist/workspace-host.d.ts.map +1 -1
  254. package/dist/workspace-host.js +2 -0
  255. package/dist/wrangler/nimbus-wrangler.d.ts +0 -2
  256. package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
  257. package/dist/wrangler/nimbus-wrangler.js +8 -22
  258. package/package.json +34 -7
  259. package/public/_assets/esbuild-0.24.2.js +2380 -0
  260. package/public/_assets/opencode/1.16.2/index-attach.js +2 -2
  261. package/public/_assets/runtime/esbuild-cli-c4fda380d52befc1.js +6892 -0
  262. package/public/_assets/runtime/git-de3e60c9f3ff006a.js +103 -0
  263. package/public/_assets/runtime/{node-shims-acaa037e6d26ec0d.js → node-shims-06565b121555c194.js} +2879 -992
  264. package/public/_assets/runtime/resident-store-6ffb1939c53d982f.js +2264 -0
  265. package/public/_assets/runtime/vfs-write-ledger-e501e8fc793bc2c1.js +889 -0
  266. package/scripts/build-opencode-attach-entry.mjs +51 -1
  267. package/scripts/bundle-esbuild-wasm.mjs +55 -39
  268. package/scripts/bundle-facet-workers.mjs +159 -10
  269. package/scripts/bundle-git.mjs +88 -27
  270. package/scripts/bundle-node-shims.mjs +72 -46
  271. package/scripts/bundle-runtime.mjs +18 -421
  272. package/scripts/cf-git-patch.mjs +137 -0
  273. package/scripts/patch-install-deps.mjs +49 -96
  274. package/scripts/runtime-specs.mjs +435 -0
  275. package/vendor/git-http.generated.d.ts +82 -0
  276. package/vendor/git-upstream.generated.d.ts +4378 -0
  277. package/vendor/git.LICENSE +7 -0
  278. package/vendor/git.generated.d.mts +21 -0
  279. package/vendor/git.generated.mjs +103 -0
  280. package/dist/replica/suspension.d.ts +0 -36
  281. package/dist/replica/suspension.d.ts.map +0 -1
  282. package/dist/replica/suspension.js +0 -49
  283. package/dist/runtime/static-server.d.ts +0 -3
  284. package/dist/runtime/static-server.d.ts.map +0 -1
  285. package/dist/runtime/static-server.js +0 -133
  286. package/scripts/clean-dist.mjs +0 -7
@@ -0,0 +1,2264 @@
1
+ // The store's tables (__residentTablesOnSql or __residentTablesInMemory),
2
+ // bound once the process exists. Module scope has no ctx, so every trap below
3
+ // runs in handler context, which is also the only context that reads the
4
+ // resident set.
5
+ let __residentT = null;
6
+ let __residentReady = false;
7
+
8
+ /**
9
+ * Reads are SEALED until this incarnation has reconciled the store against the
10
+ * authority. This is the whole coherence design, and it is a seal rather than a
11
+ * check because a check gets forgotten.
12
+ *
13
+ * The hazard is specific to putting the resident set in storage, and it is
14
+ * created by the property that makes the design affordable: a facet's SQLite
15
+ * survives a fresh module scope (measured — same facet name, new loader key,
16
+ * new module token, 7,141 rows and 45.7 MB intact). So a new incarnation opens
17
+ * onto rows written by a PREVIOUS one, while every in-heap stamp and cursor
18
+ * that described them is gone. Heap-side provenance cannot survive the thing it
19
+ * is meant to describe, so provenance lives in the rows: 'file.rev' per path,
20
+ * '(epoch, rev)' for the store, both in this SQLite.
21
+ *
22
+ * Sealed is the initial state and `__residentAdmit` is the only thing that
23
+ * clears it, so a row cannot be served by an incarnation that has not applied
24
+ * the authority's delta. There is one gate and it is on the only door —
25
+ * `__residentRequire`, which every read, scan and write goes through.
26
+ */
27
+ let __residentSealed = true;
28
+ /**
29
+ * Set when this facet booted from a snapshot carrying no cursor, so the store
30
+ * holds nothing. Read by residency-miss reporting: without it, "not resident"
31
+ * is indistinguishable from "resident set was never adoptable", and the second
32
+ * is a much more actionable thing to be told.
33
+ */
34
+ let __residentUndated = false;
35
+ let __residentSealReason = "the store has not reconciled with the authority in this incarnation";
36
+
37
+ const __RESIDENT_CHUNK_BYTES = 1048576;
38
+ const __RESIDENT_BATCH_ROWS = 512;
39
+ const __RESIDENT_BATCH_PATHS = 1024;
40
+ const __RESIDENT_BATCH_BYTES = 4194304;
41
+ const __RESIDENT_LIST_PAGE = 8192;
42
+ /**
43
+ * Pages one enumeration may take before it gives up.
44
+ *
45
+ * A bound rather than an unbounded loop because `fsList` is a network call in
46
+ * a boot path: a supervisor that never reported a null `next` would spin here
47
+ * forever and the process would never start. At the page size above this
48
+ * admits filesystems into the tens of millions of paths, so it bounds a defect
49
+ * rather than a workload.
50
+ */
51
+ const __RESIDENT_MAX_LIST_PAGES = 4096;
52
+
53
+ /**
54
+ * Cell kinds. A cell is one of the three things __vfsBundle has always held,
55
+ * and the kind is stored rather than inferred so a text file whose bytes happen
56
+ * to be valid UTF-8 does not change shape between a write and a read.
57
+ */
58
+ const __RESIDENT_SCHEMA = "2";
59
+
60
+ // N18: the session's storage ledger admits this facet's database growth
61
+ // before it happens. The supervisor grants an allowance (the ledger records
62
+ // it under this facet's name); the store never grows its database past
63
+ // `__residentCap`, and asks for more, asynchronously, when it would. What
64
+ // does not fit is not held: a fill degrades to reads through the session,
65
+ // and an own write goes to the session only. Null: no ledger (no cap).
66
+ let __residentCap = null;
67
+ let __residentFacet = null;
68
+ let __residentSupervisor = null;
69
+ let __residentGrantAsk = null;
70
+ const __RESIDENT_ROW_BYTES = 256;
71
+ function __namespaceRowBytes(parent, name, target) {
72
+ // JavaScript keeps path/target strings in the heap. Charge UTF-16 storage
73
+ // plus the existing row allowance, even when the engine can compress text.
74
+ return __RESIDENT_ROW_BYTES + 2 * (String(parent).length + String(name).length + (target == null ? 0 : String(target).length));
75
+ }
76
+
77
+ function __residentDbBytes() {
78
+ // A one-shot's heap is one budget: the store's tables and the own writes
79
+ // held beside them (__residentHold) count against the same cap.
80
+ return __residentT.bytes() + (__residentInHeap ? __residentHeldBytes + __residentNamespaceOverlayBytes + __residentNamespaceReserveBytes : 0);
81
+ }
82
+
83
+ /** Whether the store is a one-shot's heap (__residentBindInMemory) rather than a facet's SQLite. */
84
+ let __residentInHeap = false;
85
+ let __residentNamespaceOverlayBytes = 0;
86
+ let __residentNamespaceReserveBytes = 0;
87
+ function __residentNamespaceOverlayDelta(bytes) {
88
+ if (!__residentInHeap) return;
89
+ if (bytes > 0 && __residentCap !== null && __residentDbBytes() + bytes > __residentCap) {
90
+ const cause = "namespace storage budget exceeded (" + __residentCap + " bytes)";
91
+ __nsMarkReady(__residentT, false, cause);
92
+ throw Object.assign(new Error(cause), { code: "ENOSPC" });
93
+ }
94
+ __residentNamespaceOverlayBytes += bytes;
95
+ }
96
+
97
+ /**
98
+ * Whether the store may grow by `bytes` now; when not, and `ask`, more room
99
+ * is asked for. A copy of an own write never asks: the session's copy is the
100
+ * one that must have the room, and the cache must not take it first.
101
+ */
102
+ function __residentFits(bytes, ask = true) {
103
+ if (__residentCap === null) return true;
104
+ if (__residentDbBytes() + bytes <= __residentCap) return true;
105
+ if (ask) void __residentAskGrant(bytes);
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * Ask the ledger for `bytes` more, reporting what the database measures.
111
+ * Exactly what is needed: room granted and unused is room the session's own
112
+ * writes cannot have. Resolves to whether `bytes` now fit.
113
+ */
114
+ function __residentAskGrant(bytes) {
115
+ const supervisor = __residentSupervisor;
116
+ if (__residentCap === null) return Promise.resolve(true);
117
+ // A store with no ledger row (a one-shot's heap) has a fixed budget: no one to ask.
118
+ if (__residentFacet === null || !supervisor || typeof supervisor.fsStorageGrant !== "function") return Promise.resolve(false);
119
+ if (__residentGrantAsk) return __residentGrantAsk.then(() => __residentDbBytes() + bytes <= __residentCap);
120
+ const want = Math.max(0, __residentDbBytes() + bytes - __residentCap);
121
+ __residentGrantAsk = (async () => {
122
+ try {
123
+ const answer = await supervisor.fsStorageGrant(__residentFacet, want, __residentDbBytes());
124
+ const granted = Number(answer && answer.granted) || 0;
125
+ __residentCap += granted;
126
+ if (answer && typeof answer.cap === "number") __residentCap = Math.max(__residentCap, answer.cap);
127
+ } catch {}
128
+ })().finally(() => { __residentGrantAsk = null; });
129
+ return __residentGrantAsk.then(() => __residentDbBytes() + bytes <= __residentCap);
130
+ }
131
+
132
+ /** Room for `bytes` before an asynchronous fill writes them: true when they fit. */
133
+ async function __residentEnsureRoom(bytes) {
134
+ if (__residentCap === null || __residentDbBytes() + bytes <= __residentCap) return true;
135
+ return __residentAskGrant(bytes);
136
+ }
137
+
138
+ /**
139
+ * What holding a cell costs the database: its bytes (a text cell's UTF-8
140
+ * length) on overflow pages (1% over, for page headers and the b-tree pages
141
+ * above them), a row per chunk and head, and a page of slack. The manager
142
+ * charges a launch's module map the same way (manager.ts residentCellCost).
143
+ */
144
+ function __residentCellCost(cell) {
145
+ const isText = typeof cell === "string";
146
+ const size = isText ? cell.length : __residentBytes(cell).byteLength;
147
+ const limit = isText ? Math.floor(__RESIDENT_CHUNK_BYTES / 3) : __RESIDENT_CHUNK_BYTES;
148
+ const chunks = Math.max(1, Math.ceil(size / limit));
149
+ return Math.ceil((isText ? __residentUtf8Length(cell) : size) * 1.01) + (chunks + 1) * __RESIDENT_ROW_BYTES + 4096;
150
+ }
151
+
152
+ function __residentUtf8Length(text) {
153
+ let bytes = 0;
154
+ for (let i = 0; i < text.length; i++) {
155
+ const c = text.charCodeAt(i);
156
+ if (c < 0x80) bytes += 1;
157
+ else if (c < 0x800) bytes += 2;
158
+ // A surrogate pair is one 4-byte character; a lone surrogate encodes as 3.
159
+ else if (c >= 0xd800 && c < 0xdc00 && i + 1 < text.length && (text.charCodeAt(i + 1) & 0xfc00) === 0xdc00) { bytes += 4; i++; }
160
+ else bytes += 3;
161
+ }
162
+ return bytes;
163
+ }
164
+
165
+ /** Room for what an ACQUIRE answer pushes, asked for before it is applied. */
166
+ async function __residentRoomForPushed(result) {
167
+ if (__residentCap === null || !result || !Array.isArray(result.paths)) return;
168
+ let bytes = 0;
169
+ for (const entry of result.paths) {
170
+ if (entry && entry.bytes != null) bytes += __residentCellCost(__residentBytes(entry.bytes));
171
+ }
172
+ if (bytes > 0) await __residentEnsureRoom(bytes);
173
+ }
174
+
175
+ /** What the database measures, after a fill batch: the ledger settles to it, or records overshoot. */
176
+ async function __residentReportSize() {
177
+ const supervisor = __residentSupervisor;
178
+ if (__residentCap === null || __residentFacet === null || !supervisor || typeof supervisor.fsStorageGrant !== "function") return;
179
+ try { await supervisor.fsStorageGrant(__residentFacet, 0, __residentDbBytes()); } catch {}
180
+ }
181
+
182
+ /** The allowance this launch was admitted for (the fabric's startArgs.storage). */
183
+ function __residentSetStorage(storage, supervisor) {
184
+ __residentSupervisor = supervisor || null;
185
+ if (!storage || typeof storage.facet !== "string" || typeof storage.grant !== "number") return;
186
+ __residentFacet = storage.facet;
187
+ __residentCap = __residentDbBytes() + storage.grant;
188
+ }
189
+
190
+ /** The cap this store enforces, for startProcess to report. */
191
+ function __residentStorageCap() {
192
+ return __residentCap;
193
+ }
194
+
195
+ // An own write the store could not hold (no room in the session's storage)
196
+ // is kept here, so the program still reads what it wrote: a bounded LRU
197
+ // (Map order is recency) of { cell, rev }, dated like a file row (own until
198
+ // its write-back is acknowledged, then the write's revision) and judged by
199
+ // every report like one. A path it could not keep either is remembered by
200
+ // name only, so a miss on it says why.
201
+ const __residentHeld = new Map();
202
+ let __residentHeldBytes = 0;
203
+ const __residentUnheld = new Set();
204
+ const __RESIDENT_HELD_MAX_BYTES = 33554432;
205
+
206
+ function __residentCellSize(held) {
207
+ const cell = held.cell;
208
+ return typeof cell === "string" ? cell.length * 3 : __residentBytes(cell).byteLength;
209
+ }
210
+
211
+ function __residentForgetHeld(path) {
212
+ const cell = __residentHeld.get(path);
213
+ if (cell !== undefined) {
214
+ __residentHeld.delete(path);
215
+ __residentHeldBytes -= __residentCellSize(cell);
216
+ }
217
+ __residentUnheld.delete(path);
218
+ }
219
+
220
+ function __residentHold(path, cell) {
221
+ __residentForgetHeld(path);
222
+ const size = __residentCellSize({ cell });
223
+ // In a facet the held cells have their own bound. In a one-shot's heap they
224
+ // share the store's: what the cap leaves beside the tables.
225
+ const limit = __residentInHeap ? Math.max(0, Number(__residentCap) - __residentT.bytes() - __residentNamespaceOverlayBytes - __residentNamespaceReserveBytes) : __RESIDENT_HELD_MAX_BYTES;
226
+ if (size > limit) { __residentUnheld.add(path); return; }
227
+ while (__residentHeldBytes + size > limit) {
228
+ const [oldest, old] = __residentHeld.entries().next().value;
229
+ __residentHeld.delete(oldest);
230
+ __residentHeldBytes -= __residentCellSize(old);
231
+ __residentUnheld.add(oldest);
232
+ }
233
+ __residentHeld.set(path, { cell, rev: __RK_OWN_WRITE });
234
+ __residentHeldBytes += size;
235
+ }
236
+
237
+ /** Whether a miss on `path` is the storage's: its own write was held nowhere. */
238
+ function __residentStorageMiss(path) {
239
+ return __residentUnheld.has(path);
240
+ }
241
+ const __RK_TEXT = 0;
242
+ const __RK_BINARY = 1;
243
+ const __RK_DENIED = 2;
244
+
245
+ /**
246
+ * The revision of a cell holding this facet's own bytes that the authority has
247
+ * not acknowledged yet: a write that has not flushed, or a cell one of its own
248
+ * mutations holds while that mutation is in flight (`__residentLease`).
249
+ *
250
+ * Read-your-writes: strictly newer than anything the authority could report,
251
+ * so it is never evicted by a delta and never mistaken for something the
252
+ * authority vouched for. A distinct sentinel rather than NULL, because NULL
253
+ * would reintroduce the undated row the NOT NULL constraint exists to forbid.
254
+ *
255
+ * It is the ONE revision a delta cannot evict, so it must mean exactly that
256
+ * and nothing else. The acknowledgement dates the row (`__residentStamp`),
257
+ * and bytes the authority served are dated as they are installed
258
+ * (`__residentFill`). A row left here after either would hold its bytes
259
+ * against every later write by anyone, for the life of the store.
260
+ */
261
+ const __RK_OWN_WRITE = -1;
262
+
263
+ /**
264
+ * The store's own operations, over the four tables it keeps: 'file' (one row
265
+ * per held path: kind, size, chunk count, revision, content key), 'chunk'
266
+ * (the bytes), 'meta' (schema, cursor, namespace readiness) and 'ns' (the
267
+ * namespace: a stat per visible name, keyed (parent, name)). Everything above
268
+ * this seam decides; the backing only stores. There are exactly two backings,
269
+ * with one contract:
270
+ *
271
+ * - __residentTablesOnSql: the process facet's own SQLite (ctx.storage.sql),
272
+ * durable across the facet's incarnations. Every statement the store has
273
+ * ever issued lives here, unchanged.
274
+ * - __residentTablesInMemory: a one-shot process's store, in its heap, for
275
+ * the life of the run (runOnce hosts no SQLite). Same rows, same answers,
276
+ * and a byte count that plays the database's size for the store's budget.
277
+ *
278
+ * Rows come back as plain objects with the column names as keys: file heads
279
+ * { kind, size, chunks }, chunks { txt, bin } (bin as the backing holds it;
280
+ * callers normalise with __residentBytes), ns rows with every stat column.
281
+ */
282
+ function __residentTablesOnSql(sql) {
283
+ let schema = null;
284
+ for (const row of sql.exec("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'meta'")) schema = "";
285
+ if (schema !== null) for (const row of sql.exec("SELECT v FROM meta WHERE k = 'schema'")) schema = String(row.v);
286
+ if (schema !== __RESIDENT_SCHEMA) {
287
+ for (const table of ["chunk", "file", "ns", "meta"]) sql.exec("DROP TABLE IF EXISTS " + table);
288
+ }
289
+ // 'ckey' is the authority's content identity for the bytes (equal keys,
290
+ // equal bytes), when it said so; null for bytes it did not describe.
291
+ sql.exec(
292
+ "CREATE TABLE IF NOT EXISTS file (" +
293
+ "path TEXT PRIMARY KEY, kind INTEGER NOT NULL, size INTEGER NOT NULL, " +
294
+ "chunks INTEGER NOT NULL, rev INTEGER NOT NULL CHECK (rev >= -1), ckey TEXT" +
295
+ ")"
296
+ );
297
+ sql.exec("CREATE INDEX IF NOT EXISTS file_ckey ON file (ckey) WHERE ckey IS NOT NULL");
298
+ sql.exec(
299
+ "CREATE TABLE IF NOT EXISTS chunk (" +
300
+ "path TEXT NOT NULL, part INTEGER NOT NULL, txt TEXT, bin BLOB, " +
301
+ "PRIMARY KEY (path, part)" +
302
+ ")"
303
+ );
304
+ // The store's own cursor, in the same storage as the rows it describes, so
305
+ // the two cannot be separated by an isolate restart.
306
+ sql.exec("CREATE TABLE IF NOT EXISTS meta (k TEXT PRIMARY KEY, v TEXT NOT NULL)");
307
+ sql.exec("INSERT OR REPLACE INTO meta (k, v) VALUES ('schema', ?)", __RESIDENT_SCHEMA);
308
+ // The namespace: every name the process's credential can see, with its stat
309
+ // and no content. Keyed (parent, name) so a point lookup and a directory
310
+ // listing are both one index seek, and no path is stored twice.
311
+ sql.exec(
312
+ "CREATE TABLE IF NOT EXISTS ns (" +
313
+ "parent TEXT NOT NULL, name TEXT NOT NULL, kind INTEGER NOT NULL, size INTEGER NOT NULL, " +
314
+ "mode INTEGER NOT NULL, uid INTEGER NOT NULL, gid INTEGER NOT NULL, atime REAL NOT NULL, " +
315
+ "mtime REAL NOT NULL, ctime REAL NOT NULL, ino INTEGER NOT NULL, rev INTEGER NOT NULL, target TEXT, " +
316
+ "PRIMARY KEY (parent, name)" +
317
+ ") WITHOUT ROWID"
318
+ );
319
+ const first = (rows) => { for (const row of rows) return row; return undefined; };
320
+ const ranged = (select, from, to, tail = "") => to === null
321
+ ? sql.exec(select + " WHERE path >= ?" + tail, from)
322
+ : sql.exec(select + " WHERE path >= ? AND path < ?" + tail, from, to);
323
+ return {
324
+ bytes() {
325
+ const size = sql.databaseSize;
326
+ if (typeof size === "number" && size > 0) return size;
327
+ for (const row of sql.exec("SELECT page_count * page_size AS n FROM pragma_page_count(), pragma_page_size()")) return Number(row.n);
328
+ return 0;
329
+ },
330
+ databaseSize() { return Number(sql.databaseSize ?? 0); },
331
+ metaGet(k) { const row = first(sql.exec("SELECT v FROM meta WHERE k = ?", k)); return row === undefined ? undefined : String(row.v); },
332
+ metaSet(k, v) { sql.exec("INSERT OR REPLACE INTO meta (k, v) VALUES (?, ?)", k, v); },
333
+ fileHead(path) { return first(sql.exec("SELECT kind, size, chunks FROM file WHERE path = ?", path)); },
334
+ fileRev(path) { const row = first(sql.exec("SELECT rev FROM file WHERE path = ?", path)); return row === undefined ? undefined : Number(row.rev); },
335
+ fileRevKey(path) { return first(sql.exec("SELECT rev, ckey FROM file WHERE path = ?", path)); },
336
+ fileOwnSize(path) {
337
+ const row = first(sql.exec("SELECT size FROM file WHERE path = ? AND rev = ?", path, __RK_OWN_WRITE));
338
+ return row === undefined ? undefined : Number(row.size);
339
+ },
340
+ filePut(path, kind, size, chunks, rev, ckey) {
341
+ sql.exec("INSERT OR REPLACE INTO file (path, kind, size, chunks, rev, ckey) VALUES (?, ?, ?, ?, ?, ?)", path, kind, size, chunks, rev, ckey);
342
+ },
343
+ fileSetRev(path, rev) { sql.exec("UPDATE file SET rev = ? WHERE path = ?", rev, path); },
344
+ fileStampOwn(path, rev) { sql.exec("UPDATE file SET rev = ? WHERE path = ? AND rev = ?", rev, path, __RK_OWN_WRITE); },
345
+ fileDelete(path) { sql.exec("DELETE FROM file WHERE path = ?", path); },
346
+ fileByKey(ckey, notPath) {
347
+ return first(sql.exec("SELECT path, kind, size, chunks FROM file WHERE ckey = ? AND rev >= 0 AND path <> ? LIMIT 1", ckey, notPath));
348
+ },
349
+ filePaths(from, to) { return [...ranged("SELECT path FROM file", from, to)].map((row) => String(row.path)); },
350
+ fileAny(from, to) { return first(ranged("SELECT path FROM file", from, to, " LIMIT 1")) !== undefined; },
351
+ fileRevsIn(from, to) { return [...ranged("SELECT path, rev FROM file", from, to)]; },
352
+ // In key order, as every other scan here is: the primary key's index
353
+ // serves it, and both backings then answer in one order.
354
+ fileAllPaths() { return [...sql.exec("SELECT path FROM file ORDER BY path")].map((row) => String(row.path)); },
355
+ fileRows() { return [...sql.exec("SELECT path, rev, ckey FROM file ORDER BY path")]; },
356
+ fileOwnPaths() { return [...sql.exec("SELECT path FROM file WHERE rev = ? ORDER BY path", __RK_OWN_WRITE)].map((row) => String(row.path)); },
357
+ fileStats() { return first(sql.exec("SELECT count(*) AS n, coalesce(sum(size), 0) AS b FROM file")); },
358
+ chunkFirst(path) { return first(sql.exec("SELECT txt, bin FROM chunk WHERE path = ? AND part = 0", path)); },
359
+ chunkParts(path) { return [...sql.exec("SELECT txt, bin FROM chunk WHERE path = ? ORDER BY part", path)]; },
360
+ chunkAddText(path, part, txt) { sql.exec("INSERT INTO chunk (path, part, txt) VALUES (?, ?, ?)", path, part, txt); },
361
+ chunkAddBin(path, part, bin) { sql.exec("INSERT INTO chunk (path, part, bin) VALUES (?, ?, ?)", path, part, bin); },
362
+ chunkSetBin(path, part, bin) { sql.exec("INSERT OR REPLACE INTO chunk (path, part, bin) VALUES (?, ?, ?)", path, part, bin); },
363
+ chunkCopy(to, from) { sql.exec("INSERT INTO chunk (path, part, txt, bin) SELECT ?, part, txt, bin FROM chunk WHERE path = ?", to, from); },
364
+ chunkDelete(path) { sql.exec("DELETE FROM chunk WHERE path = ?", path); },
365
+ // Own rows are the one provenance a new incarnation cannot date.
366
+ dropOwn() {
367
+ sql.exec("DELETE FROM chunk WHERE path IN (SELECT path FROM file WHERE rev = ?)", __RK_OWN_WRITE);
368
+ sql.exec("DELETE FROM file WHERE rev = ?", __RK_OWN_WRITE);
369
+ },
370
+ // Every dated row, and any chunk no own row claims.
371
+ dropDated() {
372
+ sql.exec("DELETE FROM chunk WHERE path NOT IN (SELECT path FROM file WHERE rev = ?)", __RK_OWN_WRITE);
373
+ sql.exec("DELETE FROM file WHERE rev <> ?", __RK_OWN_WRITE);
374
+ },
375
+ // Every dated row and its chunks.
376
+ dropDatedFiles() {
377
+ sql.exec("DELETE FROM chunk WHERE path IN (SELECT path FROM file WHERE rev <> ?)", __RK_OWN_WRITE);
378
+ sql.exec("DELETE FROM file WHERE rev <> ?", __RK_OWN_WRITE);
379
+ },
380
+ clear() {
381
+ sql.exec("DELETE FROM chunk");
382
+ sql.exec("DELETE FROM file");
383
+ sql.exec("DELETE FROM ns");
384
+ sql.exec("DELETE FROM meta");
385
+ },
386
+ nsPut(parent, name, kind, size, mode, uid, gid, atime, mtime, ctime, ino, rev, target) {
387
+ sql.exec(
388
+ "INSERT OR REPLACE INTO ns (parent, name, kind, size, mode, uid, gid, atime, mtime, ctime, ino, rev, target) " +
389
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
390
+ parent, name, kind, size, mode, uid, gid, atime, mtime, ctime, ino, rev, target,
391
+ );
392
+ },
393
+ nsGet(parent, name) {
394
+ return first(sql.exec(
395
+ "SELECT kind, size, mode, uid, gid, atime, mtime, ctime, ino, rev, target FROM ns WHERE parent = ? AND name = ?",
396
+ parent, name,
397
+ ));
398
+ },
399
+ nsDelete(parent, name) { sql.exec("DELETE FROM ns WHERE parent = ? AND name = ?", parent, name); },
400
+ nsDeleteChildren(parent) { sql.exec("DELETE FROM ns WHERE parent = ?", parent); },
401
+ nsDeleteParents(from, to) {
402
+ if (to === null) sql.exec("DELETE FROM ns WHERE parent >= ?", from);
403
+ else sql.exec("DELETE FROM ns WHERE parent >= ? AND parent < ?", from, to);
404
+ },
405
+ nsClear() { sql.exec("DELETE FROM ns"); },
406
+ nsKeys() { return [...sql.exec("SELECT parent, name FROM ns")]; },
407
+ nsOfKind(kind) { return [...sql.exec("SELECT parent, name FROM ns WHERE kind = ?", kind)]; },
408
+ nsChildren(parent) { return [...sql.exec("SELECT name, kind FROM ns WHERE parent = ?", parent)]; },
409
+ nsCount() { const row = first(sql.exec("SELECT count(*) AS n FROM ns")); return row === undefined ? 0 : Number(row.n); },
410
+ };
411
+ }
412
+
413
+ function __residentBind(ctx) {
414
+ const sql = ctx && ctx.storage && ctx.storage.sql;
415
+ if (!sql || typeof sql.exec !== "function") {
416
+ throw new Error(
417
+ "Nimbus: this facet has no synchronous SQLite (ctx.storage.sql); " +
418
+ "the resident set cannot be served and every sync read would raise EAGAIN"
419
+ );
420
+ }
421
+ return __residentBindTables(__residentTablesOnSql(sql));
422
+ }
423
+
424
+ /**
425
+ * The one-shot backing: the same tables, in the process's heap, for the life
426
+ * of one run. A one-shot is hosted by runOnce, which gives it no SQLite, and
427
+ * it keeps nothing after it exits. Its answers are the SQLite backing's:
428
+ * scans come back in key order (a b-tree's), reads hand back copies (a
429
+ * program that writes into a Buffer it read must not change the store), and
430
+ * `bytes()` counts what it holds in the units __residentCellCost charges (a
431
+ * row, and the payload's bytes), so the store's one budget rule serves both.
432
+ */
433
+ function __residentTablesInMemory() {
434
+ const files = new Map();
435
+ const chunks = new Map();
436
+ const meta = new Map();
437
+ const ns = new Map();
438
+ const byKey = new Map();
439
+ let sorted = null;
440
+ let used = 0;
441
+ let names = 0;
442
+ let namespaceBytes = 0;
443
+ const utf8 = (text) => __residentUtf8Length(text);
444
+ // SQLite's binary collation orders TEXT by its UTF-8 bytes: code-point
445
+ // order, which UTF-16 comparison is not past the BMP.
446
+ const cmp = (a, b) => {
447
+ const n = Math.min(a.length, b.length);
448
+ for (let i = 0; i < n; i++) {
449
+ const x = a.codePointAt(i), y = b.codePointAt(i);
450
+ if (x !== y) return x < y ? -1 : 1;
451
+ if (x > 0xffff) i++;
452
+ }
453
+ return a.length === b.length ? 0 : a.length < b.length ? -1 : 1;
454
+ };
455
+ const payload = (cell) => (cell.txt != null ? utf8(cell.txt) : cell.bin ? cell.bin.byteLength : 0);
456
+ const unkey = (path, row) => {
457
+ if (row && row.ckey != null) {
458
+ const set = byKey.get(row.ckey);
459
+ if (set) { set.delete(path); if (set.size === 0) byKey.delete(row.ckey); }
460
+ }
461
+ };
462
+ const paths = () => {
463
+ if (sorted === null) sorted = [...files.keys()].sort(cmp);
464
+ return sorted;
465
+ };
466
+ const lowerBound = (list, from) => {
467
+ let lo = 0, hi = list.length;
468
+ while (lo < hi) { const mid = (lo + hi) >>> 1; if (cmp(list[mid], from) < 0) lo = mid + 1; else hi = mid; }
469
+ return lo;
470
+ };
471
+ const inRange = (from, to) => {
472
+ const list = paths();
473
+ const out = [];
474
+ for (let i = lowerBound(list, from); i < list.length && (to === null || cmp(list[i], to) < 0); i++) out.push(list[i]);
475
+ return out;
476
+ };
477
+ const copy = (cell) => ({ txt: cell.txt ?? null, bin: cell.bin ? cell.bin.slice() : null });
478
+ const dropChunks = (path) => {
479
+ const parts = chunks.get(path);
480
+ if (!parts) return;
481
+ for (const cell of parts.values()) used -= __RESIDENT_ROW_BYTES + payload(cell);
482
+ chunks.delete(path);
483
+ };
484
+ const dropFile = (path) => {
485
+ const row = files.get(path);
486
+ if (row === undefined) return;
487
+ unkey(path, row);
488
+ files.delete(path);
489
+ sorted = null;
490
+ used -= __RESIDENT_ROW_BYTES;
491
+ };
492
+ const addChunk = (path, part, cell, replace) => {
493
+ let parts = chunks.get(path);
494
+ if (!parts) { parts = new Map(); chunks.set(path, parts); }
495
+ const had = parts.get(part);
496
+ if (had !== undefined) {
497
+ if (!replace) throw new Error("UNIQUE constraint failed: chunk.path, chunk.part");
498
+ used -= __RESIDENT_ROW_BYTES + payload(had);
499
+ }
500
+ parts.set(part, cell);
501
+ used += __RESIDENT_ROW_BYTES + payload(cell);
502
+ };
503
+ const nsRow = (parent, name) => { const dir = ns.get(parent); return dir ? dir.get(name) : undefined; };
504
+ const nsDrop = (parent, name) => {
505
+ const dir = ns.get(parent);
506
+ const row = dir?.get(name);
507
+ if (row === undefined) return;
508
+ dir.delete(name);
509
+ names--;
510
+ const bytes = __namespaceRowBytes(parent, name, row.target);
511
+ used -= bytes; namespaceBytes -= bytes;
512
+ if (dir.size === 0) ns.delete(parent);
513
+ };
514
+ const nsDropParent = (parent) => {
515
+ const dir = ns.get(parent);
516
+ if (!dir) return;
517
+ names -= dir.size;
518
+ for (const [name, row] of dir) {
519
+ const bytes = __namespaceRowBytes(parent, name, row.target);
520
+ used -= bytes; namespaceBytes -= bytes;
521
+ }
522
+ ns.delete(parent);
523
+ };
524
+ const byName = cmp;
525
+ const dropWhere = (keepFile, keepChunkPath) => {
526
+ for (const path of [...chunks.keys()]) if (!keepChunkPath(path)) dropChunks(path);
527
+ for (const [path, row] of [...files]) if (!keepFile(row)) dropFile(path);
528
+ };
529
+ return {
530
+ bytes() { return used; },
531
+ databaseSize() { return used; },
532
+ metaGet(k) { return meta.get(k); },
533
+ metaSet(k, v) { meta.set(k, String(v)); },
534
+ fileHead(path) { const row = files.get(path); return row === undefined ? undefined : { kind: row.kind, size: row.size, chunks: row.chunks }; },
535
+ fileRev(path) { const row = files.get(path); return row === undefined ? undefined : row.rev; },
536
+ fileRevKey(path) { const row = files.get(path); return row === undefined ? undefined : { rev: row.rev, ckey: row.ckey }; },
537
+ fileOwnSize(path) { const row = files.get(path); return row !== undefined && row.rev === __RK_OWN_WRITE ? row.size : undefined; },
538
+ filePut(path, kind, size, chunkCount, rev, ckey) {
539
+ if (!(rev >= -1)) throw new Error("CHECK constraint failed: rev >= -1");
540
+ const had = files.get(path);
541
+ if (had === undefined) { sorted = null; used += __RESIDENT_ROW_BYTES; } else unkey(path, had);
542
+ const row = { kind: Number(kind), size: Number(size), chunks: Number(chunkCount), rev: Number(rev), ckey: ckey == null ? null : String(ckey) };
543
+ files.set(path, row);
544
+ if (row.ckey !== null) { let set = byKey.get(row.ckey); if (!set) { set = new Set(); byKey.set(row.ckey, set); } set.add(path); }
545
+ },
546
+ fileSetRev(path, rev) { const row = files.get(path); if (row !== undefined) row.rev = Number(rev); },
547
+ fileStampOwn(path, rev) { const row = files.get(path); if (row !== undefined && row.rev === __RK_OWN_WRITE) row.rev = Number(rev); },
548
+ fileDelete(path) { dropFile(path); },
549
+ fileByKey(ckey, notPath) {
550
+ const set = byKey.get(String(ckey));
551
+ if (!set) return undefined;
552
+ for (const path of set) {
553
+ const row = files.get(path);
554
+ if (path !== notPath && row && row.rev >= 0) return { path, kind: row.kind, size: row.size, chunks: row.chunks };
555
+ }
556
+ return undefined;
557
+ },
558
+ filePaths(from, to) { return inRange(from, to); },
559
+ fileAny(from, to) { const list = paths(); const i = lowerBound(list, from); return i < list.length && (to === null || cmp(list[i], to) < 0); },
560
+ fileRevsIn(from, to) { return inRange(from, to).map((path) => ({ path, rev: files.get(path).rev })); },
561
+ fileAllPaths() { return [...paths()]; },
562
+ fileRows() { return paths().map((path) => { const row = files.get(path); return { path, rev: row.rev, ckey: row.ckey }; }); },
563
+ fileOwnPaths() { return paths().filter((path) => files.get(path).rev === __RK_OWN_WRITE); },
564
+ fileStats() { let b = 0; for (const row of files.values()) b += row.size; return { n: files.size, b }; },
565
+ chunkFirst(path) { const parts = chunks.get(path); const cell = parts && parts.get(0); return cell === undefined ? undefined : copy(cell); },
566
+ chunkParts(path) {
567
+ const parts = chunks.get(path);
568
+ if (!parts) return [];
569
+ return [...parts.keys()].sort((a, b) => a - b).map((part) => copy(parts.get(part)));
570
+ },
571
+ chunkAddText(path, part, txt) { addChunk(path, Number(part), { txt: String(txt), bin: null }, false); },
572
+ chunkAddBin(path, part, bin) { addChunk(path, Number(part), { txt: null, bin: __residentBytes(bin).slice() }, false); },
573
+ chunkSetBin(path, part, bin) { addChunk(path, Number(part), { txt: null, bin: __residentBytes(bin).slice() }, true); },
574
+ chunkCopy(to, from) {
575
+ const parts = chunks.get(from);
576
+ if (!parts) return;
577
+ // One statement in SQLite: a conflicting part fails the whole copy, and nothing lands.
578
+ const held = chunks.get(to);
579
+ if (held) for (const part of parts.keys()) if (held.has(part)) throw new Error("UNIQUE constraint failed: chunk.path, chunk.part");
580
+ for (const [part, cell] of [...parts]) addChunk(to, part, copy(cell), false);
581
+ },
582
+ chunkDelete(path) { dropChunks(path); },
583
+ dropOwn() {
584
+ const own = new Set(paths().filter((path) => files.get(path).rev === __RK_OWN_WRITE));
585
+ dropWhere((row) => row.rev !== __RK_OWN_WRITE, (path) => !own.has(path));
586
+ },
587
+ dropDated() {
588
+ const own = new Set(paths().filter((path) => files.get(path).rev === __RK_OWN_WRITE));
589
+ dropWhere((row) => row.rev === __RK_OWN_WRITE, (path) => own.has(path));
590
+ },
591
+ dropDatedFiles() {
592
+ const dated = new Set(paths().filter((path) => files.get(path).rev !== __RK_OWN_WRITE));
593
+ dropWhere((row) => row.rev === __RK_OWN_WRITE, (path) => !dated.has(path));
594
+ },
595
+ clear() {
596
+ files.clear(); chunks.clear(); meta.clear(); ns.clear(); byKey.clear();
597
+ sorted = null; used = 0; names = 0; namespaceBytes = 0;
598
+ },
599
+ nsPut(parent, name, kind, size, mode, uid, gid, atime, mtime, ctime, ino, rev, target) {
600
+ let dir = ns.get(parent);
601
+ const previous = dir?.get(name);
602
+ const bytes = __namespaceRowBytes(parent, name, target);
603
+ const growth = bytes - (previous === undefined ? 0 : __namespaceRowBytes(parent, name, previous.target));
604
+ if (__residentInHeap && __residentCap !== null && used + __residentHeldBytes + __residentNamespaceOverlayBytes + __residentNamespaceReserveBytes + growth > __residentCap) {
605
+ const cause = "namespace storage budget exceeded (" + __residentCap + " bytes)";
606
+ __nsMarkReady(__residentT, false, cause);
607
+ throw Object.assign(new Error(cause), { code: "ENOSPC" });
608
+ }
609
+ if (!dir) { dir = new Map(); ns.set(parent, dir); }
610
+ if (previous === undefined) names++;
611
+ used += growth; namespaceBytes += growth;
612
+ dir.set(name, {
613
+ kind: Number(kind), size: Number(size), mode: Number(mode), uid: Number(uid), gid: Number(gid),
614
+ atime: Number(atime), mtime: Number(mtime), ctime: Number(ctime), ino: Number(ino), rev: Number(rev),
615
+ target: target == null ? null : String(target),
616
+ });
617
+ },
618
+ nsGet(parent, name) { const row = nsRow(parent, name); return row === undefined ? undefined : { ...row }; },
619
+ nsDelete(parent, name) { nsDrop(parent, name); },
620
+ nsDeleteChildren(parent) { nsDropParent(parent); },
621
+ nsDeleteParents(from, to) {
622
+ for (const parent of [...ns.keys()]) if (cmp(parent, from) >= 0 && (to === null || cmp(parent, to) < 0)) nsDropParent(parent);
623
+ },
624
+ nsClear() { used -= namespaceBytes; names = 0; namespaceBytes = 0; ns.clear(); },
625
+ nsBytes() { return namespaceBytes; },
626
+ nsKeys() {
627
+ const out = [];
628
+ for (const parent of [...ns.keys()].sort(byName)) for (const name of [...ns.get(parent).keys()].sort(byName)) out.push({ parent, name });
629
+ return out;
630
+ },
631
+ nsOfKind(kind) {
632
+ const out = [];
633
+ for (const parent of [...ns.keys()].sort(byName)) {
634
+ const dir = ns.get(parent);
635
+ for (const name of [...dir.keys()].sort(byName)) if (dir.get(name).kind === Number(kind)) out.push({ parent, name });
636
+ }
637
+ return out;
638
+ },
639
+ nsChildren(parent) {
640
+ const dir = ns.get(parent);
641
+ if (!dir) return [];
642
+ return [...dir.keys()].sort(byName).map((name) => ({ name, kind: dir.get(name).kind }));
643
+ },
644
+ nsCount() { return names; },
645
+ };
646
+ }
647
+
648
+ /**
649
+ * A one-shot's store: the heap backing, capped at `budget` bytes and never
650
+ * growing past it (there is no ledger row to grow).
651
+ */
652
+ function __residentBindInMemory(budget) {
653
+ const t = __residentBindTables(__residentTablesInMemory());
654
+ __residentInHeap = true;
655
+ __residentFacet = null;
656
+ __residentCap = Number(budget);
657
+ __residentNamespaceOverlayBytes = 0;
658
+ __residentNamespaceReserveBytes = 0;
659
+ return t;
660
+ }
661
+
662
+ /**
663
+ * Bind the store to its tables: a facet's SQLite (__residentBind), or a
664
+ * one-shot's heap (__residentTablesInMemory).
665
+ *
666
+ * 'file' carries one row per path, so existence, size and kind are one query
667
+ * that never touches content. 'chunk' carries the bytes. Splitting them is
668
+ * what makes _statLadder and __fileExists cheap: the common case reads a
669
+ * short row and never pages a megabyte of blob in to answer "is it there".
670
+ * 'rev' is never undated, and that IS the coherence guarantee rather than a
671
+ * note about it. An undated row can never be invalidated, so it would be
672
+ * served stale forever. The SQLite backing makes the column NOT NULL, so such
673
+ * a row cannot be written; this is the r2-cache posture (an unverifiable key
674
+ * cannot be constructed) moved to the only storage layer this store has.
675
+ *
676
+ * __RK_OWN_WRITE is the one negative value: this facet's own bytes that the
677
+ * authority has not acknowledged, which are strictly newer than anything it
678
+ * can report and are always readable. It is a real provenance, not an absence
679
+ * of one.
680
+ */
681
+ function __residentBindTables(t) {
682
+ // A store can outlive its process. Own rows left by an earlier one are
683
+ // writes that never reached the authority or cells whose acknowledgement it
684
+ // never saw: neither can be dated, so neither is a file.
685
+ t.dropOwn();
686
+ __residentT = t;
687
+ __residentReady = true;
688
+ __nsLoadLinks(t);
689
+ __nsOk = t.metaGet("ns") === "1";
690
+ return t;
691
+ }
692
+
693
+ function __residentRequire() {
694
+ if (!__residentReady) {
695
+ throw new Error(
696
+ "Nimbus: the resident store was read before its facet bound its SQLite. " +
697
+ "__residentBind(ctx) must run in the process DO constructor, ahead of the program."
698
+ );
699
+ }
700
+ if (__residentSealed) {
701
+ throw new Error(
702
+ "Nimbus: the resident store is sealed — " + __residentSealReason + ". " +
703
+ "__residentAdmit(fsAcquire(...)) must apply the authority's delta before " +
704
+ "the program runs; serving a row now would serve bytes of unknown age."
705
+ );
706
+ }
707
+ return __residentT;
708
+ }
709
+
710
+ /** The persisted cursor, or null when this store has never been populated. */
711
+ function __residentCursor() {
712
+ if (!__residentReady) return null;
713
+ const epoch = __residentT.metaGet("epoch");
714
+ const rev = __residentT.metaGet("rev");
715
+ return epoch === undefined || rev === undefined ? null : { epoch, rev: Number(rev) };
716
+ }
717
+
718
+ /** Stamp the store with the authority state its rows are known-good at. */
719
+ function __residentWriteCursor(t, cursor) {
720
+ t.metaSet("epoch", String(cursor.epoch));
721
+ t.metaSet("rev", String(Number(cursor.rev)));
722
+ }
723
+
724
+ /**
725
+ * Apply one authority delta and unseal.
726
+ *
727
+ * `result` is exactly what `supervisor.fsAcquire(epoch, cursor)` returns
728
+ * (`VfsAcquireResult` — runtime/os-contracts.ts), so this store slots into the
729
+ * ACQUIRE/RELEASE protocol the shims already run rather than beside it. One
730
+ * cache, one invariant.
731
+ *
732
+ * Returns the paths that WERE held and are now gone, which is what
733
+ * `_acquireAndRefetch` re-reads live — the same contract `_acquireBarrier`
734
+ * already has — and how many named rows were kept because their revision
735
+ * already covers the report (this facet's own writes coming back).
736
+ */
737
+ function __residentAdmit(result) {
738
+ if (!__residentReady) throw new Error("Nimbus: __residentAdmit before __residentBind");
739
+ const t = __residentT;
740
+ const dropped = [];
741
+ const relist = [];
742
+ let kept = 0, pushed = 0;
743
+ const epoch = result && result.epoch != null ? String(result.epoch) : null;
744
+ const rev = result && result.rev != null ? Number(result.rev) : null;
745
+ if (epoch === null || rev === null) {
746
+ throw new Error(
747
+ "Nimbus: fsAcquire returned no cursor; the resident store stays sealed " +
748
+ "rather than serving rows it cannot date"
749
+ );
750
+ }
751
+ // Answers can be admitted out of the order they were served in (two
752
+ // barriers in flight). One older than the store's cursor describes a state
753
+ // the store has already moved past: admitting it would move the cursor
754
+ // back and install its bytes over newer ones. Within an epoch the cursor
755
+ // only moves forward, and only entries above it are applied.
756
+ const at = __residentCursor();
757
+ const floor = at !== null && !__residentSealed && at.epoch === epoch && !(result && result.poison) ? at.rev : null;
758
+ if (floor !== null && rev <= floor) {
759
+ return { dropped, kept, pushed: 0, relist, cursor: at, stale: rev < floor };
760
+ }
761
+ // The namespace moves with the cursor or not at all: a delta without stats,
762
+ // or a poison, leaves it describing a revision the cursor has left, so it
763
+ // stops answering until a listing restores it.
764
+ // An answer naming no path changes no name, with or without stats.
765
+ const namespaced = __nsOk && !!result && !result.poison
766
+ && (result.namespace === true || (Array.isArray(result.paths) && result.paths.length === 0))
767
+ && (!Array.isArray(result.paths) || result.paths.every((entry) => entry.stat === null || __nsDescribes(entry.stat)));
768
+ if (!namespaced) {
769
+ __nsMarkReady(t, false, !result || result.poison ? "the session's change log was truncated past this process's cursor"
770
+ : "a change reported without the stat every name needs");
771
+ }
772
+ if (!result || result.poison) {
773
+ // A delta admission has no absolute listing to vouch for a row, so a
774
+ // poison here means nothing the authority dated can be kept.
775
+ //
776
+ // This is the LAST resort, not the poison policy.
777
+ // `__residentSynchronizeFromSupervisor` repairs the same poison against
778
+ // absolute per-path revisions and keeps every row it can prove current;
779
+ // dropping the store is what made a poison cost a whole filesystem.
780
+ __residentDropDated();
781
+ } else if (Array.isArray(result.paths)) {
782
+ for (const entry of result.paths) {
783
+ if (floor !== null && Number(entry.rev) <= floor) continue;
784
+ const path = entry.path;
785
+ if (namespaced && __nsReady()) {
786
+ const again = __nsApplyEntry(t, entry);
787
+ if (again !== null) relist.push(again);
788
+ }
789
+ // A row at or above the reported revision already holds that mutation —
790
+ // this facet's own write coming back, or bytes fetched after it; anything
791
+ // else is someone else's and is dropped. A row of own bytes
792
+ // (__RK_OWN_WRITE) is newer than any authority revision by construction
793
+ // and is kept — read-your-writes survives a peer's mutation of the same
794
+ // path, and the report is adjudicated when the write's own revision
795
+ // arrives (the shims note it for the ledger before admitting).
796
+ // A subtree-scoped or structural entry covers every row at or under its
797
+ // path, each judged the same way: something beneath it this facet may
798
+ // not see changed, or the directory went, or who may enter it did. A
799
+ // row there may be stale, or no longer this facet's to be served.
800
+ if (entry.subtree || entry.structural) {
801
+ const end = __residentPrefixEnd(path + "/");
802
+ for (const row of t.fileRevsIn(path + "/", end)) {
803
+ const stamped = Number(row.rev);
804
+ if (stamped === __RK_OWN_WRITE || stamped >= Number(entry.rev)) continue;
805
+ t.chunkDelete(row.path);
806
+ t.fileDelete(row.path);
807
+ dropped.push(String(row.path));
808
+ }
809
+ for (const [heldPath, heldCell] of [...__residentHeld]) {
810
+ if (!heldPath.startsWith(path + "/") || heldCell.rev === __RK_OWN_WRITE || heldCell.rev >= Number(entry.rev)) continue;
811
+ __residentForgetHeld(heldPath);
812
+ }
813
+ // Names under it may have become hidden or visible without being
814
+ // named: the namespace relists that subtree.
815
+ if (namespaced && entry.subtree && entry.stat && entry.stat.type === "directory") relist.push(path);
816
+ }
817
+ let held = false, stamped = 0, heldKey = null;
818
+ const row = t.fileRevKey(path);
819
+ if (row !== undefined) {
820
+ held = true; stamped = Number(row.rev); heldKey = row.ckey == null ? null : String(row.ckey);
821
+ }
822
+ if (held && (stamped === __RK_OWN_WRITE || stamped >= Number(entry.rev))) { kept++; continue; }
823
+ // A cell held in memory (an own write the store had no room for) is
824
+ // judged the same way; superseded, it goes, and the report's bytes, if
825
+ // any, are held as the store can.
826
+ const heldCell = __residentHeld.get(path);
827
+ if (heldCell !== undefined) {
828
+ if (heldCell.rev === __RK_OWN_WRITE || heldCell.rev >= Number(entry.rev)) { kept++; continue; }
829
+ __residentForgetHeld(path);
830
+ }
831
+ // The same bytes under a newer revision (a chmod, a touch, a rewrite
832
+ // with identical content): kept, and dated at the report.
833
+ if (held && heldKey !== null && entry.contentKey != null && heldKey === String(entry.contentKey)) {
834
+ t.fileSetRev(path, Number(entry.rev));
835
+ kept++;
836
+ continue;
837
+ }
838
+ // Pushed content: the file's bytes at the answer's revision, so dating
839
+ // them at the path's own revision cannot be newer than they are.
840
+ if (entry.bytes != null && entry.stat && entry.stat.type === "file") {
841
+ if (__residentPut(t, path, __residentBytes(entry.bytes), Number(entry.rev), entry.contentKey)) pushed++;
842
+ else if (held) dropped.push(path);
843
+ continue;
844
+ }
845
+ if (!held) continue;
846
+ t.chunkDelete(path);
847
+ t.fileDelete(path);
848
+ dropped.push(path);
849
+ }
850
+ }
851
+ __residentWriteCursor(t, { epoch, rev });
852
+ __residentSealed = false;
853
+ __residentSealReason = "";
854
+ return { dropped, kept, pushed, relist, cursor: { epoch, rev } };
855
+ }
856
+
857
+ /**
858
+ * Drop every row the authority dated, and nothing else: what a poison does to
859
+ * the rows when nothing can vouch for them. The cursor is left alone, because
860
+ * a store that holds no dated row cannot hold a stale one whatever its cursor
861
+ * says.
862
+ *
863
+ * This facet's own bytes are not the authority's to vouch for, and stay:
864
+ * dropping a row a write or mutation still in flight owns would hand the next
865
+ * fill the pre-write bytes. Its acknowledgement dates it, or evicts it, when
866
+ * it lands.
867
+ *
868
+ * Two statements rather than a delete per path: at pi scale that is 7,141
869
+ * rows, and this is on the hot path (measured 45 ms whole-store, vs a per-row
870
+ * walk).
871
+ */
872
+ function __residentDropDated() {
873
+ if (!__residentReady) throw new Error("Nimbus: __residentDropDated before __residentBind");
874
+ __residentT.dropDated();
875
+ for (const [path, held] of [...__residentHeld]) if (held.rev !== __RK_OWN_WRITE) __residentForgetHeld(path);
876
+ }
877
+
878
+ /** Re-seal — for a store whose backing is being replaced (slot handover). */
879
+ function __residentSeal(reason) {
880
+ __residentSealed = true;
881
+ __residentSealReason = String(reason || "resealed");
882
+ }
883
+
884
+ /** One row of 'file', or undefined. The existence/shape query. */
885
+ function __residentHead(path) {
886
+ return __residentRequire().fileHead(path);
887
+ }
888
+
889
+ /**
890
+ * The synchronous read. THE point of this module: a plain, non-async function
891
+ * that returns content, so a fs.readFileSync call stack never has to yield.
892
+ *
893
+ * Returns exactly what __vfsBundle has always returned — a string, a
894
+ * Uint8Array, or an { error: "EACCES" } denial — or undefined for a cell that
895
+ * is not held.
896
+ */
897
+ function __residentGet(path) {
898
+ if (globalThis.__nimbusProfileStaged) globalThis.__nimbusProfileStaged.delete(String(path).replace(/^\/+/, ""));
899
+ const head = __residentHead(path);
900
+ if (head === undefined) {
901
+ const held = __residentHeld.get(path);
902
+ if (held === undefined) return undefined;
903
+ __residentHeld.delete(path);
904
+ __residentHeld.set(path, held);
905
+ return held.cell;
906
+ }
907
+ const kind = Number(head.kind);
908
+ if (kind === __RK_DENIED) return { error: "EACCES" };
909
+ const t = __residentRequire();
910
+ const chunks = Number(head.chunks);
911
+ if (chunks === 1) {
912
+ const row = t.chunkFirst(path);
913
+ if (row === undefined) return undefined;
914
+ return kind === __RK_TEXT ? row.txt : __residentBytes(row.bin);
915
+ }
916
+ // Chunked: a file past the single-value ceiling. Reassembled here so callers
917
+ // never learn the file was split.
918
+ const parts = [];
919
+ for (const row of t.chunkParts(path)) {
920
+ parts.push(kind === __RK_TEXT ? row.txt : __residentBytes(row.bin));
921
+ }
922
+ if (parts.length === 0) return undefined;
923
+ if (kind === __RK_TEXT) return parts.join("");
924
+ const total = parts.reduce((n, p) => n + p.byteLength, 0);
925
+ const out = new Uint8Array(total);
926
+ let at = 0;
927
+ for (const p of parts) { out.set(p, at); at += p.byteLength; }
928
+ return out;
929
+ }
930
+
931
+ function __residentBytes(value) {
932
+ if (value instanceof Uint8Array) return value;
933
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
934
+ return new Uint8Array(0);
935
+ }
936
+
937
+ /**
938
+ * Write one cell, chunking past the single-value ceiling.
939
+ *
940
+ * `rev` is the authority revision these bytes were read at, and it is what
941
+ * makes the row datable. Omitting it means the cell is this facet's own
942
+ * unflushed write, stored as __RK_OWN_WRITE — always readable
943
+ * (read-your-writes) and never mistaken for something the authority vouched
944
+ * for. There is no third case: the column refuses one.
945
+ */
946
+ function __residentPut(t, path, cell, rev, ckey) {
947
+ // Whatever this puts supersedes a held cell, held or not.
948
+ __residentForgetHeld(path);
949
+ t.chunkDelete(path);
950
+ const stamp = typeof rev === "number" ? rev : __RK_OWN_WRITE;
951
+ if (cell && typeof cell === "object" && cell.error) {
952
+ t.filePut(path, __RK_DENIED, 0, 0, stamp, null);
953
+ return true;
954
+ }
955
+ const isText = typeof cell === "string";
956
+ const kind = isText ? __RK_TEXT : __RK_BINARY;
957
+ const body = isText ? cell : __residentBytes(cell);
958
+ const size = isText ? body.length : body.byteLength;
959
+ // Chunk on the encoded length for text, since the ceiling is on bytes and a
960
+ // multi-byte string would otherwise cross it while measuring short.
961
+ const limit = isText ? Math.floor(__RESIDENT_CHUNK_BYTES / 3) : __RESIDENT_CHUNK_BYTES;
962
+ const chunks = Math.max(1, Math.ceil(size / limit));
963
+ // Not admitted: nothing is held for the path (its old bytes are gone above).
964
+ if (!__residentFits(__residentCellCost(cell), stamp !== __RK_OWN_WRITE)) {
965
+ t.fileDelete(path);
966
+ if (stamp === __RK_OWN_WRITE) __residentHold(path, cell);
967
+ return false;
968
+ }
969
+ for (let part = 0; part < chunks; part++) {
970
+ const slice = body.slice(part * limit, (part + 1) * limit);
971
+ if (isText) t.chunkAddText(path, part, slice);
972
+ else t.chunkAddBin(path, part, slice);
973
+ }
974
+ t.filePut(path, kind, size, chunks, stamp, stamp === __RK_OWN_WRITE || ckey == null ? null : String(ckey));
975
+ return true;
976
+ }
977
+
978
+ /** One chunk row, written as it arrives. The streaming filler's primitive. */
979
+ function __residentPutChunk(t, path, part, bytes) {
980
+ if (!__residentFits(Math.ceil(bytes.byteLength * 1.01) + __RESIDENT_ROW_BYTES + 4096)) return false;
981
+ t.chunkSetBin(path, part, bytes);
982
+ return true;
983
+ }
984
+
985
+ /**
986
+ * The head row, written LAST by the streaming filler.
987
+ *
988
+ * Order is the durability rule here: without a head row `__residentHead`
989
+ * returns undefined and the path simply misses, falling through to the
990
+ * supervisor exactly as it would have. So a fill interrupted part way through
991
+ * a large file leaves chunks nothing can read, never a short file something
992
+ * can. Writing the head first would invert that into a truncated read.
993
+ */
994
+ function __residentPutHead(t, path, kind, size, chunks, rev, ckey) {
995
+ t.filePut(
996
+ path, kind, size, chunks, typeof rev === "number" ? rev : __RK_OWN_WRITE,
997
+ typeof rev === "number" && ckey != null ? String(ckey) : null,
998
+ );
999
+ }
1000
+
1001
+ /**
1002
+ * Hold `path` with the bytes of a dated row whose content identity is
1003
+ * `ckey`, by copying rows inside this store: equal keys are equal bytes, so
1004
+ * no fetch is needed. Returns whether a source existed.
1005
+ */
1006
+ function __residentCopyByKey(t, path, ckey, rev) {
1007
+ const source = t.fileByKey(String(ckey), path);
1008
+ if (source === undefined) return false;
1009
+ if (!__residentFits(Number(source.size) + (Number(source.chunks) + 1) * __RESIDENT_ROW_BYTES)) return false;
1010
+ t.chunkDelete(path);
1011
+ t.chunkCopy(path, String(source.path));
1012
+ t.filePut(path, Number(source.kind), Number(source.size), Number(source.chunks), rev, String(ckey));
1013
+ return true;
1014
+ }
1015
+
1016
+ function __residentDelete(t, path) {
1017
+ const had = __residentHead(path) !== undefined || __residentHeld.has(path);
1018
+ __residentForgetHeld(path);
1019
+ t.chunkDelete(path);
1020
+ t.fileDelete(path);
1021
+ return had;
1022
+ }
1023
+
1024
+ /**
1025
+ * What a row is dated at: its revision, __RK_OWN_WRITE for this facet's own
1026
+ * unacknowledged bytes, or undefined when the path is not held.
1027
+ */
1028
+ function __residentProvenance(path) {
1029
+ return __residentRequire().fileRev(path);
1030
+ }
1031
+
1032
+ /**
1033
+ * Date this facet's own bytes with the revision the authority acknowledged
1034
+ * them at: the revision a flushed write produced, or the one an own mutation's
1035
+ * receipt settles on. The store's half of the stamp `__vfsBundleRevisions`
1036
+ * carries in heap (_shared/vfs-write-ledger.ts), kept in the row it describes
1037
+ * so the two cannot drift.
1038
+ *
1039
+ * Only a row still holding own bytes is dated. One that was dropped or
1040
+ * replaced meanwhile holds something this acknowledgement says nothing about.
1041
+ *
1042
+ * Without it nothing dates an own row, and __RK_OWN_WRITE is the revision a
1043
+ * delta never evicts: a file this process wrote would serve its own bytes over
1044
+ * every later write to it by anyone.
1045
+ */
1046
+ function __residentStamp(path, rev) {
1047
+ const heldCell = __residentHeld.get(path);
1048
+ if (heldCell !== undefined && heldCell.rev === __RK_OWN_WRITE) heldCell.rev = rev;
1049
+ const t = __residentRequire();
1050
+ const size = t.fileOwnSize(path);
1051
+ if (size === undefined) return;
1052
+ t.fileStampOwn(path, rev);
1053
+ __nsNoteOwnFile(path, size, rev);
1054
+ }
1055
+
1056
+ /**
1057
+ * Hold a dated row as this facet's own for the window of one of its own
1058
+ * mutations (a ranged write, a truncate, a chmod), and return the revision it
1059
+ * was dated at: the mutation's receipt is judged against it when the window
1060
+ * closes (`__nimbusEndOwnMutation`). Undefined when there is no dated row —
1061
+ * an own row is held already, and an absent one has nothing to protect.
1062
+ *
1063
+ * The hold is what keeps a barrier that reports the mutation before its own
1064
+ * response arrives from evicting the cell the mutation is about to overlay.
1065
+ * The supervisor answers fsAcquire at once, while a write's response waits on
1066
+ * the Durable Object's output gate, so that ordering is ordinary.
1067
+ */
1068
+ function __residentLease(path) {
1069
+ const t = __residentRequire();
1070
+ const rev = t.fileRev(path);
1071
+ if (rev === undefined || rev === __RK_OWN_WRITE) return undefined;
1072
+ t.fileSetRev(path, __RK_OWN_WRITE);
1073
+ return rev;
1074
+ }
1075
+
1076
+ /**
1077
+ * Install bytes a live read returned, dated `rev`: the cursor the read was
1078
+ * issued under, so the bytes hold every mutation at or below it. An async read
1079
+ * must write through, or the next synchronous read goes back in time relative
1080
+ * to what the program was just handed (protocol §5.9).
1081
+ *
1082
+ * Refused over a row of own bytes, which are newer than anything a read can
1083
+ * return, and over a row dated after `rev`, which is newer than this read can
1084
+ * vouch for. Returns whether the bytes were installed.
1085
+ */
1086
+ function __residentFill(path, cell, rev) {
1087
+ if (typeof rev !== "number" || rev < 0) {
1088
+ throw new Error(
1089
+ "Nimbus: refusing to fill '" + path + "' with no authority revision. " +
1090
+ "An undated row cannot be invalidated, so it would be served stale forever."
1091
+ );
1092
+ }
1093
+ const t = __residentRequire();
1094
+ const held = t.fileRev(path);
1095
+ if (held !== undefined && (held === __RK_OWN_WRITE || held > rev)) return false;
1096
+ const heldCell = __residentHeld.get(path);
1097
+ if (heldCell !== undefined && (heldCell.rev === __RK_OWN_WRITE || heldCell.rev > rev)) return false;
1098
+ return __residentPut(t, path, cell, rev);
1099
+ }
1100
+
1101
+ /** Every held path. Backs the Object.keys / for-in scans in the shims. */
1102
+ function __residentKeys() {
1103
+ return __residentRequire().fileAllPaths();
1104
+ }
1105
+
1106
+ /**
1107
+ * Every path held as this facet's own unacknowledged bytes: the rows a
1108
+ * poison's repair keeps without a revision to vouch for them, whose reports
1109
+ * the shims deliver to their owners (`_settleSkippedReports`).
1110
+ */
1111
+ function __residentOwnPaths() {
1112
+ return __residentRequire().fileOwnPaths();
1113
+ }
1114
+
1115
+ /**
1116
+ * The exclusive upper bound of a prefix under the BINARY collation SQLite gives
1117
+ * a TEXT PRIMARY KEY — the successor of the last code unit it can increment.
1118
+ * Null when there is none (an empty prefix, or one ending in U+FFFF), and the
1119
+ * caller then scans open-ended, which is correct and merely slower.
1120
+ *
1121
+ * Every caller passes a directory prefix ending in '/', so the increment is
1122
+ * '/' → '0' and UTF-16 and UTF-8 orderings agree exactly. They diverge only
1123
+ * around the surrogate range, which no path segment separator lives in.
1124
+ */
1125
+ function __residentPrefixEnd(prefix) {
1126
+ for (let i = prefix.length - 1; i >= 0; i--) {
1127
+ const code = prefix.charCodeAt(i);
1128
+ if (code < 0xffff) return prefix.slice(0, i) + String.fromCharCode(code + 1);
1129
+ }
1130
+ return null;
1131
+ }
1132
+
1133
+ /**
1134
+ * Paths under a directory prefix, as a bounded index range scan.
1135
+ *
1136
+ * The shims ask this constantly — readdirSync's union with the manifest,
1137
+ * existsSync of a directory, node's own module resolution, rm -rf, watch
1138
+ * globbing. In heap it was a for-in over every key; here it must be a range
1139
+ * scan on both ends, because that is the only version that is actually cheaper:
1140
+ * an open-ended `path >= prefix` with a LIKE filter still visits every row
1141
+ * after the prefix, and going back through the object protocol visits every row
1142
+ * AND reads every file's bytes.
1143
+ *
1144
+ * Two bounds also mean no LIKE, so a real path containing '%' or '_' needs no
1145
+ * escaping — those are only wildcards to an operator this no longer uses.
1146
+ */
1147
+ function __residentKeysUnder(prefix) {
1148
+ const from = String(prefix);
1149
+ const to = __residentPrefixEnd(from);
1150
+ return __residentRequire().filePaths(from, to);
1151
+ }
1152
+
1153
+ /**
1154
+ * Whether ANY path is held under a prefix — the same range, stopped at the
1155
+ * first row. The hot half: it is what answers "is this a directory" on node's
1156
+ * module-resolution path, where materialising the subtree would be the cost all
1157
+ * over again.
1158
+ */
1159
+ function __residentHasUnder(prefix) {
1160
+ const from = String(prefix);
1161
+ const to = __residentPrefixEnd(from);
1162
+ return __residentRequire().fileAny(from, to);
1163
+ }
1164
+
1165
+ // ── The namespace ───────────────────────────────────────────────────────────
1166
+ //
1167
+ // Every name the credential can see, exact at the store's cursor: filled from
1168
+ // a complete listing, then moved forward by each ACQUIRE delta, whose entries
1169
+ // carry the path's stat at the answer's revision (null when it is gone). A
1170
+ // change of presence or stat is always a logged mutation of that path, except
1171
+ // what vanishes beneath a removed or replaced directory, which the delta
1172
+ // application drops by subtree. So a synchronous stat, exists or readdir is
1173
+ // answered exactly, and an absent name is known to be absent.
1174
+ //
1175
+ // This table holds the AUTHORITY's view only. The process's own pending
1176
+ // structural effects are an overlay kept by the shims, retired once a barrier
1177
+ // begun after they settled has applied.
1178
+
1179
+ const __NS_FILE = 0;
1180
+ const __NS_DIR = 1;
1181
+ const __NS_LINK = 2;
1182
+ /** Symlink hops before a resolution is ELOOP, as Linux's MAXSYMLINKS. */
1183
+ const __NS_MAX_HOPS = 40;
1184
+ /** Paths of every symlink in the table: resolution walks only past these. */
1185
+ let __nsLinks = new Set();
1186
+ /** The credential names are judged traversable for. */
1187
+ let __nsCred = null;
1188
+
1189
+ function __nsSetCred(cred) { __nsCred = cred || null; }
1190
+
1191
+ function __nsSplit(k) {
1192
+ const i = k.lastIndexOf("/");
1193
+ return i < 0 ? ["", k] : [k.slice(0, i), k.slice(i + 1)];
1194
+ }
1195
+
1196
+ function __nsKindCode(type) {
1197
+ return type === "directory" ? __NS_DIR : type === "symlink" ? __NS_LINK : __NS_FILE;
1198
+ }
1199
+
1200
+ function __nsLoadLinks(t) {
1201
+ __nsLinks = new Set();
1202
+ for (const row of t.nsOfKind(__NS_LINK)) {
1203
+ const parent = String(row.parent);
1204
+ __nsLinks.add(parent ? parent + "/" + String(row.name) : String(row.name));
1205
+ }
1206
+ }
1207
+
1208
+ /** Whether the table describes the store's cursor. */
1209
+ function __nsReady() {
1210
+ return __residentReady && !__residentSealed && __nsOk;
1211
+ }
1212
+
1213
+ /** The persisted 'ns' flag, mirrored in heap because every stat asks it. */
1214
+ let __nsOk = false;
1215
+ /** Why the namespace stopped answering, while it does not: what a refused sync call names. */
1216
+ let __nsCause = "the process has not listed it yet";
1217
+
1218
+ function __nsMarkReady(t, ready, cause) {
1219
+ t.metaSet("ns", ready ? "1" : "0");
1220
+ __nsOk = !!ready;
1221
+ if (!ready) __nsCause = String(cause || "a change it could not follow");
1222
+ }
1223
+
1224
+ /** Why the namespace is not answering (meaningful only while __nsReady() is false). */
1225
+ function __nsNotReadyCause() {
1226
+ if (!__residentReady) return "the process's store is not bound";
1227
+ if (__residentSealed) return __residentSealReason || "the store has not reconciled with the session";
1228
+ return __nsCause;
1229
+ }
1230
+
1231
+ /**
1232
+ * Whether a stat from the authority describes its name: a type, and the
1233
+ * mode, owner and group every permission check reads. One without them is
1234
+ * no record. Stored, its missing fields would become zeros, a root-owned
1235
+ * mode-0 file the authority never reported. The namespace then stops
1236
+ * answering (as for a delta without stats) until a listing that does
1237
+ * describe every name restores it.
1238
+ */
1239
+ function __nsDescribes(stat) {
1240
+ if (!stat || typeof stat.type !== "string") return false;
1241
+ for (const field of ["mode", "uid", "gid"]) {
1242
+ if (stat[field] === null || stat[field] === undefined || !Number.isInteger(Number(stat[field]))) return false;
1243
+ }
1244
+ return true;
1245
+ }
1246
+
1247
+ function __nsPut(t, k, stat, rev, target) {
1248
+ const [parent, name] = __nsSplit(k);
1249
+ const kind = __nsKindCode(stat.type);
1250
+ t.nsPut(
1251
+ parent, name, kind, Number(stat.size) || 0, Number(stat.mode) || 0, Number(stat.uid) || 0,
1252
+ Number(stat.gid) || 0, Number(stat.atime) || 0, Number(stat.mtime) || 0, Number(stat.ctime) || 0,
1253
+ Number(stat.ino) || 0, Number(rev) || 0, kind === __NS_LINK ? String(target ?? "") : null,
1254
+ );
1255
+ if (kind === __NS_LINK) __nsLinks.add(k); else __nsLinks.delete(k);
1256
+ }
1257
+
1258
+ // The authority operation may succeed while metadata cannot be cached.
1259
+ // nsPut has sealed the sync view on heap ENOSPC; preserve the async result.
1260
+ function __nsTryPut(t, k, stat, rev, target) {
1261
+ try { __nsPut(t, k, stat, rev, target); return true; }
1262
+ catch (error) {
1263
+ if (__residentInHeap && error && error.code === "ENOSPC") return false;
1264
+ throw error;
1265
+ }
1266
+ }
1267
+
1268
+ /** Delete everything beneath `k`, and `k` itself when `self`. */
1269
+ function __nsDeleteTree(t, k, self) {
1270
+ if (k === "") {
1271
+ t.nsClear();
1272
+ __nsLinks = new Set();
1273
+ return;
1274
+ }
1275
+ const prefix = k + "/";
1276
+ const end = __residentPrefixEnd(prefix);
1277
+ t.nsDeleteChildren(k);
1278
+ t.nsDeleteParents(prefix, end);
1279
+ if (self) {
1280
+ const [parent, name] = __nsSplit(k);
1281
+ t.nsDelete(parent, name);
1282
+ __nsLinks.delete(k);
1283
+ }
1284
+ for (const link of __nsLinks) if (link.startsWith(prefix)) __nsLinks.delete(link);
1285
+ }
1286
+
1287
+ /** The row at exactly `k` (no symlink resolution), or undefined. */
1288
+ function __nsRowAt(t, k) {
1289
+ if (k === "") {
1290
+ return { kind: __NS_DIR, size: 0, mode: 0o40755, uid: 0, gid: 0, atime: 0, mtime: 0, ctime: 0, ino: 1, rev: 0, target: null };
1291
+ }
1292
+ const [parent, name] = __nsSplit(k);
1293
+ return t.nsGet(parent, name);
1294
+ }
1295
+
1296
+ function __nsJoinTarget(linkPath, target) {
1297
+ const t = String(target);
1298
+ const parts = (t.startsWith("/") ? [] : __nsSplit(linkPath)[0].split("/").filter(Boolean));
1299
+ for (const seg of t.split("/")) {
1300
+ if (seg === "" || seg === ".") continue;
1301
+ if (seg === "..") parts.pop(); else parts.push(seg);
1302
+ }
1303
+ return parts.join("/");
1304
+ }
1305
+
1306
+ /**
1307
+ * Resolve `k` through the symlinks the namespace holds: { path, row } for the
1308
+ * name it denotes, null when nothing is there, or "ELOOP". The leaf is
1309
+ * followed only when `followLeaf`. Walks component by component only when a
1310
+ * symlink sits on the path; otherwise it is one lookup.
1311
+ */
1312
+ function __nsResolve(k, followLeaf) {
1313
+ const t = __residentRequire();
1314
+ let path = k;
1315
+ for (let hops = 0; hops <= __NS_MAX_HOPS; hops++) {
1316
+ let redirected = false;
1317
+ if (__nsLinks.size > 0) {
1318
+ const segs = path.split("/");
1319
+ for (let i = 1; i <= segs.length; i++) {
1320
+ const prefix = segs.slice(0, i).join("/");
1321
+ if (!__nsLinks.has(prefix)) continue;
1322
+ if (i === segs.length && !followLeaf) break;
1323
+ const row = __nsRowAt(t, prefix);
1324
+ if (!row || Number(row.kind) !== __NS_LINK) break;
1325
+ const rest = segs.slice(i).join("/");
1326
+ const target = __nsJoinTarget(prefix, row.target);
1327
+ path = rest ? (target ? target + "/" + rest : rest) : target;
1328
+ redirected = true;
1329
+ break;
1330
+ }
1331
+ }
1332
+ if (redirected) continue;
1333
+ const row = __nsRowAt(t, path);
1334
+ return row === undefined ? null : { path, row };
1335
+ }
1336
+ return "ELOOP";
1337
+ }
1338
+
1339
+ /** The entries directly under directory `k` (already resolved): [{ name, kind }]. */
1340
+ function __nsChildren(k) {
1341
+ return __residentRequire().nsChildren(k).map((row) => ({ name: String(row.name), kind: Number(row.kind) }));
1342
+ }
1343
+
1344
+ /** Whether the credential may search directory `row` (POSIX x). */
1345
+ function __nsTraversable(row) {
1346
+ const cred = __nsCred;
1347
+ if (!row || Number(row.kind) !== __NS_DIR) return false;
1348
+ const mode = Number(row.mode) & 0o777;
1349
+ if (!cred) return true;
1350
+ if (Number(cred.uid) === 0) return (mode & 0o111) !== 0;
1351
+ const groups = Array.isArray(cred.groups) ? cred.groups.map(Number) : [];
1352
+ const shift = Number(cred.uid) === Number(row.uid) ? 6
1353
+ : (Number(cred.gid) === Number(row.gid) || groups.includes(Number(row.gid))) ? 3 : 0;
1354
+ return ((mode >> shift) & 1) === 1;
1355
+ }
1356
+
1357
+ /**
1358
+ * Apply one delta entry that carries a stat. Returns a directory whose
1359
+ * subtree must be relisted — one that just became searchable, whose
1360
+ * descendants no delta names — or null.
1361
+ */
1362
+ function __nsApplyEntry(t, entry) {
1363
+ const k = String(entry.path).replace(/^\/+/, "");
1364
+ if (k === "") return null;
1365
+ if (entry.stat === null) { __nsDeleteTree(t, k, true); return null; }
1366
+ const before = __nsRowAt(t, k);
1367
+ if (!__nsTryPut(t, k, entry.stat, entry.rev, entry.linkTarget)) return null;
1368
+ const after = __nsRowAt(t, k);
1369
+ if (Number(after.kind) !== __NS_DIR) { __nsDeleteTree(t, k, false); return null; }
1370
+ const was = before !== undefined && __nsTraversable(before);
1371
+ const now = __nsTraversable(after);
1372
+ if (was && !now) __nsDeleteTree(t, k, false);
1373
+ if (!was && now && before !== undefined) return k;
1374
+ return null;
1375
+ }
1376
+
1377
+ /**
1378
+ * Bring the table to a complete listing: upsert what differs, delete what the
1379
+ * listing no longer names. `entries` are VfsListEntry (path, stat, rev,
1380
+ * linkTarget). Called once per enumeration, after its last page.
1381
+ */
1382
+ function __nsReplace(t, entries) {
1383
+ const listed = new Set();
1384
+ for (const entry of entries) listed.add(String(entry.path).replace(/^\/+/, ""));
1385
+ // Remove stale rows before admitting replacements; do not transiently
1386
+ // charge both versions of a complete namespace against the fixed cap.
1387
+ for (const row of t.nsKeys()) {
1388
+ const parent = String(row.parent);
1389
+ const k = parent ? parent + "/" + String(row.name) : String(row.name);
1390
+ if (!listed.has(k)) { t.nsDelete(parent, String(row.name)); __nsLinks.delete(k); }
1391
+ }
1392
+ // Free shrinking text before growing another surviving row; final-state
1393
+ // admission must not depend on alphabetical replacement order.
1394
+ if (__residentInHeap) {
1395
+ for (const entry of entries) {
1396
+ const k = String(entry.path).replace(/^\/+/, "");
1397
+ if (!k) continue;
1398
+ const [parent, name] = __nsSplit(k);
1399
+ const held = t.nsGet(parent, name);
1400
+ if (held && __namespaceRowBytes(parent, name, held.target) > __namespaceRowBytes(parent, name, entry.linkTarget)) {
1401
+ __nsPut(t, k, entry.stat, entry.rev, entry.linkTarget);
1402
+ }
1403
+ }
1404
+ }
1405
+ for (const entry of entries) {
1406
+ const k = String(entry.path).replace(/^\/+/, "");
1407
+ if (k === "") continue;
1408
+ listed.add(k);
1409
+ const held = __nsRowAt(t, k);
1410
+ const s = entry.stat;
1411
+ if (held !== undefined
1412
+ && Number(held.kind) === __nsKindCode(s.type) && Number(held.size) === Number(s.size)
1413
+ && Number(held.mode) === Number(s.mode) && Number(held.uid) === Number(s.uid)
1414
+ && Number(held.gid) === Number(s.gid) && Number(held.mtime) === Number(s.mtime)
1415
+ && Number(held.ctime) === Number(s.ctime) && Number(held.ino) === Number(s.ino)
1416
+ && Number(held.rev) === Number(entry.rev)
1417
+ && (held.target ?? null) === (entry.linkTarget ?? null)) continue;
1418
+ __nsPut(t, k, s, entry.rev, entry.linkTarget);
1419
+ }
1420
+ }
1421
+
1422
+ /**
1423
+ * Record this facet's own acknowledged write in the namespace before the
1424
+ * delta that reports it: a file it created is then listed at once. Only
1425
+ * when the table has nothing as new, so an authority stat already applied at
1426
+ * or past `rev` is never overwritten by this approximation; the delta that
1427
+ * reports the write replaces it with the authority's stat.
1428
+ */
1429
+ function __nsNoteOwnFile(k, size, rev) {
1430
+ if (!__nsReady()) return;
1431
+ const t = __residentT;
1432
+ const held = __nsRowAt(t, k);
1433
+ if (held !== undefined && Number(held.rev) >= rev) return;
1434
+ const now = Date.now();
1435
+ __nsTryPut(t, k, {
1436
+ // A file the process created: its umask's mode, until the delta reports
1437
+ // the authority's own stat for it.
1438
+ type: "file", size, mode: held ? Number(held.mode) : 0o100666 & ~Number(__nsCred?.umask ?? 0o022),
1439
+ uid: held ? Number(held.uid) : Number(__nsCred?.uid ?? 0),
1440
+ gid: held ? Number(held.gid) : Number(__nsCred?.gid ?? 0),
1441
+ atime: now, mtime: now, ctime: now, ino: held ? Number(held.ino) : 0,
1442
+ }, rev, null);
1443
+ }
1444
+
1445
+ /**
1446
+ * Record what the authority just said of a name this process asked about (an
1447
+ * async stat, read or accepted write): the sync view answers it at once,
1448
+ * as it would after the delta that reports it. Dated at the store's cursor,
1449
+ * so any row as new is kept, and the next delta naming the path replaces it
1450
+ * with the authority's own dating. `stat` null: the authority said it is not
1451
+ * there, and a row older than the cursor goes.
1452
+ */
1453
+ function __nsNoteLiveStat(k, stat) {
1454
+ if (!__nsReady()) return;
1455
+ const cursor = __residentCursor();
1456
+ if (cursor === null) return;
1457
+ const t = __residentT;
1458
+ const held = __nsRowAt(t, k);
1459
+ if (held !== undefined && Number(held.rev) >= cursor.rev) return;
1460
+ if (stat === null) {
1461
+ if (held !== undefined) { const [parent, name] = __nsSplit(k); t.nsDelete(parent, name); __nsLinks.delete(k); }
1462
+ return;
1463
+ }
1464
+ if (!__nsDescribes(stat)) return;
1465
+ __nsTryPut(t, k, stat, cursor.rev, stat.linkTarget ?? stat.target ?? null);
1466
+ }
1467
+
1468
+ /**
1469
+ * Drop every cell AND the cursor, and re-seal.
1470
+ *
1471
+ * This is slot handover, not invalidation: a returned slot must not hand the
1472
+ * next tenant a filesystem, and dropping the rows while leaving a cursor behind
1473
+ * would leave the store claiming to be current at a revision it holds nothing
1474
+ * from. Clearing the cursor forces the next incarnation through
1475
+ * `__residentAdmit` from scratch. `__residentBoot` clears the same way when a
1476
+ * kept store cannot be reconciled.
1477
+ */
1478
+ function __residentClear() {
1479
+ if (!__residentReady) throw new Error("Nimbus: __residentClear before __residentBind");
1480
+ __residentT.clear();
1481
+ // Held cells go with the rows, but for own writes still in flight.
1482
+ for (const [path, held] of [...__residentHeld]) if (held.rev !== __RK_OWN_WRITE) __residentForgetHeld(path);
1483
+ __nsLinks = new Set();
1484
+ __nsOk = false;
1485
+ __residentSeal("the store was cleared");
1486
+ }
1487
+
1488
+ function __residentStats() {
1489
+ if (!__residentReady) throw new Error("Nimbus: __residentStats before __residentBind");
1490
+ const t = __residentT;
1491
+ const counted = t.fileStats();
1492
+ const files = counted === undefined ? 0 : Number(counted.n);
1493
+ const bytes = counted === undefined ? 0 : Number(counted.b);
1494
+ const names = t.nsCount();
1495
+ return {
1496
+ files, bytes, names,
1497
+ namespace: __nsReady(),
1498
+ databaseSize: t.databaseSize(),
1499
+ sealed: __residentSealed,
1500
+ undatedSnapshot: __residentUndated,
1501
+ cursor: __residentCursor(),
1502
+ };
1503
+ }
1504
+
1505
+ /**
1506
+ * Make this incarnation's store servable before the program's first
1507
+ * instruction. The one boot path the resident body takes.
1508
+ *
1509
+ * A COLD store — no persisted cursor — adopts this spawn's own snapshot, the
1510
+ * module bundle dated at the cursor it was read at, and then fills the rest of
1511
+ * the filesystem from the authority. Its rows are current as of the spawn
1512
+ * whether or not the fill completes, which is all the protocol asks of a
1513
+ * process's first block (§5.3); the first ACQUIRE's delta brings them the rest
1514
+ * of the way.
1515
+ *
1516
+ * A KEPT store holds a PREVIOUS process's rows, dated against a cursor this
1517
+ * incarnation has never applied. A durable application's facet is one: it
1518
+ * keeps its `app-slot-` name across launches and its release never deletes
1519
+ * storage, so every relaunch or re-drive of the application opens what its
1520
+ * last process left. The rows are the asset — inside one supervisor
1521
+ * incarnation a relaunch fetches what changed, not the filesystem; across two,
1522
+ * whose revisions are unrelated, the reconcile rebuilds every dated row — and
1523
+ * they are also arbitrarily old: only the reconcile says which of them still
1524
+ * describe the filesystem, and nothing else runs before the program's first
1525
+ * synchronous reads. So the store stays SEALED through the reconcile, and a
1526
+ * reconcile that cannot vouch for its rows — the listing failed, came back
1527
+ * short, or there is no supervisor to ask — does not open it. The kept store
1528
+ * is emptied instead and this launch boots exactly as a cold one does: the
1529
+ * rare failure costs a relaunch its speedup, never a stale byte.
1530
+ *
1531
+ * The previous process's own unacknowledged rows are the one thing the
1532
+ * reconcile cannot judge, since it keeps own rows whatever the listing says.
1533
+ * `__residentBind` drops them before any of this runs.
1534
+ *
1535
+ * `takeBundle` hands over the module bundle and drops the module's own
1536
+ * reference to it. The parsed bundle is the largest allocation in the facet
1537
+ * before the program starts, so a cold boot releases it the moment it is
1538
+ * adopted, and a kept store that reconciles never adopts it at all.
1539
+ *
1540
+ * Returns the cursor to publish and, when the boot fell short of the whole
1541
+ * filesystem, why.
1542
+ */
1543
+ async function __residentBoot(takeBundle, moduleCursor, supervisor) {
1544
+ if (!__residentReady) throw new Error("Nimbus: __residentBoot before __residentBind");
1545
+ let failure = null;
1546
+ if (__residentCursor() !== null) {
1547
+ let pass = null;
1548
+ try { pass = await __residentSynchronizeFromSupervisor(supervisor); }
1549
+ catch (e) { failure = (e && e.message) || String(e); }
1550
+ if (pass && pass.cursor) return { cursor: await __residentCatchUp(supervisor, pass.cursor), failure: null };
1551
+ failure = "a kept store could not be reconciled ("
1552
+ + (failure || (pass && (pass.skipped || pass.incomplete)) || "the listing vouched for nothing")
1553
+ + "); it was emptied and this launch booted from its own snapshot";
1554
+ }
1555
+ // A store with no cursor holds nothing any incarnation can date.
1556
+ __residentClear();
1557
+ const bundle = takeBundle();
1558
+ // The module map is the program's code: it is held whole or the launch
1559
+ // fails. Spawn admitted it (N18); this is room for it now, asked for when
1560
+ // the store's cap does not already cover it.
1561
+ let moduleBytes = 0;
1562
+ for (const path of Object.keys(bundle || {})) moduleBytes += __residentCellCost(bundle[path]);
1563
+ if (!(await __residentEnsureRoom(moduleBytes))) {
1564
+ throw Object.assign(new Error(
1565
+ "ENOSPC: workspace storage is full: this process's modules (" + Math.ceil(moduleBytes / 1048576)
1566
+ + " MiB) do not fit in what is left of the session's storage"
1567
+ ), { code: "ENOSPC" });
1568
+ }
1569
+ const adopted = __residentAdoptModuleBundle(bundle, moduleCursor);
1570
+ // The pass that just failed is not retried. The store now holds what a
1571
+ // cold launch whose fill failed holds, and its first ACQUIRE's delta
1572
+ // brings it current the same way.
1573
+ if (failure !== null) return { cursor: adopted, failure };
1574
+ let pass = null;
1575
+ try { pass = await __residentSynchronizeFromSupervisor(supervisor); }
1576
+ catch (e) { failure = (e && e.message) || String(e); }
1577
+ if (pass && pass.cursor) return { cursor: await __residentCatchUp(supervisor, pass.cursor), failure };
1578
+ return { cursor: adopted, failure };
1579
+ }
1580
+
1581
+ /**
1582
+ * The acquire options this store asks for: stats with every delta entry, and
1583
+ * the content of files changed under the push roots — the process's working
1584
+ * tree and /tmp, where what a peer writes after launch is what it goes on to
1585
+ * read (build output, manifests, temp files), less dependencies and VCS state.
1586
+ */
1587
+ let __residentPushRoots = [];
1588
+ /** Dependencies and VCS state: written in bulk, read through the module map or not at all. */
1589
+ const __RESIDENT_PUSH_EXCLUDE = ["node_modules", ".git"];
1590
+ function __residentSetPushRoots(roots) {
1591
+ __residentPushRoots = (Array.isArray(roots) ? roots : []).map((r) => String(r).replace(/^\/+|\/+$/g, ""));
1592
+ }
1593
+ /** Whether a changed file at `path` is one the push roots would carry. */
1594
+ function __residentPushable(path) {
1595
+ for (const root of __residentPushRoots) {
1596
+ if (root !== "" && path !== root && !path.startsWith(root + "/")) continue;
1597
+ const rest = root === "" ? path : path.slice(root.length + 1);
1598
+ if (!rest.split("/").some((seg) => __RESIDENT_PUSH_EXCLUDE.includes(seg))) return true;
1599
+ }
1600
+ return false;
1601
+ }
1602
+ function __residentAcquireOptions() {
1603
+ return __residentPushRoots.length > 0
1604
+ ? { namespace: true, push: { roots: __residentPushRoots, exclude: __RESIDENT_PUSH_EXCLUDE } }
1605
+ : { namespace: true };
1606
+ }
1607
+
1608
+ /**
1609
+ * One delta from a listing's cursor, applied before the program runs. A
1610
+ * listing's pages are walked at successive revisions, so the namespace it
1611
+ * builds can mix states no single revision had (a rename seen on both sides
1612
+ * or neither); the delta from its cursor names every path that moved during
1613
+ * the walk, and leaves the table exact at the delta's revision. At boot there
1614
+ * are no own writes, so nothing here needs the ledger's report notes.
1615
+ */
1616
+ async function __residentCatchUp(supervisor, cursor) {
1617
+ if (!supervisor || typeof supervisor.fsAcquire !== "function") return cursor;
1618
+ let result;
1619
+ try { result = await supervisor.fsAcquire(cursor.epoch, cursor.rev, __residentAcquireOptions()); }
1620
+ catch { result = null; }
1621
+ if (!result || result.poison || typeof result.rev !== "number" || typeof result.epoch !== "string") {
1622
+ // Unanswered: the namespace keeps the listing's revision, which the
1623
+ // program's first barrier brings forward like any other.
1624
+ return cursor;
1625
+ }
1626
+ await __residentRoomForPushed(result);
1627
+ const applied = __residentAdmit(result);
1628
+ for (const dir of applied.relist) await __nsRelist(supervisor, dir);
1629
+ // A dropped row was held, so it is wanted again at its new revision.
1630
+ const refetch = [];
1631
+ for (const path of applied.dropped) {
1632
+ const found = __nsResolve(path, false);
1633
+ if (!found || found === "ELOOP" || Number(found.row.kind) !== __NS_FILE) continue;
1634
+ refetch.push({ path, size: Number(found.row.size), rev: Number(found.row.rev), epoch: applied.cursor.epoch });
1635
+ }
1636
+ if (refetch.length > 0) await __residentFetchFiles(supervisor, refetch);
1637
+ return applied.cursor;
1638
+ }
1639
+
1640
+ /**
1641
+ * Re-read the names beneath `dir` from the authority: a directory that just
1642
+ * became searchable, whose descendants no delta will ever name.
1643
+ */
1644
+ async function __nsRelist(supervisor, dir) {
1645
+ if (!supervisor || typeof supervisor.fsList !== "function") { __nsMarkReady(__residentT, false, "the session cannot list its filesystem"); return; }
1646
+ const t = __residentT;
1647
+ const prefix = dir + "/";
1648
+ const seen = [];
1649
+ let replaceBytes = 0;
1650
+ let incomingBytes = 0;
1651
+ if (__residentInHeap) {
1652
+ for (const row of t.nsKeys()) {
1653
+ const key = row.parent ? row.parent + "/" + row.name : row.name;
1654
+ if (!key.startsWith(prefix)) continue;
1655
+ replaceBytes += __namespaceRowBytes(row.parent, row.name, t.nsGet(row.parent, row.name)?.target);
1656
+ }
1657
+ }
1658
+ let after = prefix;
1659
+ let done = false;
1660
+ for (let page = 0; page < __RESIDENT_MAX_LIST_PAGES && !done; page++) {
1661
+ let listed;
1662
+ try { listed = await supervisor.fsList(after, __RESIDENT_LIST_PAGE); }
1663
+ catch (e) { __nsMarkReady(t, false, "a listing of /" + dir + " failed (" + ((e && e.message) || String(e)) + ")"); return; }
1664
+ if (!listed || !Array.isArray(listed.entries)) { __nsMarkReady(t, false, "a listing of /" + dir + " came back without entries"); return; }
1665
+ for (const entry of listed.entries) {
1666
+ const k = String(entry.path).replace(/^\/+/, "");
1667
+ if (!k.startsWith(prefix)) {
1668
+ if (k > prefix) { done = true; break; }
1669
+ continue;
1670
+ }
1671
+ if (entry.stat) {
1672
+ if (__residentInHeap && __residentCap !== null) {
1673
+ const [parent, name] = __nsSplit(k);
1674
+ incomingBytes += __namespaceRowBytes(parent, name, entry.linkTarget);
1675
+ if (__residentDbBytes() - replaceBytes + incomingBytes > __residentCap) {
1676
+ __nsMarkReady(t, false, "namespace storage budget exceeded (" + __residentCap + " bytes)");
1677
+ return;
1678
+ }
1679
+ }
1680
+ seen.push(entry);
1681
+ }
1682
+ }
1683
+ if (listed.next === null || listed.next === undefined) done = true;
1684
+ else after = listed.next;
1685
+ }
1686
+ if (!done) { __nsMarkReady(t, false, "a listing of /" + dir + " did not finish"); return; }
1687
+ if (!seen.every((entry) => __nsDescribes(entry.stat))) { __nsMarkReady(t, false, "a listing of /" + dir + " named an entry without its stat"); return; }
1688
+ // Exactly what the listing has under the directory: a name it no longer
1689
+ // shows (hidden now, or gone) leaves, one it shows is written.
1690
+ __nsDeleteTree(t, dir, false);
1691
+ for (const entry of seen) __nsPut(t, String(entry.path).replace(/^\/+/, ""), entry.stat, entry.rev, entry.linkTarget);
1692
+ }
1693
+
1694
+ /**
1695
+ * The launch's data plan (facets/data-plan.ts): the paths whose content a
1696
+ * synchronous read may want, held from boot beside the module map. A launch
1697
+ * with none (a re-entry by an inbound request) holds what it already had.
1698
+ */
1699
+ let __residentPlan = new Set();
1700
+ function __residentSetPlan(paths) {
1701
+ __residentPlan = new Set(Array.isArray(paths) ? paths.map((p) => String(p).replace(/^\/+/, "")) : []);
1702
+ }
1703
+
1704
+ /**
1705
+ * Adopt the module map's bundle into an EMPTY store — the first fill, and the
1706
+ * one that costs nothing extra, because those bytes are already in the facet.
1707
+ * The cursor the bundle was read at becomes the store's.
1708
+ *
1709
+ * Returns the cursor the caller should publish, so there is one answer to
1710
+ * "what state does this facet cache" rather than two that can disagree.
1711
+ */
1712
+ function __residentAdoptModuleBundle(bundle, moduleCursor) {
1713
+ if (!__residentReady) throw new Error("Nimbus: __residentAdoptModuleBundle before __residentBind");
1714
+ if (__residentCursor() !== null) {
1715
+ // Adopting over a kept store would re-date its rows at this spawn's
1716
+ // cursor without checking one of them. __residentBoot reconciles a kept
1717
+ // store, or empties it first.
1718
+ throw new Error("Nimbus: a module bundle is adopted only into an empty store");
1719
+ }
1720
+ if (!moduleCursor || moduleCursor.epoch == null || moduleCursor.rev == null) {
1721
+ // An undated snapshot. Adopt NOTHING and serve an empty store.
1722
+ //
1723
+ // Not a throw, because this is a real path rather than a misuse: a facet
1724
+ // whose first contact is an inbound HTTP request runs
1725
+ // `__nimbusEnsureStarted(env, ctx)` with no start args, so it has no
1726
+ // cursor to offer. Refusing would turn a served port into a dead one.
1727
+ //
1728
+ // Not an adopt either, because rows nothing can date are the stale-read
1729
+ // bug. An EMPTY store is safe to serve for the reason the seal exists at
1730
+ // all — it has no row whose age could be wrong — and a miss is an answer
1731
+ // the shims already handle, falling through to the supervisor exactly as
1732
+ // they do today after a poison. So this is strictly no worse than the
1733
+ // current behaviour and cannot serve a stale byte.
1734
+ __residentUndated = true;
1735
+ __residentSealed = false;
1736
+ __residentSealReason = "";
1737
+ return null;
1738
+ }
1739
+ const rev = Number(moduleCursor.rev);
1740
+ for (const path of Object.keys(bundle || {})) {
1741
+ __residentPut(__residentT, path, bundle[path], rev);
1742
+ }
1743
+ return __residentAdmit({ poison: false, paths: [], epoch: String(moduleCursor.epoch), rev }).cursor;
1744
+ }
1745
+
1746
+ /**
1747
+ * Enumerate the whole filesystem, one page at a time.
1748
+ *
1749
+ * WHAT EXISTS has to come from the authority, not from anything the facet was
1750
+ * shipped. That was measured rather than assumed: for a working tree holding a
1751
+ * 25 MiB file outside the cwd, the spawn-time stat table this replaced arrived
1752
+ * with FOUR entries ("home", "opt", "var", "home/user"), every one a
1753
+ * directory. A launch ships only what it staged plus its ancestors; a store
1754
+ * that enumerated from that would hold only what the bundle already had, which
1755
+ * is the admission problem it exists to delete.
1756
+ *
1757
+ * Every page is dated, and a page whose epoch differs from the first ABORTS
1758
+ * the listing. The supervisor was replaced mid-walk, so the pages already
1759
+ * collected describe a filesystem that no longer exists and the revision they
1760
+ * would be dated at is meaningless. Returning short is safe — the rows simply
1761
+ * are not there and the reads miss — while stitching the two halves together
1762
+ * would date one incarnation's bytes at another's clock.
1763
+ */
1764
+ async function __residentEnumerate(supervisor) {
1765
+ const entries = [];
1766
+ // Every name, whatever its kind: the namespace is built from these.
1767
+ const names = [];
1768
+ let namespaceBytes = 0;
1769
+ let after = null;
1770
+ let cursor = null;
1771
+ for (let page = 0; page < __RESIDENT_MAX_LIST_PAGES; page++) {
1772
+ const listed = await supervisor.fsList(after, __RESIDENT_LIST_PAGE);
1773
+ if (!listed || !Array.isArray(listed.entries)) {
1774
+ throw new Error("Nimbus: fsList returned no page");
1775
+ }
1776
+ if (cursor === null) {
1777
+ cursor = { epoch: String(listed.epoch), rev: Number(listed.rev) };
1778
+ } else if (String(listed.epoch) !== cursor.epoch) {
1779
+ return { entries, names, cursor, complete: false, reason: "the supervisor changed incarnation mid-enumeration" };
1780
+ }
1781
+ for (const entry of listed.entries) {
1782
+ if (entry.stat) {
1783
+ if (__residentInHeap && __residentCap !== null) {
1784
+ const key = String(entry.path).replace(/^\/+/, "");
1785
+ const [parent, name] = __nsSplit(key);
1786
+ if (key) namespaceBytes += __namespaceRowBytes(parent, name, entry.linkTarget);
1787
+ if (__residentDbBytes() - __residentT.nsBytes() + namespaceBytes > __residentCap) {
1788
+ const cause = "namespace storage budget exceeded (" + __residentCap + " bytes)";
1789
+ __nsMarkReady(__residentT, false, cause);
1790
+ throw Object.assign(new Error(cause), { code: "ENOSPC" });
1791
+ }
1792
+ }
1793
+ names.push(entry);
1794
+ }
1795
+ // Directories carry no content, and a symlink's size is its TARGET's
1796
+ // length rather than the resolved file's — reading one by that size
1797
+ // would store a truncated file and call it whole. Content is fetched
1798
+ // by the path a symlink resolves to.
1799
+ if (entry.kind !== "file") continue;
1800
+ const path = String(entry.path).replace(/^\/+/, "");
1801
+ if (!path) continue;
1802
+ const size = Number(entry.size);
1803
+ // The path's own last-mutation revision. It is what makes a held row
1804
+ // verifiable without fetching its bytes, and so what makes a poison
1805
+ // repairable rather than fatal — see __residentSynchronizeFromSupervisor.
1806
+ const rev = Number(entry.rev);
1807
+ if (!Number.isSafeInteger(size) || size < 0 || !Number.isSafeInteger(rev) || rev < 0) {
1808
+ throw new Error("Nimbus: fsList returned unusable metadata for '" + path + "'");
1809
+ }
1810
+ entries.push({ path, size, rev, epoch: cursor.epoch, ckey: entry.contentKey == null ? null : String(entry.contentKey) });
1811
+ }
1812
+ if (listed.next === null || listed.next === undefined) {
1813
+ return { entries, names, namespaceBytes, cursor, complete: true, reason: null };
1814
+ }
1815
+ after = listed.next;
1816
+ }
1817
+ return { entries, names, cursor, complete: false, reason: "the filesystem exceeded the enumeration page bound" };
1818
+ }
1819
+
1820
+ /**
1821
+ * Fetch these files from the authority and install them, each dated at the
1822
+ * revision the listing reported it at.
1823
+ *
1824
+ * THE BATCH BOUNDS ARE THE SUPERVISOR'S AND ARE NOT NEGOTIABLE HERE.
1825
+ * `_rpcFsReadBatch` validates with zod and rejects the WHOLE call, so an
1826
+ * over-packed batch loses every path in it rather than degrading. Three
1827
+ * separate bounds apply at once and the packing below respects all three:
1828
+ *
1829
+ * paths per call __RESIDENT_BATCH_PATHS (FS_READ_BATCH_PATH_LIMIT)
1830
+ * requested bytes __RESIDENT_BATCH_BYTES (FS_READ_BATCH_REQUEST_BYTES)
1831
+ * bytes per range __RESIDENT_CHUNK_BYTES (the single-value ceiling)
1832
+ *
1833
+ * Every file is split into __RESIDENT_CHUNK_BYTES ranges — most files are one
1834
+ * short range — and the ranges are packed into calls. That makes the large
1835
+ * file the SAME path as the small one rather than a special case, and it means
1836
+ * no whole-file buffer is ever held: each range is written straight into its
1837
+ * own chunk row as it lands, and the head row follows only once every range of
1838
+ * that file has.
1839
+ *
1840
+ * A row is stamped with its OWN listed revision rather than with the global
1841
+ * cursor. Both are conservative — a write landing mid-fetch reports a revision
1842
+ * above the stamp, so the next ACQUIRE evicts and refetches, while stamping
1843
+ * newer than the bytes would keep a stale row forever — but the per-path
1844
+ * revision is the tighter of the two, and it is the one that survives a
1845
+ * reconcile: a row dated at the global cursor is indistinguishable from a row
1846
+ * whose path moved at that same revision.
1847
+ */
1848
+ async function __residentFetchFiles(supervisor, files) {
1849
+ // Content already held under another path, or wanted twice in this batch,
1850
+ // is fetched once: equal keys are equal bytes (the second copy is local).
1851
+ const t = __residentT;
1852
+ const requested = files.length;
1853
+ let copied = 0;
1854
+ const wanted = [];
1855
+ const later = [];
1856
+ const fetching = new Set();
1857
+ for (const file of files) {
1858
+ if (file.ckey != null) {
1859
+ if (__residentCopyByKey(t, file.path, file.ckey, file.rev)) { copied++; continue; }
1860
+ if (fetching.has(file.ckey)) { later.push(file); continue; }
1861
+ fetching.add(file.ckey);
1862
+ }
1863
+ wanted.push(file);
1864
+ }
1865
+ files = wanted;
1866
+ // One flat list of ranges across all files, so packing is a single pass and
1867
+ // a 25 MiB file and a 40-byte one are the same shape.
1868
+ const ranges = [];
1869
+ for (const file of files) {
1870
+ const parts = Math.max(1, Math.ceil(file.size / __RESIDENT_CHUNK_BYTES));
1871
+ for (let part = 0; part < parts; part++) {
1872
+ const offset = part * __RESIDENT_CHUNK_BYTES;
1873
+ ranges.push({
1874
+ path: file.path,
1875
+ rev: file.rev,
1876
+ epoch: file.epoch,
1877
+ part,
1878
+ parts,
1879
+ offset,
1880
+ length: Math.min(__RESIDENT_CHUNK_BYTES, Math.max(0, file.size - offset)),
1881
+ size: file.size,
1882
+ ckey: file.ckey ?? null,
1883
+ });
1884
+ }
1885
+ }
1886
+
1887
+ // Per-file landed-part counts, so a head row is written only once every
1888
+ // range of that file is in. See __residentPutHead.
1889
+ const landed = new Map();
1890
+ let filled = 0, failed = 0, fetchedBytes = 0;
1891
+ const failedPaths = new Set();
1892
+
1893
+ for (let at = 0; at < ranges.length;) {
1894
+ const batch = [];
1895
+ let bytes = 0;
1896
+ while (
1897
+ at < ranges.length
1898
+ && batch.length < __RESIDENT_BATCH_PATHS
1899
+ && (batch.length === 0 || bytes + ranges[at].length <= __RESIDENT_BATCH_BYTES)
1900
+ ) {
1901
+ bytes += ranges[at].length;
1902
+ batch.push(ranges[at]);
1903
+ at++;
1904
+ }
1905
+ // N18: room for the batch first; what the ledger will not admit is read
1906
+ // through the session instead of held.
1907
+ if (!(await __residentEnsureRoom(bytes + batch.length * __RESIDENT_ROW_BYTES))) {
1908
+ for (const r of batch) failedPaths.add(r.path);
1909
+ continue;
1910
+ }
1911
+ let results;
1912
+ try {
1913
+ results = await supervisor.fsReadBatch(
1914
+ batch.map((r) => ({ path: r.path, offset: r.offset, length: r.length, expectedEpoch: r.epoch, expectedRevision: r.rev }))
1915
+ );
1916
+ } catch {
1917
+ for (const r of batch) failedPaths.add(r.path);
1918
+ continue;
1919
+ }
1920
+ for (let i = 0; i < batch.length; i++) {
1921
+ const range = batch[i];
1922
+ const entry = results && results[i];
1923
+ if (!entry || entry.error) {
1924
+ // A denial is a real answer and is stored as one, so a later read
1925
+ // reports EACCES synchronously instead of missing. Anything else is a
1926
+ // path this store will not hold, and a read of it falls through to
1927
+ // exactly the miss it would have had before.
1928
+ if (entry && entry.error && entry.error.code === "EACCES" && range.part === 0) {
1929
+ __residentPut(t, range.path, { error: "EACCES" }, range.rev);
1930
+ filled++;
1931
+ } else failedPaths.add(range.path);
1932
+ continue;
1933
+ }
1934
+ if (entry.bytes === null || entry.bytes === undefined) { failedPaths.add(range.path); continue; }
1935
+ const chunk = __residentBytes(entry.bytes);
1936
+ if (chunk.byteLength !== range.length) { failedPaths.add(range.path); continue; }
1937
+ fetchedBytes += chunk.byteLength;
1938
+ if (!__residentPutChunk(t, range.path, range.part, chunk)) { failedPaths.add(range.path); continue; }
1939
+ const seen = (landed.get(range.path) || 0) + 1;
1940
+ landed.set(range.path, seen);
1941
+ if (seen === range.parts && !failedPaths.has(range.path)) {
1942
+ __residentPutHead(t, range.path, __RK_BINARY, range.size, range.parts, range.rev, range.ckey);
1943
+ filled++;
1944
+ }
1945
+ }
1946
+ }
1947
+
1948
+ // A file whose ranges did not all land has chunk rows and no head, so it is
1949
+ // unreadable rather than short. Drop them so the slot does not carry bytes
1950
+ // nothing will ever serve.
1951
+ for (const path of failedPaths) {
1952
+ failed++;
1953
+ t.chunkDelete(path);
1954
+ t.fileDelete(path);
1955
+ }
1956
+
1957
+ for (const file of later) {
1958
+ if (__residentCopyByKey(t, file.path, file.ckey, file.rev)) copied++;
1959
+ else failed++;
1960
+ }
1961
+ if (ranges.length > 0) await __residentReportSize();
1962
+ return {
1963
+ requested, filled: filled + copied, failed,
1964
+ bytes: fetchedBytes, ranges: ranges.length, copied,
1965
+ };
1966
+ }
1967
+
1968
+ /**
1969
+ * Bring the store to the authority's current state — the one repair path, used
1970
+ * both to populate a facet before its first instruction and to recover a
1971
+ * poisoned cursor.
1972
+ *
1973
+ * It turns "a capped prefetch" into "the filesystem", which is what makes a
1974
+ * first synchronous read of an untouched file succeed, and it is the only
1975
+ * blocking step: the waiting is done here so the reads that follow never have
1976
+ * to.
1977
+ *
1978
+ * WHY A POISON DOES NOT DROP THE ROWS. A poison says only that
1979
+ * `invalidatedSince` cannot describe the distance from our cursor to now —
1980
+ * the invalidation log is bounded at 256 KiB (sqlite-vfs.ts) and ordinary
1981
+ * write churn trims it past a live cursor as a matter of course. It says
1982
+ * nothing about the rows, and the rows are the asset: at pi scale ~16k files
1983
+ * and ~96 MB that dropping forces back over the wire, awaited inside the
1984
+ * ACQUIRE barrier, on every poison. That is a cost defect, not a coherence
1985
+ * one, and it was measured taking an agent turn past the DO CPU limit.
1986
+ *
1987
+ * `fsList` does not touch the log and reports every path's ABSOLUTE
1988
+ * revision, which is strictly more information than a delta. So the rows are
1989
+ * RECONCILED instead: a row at or above its listed revision is proven current
1990
+ * and kept, a row below it or no longer listed goes, and only what actually
1991
+ * moved is refetched. Every surviving row is vouched for by revision, so the
1992
+ * no-stale-byte guarantee is exactly as unconditional as it was on the drop
1993
+ * path — the delta is simply not the only way to establish it.
1994
+ *
1995
+ * The comparison is sound ONLY within one supervisor incarnation and ONLY
1996
+ * against a complete enumeration:
1997
+ *
1998
+ * - revisions from different epochs are unrelated clocks, and after a
1999
+ * restart an untouched path lists at rev 0, which would vouch for anything;
2000
+ * - a truncated listing cannot tell a path that was REMOVED from one that
2001
+ * was never walked, so dropping unlisted rows against it would delete a
2002
+ * live cache for no reason.
2003
+ *
2004
+ * Neither is verifiable ⇒ nothing is dropped and nothing is vouched for; the
2005
+ * pass degrades to filling what the store does not hold, which is exactly what
2006
+ * it did before rows could be verified. The caller keeps whatever cursor it
2007
+ * had, and a poisoned caller has already taken its cold cache through
2008
+ * `__residentAdmit`.
2009
+ *
2010
+ * A row stamped __RK_OWN_WRITE is this facet's own unacknowledged bytes —
2011
+ * newer than anything the authority can report — and is kept whether listed
2012
+ * or not, the same read-your-writes rule the delta path applies. But the
2013
+ * pass moves the cursor without a delta, so the report a delta would have
2014
+ * carried for such a path is returned instead (`own`): the listed revision,
2015
+ * or the listing's own cursor for a path it no longer lists, which bounds
2016
+ * whatever removed it. The write or mutation that owns the row adjudicates it
2017
+ * when its revision arrives, exactly as it does a delta's report. Across
2018
+ * incarnations, or against a short listing, no report can be dated and each
2019
+ * comes back null.
2020
+ *
2021
+ * Writes landing DURING the pass are covered the way they always were: the
2022
+ * published cursor is the one read BEFORE the walk, so they report revisions
2023
+ * above it and the next ACQUIRE evicts whatever they touched.
2024
+ */
2025
+ async function __residentSynchronizeFromSupervisor(supervisor) {
2026
+ if (!__residentReady) throw new Error("Nimbus: __residentSynchronizeFromSupervisor before __residentBind");
2027
+ if (!supervisor || typeof supervisor.fsReadBatch !== "function") {
2028
+ return { requested: 0, filled: 0, failed: 0, skipped: "no fsReadBatch on this supervisor" };
2029
+ }
2030
+ if (typeof supervisor.fsList !== "function") {
2031
+ // Without the enumeration the store cannot know what it is missing, so it
2032
+ // fills nothing beyond the adopted bundle and every read behaves exactly
2033
+ // as it did before this store existed — a smaller resident set, never a
2034
+ // wrong one. That is the failure direction to keep.
2035
+ return { requested: 0, filled: 0, failed: 0, skipped: "supervisor cannot enumerate the filesystem (no fsList)" };
2036
+ }
2037
+
2038
+ let listing;
2039
+ try {
2040
+ listing = await __residentEnumerate(supervisor);
2041
+ } catch (e) {
2042
+ return { requested: 0, filled: 0, failed: 0, skipped: "fsList failed: " + ((e && e.message) || String(e)) };
2043
+ }
2044
+ if (!listing.cursor) {
2045
+ return { requested: 0, filled: 0, failed: 0, skipped: "fsList reported no authority cursor" };
2046
+ }
2047
+
2048
+ const t = __residentT;
2049
+ const held = __residentCursor();
2050
+ // May a row this pass cannot prove current be DROPPED? Only against a
2051
+ // complete enumeration: a truncated one cannot tell a path that was removed
2052
+ // from one that was never walked.
2053
+ const judgeable = listing.complete;
2054
+ const describable = judgeable && listing.names.every((entry) => __nsDescribes(entry.stat));
2055
+ // May a held revision be COMPARED with a listed one? Only inside one
2056
+ // supervisor incarnation: across a restart the clocks are unrelated, and an
2057
+ // untouched path lists at rev 0, which would vouch for anything.
2058
+ const comparable = judgeable && !!held && held.epoch === listing.cursor.epoch;
2059
+
2060
+ const listed = new Map();
2061
+ for (const file of listing.entries) listed.set(file.path, file);
2062
+
2063
+ const rows = [];
2064
+ for (const row of t.fileRows()) {
2065
+ rows.push({ path: String(row.path), rev: Number(row.rev), ckey: row.ckey == null ? null : String(row.ckey) });
2066
+ }
2067
+ const current = new Set();
2068
+ const dropped = [];
2069
+ const own = [];
2070
+ let rekeyed = 0;
2071
+ for (const row of rows) {
2072
+ const entry = listed.get(row.path);
2073
+ if (row.rev === __RK_OWN_WRITE) {
2074
+ current.add(row.path);
2075
+ own.push({
2076
+ path: row.path,
2077
+ rev: !comparable ? null : entry !== undefined ? entry.rev : listing.cursor.rev,
2078
+ });
2079
+ continue;
2080
+ }
2081
+ const keep = (comparable && entry !== undefined && row.rev >= entry.rev) || !judgeable;
2082
+ if (keep) { current.add(row.path); continue; }
2083
+ // The listing names the same bytes (a revision moved, the content did
2084
+ // not, or another epoch): kept and dated at the listing. Equal keys are
2085
+ // equal bytes, so this needs no comparable clock.
2086
+ if (entry !== undefined && row.ckey !== null && entry.ckey != null && row.ckey === entry.ckey) {
2087
+ t.fileSetRev(row.path, entry.rev);
2088
+ current.add(row.path);
2089
+ rekeyed++;
2090
+ continue;
2091
+ }
2092
+ dropped.push(row.path);
2093
+ }
2094
+ // Cells held in the heap (own writes the store had no room for) are judged
2095
+ // as file rows are, on every path a file row is (N18). Undated, a write-back
2096
+ // still in flight: newer than anything listed, it stays and is reported as
2097
+ // own. Dated: kept only where the listing proves it current; otherwise it
2098
+ // goes, and is fetched again like any dropped row, so a sync read of it is
2099
+ // a miss rather than bytes the authority has replaced.
2100
+ for (const [path, cell] of [...__residentHeld]) {
2101
+ if (current.has(path)) continue;
2102
+ const entry = listed.get(path);
2103
+ if (cell.rev === __RK_OWN_WRITE) {
2104
+ current.add(path);
2105
+ own.push({ path, rev: !comparable ? null : entry !== undefined ? entry.rev : listing.cursor.rev });
2106
+ continue;
2107
+ }
2108
+ if ((comparable && entry !== undefined && cell.rev >= entry.rev) || !judgeable) { current.add(path); continue; }
2109
+ __residentForgetHeld(path);
2110
+ dropped.push(path);
2111
+ }
2112
+ // Sweep what could not be vouched for. When NOTHING could — a new supervisor
2113
+ // incarnation, whose revisions say nothing about ours — that is every row but
2114
+ // this facet's own unflushed writes, and at pi scale a per-path delete would
2115
+ // be ~32,000 statements in one turn against an object that has been observed
2116
+ // resetting under exactly that load. So it is a predicate there, and per path
2117
+ // where a reconcile has made the set small by construction.
2118
+ if (dropped.length > 0 && !comparable && rekeyed === 0) {
2119
+ t.dropDatedFiles();
2120
+ } else {
2121
+ for (const path of dropped) {
2122
+ t.chunkDelete(path);
2123
+ t.fileDelete(path);
2124
+ }
2125
+ }
2126
+
2127
+ // What to hold: the launch's data plan, every row this pass had to drop as
2128
+ // stale — it was held for a reason — and what the pushes a poison lost
2129
+ // would have carried: files under the push roots changed since the cursor
2130
+ // (all of them there, when the revisions are not comparable).
2131
+ const plan = __residentPlan;
2132
+ const wanted = new Set(dropped);
2133
+ const fetch = [];
2134
+ for (const file of listing.entries) {
2135
+ if (current.has(file.path)) continue;
2136
+ if (plan.has(file.path) || wanted.has(file.path)
2137
+ || (__residentPushable(file.path) && (!comparable || file.rev > held.rev))) fetch.push(file);
2138
+ }
2139
+ // Reserve mandatory metadata growth while optional data is fetched, but
2140
+ // retain the existing namespace publication order across the await.
2141
+ const namespaceGrowth = __residentInHeap && describable ? Math.max(0, listing.namespaceBytes - t.nsBytes()) : 0;
2142
+ if (__residentInHeap && __residentCap !== null && __residentDbBytes() + namespaceGrowth > __residentCap) {
2143
+ const cause = "namespace storage budget exceeded (" + __residentCap + " bytes)";
2144
+ __nsMarkReady(t, false, cause);
2145
+ throw Object.assign(new Error(cause), { code: "ENOSPC" });
2146
+ }
2147
+ __residentNamespaceReserveBytes += namespaceGrowth;
2148
+ let filled;
2149
+ try { filled = await __residentFetchFiles(supervisor, fetch); }
2150
+ finally { __residentNamespaceReserveBytes -= namespaceGrowth; }
2151
+
2152
+ // The cursor may only advance to a state the rows actually describe, and
2153
+ // after a truncated listing they do not: a path in an unwalked page could
2154
+ // have moved since the held cursor and would never be reported again.
2155
+ if (judgeable) {
2156
+ // A listing that cannot describe every name restores the rows it vouches
2157
+ // for and the cursor, but not the namespace (see __nsDescribes).
2158
+ if (describable) __nsReplace(t, listing.names);
2159
+ __residentWriteCursor(t, listing.cursor);
2160
+ __nsMarkReady(t, describable, "the session's listing named an entry without its stat");
2161
+ __residentUndated = false;
2162
+ __residentSealed = false;
2163
+ __residentSealReason = "";
2164
+ }
2165
+
2166
+ return {
2167
+ requested: filled.requested,
2168
+ filled: filled.filled,
2169
+ failed: filled.failed,
2170
+ bytes: filled.bytes,
2171
+ ranges: filled.ranges,
2172
+ kept: current.size,
2173
+ rekeyed,
2174
+ dropped: dropped.length,
2175
+ own,
2176
+ reconciled: comparable,
2177
+ complete: listing.complete,
2178
+ cursor: judgeable ? listing.cursor : null,
2179
+ ...(listing.reason ? { incomplete: listing.reason } : {}),
2180
+ };
2181
+ }
2182
+
2183
+ /**
2184
+ * Refetch, after a delta the store applied without a listing, what it should
2185
+ * hold and does not: the paths the delta dropped and the launch's data plan.
2186
+ * The namespace (current as of that delta) gives each one's size and
2187
+ * revision, as a listing entry would.
2188
+ */
2189
+ async function __residentRefillFromNamespace(supervisor, dropped) {
2190
+ const t = __residentT;
2191
+ const cursor = __residentCursor();
2192
+ if (cursor === null) return { requested: 0, filled: 0, failed: 0, bytes: 0 };
2193
+ const fetch = [];
2194
+ const seen = new Set();
2195
+ for (const path of [...dropped, ...__residentPlan]) {
2196
+ if (seen.has(path)) continue;
2197
+ seen.add(path);
2198
+ if (__residentHead(path) !== undefined || __residentHeld.has(path)) continue;
2199
+ const row = __nsRowAt(t, path);
2200
+ if (row === undefined || Number(row.kind) !== __NS_FILE) continue;
2201
+ fetch.push({ path, size: Number(row.size), rev: Number(row.rev), epoch: cursor.epoch, ckey: null });
2202
+ }
2203
+ return __residentFetchFiles(supervisor, fetch);
2204
+ }
2205
+
2206
+ /**
2207
+ * The launch's last step before user code: the namespace must answer, since
2208
+ * every synchronous stat, exists and readdir reads it. A boot that left it
2209
+ * unready (its listing failed, came back short, or could not describe every
2210
+ * name) is repaired once more. Resolves to null when it answers, or to the
2211
+ * launch's failure, naming why; nothing is ever run on a namespace that
2212
+ * does not.
2213
+ */
2214
+ async function __residentRequireNamespace(supervisor, bootFailure) {
2215
+ if (__nsReady()) return null;
2216
+ try { await __residentSynchronizeFromSupervisor(supervisor); } catch {}
2217
+ if (__nsReady()) return null;
2218
+ return "node: the process was not started: its view of the filesystem could not be listed ("
2219
+ + __nsNotReadyCause() + (bootFailure ? "; " + bootFailure : "") + ")";
2220
+ }
2221
+
2222
+ /**
2223
+ * The resident set, as the shims have always seen it.
2224
+ *
2225
+ * Every trap is synchronous, because every reader of __vfsBundle is.
2226
+ */
2227
+ const __nimbusResidentBundle = new Proxy(Object.create(null), {
2228
+ get(_t, path) {
2229
+ if (typeof path !== "string") return undefined;
2230
+ return __residentGet(path);
2231
+ },
2232
+ has(_t, path) {
2233
+ if (typeof path !== "string") return false;
2234
+ return __residentHead(path) !== undefined || __residentHeld.has(path);
2235
+ },
2236
+ set(_t, path, cell) {
2237
+ // The program's own write, stamped __RK_OWN_WRITE: not the authority's
2238
+ // yet, newer than anything it could report, and replaced with a real
2239
+ // revision by the ledger when the write-back lands.
2240
+ if (typeof path !== "string") return true;
2241
+ __residentPut(__residentRequire(), path, cell, undefined);
2242
+ return true;
2243
+ },
2244
+ deleteProperty(_t, path) {
2245
+ if (typeof path === "string") __residentDelete(__residentRequire(), path);
2246
+ return true;
2247
+ },
2248
+ ownKeys() { return __residentKeys(); },
2249
+ getOwnPropertyDescriptor(_t, path) {
2250
+ if (typeof path !== "string") return undefined;
2251
+ if (__residentHead(path) === undefined) return undefined;
2252
+ // Configurable so 'delete' is legal and enumerable so for-in and
2253
+ // Object.keys see it — the two things every scan site depends on.
2254
+ //
2255
+ // An ACCESSOR, not a data descriptor, and that is the whole point: a
2256
+ // for-in or Object.keys over a Proxy asks this trap about every key, so a
2257
+ // data descriptor had to produce `value` — reading every file's bytes out
2258
+ // of SQLite to answer a question about NAMES. Measured at pi scale, one
2259
+ // such scan read 87 MiB and threw all of it away. A getter answers the
2260
+ // name question for free and still returns the bytes to anything that
2261
+ // genuinely reads the property.
2262
+ return { configurable: true, enumerable: true, get() { return __residentGet(path); } };
2263
+ },
2264
+ });