@nimbus-sh/core 0.5.0 → 0.7.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 (145) hide show
  1. package/README.md +47 -26
  2. package/dist/_shared/byte-stream.d.ts +5 -4
  3. package/dist/_shared/byte-stream.d.ts.map +1 -1
  4. package/dist/_shared/cache-stats.d.ts +23 -1
  5. package/dist/_shared/cache-stats.d.ts.map +1 -1
  6. package/dist/_shared/cache-stats.js +13 -1
  7. package/dist/_shared/exports-resolver.d.ts +1 -1
  8. package/dist/_shared/exports-resolver.js +1 -1
  9. package/dist/_shared/real-node-imports.d.ts +7 -0
  10. package/dist/_shared/real-node-imports.d.ts.map +1 -1
  11. package/dist/_shared/real-node-imports.js +8 -0
  12. package/dist/_shared/retry.js +1 -1
  13. package/dist/_shared/tarball.d.ts +23 -5
  14. package/dist/_shared/tarball.d.ts.map +1 -1
  15. package/dist/_shared/tarball.js +38 -5
  16. package/dist/_shared/vfs-write-ledger.d.ts.map +1 -1
  17. package/dist/_shared/vfs-write-ledger.js +22 -16
  18. package/dist/constants.d.ts +5 -11
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/constants.js +16 -56
  21. package/dist/runtime/assets-loader.js +1 -1
  22. package/dist/runtime/comment-strip.d.ts +0 -35
  23. package/dist/runtime/comment-strip.d.ts.map +1 -1
  24. package/dist/runtime/comment-strip.js +48 -20
  25. package/dist/runtime/esbuild-service.d.ts +38 -42
  26. package/dist/runtime/esbuild-service.d.ts.map +1 -1
  27. package/dist/runtime/esbuild-service.js +494 -197
  28. package/dist/runtime/javascript-ast.d.ts.map +1 -1
  29. package/dist/runtime/javascript-ast.js +45 -7
  30. package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -1
  31. package/dist/runtime/opentui-wasm-backend.js +18 -16
  32. package/dist/runtime/port-registry.d.ts +4 -2
  33. package/dist/runtime/port-registry.d.ts.map +1 -1
  34. package/dist/runtime/port-registry.js +60 -8
  35. package/dist/runtime/virtual-socket-kernel.generated.d.ts +1 -1
  36. package/dist/runtime/virtual-socket-kernel.generated.js +1 -1
  37. package/dist/shell/stdin-adapter.d.ts +11 -0
  38. package/dist/shell/stdin-adapter.d.ts.map +1 -0
  39. package/dist/shell/stdin-adapter.js +59 -0
  40. package/dist/shell/unix-commands.d.ts +2 -1
  41. package/dist/shell/unix-commands.d.ts.map +1 -1
  42. package/dist/shell/unix-commands.js +1078 -50
  43. package/dist/substrate/lifo/commands/io/dd.js +1 -1
  44. package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -1
  45. package/dist/substrate/lifo/commands/net/curl.js +270 -54
  46. package/dist/substrate/lifo/commands/system/npm.d.ts +18 -1
  47. package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -1
  48. package/dist/substrate/lifo/commands/system/npm.js +26 -43
  49. package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -1
  50. package/dist/substrate/lifo/commands/text/head.js +2 -1
  51. package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -1
  52. package/dist/substrate/lifo/commands/text/sed.js +499 -117
  53. package/dist/substrate/lifo/commands/types.d.ts +8 -4
  54. package/dist/substrate/lifo/commands/types.d.ts.map +1 -1
  55. package/dist/substrate/lifo/node-compat/zlib.d.ts +37 -18
  56. package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -1
  57. package/dist/substrate/lifo/node-compat/zlib.js +122 -23
  58. package/dist/substrate/lifo/shell/interpreter.d.ts +9 -1
  59. package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -1
  60. package/dist/substrate/lifo/shell/interpreter.js +79 -29
  61. package/dist/substrate/lifo/shell/pipe.d.ts +16 -0
  62. package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -1
  63. package/dist/substrate/lifo/shell/pipe.js +62 -18
  64. package/dist/substrate/lifo/shell/terminal-stdin.d.ts +20 -3
  65. package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -1
  66. package/dist/substrate/lifo/shell/terminal-stdin.js +60 -24
  67. package/dist/vfs/seed-project.d.ts.map +1 -1
  68. package/dist/vfs/seed-project.js +1 -1
  69. package/dist/vfs/sqlite-vfs.d.ts +21 -30
  70. package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
  71. package/dist/vfs/sqlite-vfs.js +113 -29
  72. package/dist/workspace/nimbus-workspace.d.ts +15 -0
  73. package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
  74. package/dist/workspace/nimbus-workspace.js +20 -3
  75. package/dist/workspace/supervisor-op.d.ts +23 -0
  76. package/dist/workspace/supervisor-op.d.ts.map +1 -0
  77. package/dist/workspace/supervisor-op.js +92 -0
  78. package/package.json +3 -2
  79. package/src/_shared/byte-stream.ts +5 -4
  80. package/src/_shared/cache-stats.ts +27 -1
  81. package/src/_shared/exports-resolver.ts +1 -1
  82. package/src/_shared/real-node-imports.ts +8 -0
  83. package/src/_shared/retry.ts +1 -1
  84. package/src/_shared/tarball.ts +56 -5
  85. package/src/_shared/vfs-write-ledger.ts +22 -16
  86. package/src/constants.ts +30 -60
  87. package/src/runtime/assets-loader.ts +1 -1
  88. package/src/runtime/comment-strip.ts +42 -23
  89. package/src/runtime/esbuild-service.ts +566 -217
  90. package/src/runtime/javascript-ast.ts +40 -9
  91. package/src/runtime/opentui-wasm-backend.ts +19 -17
  92. package/src/runtime/port-registry.ts +66 -8
  93. package/src/runtime/virtual-socket-kernel.generated.ts +1 -1
  94. package/src/shell/stdin-adapter.ts +58 -0
  95. package/src/shell/unix-commands.ts +1054 -50
  96. package/src/substrate/lifo/commands/io/dd.ts +2 -2
  97. package/src/substrate/lifo/commands/net/curl.ts +310 -58
  98. package/src/substrate/lifo/commands/system/npm.ts +58 -38
  99. package/src/substrate/lifo/commands/text/head.ts +2 -1
  100. package/src/substrate/lifo/commands/text/sed.ts +519 -109
  101. package/src/substrate/lifo/commands/types.ts +8 -5
  102. package/src/substrate/lifo/node-compat/zlib.ts +155 -26
  103. package/src/substrate/lifo/shell/interpreter.ts +74 -27
  104. package/src/substrate/lifo/shell/pipe.ts +65 -24
  105. package/src/substrate/lifo/shell/terminal-stdin.ts +66 -30
  106. package/src/vfs/seed-project.ts +3 -3
  107. package/src/vfs/sqlite-vfs.ts +128 -64
  108. package/src/workspace/nimbus-workspace.ts +26 -2
  109. package/src/workspace/supervisor-op.ts +117 -0
  110. package/dist/_shared/install-phase.d.ts +0 -47
  111. package/dist/_shared/install-phase.d.ts.map +0 -1
  112. package/dist/_shared/install-phase.js +0 -1
  113. package/dist/_shared/rpc-dispose.d.ts +0 -4
  114. package/dist/_shared/rpc-dispose.d.ts.map +0 -1
  115. package/dist/_shared/rpc-dispose.js +0 -30
  116. package/dist/_shared/w7-frame.d.ts +0 -79
  117. package/dist/_shared/w7-frame.d.ts.map +0 -1
  118. package/dist/_shared/w7-frame.js +0 -840
  119. package/dist/_shared/weighted-credit-pool.d.ts +0 -61
  120. package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
  121. package/dist/_shared/weighted-credit-pool.js +0 -196
  122. package/dist/observability/diag-counters.d.ts +0 -236
  123. package/dist/observability/diag-counters.d.ts.map +0 -1
  124. package/dist/observability/diag-counters.js +0 -195
  125. package/dist/observability/heap-estimate.d.ts +0 -225
  126. package/dist/observability/heap-estimate.d.ts.map +0 -1
  127. package/dist/observability/heap-estimate.js +0 -185
  128. package/dist/observability/heavy-alloc-coord.d.ts +0 -93
  129. package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
  130. package/dist/observability/heavy-alloc-coord.js +0 -181
  131. package/dist/observability/oom-classify.d.ts +0 -138
  132. package/dist/observability/oom-classify.d.ts.map +0 -1
  133. package/dist/observability/oom-classify.js +0 -268
  134. package/dist/observability/oom-discriminator.d.ts +0 -176
  135. package/dist/observability/oom-discriminator.d.ts.map +0 -1
  136. package/dist/observability/oom-discriminator.js +0 -272
  137. package/src/_shared/install-phase.ts +0 -56
  138. package/src/_shared/rpc-dispose.ts +0 -31
  139. package/src/_shared/w7-frame.ts +0 -1013
  140. package/src/_shared/weighted-credit-pool.ts +0 -244
  141. package/src/observability/diag-counters.ts +0 -343
  142. package/src/observability/heap-estimate.ts +0 -331
  143. package/src/observability/heavy-alloc-coord.ts +0 -241
  144. package/src/observability/oom-classify.ts +0 -260
  145. package/src/observability/oom-discriminator.ts +0 -405
@@ -41,13 +41,14 @@
41
41
  */
42
42
  import { VfsEventEmitter } from './events.js';
43
43
  import { normalizeVfsPath } from './path.js';
44
- import { CHUNK_SIZE, LRU_MAX_ENTRIES, MAX_TX_BLOB_BYTES, MAX_TX_LOGICAL_ROWS, MAX_TX_SQL_EXECS, MAX_GLOBAL_WRITE_STREAM_CREDIT_BYTES, FS_LIST_PAGE_LIMIT, } from '../constants.js';
45
- import { recordFailure } from '../observability/oom-discriminator.js';
46
- import { classifyError } from '../observability/oom-classify.js';
47
- import { acquireSupervisorAllocation } from '../observability/heavy-alloc-coord.js';
44
+ import { LRU_MAX_ENTRIES, FS_LIST_PAGE_LIMIT, } from '../constants.js';
45
+ import { CHUNK_SIZE, MAX_TX_BLOB_BYTES, MAX_TX_LOGICAL_ROWS, MAX_TX_SQL_EXECS, MAX_GLOBAL_WRITE_STREAM_CREDIT_BYTES, } from '@nimbus-sh/platform/limits.js';
46
+ import { recordFailure } from '@nimbus-sh/platform/oom-discriminator.js';
47
+ import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
48
+ import { acquireSupervisorAllocation } from '@nimbus-sh/platform/heavy-alloc-coord.js';
48
49
  import { enc, dec } from '../_shared/bytes.js';
49
- import { decodeWriteBatchStream, } from '../_shared/w7-frame.js';
50
- import { WeightedCreditPool, } from '../_shared/weighted-credit-pool.js';
50
+ import { decodeWriteBatchStream, } from '@nimbus-sh/platform/w7-frame.js';
51
+ import { WeightedCreditPool, } from '@nimbus-sh/platform/weighted-credit-pool.js';
51
52
  import { LEGACY_SYMLINK_REGISTRY_PATH } from './symlink-registry.js';
52
53
  import { CRED_KERNEL, } from '../runtime/os-contracts.js';
53
54
  const CONTENT_ID_ALLOCATION_ATTEMPTS = 8;
@@ -305,10 +306,9 @@ export class SqliteVFS {
305
306
  _lruShrinkRefcount = 0;
306
307
  // ── Running counters for O(1) getStats() (B3 / AUDIT M10 / M-S8) ──
307
308
  // Replaces the triple scan of this.inodes on every /api/stats poll.
308
- // Bootstrapped in loadInodes(); maintained at every mutator entry:
309
- // mkdir/rmdir/writeFile/unlink/writeBatch (rename is a no-op
310
- // same inode, new path). Invariant: these match a fresh O(N)
311
- // walk of this.inodes. Unit-tested in the A5/B3 runtime tests.
309
+ // Bootstrapped in loadInodes(); maintained at committed publication by
310
+ // mkdir, batch writes/deletes and rename. These match a fresh O(N)
311
+ // walk of this.inodes; rollback reloads them from the durable rows.
312
312
  _totalFiles = 0;
313
313
  _totalDirs = 0;
314
314
  _usedBytes = 0;
@@ -322,6 +322,7 @@ export class SqliteVFS {
322
322
  // so unrelated writes no longer invalidate them. In-memory only — the
323
323
  // clock resets with the DO lifetime, exactly like the caches keyed on it.
324
324
  _pathRevisions = new Map();
325
+ transactionPublication = null;
325
326
  // ── Invalidation log (facet cache coherence) ──────────────────────────
326
327
  // A facet's resident set is a cache of this VFS, and it learns what to
327
328
  // drop by asking `invalidatedSince(cursor)` for the delta. _pathRevisions
@@ -1254,6 +1255,11 @@ export class SqliteVFS {
1254
1255
  * additional coverage, since no facet view keys on a grandparent.
1255
1256
  */
1256
1257
  bumpRevision(paths) {
1258
+ if (this.transactionPublication) {
1259
+ for (const path of paths)
1260
+ this.transactionPublication.paths.add(path);
1261
+ return;
1262
+ }
1257
1263
  const rev = ++this._revision;
1258
1264
  for (const path of paths) {
1259
1265
  let p = normalizeVfsPath(path);
@@ -1454,7 +1460,7 @@ export class SqliteVFS {
1454
1460
  this._addToChildrenIndex(pp, path);
1455
1461
  this._totalDirs++; // B3
1456
1462
  this.bumpRevision([path]);
1457
- this.events.emit('addDir', path);
1463
+ this.emitMutation('addDir', path);
1458
1464
  }
1459
1465
  writeFile(path, content, options, cred, onCommit) {
1460
1466
  this.assertMutationsAllowed([path]);
@@ -2223,11 +2229,11 @@ export class SqliteVFS {
2223
2229
  }
2224
2230
  const atime = atimeMs !== null && Number.isFinite(atimeMs) ? Math.trunc(atimeMs) : this.now();
2225
2231
  const mtime = mtimeMs !== null && Number.isFinite(mtimeMs) ? Math.trunc(mtimeMs) : this.now();
2232
+ this.sql.exec("UPDATE inodes SET atime = ?, mtime = ? WHERE path = ?", atime, mtime, inode.path);
2226
2233
  inode.atime = atime;
2227
2234
  inode.mtime = mtime;
2228
- this.sql.exec("UPDATE inodes SET atime = ?, mtime = ? WHERE path = ?", atime, mtime, inode.path);
2229
2235
  this.bumpRevision([inode.path]);
2230
- this.events.emit('change', inode.path);
2236
+ this.emitMutation('change', inode.path);
2231
2237
  }
2232
2238
  /**
2233
2239
  * Set the permission bits durably. Follows symlinks (POSIX chmod).
@@ -2265,10 +2271,10 @@ export class SqliteVFS {
2265
2271
  }
2266
2272
  this.assertMutationsAllowed([inode.path]);
2267
2273
  const full = inodeTypeBits(inode.kind) | (mode & 0o7777);
2268
- inode.mode = full;
2269
2274
  this.sql.exec("UPDATE inodes SET mode = ? WHERE path = ?", full, inode.path);
2275
+ inode.mode = full;
2270
2276
  this.bumpRevision([inode.path]);
2271
- this.events.emit('change', inode.path);
2277
+ this.emitMutation('change', inode.path);
2272
2278
  }
2273
2279
  chown(path, uid, gid, cred, followLeaf) {
2274
2280
  const resolved = this.checkAccess(path, 0, cred, { followLeaf });
@@ -2290,13 +2296,15 @@ export class SqliteVFS {
2290
2296
  if (gid !== null && gid === inode.gid && cred.uid !== 0 && !owner)
2291
2297
  throw vfsError('EPERM', resolved.path);
2292
2298
  this.assertMutationsAllowed([inode.path]);
2293
- inode.uid = uid ?? inode.uid;
2294
- inode.gid = gid ?? inode.gid;
2295
- if (cred.uid !== 0)
2296
- inode.mode &= ~0o6000;
2297
- this.sql.exec('UPDATE inodes SET uid = ?, gid = ?, mode = ? WHERE path = ?', inode.uid, inode.gid, inode.mode, inode.path);
2299
+ const nextUid = uid ?? inode.uid;
2300
+ const nextGid = gid ?? inode.gid;
2301
+ const nextMode = cred.uid === 0 ? inode.mode : inode.mode & ~0o6000;
2302
+ this.sql.exec('UPDATE inodes SET uid = ?, gid = ?, mode = ? WHERE path = ?', nextUid, nextGid, nextMode, inode.path);
2303
+ inode.uid = nextUid;
2304
+ inode.gid = nextGid;
2305
+ inode.mode = nextMode;
2298
2306
  this.bumpRevision([inode.path]);
2299
- this.events.emit('change', inode.path);
2307
+ this.emitMutation('change', inode.path);
2300
2308
  }
2301
2309
  /**
2302
2310
  * Enumerate the filesystem, one bounded page at a time.
@@ -2506,7 +2514,14 @@ export class SqliteVFS {
2506
2514
  }
2507
2515
  rename(oldPath, newPath, cred) {
2508
2516
  this.assertMutationsAllowed([oldPath, newPath]);
2509
- this.checkAccess(oldPath, 0, cred, { followLeaf: false });
2517
+ // Resolve private /tmp names to storage keys before reading or mutating.
2518
+ const source = this.checkAccess(oldPath, 0, cred, { followLeaf: false });
2519
+ const inode = source.inode;
2520
+ if (!inode)
2521
+ throw vfsError('ENOENT', oldPath);
2522
+ const target = this.checkAccess(newPath, 0, cred, { followLeaf: false, allowMissingLeaf: true });
2523
+ oldPath = source.path;
2524
+ newPath = target.path;
2510
2525
  this.checkParentAccess(oldPath, cred);
2511
2526
  this.checkParentAccess(newPath, cred);
2512
2527
  if (oldPath === newPath)
@@ -2514,9 +2529,6 @@ export class SqliteVFS {
2514
2529
  if (newPath.startsWith(`${oldPath}/`)) {
2515
2530
  throw new Error(`EINVAL: cannot move ${oldPath} inside itself`);
2516
2531
  }
2517
- const inode = this.inodes.get(oldPath);
2518
- if (!inode)
2519
- throw new Error("ENOENT: " + oldPath);
2520
2532
  this.checkStickyParentMutation(oldPath, inode, cred);
2521
2533
  // W-3 (WASI filesystem WASI): if newPath already exists, unlink it first so the
2522
2534
  // SQL UPDATE doesn't conflict on inodes.path uniqueness. POSIX rename(2)
@@ -2525,7 +2537,7 @@ export class SqliteVFS {
2525
2537
  // a 2nd `make` after a prior successful build throws on UPDATE
2526
2538
  // failure. Pre-unlink covers both file-over-file and file-over-dir
2527
2539
  // (the latter is rare but POSIX permits it for empty dirs).
2528
- const destInode = this.inodes.get(newPath);
2540
+ const destInode = target.inode;
2529
2541
  if (destInode) {
2530
2542
  this.checkStickyParentMutation(newPath, destInode, cred);
2531
2543
  if (destInode.isDir) {
@@ -2697,7 +2709,7 @@ export class SqliteVFS {
2697
2709
  }
2698
2710
  }
2699
2711
  this.bumpRevision([...touchedPaths]);
2700
- this.events.emit('rename', newPath, oldPath);
2712
+ this.emitMutation('rename', newPath, oldPath);
2701
2713
  this.runContentMaintenanceSafely(1);
2702
2714
  }
2703
2715
  /**
@@ -3346,6 +3358,76 @@ export class SqliteVFS {
3346
3358
  }
3347
3359
  return this.errorMessage(error).toUpperCase().includes('SQLITE_NOMEM');
3348
3360
  }
3361
+ /**
3362
+ * Participate in an embedder's synchronous transaction: VFS writes and SQL
3363
+ * issued by callback commit together; on rollback the in-memory mirror
3364
+ * returns to the committed state before the error is rethrown with cause.
3365
+ *
3366
+ * This method MUST own the outermost transaction on this VFS's SQL host;
3367
+ * use it instead of wrapping VFS calls in storage.transactionSync. Do not
3368
+ * nest it, return a Promise/thenable, or start asynchronous work inside it.
3369
+ * The callback may read its writes. Revisions and events publish only on
3370
+ * commit, once for the combined mutation. Existing credential checks apply.
3371
+ *
3372
+ * Inodes are an always-resident cache (absence means ENOENT), so rollback
3373
+ * discards cached chunks and rebuilds metadata/children/counters from SQL.
3374
+ * No inode snapshot or undo log is retained. Recovery failure is surfaced
3375
+ * with both errors; the embedder must discard this VFS in that case.
3376
+ */
3377
+ withTransaction(callback) {
3378
+ if (this.transactionPublication !== null) {
3379
+ throw new Error('[sqlite-vfs] nested embedder transactions are not supported');
3380
+ }
3381
+ const storage = this.ctx.storage;
3382
+ if (!storage)
3383
+ throw new Error('[sqlite-vfs] atomic storage operation requires transactionSync');
3384
+ const publication = {
3385
+ paths: new Set(),
3386
+ events: new Array(),
3387
+ };
3388
+ const maintenancePending = this.maintenancePending;
3389
+ this.transactionPublication = publication;
3390
+ let result;
3391
+ try {
3392
+ result = storage.transactionSync(() => {
3393
+ const value = callback();
3394
+ if (value !== null && (typeof value === 'object' || typeof value === 'function')
3395
+ && 'then' in value && typeof value.then === 'function') {
3396
+ throw new TypeError('[sqlite-vfs] transaction callback must be synchronous');
3397
+ }
3398
+ return value;
3399
+ });
3400
+ }
3401
+ catch (error) {
3402
+ this.evictAll();
3403
+ this.maintenancePending = maintenancePending;
3404
+ try {
3405
+ this.loadInodes();
3406
+ }
3407
+ catch (reloadError) {
3408
+ throw new AggregateError([error, reloadError], '[sqlite-vfs] transaction rollback reload failed', { cause: error });
3409
+ }
3410
+ throw new Error('[sqlite-vfs] embedder transaction rolled back', { cause: error });
3411
+ }
3412
+ finally {
3413
+ this.transactionPublication = null;
3414
+ }
3415
+ if (publication.paths.size > 0)
3416
+ this.bumpRevision([...publication.paths]);
3417
+ for (const event of publication.events) {
3418
+ this.events.emit(event.type, event.path, event.oldPath);
3419
+ }
3420
+ this.runContentMaintenanceSafely(1);
3421
+ return result;
3422
+ }
3423
+ emitMutation(type, path, oldPath) {
3424
+ if (this.transactionPublication) {
3425
+ this.transactionPublication.events.push({ type, path, oldPath });
3426
+ }
3427
+ else {
3428
+ this.events.emit(type, path, oldPath);
3429
+ }
3430
+ }
3349
3431
  transactionSync(callback) {
3350
3432
  if (!this.ctx?.storage?.transactionSync) {
3351
3433
  throw new Error('[sqlite-vfs] atomic storage operation requires transactionSync');
@@ -3601,6 +3683,8 @@ export class SqliteVFS {
3601
3683
  return { transactions };
3602
3684
  }
3603
3685
  runContentMaintenanceSafely(maxTransactions, force = false) {
3686
+ if (this.transactionPublication)
3687
+ return;
3604
3688
  if (!force && !this.maintenancePending)
3605
3689
  return;
3606
3690
  const startedAt = performance.now();
@@ -3906,10 +3990,10 @@ export class SqliteVFS {
3906
3990
  }
3907
3991
  // 5. Events observe the already-published metadata and revision.
3908
3992
  for (const inode of deletedInodes) {
3909
- this.events.emit(inode.isDir ? 'unlinkDir' : 'unlink', inode.path);
3993
+ this.emitMutation(inode.isDir ? 'unlinkDir' : 'unlink', inode.path);
3910
3994
  }
3911
3995
  for (const entry of plan.inodes) {
3912
- this.events.emit(entry.isDir ? 'addDir' : replacedPaths.has(entry.path) ? 'change' : 'add', entry.path);
3996
+ this.emitMutation(entry.isDir ? 'addDir' : replacedPaths.has(entry.path) ? 'change' : 'add', entry.path);
3913
3997
  }
3914
3998
  this.recordDuration(this._postCommitDuration, performance.now() - postCommitStartedAt);
3915
3999
  return { inodes: inodeCount, chunks: chunkCount };
@@ -27,8 +27,11 @@ import type { CommandResult, RunOptions, SandboxFs } from '../substrate/lifo/san
27
27
  import type { ITerminal } from '../substrate/lifo/terminal/ITerminal.js';
28
28
  import { SqliteVFS } from '../vfs/sqlite-vfs.js';
29
29
  import type { SqlDatabase, TransactionHost } from '../runtime/os-contracts.js';
30
+ import { SessionProcessSupervisor } from '../runtime/session-process-supervisor.js';
30
31
  import type { FacetHost } from '../runtime/facet-host.js';
31
32
  import { type RuntimePackage } from '../runtime/runtime-package.js';
33
+ import { type CtxExports, type FabricComposition } from '@nimbus-sh/platform/composition.js';
34
+ import { type SupervisorOpEnvelope, type SupervisorOpHandler } from './supervisor-op.js';
32
35
  export interface NimbusWorkspaceOptions {
33
36
  /** The host's SQLite. In a Durable Object: `ctx.storage.sql`. */
34
37
  readonly sql: SqlDatabase;
@@ -106,6 +109,15 @@ export interface NimbusWorkspaceOptions {
106
109
  * carry REPLs and a resident-process substrate this cannot reach.
107
110
  */
108
111
  readonly facets?: FacetHost;
112
+ /** Isolate-wide fabric composition, including the hosting namespace. */
113
+ readonly fabric?: FabricComposition;
114
+ /** Explicit exports override the bag on the transaction host. */
115
+ readonly ctxExports?: CtxExports;
116
+ /** The process table that allocated supervisor-binding pids. */
117
+ readonly processes?: SessionProcessSupervisor;
118
+ readonly processOutput?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
119
+ /** Host operations, including overrides for host-specific accounting. */
120
+ readonly supervisorOps?: Readonly<Record<string, SupervisorOpHandler>>;
109
121
  }
110
122
  /**
111
123
  * A durable filesystem and a shell over it.
@@ -120,6 +132,7 @@ export interface NimbusWorkspaceOptions {
120
132
  */
121
133
  export declare class NimbusWorkspace {
122
134
  private readonly sql;
135
+ private readonly supervisorOps;
123
136
  /**
124
137
  * Credentialed and mount-aware. Acts as the session user, never as the
125
138
  * kernel: a pid-less caller must not gain more authority than the shell it
@@ -142,6 +155,8 @@ export declare class NimbusWorkspace {
142
155
  private constructor();
143
156
  static create(options: NimbusWorkspaceOptions): Promise<NimbusWorkspace>;
144
157
  exec(command: string, options?: RunOptions): Promise<CommandResult>;
158
+ /** The hosting object forwards its supervisorOp RPC to this method. */
159
+ supervisorOp(envelope: SupervisorOpEnvelope): Promise<unknown>;
145
160
  /**
146
161
  * Apply the login files, and begin reading the terminal when there is one.
147
162
  *
@@ -1 +1 @@
1
- {"version":3,"file":"nimbus-workspace.d.ts","sourceRoot":"","sources":["../../src/workspace/nimbus-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,kCAAkC,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAE7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAI9E,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,SAAS,EAAqB,MAAM,sBAAsB,CAAC;AAMpE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAK1D,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAKxF,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,eAAe,CAAC;IACxC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,kDAAkD;IAClD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IACzC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC9C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IA4BxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IA3BtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAE/C,OAAO;WAoBM,MAAM,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC;IAwD9E,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC;IAInE;;;;;;OAMG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;;;;;OAMG;IACH,KAAK,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAK3D;;;;;;;OAOG;IACH,OAAO,IAAI,IAAI;CAKhB;AAwJD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAoElF"}
1
+ {"version":3,"file":"nimbus-workspace.d.ts","sourceRoot":"","sources":["../../src/workspace/nimbus-workspace.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,8 @@ import { rehydrateInstalledRuntimesView, } from '../runtime/installed-runtimes.j
34
34
  import { seedRuntimePackage } from '../runtime/runtime-package.js';
35
35
  import { registerUnixCommands } from '../shell/unix-commands.js';
36
36
  import { installPathExecResolver } from '../shell/exec-dispatch.js';
37
+ import { adoptCtxExports, composeFabric } from '@nimbus-sh/platform/composition.js';
38
+ import { createSupervisorOpHandler } from './supervisor-op.js';
37
39
  /**
38
40
  * A durable filesystem and a shell over it.
39
41
  *
@@ -47,6 +49,7 @@ import { installPathExecResolver } from '../shell/exec-dispatch.js';
47
49
  */
48
50
  export class NimbusWorkspace {
49
51
  sql;
52
+ supervisorOps;
50
53
  /**
51
54
  * Credentialed and mount-aware. Acts as the session user, never as the
52
55
  * kernel: a pid-less caller must not gain more authority than the shell it
@@ -66,8 +69,9 @@ export class NimbusWorkspace {
66
69
  */
67
70
  env;
68
71
  commands;
69
- constructor(vfs, kernel, shell, registry, env, sql) {
72
+ constructor(vfs, kernel, shell, registry, env, sql, supervisorOps) {
70
73
  this.sql = sql;
74
+ this.supervisorOps = supervisorOps;
71
75
  this.vfs = vfs;
72
76
  this.kernel = kernel;
73
77
  this.shell = shell;
@@ -80,6 +84,12 @@ export class NimbusWorkspace {
80
84
  this.fs = new SandboxFsImpl(kernel.vfs.as(CRED_SESSION_USER), () => shell.getCwd());
81
85
  }
82
86
  static async create(options) {
87
+ if (options.fabric)
88
+ composeFabric(options.fabric);
89
+ const exports = options.ctxExports
90
+ ?? options.transactions?.exports;
91
+ if (exports)
92
+ adoptCtxExports(exports);
83
93
  const vfs = options.vfs ?? openFilesystem(options);
84
94
  const mounts = options.mounts ?? DEFAULT_MOUNT_POINTS;
85
95
  seedBaseFilesystem(vfs, mounts);
@@ -118,11 +128,18 @@ export class NimbusWorkspace {
118
128
  home,
119
129
  });
120
130
  }
121
- return new NimbusWorkspace(vfs, kernel, shell, registry, env, options.sql);
131
+ const supervisorOps = createSupervisorOpHandler({
132
+ vfs, processes: options.processes, output: options.processOutput, extend: options.supervisorOps,
133
+ });
134
+ return new NimbusWorkspace(vfs, kernel, shell, registry, env, options.sql, supervisorOps);
122
135
  }
123
136
  exec(command, options) {
124
137
  return this.commands.run(command, options);
125
138
  }
139
+ /** The hosting object forwards its supervisorOp RPC to this method. */
140
+ supervisorOp(envelope) {
141
+ return this.supervisorOps(envelope);
142
+ }
126
143
  /**
127
144
  * Apply the login files, and begin reading the terminal when there is one.
128
145
  *
@@ -243,7 +260,7 @@ async function registerWasmRuntimes(deps) {
243
260
  getEsbuild: () => {
244
261
  if (!esbuild) {
245
262
  esbuild = import('../runtime/esbuild-service.js')
246
- .then((module) => new module.EsbuildService(deps.vfs));
263
+ .then((module) => new module.EsbuildService(deps.vfs.as(CRED_KERNEL)));
247
264
  }
248
265
  return esbuild;
249
266
  },
@@ -0,0 +1,23 @@
1
+ import type { SqliteVFS } from '../vfs/sqlite-vfs.js';
2
+ import type { SessionProcessSupervisor } from '../runtime/session-process-supervisor.js';
3
+ /** Identity comes from the supervisor binding, never from facet arguments. */
4
+ export interface SupervisorOpEnvelope {
5
+ readonly op: string;
6
+ readonly args?: readonly unknown[];
7
+ readonly pid?: number;
8
+ readonly writerId?: string;
9
+ readonly mutationOwner?: string;
10
+ readonly stream?: ReadableStream<Uint8Array>;
11
+ }
12
+ export type SupervisorOpHandler = (envelope: SupervisorOpEnvelope) => unknown;
13
+ export interface SupervisorOpDeps {
14
+ readonly vfs: SqliteVFS;
15
+ /** Absent a process table, operations use the unprivileged session user. */
16
+ readonly processes?: SessionProcessSupervisor;
17
+ readonly output?: (stream: 'stdout' | 'stderr', pid: number, data: string) => void;
18
+ /** Host handlers override defaults, for example to account for stream drains. */
19
+ readonly extend?: Readonly<Record<string, SupervisorOpHandler>>;
20
+ }
21
+ /** One dispatch method lets any host serve its workspace to process facets. */
22
+ export declare function createSupervisorOpHandler(deps: SupervisorOpDeps): (envelope: SupervisorOpEnvelope) => Promise<unknown>;
23
+ //# sourceMappingURL=supervisor-op.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-op.d.ts","sourceRoot":"","sources":["../../src/workspace/supervisor-op.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAItD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAEzF,8EAA8E;AAC9E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;CAC9C;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,oBAAoB,KAAK,OAAO,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,wBAAwB,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnF,iFAAiF;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;CACjE;AAiCD,+EAA+E;AAC/E,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,gBAAgB,GACrB,CAAC,QAAQ,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAuDtD"}
@@ -0,0 +1,92 @@
1
+ import { CRED_SESSION_USER } from '../runtime/os-contracts.js';
2
+ import { SqliteRuntimeFsBridge } from '../runtime/sqlite-runtime-fs-bridge.js';
3
+ import { getSymlinkRegistry } from '../vfs/symlink-registry.js';
4
+ function stringArg(envelope, index) {
5
+ const value = envelope.args?.[index];
6
+ if (typeof value !== 'string') {
7
+ throw new Error(`supervisor op ${envelope.op}: argument ${index} must be a string`);
8
+ }
9
+ return value;
10
+ }
11
+ function numberArg(envelope, index) {
12
+ const value = envelope.args?.[index];
13
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
14
+ throw new Error(`supervisor op ${envelope.op}: argument ${index} must be a number`);
15
+ }
16
+ return value;
17
+ }
18
+ function contentArg(envelope, index) {
19
+ const value = envelope.args?.[index];
20
+ if (typeof value === 'string' || value instanceof Uint8Array)
21
+ return value;
22
+ if (value instanceof ArrayBuffer)
23
+ return new Uint8Array(value);
24
+ throw new Error(`supervisor op ${envelope.op}: argument ${index} must be bytes or text`);
25
+ }
26
+ function credFor(deps, pid) {
27
+ if (pid === undefined)
28
+ return CRED_SESSION_USER;
29
+ if (!Number.isInteger(pid) || pid <= 0) {
30
+ throw new Error('supervisor op: filesystem operation requires a valid process pid');
31
+ }
32
+ return deps.processes ? deps.processes.cred(pid) : CRED_SESSION_USER;
33
+ }
34
+ /** One dispatch method lets any host serve its workspace to process facets. */
35
+ export function createSupervisorOpHandler(deps) {
36
+ const bridges = new Map();
37
+ const bridgeFor = (pid) => {
38
+ const key = pid ?? 0;
39
+ const credentialed = deps.vfs.as(credFor(deps, pid));
40
+ const held = bridges.get(key);
41
+ if (held) {
42
+ held.updateCredential(credentialed);
43
+ return held;
44
+ }
45
+ const built = new SqliteRuntimeFsBridge(credentialed, deps.vfs);
46
+ bridges.set(key, built);
47
+ return built;
48
+ };
49
+ const ops = {
50
+ readFile: async (e) => {
51
+ const bytes = await bridgeFor(e.pid).readFile(stringArg(e, 0));
52
+ return bytes === null ? null : new TextDecoder().decode(bytes);
53
+ },
54
+ readFileBytes: (e) => bridgeFor(e.pid).readFile(stringArg(e, 0)),
55
+ stat: (e) => bridgeFor(e.pid).stat(stringArg(e, 0)),
56
+ lstat: (e) => bridgeFor(e.pid).stat(stringArg(e, 0), { followSymlinks: false }),
57
+ exists: async (e) => (await bridgeFor(e.pid).stat(stringArg(e, 0))) !== null,
58
+ readdir: (e) => bridgeFor(e.pid).readdir(stringArg(e, 0)),
59
+ readlink: (e) => bridgeFor(e.pid).readlink(stringArg(e, 0)),
60
+ fsReadRange: (e) => bridgeFor(e.pid).readRange(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2)),
61
+ fsReadRangeUncached: (e) => bridgeFor(e.pid).readRange(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2), { cached: false }),
62
+ fsRevision: (e) => bridgeFor(e.pid).revision(e.args?.[0] === undefined ? undefined : stringArg(e, 0)),
63
+ hasLegacySymlinkUnder: (e) => getSymlinkRegistry(deps.vfs).hasAtOrBelow(stringArg(e, 0)),
64
+ writeFile: (e) => bridgeFor(e.pid).writeFile(stringArg(e, 0), contentArg(e, 1)),
65
+ mkdir: (e) => bridgeFor(e.pid).mkdir(stringArg(e, 0), { recursive: true }),
66
+ rmdir: (e) => bridgeFor(e.pid).rmdir(stringArg(e, 0)),
67
+ unlink: (e) => bridgeFor(e.pid).unlink(stringArg(e, 0)),
68
+ rename: (e) => bridgeFor(e.pid).rename(stringArg(e, 0), stringArg(e, 1)),
69
+ symlink: (e) => bridgeFor(e.pid).symlink(stringArg(e, 0), stringArg(e, 1)),
70
+ chmod: (e) => bridgeFor(e.pid).chmod(stringArg(e, 0), numberArg(e, 1)),
71
+ utimes: (e) => bridgeFor(e.pid).utimes(stringArg(e, 0), numberArg(e, 1), numberArg(e, 2)),
72
+ fsTruncate: (e) => bridgeFor(e.pid).truncate(stringArg(e, 0), numberArg(e, 1)),
73
+ writeBatchStream: (e) => {
74
+ if (!e.stream)
75
+ throw new Error('supervisor op writeBatchStream: no stream');
76
+ return deps.vfs.as(credFor(deps, e.pid)).writeStream(e.stream, { mutationOwner: e.mutationOwner });
77
+ },
78
+ stdout: (e) => { deps.output?.('stdout', e.pid ?? 0, stringArg(e, 0)); },
79
+ stderr: (e) => { deps.output?.('stderr', e.pid ?? 0, stringArg(e, 0)); },
80
+ };
81
+ const extend = deps.extend ?? {};
82
+ return async (envelope) => {
83
+ if (!envelope || typeof envelope.op !== 'string') {
84
+ throw new Error('supervisor op: envelope names no operation');
85
+ }
86
+ const handler = Object.hasOwn(extend, envelope.op) ? extend[envelope.op]
87
+ : Object.hasOwn(ops, envelope.op) ? ops[envelope.op] : undefined;
88
+ if (!handler)
89
+ throw new Error(`supervisor op: '${envelope.op}' is not served by this host`);
90
+ return handler(envelope);
91
+ };
92
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nimbus-sh/core",
3
- "version": "0.5.0",
4
- "description": "Backend-agnostic half of Nimbus \u2014 the durable filesystem, the shell, and the OS/process contracts, over a host-supplied SQL port.",
3
+ "version": "0.7.0",
4
+ "description": "Backend-agnostic half of Nimbus the durable filesystem, the shell, and the OS/process contracts, over a host-supplied SQL port.",
5
5
  "keywords": [
6
6
  "filesystem",
7
7
  "shell",
@@ -55,6 +55,7 @@
55
55
  "typecheck": "tsc --noEmit"
56
56
  },
57
57
  "dependencies": {
58
+ "@nimbus-sh/platform": "^0.2.0",
58
59
  "@renovatebot/pep440": "^5.0.0",
59
60
  "acorn": "^8.15.0",
60
61
  "acorn-walk": "^8.3.5",
@@ -18,10 +18,11 @@
18
18
  /**
19
19
  * A command's output destination.
20
20
  *
21
- * `writeBytes` is present on sinks that store bytes verbatim (files). Sinks
22
- * that are inherently textual — the terminal, shell pipes omit it and
23
- * receive decoded text instead. This is a real capability difference, not a
24
- * fallback: a terminal has no way to hold a byte that is not text.
21
+ * `writeBytes` is present on sinks that store bytes verbatim files,
22
+ * `/dev/null`, and shell pipes, which carry the producer's exact bytes.
23
+ * Sinks without it take decoded text instead. This is a real capability
24
+ * difference, not a fallback: a terminal has no way to hold a byte that is
25
+ * not text.
25
26
  */
26
27
  export interface ByteSink {
27
28
  write(text: string): void;
@@ -11,7 +11,7 @@
11
11
  * L4 — registry.npmjs.org origin ~100-300 ms cross-region
12
12
  *
13
13
  * Pre-cache metrics support: hit/miss counters lived in
14
- * src/observability/diag-counters.ts but only tracked the RPC-call
14
+ * @nimbus-sh/platform/diag-counters.js but only tracked the RPC-call
15
15
  * boundary, not per-tier. Those flat counters have since been removed.
16
16
  *
17
17
  * This module adds per-tier × per-kind counters. SAME singleton-per-
@@ -229,3 +229,29 @@ export function reset(): void {
229
229
  }
230
230
  _lastResetAt = Date.now();
231
231
  }
232
+
233
+ /**
234
+ * cache-obs-2: fold facet-collected per-tier cache events into the
235
+ * DO-side cache-stats singleton. Called from installer.ts after a
236
+ * batch-facet / resolve-facet returns — mirrors recordR2RaceCounters
237
+ * (the facet collects metrics in its result and the supervisor folds
238
+ * them into the DO isolate).
239
+ *
240
+ * Each event has shape:
241
+ * { kind: 'hit', tier: 'L2'|'L3'|'L4', cacheKind: 'tarball'|'packument'|'asset', bytes: number }
242
+ * { kind: 'miss', tier: ..., cacheKind: ... }
243
+ */
244
+ export type CacheStatEvent =
245
+ | { kind: 'hit'; tier: CacheTier; cacheKind: CacheKind; bytes: number }
246
+ | { kind: 'miss'; tier: CacheTier; cacheKind: CacheKind };
247
+
248
+ export function recordCacheStatEvents(events: readonly CacheStatEvent[] | undefined): void {
249
+ if (!events || events.length === 0) return;
250
+ for (const e of events) {
251
+ if (e.kind === 'hit') {
252
+ recordHit(e.tier, e.cacheKind, e.bytes);
253
+ } else {
254
+ recordMiss(e.tier, e.cacheKind);
255
+ }
256
+ }
257
+ }
@@ -7,7 +7,7 @@
7
7
  * 1. Install-time supervisor (TS) — `src/npm-resolver.ts` re-exports the
8
8
  * typed functions for tree resolution.
9
9
  *
10
- * 2. LoaderPool isolates (JS string) — `src/loaders/pre-bundle-preamble.ts`
10
+ * 2. IsolatePool isolates (JS string) — `src/loaders/pre-bundle-preamble.ts`
11
11
  * embeds `getExportsResolverJS()` as part of the pool preamble so the
12
12
  * pre-bundle facet uses identical resolution semantics to the supervisor.
13
13
  *
@@ -38,6 +38,13 @@
38
38
  * constructed Session's connect/post are inert.
39
39
  * Tools (e.g. nuxi) that open a Session purely
40
40
  * for optional profiling degrade cleanly.
41
+ * - node:zlib — full surface: every *Sync variant, brotli/zstd,
42
+ * crc32, constants, and streaming create* factories
43
+ * (probe-verified 2026-08-23 at compat date
44
+ * 2026-04-01). Forwarded verbatim by the zlib
45
+ * block in node-shims.ts; results are the host
46
+ * realm's own Buffers, which the widened
47
+ * __BufferMod.isBuffer recognizes.
41
48
  */
42
49
 
43
50
  export function getRealNodeImportsCode(): string {
@@ -49,5 +56,6 @@ import * as __real_diagnostics_channel from 'node:diagnostics_channel';
49
56
  import * as __real_repl from 'node:repl';
50
57
  import * as __real_vm from 'node:vm';
51
58
  import * as __real_inspector from 'node:inspector';
59
+ import * as __real_zlib from 'node:zlib';
52
60
  `.trim();
53
61
  }
@@ -23,7 +23,7 @@
23
23
  * equivalent retry loops inside their isolated function bodies.
24
24
  */
25
25
 
26
- import { disposeRpcResource } from './rpc-dispose.js';
26
+ import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
27
27
  import { errorText } from './error-text.js';
28
28
 
29
29
  /** Default retry count AFTER the first attempt (3 = up to 4 total attempts). */