@ai-ecoverse/slicc-kernel 1.167.4 → 1.168.1

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.
@@ -1753,9 +1753,11 @@ var OpfsFs = class {
1753
1753
  this.channel.onmessage = ({ data }) => {
1754
1754
  const { paths, trees, outside } = noticeOf(data);
1755
1755
  for (const path of [...paths, ...trees]) this.forget(normalizePath(path));
1756
- const tell = () => remote?.emit({ paths, trees });
1757
- if (!outside) tell();
1758
- else void this.noticed(paths.map(normalizePath), Date.now()).finally(tell);
1756
+ if (!outside) remote?.emit({ paths, trees });
1757
+ else
1758
+ void this.noticed(paths.map(normalizePath), Date.now()).finally(
1759
+ () => remote?.emit({ paths, trees, outside: true })
1760
+ );
1759
1761
  };
1760
1762
  this.channel.unref?.();
1761
1763
  }
@@ -2121,6 +2123,9 @@ var OpfsFs = class {
2121
2123
  this.exact--;
2122
2124
  }
2123
2125
  }
2126
+ refresh(trees) {
2127
+ for (const tree2 of trees) this.forget(normalizePath(tree2));
2128
+ }
2124
2129
  forget(path) {
2125
2130
  this.changes++;
2126
2131
  this.dirs.deleteTree(path);
@@ -3077,15 +3082,15 @@ var OpfsFs = class {
3077
3082
  const hooks = {};
3078
3083
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
3079
3084
  const copying = sourceStat.isDirectory && !movable(source.handle);
3080
- const staged = resolvePath(parent.path, staging);
3085
+ const staged2 = resolvePath(parent.path, staging);
3081
3086
  const place = async () => {
3082
3087
  this.forget(destination);
3083
3088
  const copies = /* @__PURE__ */ new Map();
3084
3089
  const root = { ino: inodeOf(source.path), ...moving };
3085
3090
  const owned4 = lock ? { lock } : {};
3086
3091
  hooks.notify?.();
3087
- const first = copying ? await this.intend({ from: source.path, to: staged, root, ...owned4 }) : void 0;
3088
- const second = copying && swap ? await this.intend({ from: staged, to: destination, replace: true, root, ...owned4 }) : void 0;
3092
+ const first = copying ? await this.intend({ from: source.path, to: staged2, root, ...owned4 }) : void 0;
3093
+ const second = copying && swap ? await this.intend({ from: staged2, to: destination, replace: true, root, ...owned4 }) : void 0;
3089
3094
  await hooks.started?.().catch(async (err) => {
3090
3095
  for (const intent of [first, second]) {
3091
3096
  if (intent) await this.meta.update(intent, () => void 0);
@@ -3096,11 +3101,11 @@ var OpfsFs = class {
3096
3101
  targetStat !== null,
3097
3102
  () => this.place(source.handle, dir, staging, copies)
3098
3103
  ).catch(async (err) => {
3099
- if (first) await this.rollBack(staged, source.path, [first, second]);
3104
+ if (first) await this.rollBack(staged2, source.path, [first, second]);
3100
3105
  throw err;
3101
3106
  });
3102
3107
  if (swap) await this.rm(destination, { recursive: true });
3103
- if (second) await this.intend({ from: staged, to: destination, root, ...owned4 }, second);
3108
+ if (second) await this.intend({ from: staged2, to: destination, root, ...owned4 }, second);
3104
3109
  await this.meta.move(source.path, destination, carry, left);
3105
3110
  this.movePending(source.path, destination);
3106
3111
  if (!moved2) {
@@ -7598,14 +7603,14 @@ var UserDb = class {
7598
7603
  this.fs = fs;
7599
7604
  this.owners = owners;
7600
7605
  }
7601
- async stampHome(row, shared, homeMode) {
7606
+ async stampHome(row, shared2, homeMode) {
7602
7607
  const home = this.fs.resolvePath("/", row.home);
7603
7608
  if (!this.owners || !this.fs.chown || !HOME_DIR.test(home)) return;
7604
7609
  if (!await this.realDir(home)) return;
7605
- const gid = shared?.gid ?? row.gid;
7610
+ const gid = shared2?.gid ?? row.gid;
7606
7611
  await this.fs.chown(home, { uid: row.uid, gid, follow: false, kind: "adopt" });
7607
7612
  const scoop = row.name.startsWith("scoop-");
7608
- await this.fs.chmod(home, homeMode ?? (shared ? 1512 : scoop ? 488 : 448));
7613
+ await this.fs.chmod(home, homeMode ?? (shared2 ? 1512 : scoop ? 488 : 448));
7609
7614
  const pnpm = `${home}/.local/share/pnpm`;
7610
7615
  if (await this.realDir(pnpm)) await this.fs.chmod(pnpm, scoop ? 488 : 448);
7611
7616
  }
@@ -7642,9 +7647,9 @@ var UserDb = class {
7642
7647
  const [passwd, group] = await this.texts();
7643
7648
  return { passwd: parsePasswd(passwd), group: parseGroup(group) };
7644
7649
  }
7645
- async provision(row, umask, shared) {
7646
- const dirs = 511 & ~umask | (shared ? 1024 : 0);
7647
- const gid = shared?.gid ?? row.gid;
7650
+ async provision(row, umask, shared2) {
7651
+ const dirs = 511 & ~umask | (shared2 ? 1024 : 0);
7652
+ const gid = shared2?.gid ?? row.gid;
7648
7653
  const owner = this.owners ? { uid: row.uid, gid } : void 0;
7649
7654
  const made2 = owner ? { recursive: true, owner: { ...owner, mode: dirs, parents: dirs } } : { recursive: true };
7650
7655
  const pnpm = `${row.home}/.local/share/pnpm`;
@@ -7748,7 +7753,7 @@ var UserDb = class {
7748
7753
  const users = parsed(passwd);
7749
7754
  const groups = parsed(group);
7750
7755
  const found = users.find((r) => r.name === name2);
7751
- const shared = homeGroupOf(groups, options.homeGroup);
7756
+ const shared2 = homeGroupOf(groups, options.homeGroup);
7752
7757
  const homeMode = homeModeOf(options.homeMode);
7753
7758
  if (found) {
7754
7759
  const more = groupsOf(groups, options.groups ?? []);
@@ -7756,8 +7761,8 @@ var UserDb = class {
7756
7761
  throw fsError("EINVAL", `a user is no member of its own group ${name2}`);
7757
7762
  }
7758
7763
  if (found.uid >= FIRST_UID && found.uid <= LAST_UID) {
7759
- await this.provision(found, this.umaskFor(options), shared);
7760
- await this.stampHome(found, shared, homeMode);
7764
+ await this.provision(found, this.umaskFor(options), shared2);
7765
+ await this.stampHome(found, shared2, homeMode);
7761
7766
  }
7762
7767
  if (joined(name2, more)) await this.fs.writeFile(GROUP, formatGroup(group));
7763
7768
  remember();
@@ -7774,8 +7779,8 @@ var UserDb = class {
7774
7779
  const fresh3 = !await this.fs.exists(home);
7775
7780
  await this.fs.mkdir(home, { recursive: true });
7776
7781
  try {
7777
- await this.provision(row, this.umaskFor(options), shared);
7778
- await this.stampHome(row, shared, homeMode);
7782
+ await this.provision(row, this.umaskFor(options), shared2);
7783
+ await this.stampHome(row, shared2, homeMode);
7779
7784
  await this.fs.mkdir("/etc", { recursive: true });
7780
7785
  await this.commit(formatPasswd(passwd), formatGroup(group));
7781
7786
  } catch (err) {
@@ -7824,19 +7829,19 @@ var UserDb = class {
7824
7829
  );
7825
7830
  for (const r of parsed(keptGroups)) r.members = r.members.filter((m) => m !== name2);
7826
7831
  const home = this.fs.resolvePath("/", row.home);
7827
- const shared = parsed(passwd).some(
7832
+ const shared2 = parsed(passwd).some(
7828
7833
  (r) => r !== row && this.fs.resolvePath("/", r.home) === home
7829
7834
  );
7830
- const removeHome = !options.keepHome && !shared && HOME_DIR.test(home) && await this.fs.exists(home);
7831
- const staged = `/home/.${name2}.removed-${Date.now()}`;
7835
+ const removeHome = !options.keepHome && !shared2 && HOME_DIR.test(home) && await this.fs.exists(home);
7836
+ const staged2 = `/home/.${name2}.removed-${Date.now()}`;
7832
7837
  await this.commit(
7833
7838
  formatPasswd(passwd.filter((r) => r !== row)),
7834
7839
  formatGroup(keptGroups),
7835
- removeHome ? () => this.fs.rename(home, staged) : void 0
7840
+ removeHome ? () => this.fs.rename(home, staged2) : void 0
7836
7841
  );
7837
7842
  this.umasks.delete(name2);
7838
7843
  if (removeHome)
7839
- await this.fs.rm(staged, { recursive: true, force: true }).catch(() => void 0);
7844
+ await this.fs.rm(staged2, { recursive: true, force: true }).catch(() => void 0);
7840
7845
  return true;
7841
7846
  });
7842
7847
  }
@@ -8492,7 +8497,7 @@ function satisfies(version, range2) {
8492
8497
  }
8493
8498
 
8494
8499
  // src/version.ts
8495
- var KERNEL_VERSION = true ? "1.167.4" : "0.0.0-dev";
8500
+ var KERNEL_VERSION = true ? "1.168.1" : "0.0.0-dev";
8496
8501
  var OS_RELEASE = "6.0.0-slicc";
8497
8502
  function procVersion() {
8498
8503
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -8575,8 +8580,8 @@ function abiOf(raw2, fallback) {
8575
8580
  const abi = raw2 ?? fallback;
8576
8581
  return abi === "emscripten" || abi === "wasi" || abi === "js" ? abi : void 0;
8577
8582
  }
8578
- function commandOf(pkg, name2, raw2, packageAbi, shared) {
8579
- const withEnv = envFields({ ...shared, ...envOf(pkg, raw2.env) });
8583
+ function commandOf(pkg, name2, raw2, packageAbi, shared2) {
8584
+ const withEnv = envFields({ ...shared2, ...envOf(pkg, raw2.env) });
8580
8585
  const script = inside(pkg, raw2.script);
8581
8586
  if (script) {
8582
8587
  return { name: name2, abi: packageAbi, glue: script, wasm: script, argv0: name2, script, ...withEnv };
@@ -8626,13 +8631,13 @@ function commandsOf(pkg, manifest) {
8626
8631
  const packageAbi = abiOf(slicc.abi, "emscripten");
8627
8632
  const entries = slicc.commands;
8628
8633
  if (!packageAbi || !entries || typeof entries !== "object") return [];
8629
- const shared = envOf(pkg, slicc.env);
8634
+ const shared2 = envOf(pkg, slicc.env);
8630
8635
  const out = [];
8631
8636
  for (const [name2, raw2] of Object.entries(entries)) {
8632
8637
  if (!commandName(name2) || !raw2 || typeof raw2 !== "object") {
8633
8638
  continue;
8634
8639
  }
8635
- const command = commandOf(pkg, name2, raw2, packageAbi, shared);
8640
+ const command = commandOf(pkg, name2, raw2, packageAbi, shared2);
8636
8641
  if (command) out.push(command);
8637
8642
  }
8638
8643
  return out;
@@ -9793,8 +9798,8 @@ var UnlinkedKeeper = class {
9793
9798
  void peers.ask(tab, { kept: { items: [], paths } }, PEER_RM_MS);
9794
9799
  throw fsError("EBUSY", `${target}: open in another tab that did not answer`);
9795
9800
  }
9796
- const { whole, sends, shared } = await this.setAside(target, asked);
9797
- return { whole, undoing: (op) => this.undoing(op, peers, sends, heard, shared) };
9801
+ const { whole, sends, shared: shared2 } = await this.setAside(target, asked);
9802
+ return { whole, undoing: (op) => this.undoing(op, peers, sends, heard, shared2) };
9798
9803
  }
9799
9804
  async setAside(target, asked) {
9800
9805
  const mine = this.heldWithin(target);
@@ -9802,25 +9807,25 @@ var UnlinkedKeeper = class {
9802
9807
  for (const { holders } of asked)
9803
9808
  for (const { path } of holders) tabsOf.set(path, (tabsOf.get(path) ?? 0) + 1);
9804
9809
  const done5 = /* @__PURE__ */ new Map();
9805
- const shared = [];
9810
+ const shared2 = [];
9806
9811
  const sends = [];
9807
9812
  for (const { tab, holders, paths } of asked) {
9808
9813
  const items = [];
9809
9814
  for (const holder of holders) {
9810
9815
  if (!done5.has(holder.path)) {
9811
9816
  const many = mine.has(holder.path) || (tabsOf.get(holder.path) ?? 0) > 1;
9812
- done5.set(holder.path, await this.asideOne(holder, many, mine, shared));
9817
+ done5.set(holder.path, await this.asideOne(holder, many, mine, shared2));
9813
9818
  }
9814
9819
  items.push({ path: holder.path, hidden: done5.get(holder.path), fid: holder.fid });
9815
9820
  }
9816
9821
  sends.push({ tab, items, paths });
9817
9822
  }
9818
- return { whole: done5.get(target) !== void 0, sends, shared };
9823
+ return { whole: done5.get(target) !== void 0, sends, shared: shared2 };
9819
9824
  }
9820
- async asideOne(holder, many, mine, shared) {
9825
+ async asideOne(holder, many, mine, shared2) {
9821
9826
  if (!many) return this.asideFor(holder.path, holder.instance);
9822
9827
  const one = await this.sharedAside(holder.path, mine);
9823
- if (one) shared.push(one);
9828
+ if (one) shared2.push(one);
9824
9829
  return one?.hidden;
9825
9830
  }
9826
9831
  async sharedAside(path, mine) {
@@ -9865,16 +9870,16 @@ var UnlinkedKeeper = class {
9865
9870
  sharedLocks() {
9866
9871
  return this.locks;
9867
9872
  }
9868
- async undoing(op, peers, sends, heard, shared = []) {
9873
+ async undoing(op, peers, sends, heard, shared2 = []) {
9869
9874
  let failed3;
9870
9875
  let out;
9871
9876
  try {
9872
9877
  out = await op();
9873
9878
  } catch (err) {
9874
9879
  failed3 = err ?? new Error("failed");
9875
- await this.rollBackAsides(sends, shared);
9880
+ await this.rollBackAsides(sends, shared2);
9876
9881
  }
9877
- const hiddenShared = new Set(shared.map((one) => one.hidden));
9882
+ const hiddenShared = new Set(shared2.map((one) => one.hidden));
9878
9883
  const answers2 = sends.map(({ tab, items, paths }) => {
9879
9884
  const keeps = items.some((item) => item.hidden && hiddenShared.has(item.hidden));
9880
9885
  return {
@@ -9885,7 +9890,7 @@ var UnlinkedKeeper = class {
9885
9890
  await Promise.all(
9886
9891
  answers2.map(({ told }, i) => heard[i] ? Promise.race([told, delay(PEER_RM_MS)]) : void 0)
9887
9892
  );
9888
- for (const one of shared) {
9893
+ for (const one of shared2) {
9889
9894
  const waits = answers2.filter(
9890
9895
  ({ keeps }, i) => keeps && sends[i]?.items.some((item) => item.hidden === one.hidden)
9891
9896
  );
@@ -9895,7 +9900,7 @@ var UnlinkedKeeper = class {
9895
9900
  if (failed3 !== void 0) throw failed3;
9896
9901
  return out;
9897
9902
  }
9898
- async rollBackAsides(sends, shared) {
9903
+ async rollBackAsides(sends, shared2) {
9899
9904
  const back = /* @__PURE__ */ new Map();
9900
9905
  for (const item of sends.flatMap((send2) => send2.items)) {
9901
9906
  const { hidden, path } = item;
@@ -9903,7 +9908,7 @@ var UnlinkedKeeper = class {
9903
9908
  if (!back.has(hidden)) back.set(hidden, await this.moveBack(hidden, path));
9904
9909
  if (back.get(hidden)) item.hidden = void 0;
9905
9910
  }
9906
- for (const { hidden, path, local: local2, file } of shared) {
9911
+ for (const { hidden, path, local: local2, file } of shared2) {
9907
9912
  if (!back.get(hidden) || !file) continue;
9908
9913
  for (const holder of local2) {
9909
9914
  holder.unkeep(path, file);
@@ -13144,8 +13149,8 @@ var CwdTable = class {
13144
13149
  this.inodeOf = inodeOf2;
13145
13150
  }
13146
13151
  async hold(path, from) {
13147
- const shared = from === void 0 ? void 0 : this.held.get(from);
13148
- if (shared && shared.path === path) return this.share(from);
13152
+ const shared2 = from === void 0 ? void 0 : this.held.get(from);
13153
+ if (shared2 && shared2.path === path) return this.share(from);
13149
13154
  const ino = await this.inodeOf(path).catch(() => void 0);
13150
13155
  const slot2 = this.free.pop() ?? (this.next < UNTRACKED ? this.next++ : UNTRACKED);
13151
13156
  if (slot2 === UNTRACKED) return slot2;
@@ -16294,6 +16299,8 @@ var SYS_TREE = {
16294
16299
  slicc_opfs_ops: (listing) => `${listing.opfsOps ?? 0}
16295
16300
  `,
16296
16301
  slicc_cwd_checks: (listing) => `${listing.cwdChecks ?? 0}
16302
+ `,
16303
+ slicc_package_swaps: ({ swaps }) => `${swaps?.swaps ?? 0} ${(swaps?.longestMs ?? 0).toFixed(1)} ${swaps?.waits ?? 0} ${(swaps?.waitedMs ?? 0).toFixed(1)}
16297
16304
  `,
16298
16305
  slicc_rename_ack_busy: (listing) => `${listing.renameAcks?.busy ?? 0}
16299
16306
  `,
@@ -17509,13 +17516,13 @@ async function flock(req, ctx) {
17509
17516
  if (!FLOCK_KINDS.has(req.kind)) throw new KernelError("EINVAL");
17510
17517
  const { key, owner } = await flockOf(req.fd);
17511
17518
  const granted2 = req.kind !== "unlock";
17512
- const shared = locks.shared?.() === true;
17513
- if (shared && !granted2) await ctx.settle?.(req.fd, false);
17519
+ const shared2 = locks.shared?.() === true;
17520
+ if (shared2 && !granted2) await ctx.settle?.(req.fd, false);
17514
17521
  if (req.kind !== "unlock" && req.wait)
17515
17522
  await locks.flocks.wait(key, owner, req.kind, ctx.blocking);
17516
17523
  else if (!await locks.flocks.set(key, owner, req.kind)) throw new KernelError("EAGAIN");
17517
- if (shared && granted2) await grantedAcross(ctx, req.fd);
17518
- return shared ? json3(true) : done3;
17524
+ if (shared2 && granted2) await grantedAcross(ctx, req.fd);
17525
+ return shared2 ? json3(true) : done3;
17519
17526
  }
17520
17527
  async function grantedAcross(ctx, fd) {
17521
17528
  ctx.locks?.fresh?.();
@@ -17543,15 +17550,15 @@ async function recordLock2(req, ctx) {
17543
17550
  return json3({ kind: held.kind, pid: held.owner, start: held.start, len });
17544
17551
  }
17545
17552
  const granted2 = req.kind !== "unlock";
17546
- const shared = locks.shared?.() === true;
17547
- const fd = shared ? req.fd : void 0;
17553
+ const shared2 = locks.shared?.() === true;
17554
+ const fd = shared2 ? req.fd : void 0;
17548
17555
  if (!granted2 && fd !== void 0) await ctx.settle?.(fd, false);
17549
17556
  if (req.kind !== "unlock" && req.wait)
17550
17557
  await locks.wait(key, owner, req.kind, range2, ctx.blocking);
17551
17558
  else if (!await locks.set(key, owner, req.kind, range2.start, range2.end))
17552
17559
  throw new KernelError("EAGAIN");
17553
- if (granted2 && shared) await grantedAcross(ctx, fd);
17554
- return shared ? json3(true) : done3;
17560
+ if (granted2 && shared2) await grantedAcross(ctx, fd);
17561
+ return shared2 ? json3(true) : done3;
17555
17562
  }
17556
17563
 
17557
17564
  // src/kernel/net/loopback-transport.ts
@@ -23791,9 +23798,9 @@ function pausing(signal) {
23791
23798
  return { pausedAt, select };
23792
23799
  }
23793
23800
  async function serveSecure(proc, socket, req) {
23794
- const shared = req.listener?.serving;
23795
- if (req.listener && !shared) throw new KernelError("EINVAL", "the listener has no tls");
23796
- const server = shared ?? await serverOf(proc, req);
23801
+ const shared2 = req.listener?.serving;
23802
+ if (req.listener && !shared2) throw new KernelError("EINVAL", "the listener has no tls");
23803
+ const server = shared2 ?? await serverOf(proc, req);
23797
23804
  const { engine, leaf, alpn, store, flags: flags2 } = server;
23798
23805
  const limit = handshakeSignal(proc, server.timeoutMs);
23799
23806
  const { pausedAt, select } = pausing(limit.signal);
@@ -23807,12 +23814,12 @@ async function serveSecure(proc, socket, req) {
23807
23814
  (flags2 & TLS_KEYLOG) !== 0
23808
23815
  ),
23809
23816
  limit.signal,
23810
- (reason) => !shared && reason === timedOut
23817
+ (reason) => !shared2 && reason === timedOut
23811
23818
  ).finally(() => {
23812
23819
  limit.done();
23813
23820
  selections.delete(socket);
23814
23821
  socket.pending = void 0;
23815
- if (!shared) server.release();
23822
+ if (!shared2) server.release();
23816
23823
  });
23817
23824
  run2.catch(() => void 0);
23818
23825
  const first = await Promise.race([run2, pausedAt.then((pause2) => ({ pause: pause2 }))]);
@@ -29010,6 +29017,105 @@ function replayingHops(inner, heldMs = HELD_MS) {
29010
29017
  };
29011
29018
  }
29012
29019
 
29020
+ // src/kernel/package-swaps.ts
29021
+ var SWAP_GAP_MS = 2e3;
29022
+ var STAGES = ["_tmp_", "_pacquet-stage_"];
29023
+ function staged(source, target) {
29024
+ if (parentOf4(source) !== parentOf4(target)) return false;
29025
+ const name2 = target.slice(target.lastIndexOf("/") + 1);
29026
+ const from = source.slice(source.lastIndexOf("/") + 1);
29027
+ return from.startsWith(`.${name2}-`) || STAGES.some((stage) => from.startsWith(`${name2}${stage}`));
29028
+ }
29029
+ var PackageSwaps = class {
29030
+ gaps = /* @__PURE__ */ new Map();
29031
+ isRoot;
29032
+ hold;
29033
+ closed;
29034
+ gapMs;
29035
+ tally = { swaps: 0, longestMs: 0, waits: 0, waitedMs: 0 };
29036
+ constructor(isRoot, hold, closed, gapMs = SWAP_GAP_MS) {
29037
+ this.isRoot = isRoot;
29038
+ this.hold = hold;
29039
+ this.closed = closed;
29040
+ this.gapMs = gapMs;
29041
+ }
29042
+ counts() {
29043
+ return { ...this.tally };
29044
+ }
29045
+ settled() {
29046
+ if (this.gaps.size === 0) return Promise.resolve();
29047
+ const started = performance.now();
29048
+ this.tally.waits++;
29049
+ return Promise.all([...this.gaps.values()].map((gap) => gap.settled)).then(() => {
29050
+ this.tally.waitedMs += performance.now() - started;
29051
+ });
29052
+ }
29053
+ wrap(fs) {
29054
+ return { ...fs, rename: (from, to) => this.rename(fs, from, to) };
29055
+ }
29056
+ async rename(fs, from, to) {
29057
+ const source = fs.resolvePath("/", from);
29058
+ const target = fs.resolvePath("/", to);
29059
+ const gap = this.gaps.get(target);
29060
+ if (gap) return this.land(gap, target, () => fs.rename(from, to));
29061
+ if (source === target || this.gaps.has(source) || staged(source, target) || !this.isRoot(source)) {
29062
+ return fs.rename(from, to);
29063
+ }
29064
+ const release = await this.hold(source);
29065
+ let kept = false;
29066
+ try {
29067
+ await fs.rename(from, to);
29068
+ kept = await fs.stat(`${target}/package.json`).then(
29069
+ (st) => st.isFile,
29070
+ () => false
29071
+ );
29072
+ if (kept) this.open(source, release);
29073
+ } finally {
29074
+ if (!kept) release();
29075
+ }
29076
+ }
29077
+ open(root, release) {
29078
+ const { promise, resolve: resolve2 } = Promise.withResolvers();
29079
+ const gap = {
29080
+ release,
29081
+ done: () => resolve2(),
29082
+ settled: promise,
29083
+ timer: void 0,
29084
+ landing: 0,
29085
+ opened: performance.now()
29086
+ };
29087
+ this.gaps.set(root, gap);
29088
+ this.tally.swaps++;
29089
+ this.arm(root, gap);
29090
+ }
29091
+ arm(root, gap) {
29092
+ const timer = setTimeout(() => this.close(root, gap), this.gapMs);
29093
+ timer.unref?.();
29094
+ gap.timer = timer;
29095
+ }
29096
+ close(root, gap) {
29097
+ if (this.gaps.get(root) !== gap) return;
29098
+ clearTimeout(gap.timer);
29099
+ this.gaps.delete(root);
29100
+ this.tally.longestMs = Math.max(this.tally.longestMs, performance.now() - gap.opened);
29101
+ this.closed();
29102
+ gap.done();
29103
+ gap.release();
29104
+ }
29105
+ async land(gap, root, run2) {
29106
+ gap.landing++;
29107
+ clearTimeout(gap.timer);
29108
+ try {
29109
+ await run2();
29110
+ } catch (err) {
29111
+ gap.landing--;
29112
+ if (gap.landing === 0 && this.gaps.get(root) === gap) this.arm(root, gap);
29113
+ throw err;
29114
+ }
29115
+ this.close(root, gap);
29116
+ }
29117
+ };
29118
+
29013
29119
  // src/kernel/rename-log.ts
29014
29120
  var RENAME_LOG_SIZE = 4096;
29015
29121
  function within9(path, root) {
@@ -29047,6 +29153,51 @@ var RenameLog = class {
29047
29153
  }
29048
29154
  };
29049
29155
 
29156
+ // src/kernel/replacing.ts
29157
+ var REPLACING_LOCK = "slicc-kernel-replacing:v1:";
29158
+ function replacingLocks(scope = globalThis) {
29159
+ const locks = scope.navigator?.locks;
29160
+ return typeof locks?.request === "function" ? locks : void 0;
29161
+ }
29162
+ function replacingLock(metadata, root) {
29163
+ return `${REPLACING_LOCK}${metadata}:${root}`;
29164
+ }
29165
+ function shared(locks, names2, run2, waited) {
29166
+ const [name2, ...rest2] = names2;
29167
+ if (name2 === void 0) return run2(waited);
29168
+ const next3 = (after) => shared(locks, rest2, run2, after);
29169
+ return locks.request(
29170
+ name2,
29171
+ { mode: "shared", ifAvailable: true },
29172
+ (lock) => lock ? next3(waited) : locks.request(name2, { mode: "shared" }, () => next3(true))
29173
+ );
29174
+ }
29175
+ var PackageLocks = class {
29176
+ locks;
29177
+ metadata;
29178
+ constructor(metadata, locks = replacingLocks()) {
29179
+ this.metadata = metadata;
29180
+ this.locks = locks;
29181
+ }
29182
+ loading(roots, run2) {
29183
+ if (!this.locks || roots.length === 0) return run2(false);
29184
+ const names2 = roots.map((root) => replacingLock(this.metadata, root));
29185
+ return shared(this.locks, names2, run2, false);
29186
+ }
29187
+ holding(root) {
29188
+ const { locks } = this;
29189
+ if (!locks) return Promise.resolve(() => {
29190
+ });
29191
+ const { promise: held, resolve: release } = Promise.withResolvers();
29192
+ return new Promise((granted2, failed3) => {
29193
+ locks.request(replacingLock(this.metadata, root), { mode: "exclusive" }, () => {
29194
+ granted2(() => release());
29195
+ return held;
29196
+ }).catch(failed3);
29197
+ });
29198
+ }
29199
+ };
29200
+
29050
29201
  // src/kernel/settling.ts
29051
29202
  var SETUP_OPS = /* @__PURE__ */ new Set([
29052
29203
  "proc-list",
@@ -31297,6 +31448,7 @@ var COMMAND2 = /^\/(?:usr\/)?bin\/([^/]+)$/;
31297
31448
  var SHIM_MARKER = /^\/\/ pnpm-wasm-shim=(\{.*\})$/gm;
31298
31449
  var SHIM_MAX = 64 * 1024;
31299
31450
  var PACKAGE_ROOT = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//;
31451
+ var NODE_MODULES = /\/node_modules(?:\/|$)/;
31300
31452
  var ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
31301
31453
  var WASM_MAGIC = [0, 97, 115, 109];
31302
31454
  var SHEBANG_MAX = 256;
@@ -31364,6 +31516,21 @@ function packageRootOf(path, roots) {
31364
31516
  }
31365
31517
  return void 0;
31366
31518
  }
31519
+ function planFiles(planned) {
31520
+ const { target } = planned;
31521
+ const files = [
31522
+ target.glue,
31523
+ target.wasm,
31524
+ target.imports,
31525
+ target.copy,
31526
+ ...(target.variants ?? []).map((variant) => variant.wasm),
31527
+ ...planned.sources ?? []
31528
+ ];
31529
+ return files.filter((file) => file !== void 0);
31530
+ }
31531
+ function sameList(a, b) {
31532
+ return a.length === b.length && a.every((item, i) => item === b[i]);
31533
+ }
31367
31534
  function modulePath(glue) {
31368
31535
  return glue.endsWith(".js") ? `${glue.slice(0, -3)}.wasm` : `${glue}.wasm`;
31369
31536
  }
@@ -31537,6 +31704,12 @@ var Launcher = class {
31537
31704
  sudoer;
31538
31705
  jobs = new JobTable();
31539
31706
  locks;
31707
+ packageLocks;
31708
+ swaps = new PackageSwaps(
31709
+ (path) => this.packageRootIs(path),
31710
+ (root) => this.packageLocks.holding(root),
31711
+ () => this.resetCatalogs()
31712
+ );
31540
31713
  fifos = new FifoTable();
31541
31714
  memories;
31542
31715
  sems = new SemTable();
@@ -31609,6 +31782,7 @@ var Launcher = class {
31609
31782
  this.memories = memoriesOf(options.wasmMemories);
31610
31783
  this.rootFs = options.fs;
31611
31784
  this.locks = kernelLocks(options.fs.rootName, this.flushingPeers(options.fs));
31785
+ this.packageLocks = new PackageLocks(options.packageLocks ?? META_DB);
31612
31786
  this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
31613
31787
  if (this.dac) this.wireDac(this.dac);
31614
31788
  this.clipboard = new ClipboardBroker(
@@ -31702,15 +31876,7 @@ var Launcher = class {
31702
31876
  this.nodes.keepsOnDisk = true;
31703
31877
  this.openFiles.add(this.nodes);
31704
31878
  options.fs.onPeer?.(() => this.flushOpen());
31705
- options.fs.remoteChanges?.((change) => {
31706
- this.held.doubt(change.paths, change.moved?.to);
31707
- if (change.moved) this.moved(change.moved.from, change.moved.to);
31708
- this.changes.flush();
31709
- this.cwds.touched(change.paths, change.trees);
31710
- this.rewritten(change.paths, change.trees);
31711
- this.watchers.heard(change.content ? "change" : "rename", change.paths);
31712
- this.watchers.heard("rename", change.trees ?? []);
31713
- });
31879
+ options.fs.remoteChanges?.((change) => this.remoteChanged(change));
31714
31880
  const holders = [this.held, this.openFiles];
31715
31881
  options.fs.heldBy?.(
31716
31882
  (dir) => holders.flatMap((set3) => [...set3].flatMap((holder) => [...holder.held()])).filter((p) => p.startsWith(`${dir}/`) && this.mounts.locate(p) === void 0)
@@ -31834,7 +32000,7 @@ var Launcher = class {
31834
32000
  commands(scope = "") {
31835
32001
  let found = this.catalogs.get(scope);
31836
32002
  if (!found) {
31837
- found = this.trustedAppended().then(async (appended2) => {
32003
+ found = this.swaps.settled().then(() => this.trustedAppended()).then(async (appended2) => {
31838
32004
  const tiers = await this.tiers(scope, appended2);
31839
32005
  const refused3 = [];
31840
32006
  const commands = await scanCommands(
@@ -31920,6 +32086,26 @@ var Launcher = class {
31920
32086
  }
31921
32087
  return trusted;
31922
32088
  }
32089
+ remoteChanged(change) {
32090
+ this.held.doubt(change.paths, change.moved?.to);
32091
+ if (change.moved) this.moved(change.moved.from, change.moved.to);
32092
+ this.changes.flush();
32093
+ this.cwds.touched(change.paths, change.trees);
32094
+ this.rewritten(change.paths, change.trees);
32095
+ this.watchers.heard(change.content ? "change" : "rename", change.paths);
32096
+ this.watchers.heard("rename", change.trees ?? []);
32097
+ if (change.outside && this.catalogChanged([...change.paths, ...change.trees ?? []])) {
32098
+ this.resetCatalogs();
32099
+ }
32100
+ }
32101
+ catalogChanged(paths) {
32102
+ const roots = [this.modulesDir, this.pnpmHome, ...this.appended].map(normalizePath);
32103
+ return paths.map(normalizePath).some(
32104
+ (at) => at === "/" || NODE_MODULES.test(at) || roots.some(
32105
+ (root) => at === root || at.startsWith(`${root}/`) || root.startsWith(`${at}/`)
32106
+ )
32107
+ );
32108
+ }
31923
32109
  resetCatalogs() {
31924
32110
  this.catalogs.clear();
31925
32111
  this.ownCatalogs.clear();
@@ -32320,7 +32506,11 @@ var Launcher = class {
32320
32506
  secrets.push({ name: name2, value: value2, placeholder });
32321
32507
  }
32322
32508
  const scrub = scrubberOf(secrets);
32323
- return { env, pid, cwd: booked.cwd, ...scrub ? { scrub } : {} };
32509
+ const recheck = async (next3) => {
32510
+ const same3 = sameList(planFiles(next3), planFiles(planned)) && sameList(await this.interpreterChain(next3, booked.cwd, scope), interpreters) && (this.usersMode === "enforce" && await this.trustedFiles(next3, req.file, booked.cwd, scope)) === trusted;
32511
+ if (!same3) throw new SpawnError("EACCES");
32512
+ };
32513
+ return { env, pid, cwd: booked.cwd, ...scrub ? { scrub } : {}, recheck };
32324
32514
  }
32325
32515
  async interpreterChain(planned, cwd, scope) {
32326
32516
  const out = [];
@@ -32605,25 +32795,16 @@ var Launcher = class {
32605
32795
  async launch(asked, req) {
32606
32796
  const cwd = await physicalPathAsync(this.base, req.cwd);
32607
32797
  const seen = { ...req.env, HOSTNAME: this.hostname, cwd };
32608
- const env = { ...expandDefaults(asked.target.env, seen), ...req.env };
32609
- for (const key of asked.target.unset ?? []) delete env[key];
32610
- delete env[TICKET_ENV];
32611
- const own = env.HOSTNAME;
32612
- if (asked.target.abi === "wasi") env.HOSTNAME = this.hostname;
32613
- const planned = chosen2(asked, env);
32614
- const { target, args } = planned;
32615
- let program;
32616
- let exeId;
32617
32798
  await this.pnpmReady();
32799
+ let loaded;
32618
32800
  try {
32619
- const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tags?.[0] };
32620
- if (this.integrity) await this.checkedSources(planned, caller);
32621
- [program, exeId] = await this.loaded(target, env, caller);
32622
- if (readsEtcHostname(program)) setOrDrop(env, "HOSTNAME", own);
32801
+ loaded = await this.loadedUnder(asked, (planned2) => this.loadPlanned(planned2, req, seen));
32623
32802
  } catch (err) {
32624
32803
  await req.fds.closeAll();
32625
32804
  throw err;
32626
32805
  }
32806
+ const { planned, env, program, exeId } = loaded;
32807
+ const { target, args } = planned;
32627
32808
  const done5 = this.heldUrl(program);
32628
32809
  try {
32629
32810
  const cwdSlot = await this.cwds.hold(cwd, req.cwdFrom);
@@ -32651,6 +32832,59 @@ var Launcher = class {
32651
32832
  throw err;
32652
32833
  }
32653
32834
  }
32835
+ async loadPlanned(asked, req, seen) {
32836
+ const env = { ...expandDefaults(asked.target.env, seen), ...req.env };
32837
+ for (const key of asked.target.unset ?? []) delete env[key];
32838
+ delete env[TICKET_ENV];
32839
+ const own = env.HOSTNAME;
32840
+ if (asked.target.abi === "wasi") env.HOSTNAME = this.hostname;
32841
+ const planned = chosen2(asked, env);
32842
+ const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tags?.[0] };
32843
+ if (this.integrity) await this.checkedSources(planned, caller);
32844
+ const [program, exeId] = await this.loaded(planned.target, env, caller);
32845
+ if (readsEtcHostname(program)) setOrDrop(env, "HOSTNAME", own);
32846
+ return { planned, env, program, exeId };
32847
+ }
32848
+ packageRootIs(path) {
32849
+ const at = normalizePath(path);
32850
+ const within10 = `${at}/`;
32851
+ const roots = [this.modulesDir, ...this.appended];
32852
+ const root = PACKAGE_ROOT.exec(within10)?.[1] ?? packageRootOf(within10, roots);
32853
+ return root === at;
32854
+ }
32855
+ packageRoots(planned) {
32856
+ const packages2 = [this.modulesDir, ...this.appended];
32857
+ const roots = /* @__PURE__ */ new Set();
32858
+ for (const file of planFiles(planned)) {
32859
+ if (!file.startsWith("/")) continue;
32860
+ const at = normalizePath(file);
32861
+ const root = PACKAGE_ROOT.exec(at)?.[1] ?? packageRootOf(at, packages2);
32862
+ if (root !== void 0 && root !== at) roots.add(root);
32863
+ }
32864
+ return [...roots].sort();
32865
+ }
32866
+ async loadedUnder(asked, load) {
32867
+ const { replan, recheck } = asked;
32868
+ let planned = asked;
32869
+ let roots = this.packageRoots(planned);
32870
+ let stale = false;
32871
+ for (; ; ) {
32872
+ const held = roots;
32873
+ const done5 = await this.packageLocks.loading(held, async (waited) => {
32874
+ if (!waited && !stale) return { value: await load(planned) };
32875
+ this.rootFs.refresh?.(held);
32876
+ this.resetCatalogs();
32877
+ if (replan) planned = await replan() ?? planned;
32878
+ await recheck?.(planned);
32879
+ const now = this.packageRoots(planned);
32880
+ if (now.every((root) => held.includes(root))) return { value: await load(planned) };
32881
+ roots = [.../* @__PURE__ */ new Set([...held, ...now])].sort();
32882
+ stale = true;
32883
+ return void 0;
32884
+ });
32885
+ if (done5) return done5.value;
32886
+ }
32887
+ }
32654
32888
  groupSession(pgid) {
32655
32889
  return this.jobs.sessionOf(pgid);
32656
32890
  }
@@ -32803,6 +33037,7 @@ var Launcher = class {
32803
33037
  opfsOps: this.rootFs.opfsOps?.() ?? 0,
32804
33038
  cwdChecks: this.cwds.checks,
32805
33039
  renameAcks: this.rootFs.renameWaits?.(),
33040
+ swaps: this.swaps.counts(),
32806
33041
  staleFds: staleFdCount(),
32807
33042
  fileIds: this.rootFs.fileIdWrites?.() ?? 0,
32808
33043
  holds: this.rootFs.holdCounts?.()
@@ -33059,6 +33294,7 @@ var Launcher = class {
33059
33294
  spawner(ppid, report) {
33060
33295
  return async (req, fds, grant) => {
33061
33296
  const booked = this.takeTicket(req);
33297
+ await this.swaps.settled();
33062
33298
  if (grant && !await this.grantedCwd(grant.caller, req.cwd)) {
33063
33299
  await fds.closeAll();
33064
33300
  throw new SpawnError("EACCES");
@@ -33079,7 +33315,7 @@ var Launcher = class {
33079
33315
  const scope = grant ? "" : await this.scopeOf(cred.euid);
33080
33316
  const file = booked === void 0 ? req.file : await this.ticketFile(req, fds);
33081
33317
  const planned = await this.checkedPlan({ ...req, file }, { ppid, cred, scope }, fds);
33082
- const redeemed = await this.redeemed(
33318
+ const { recheck, ...redeemed } = await this.redeemed(
33083
33319
  { ...req, file },
33084
33320
  { ppid, cred, scope, booked },
33085
33321
  planned,
@@ -33087,7 +33323,9 @@ var Launcher = class {
33087
33323
  );
33088
33324
  const exec = req.exec ? { exec: true } : {};
33089
33325
  const deciding = req.exec ? void 0 : this.undecided(ppid);
33090
- const handle = await this.launch(planned, {
33326
+ const replan = () => this.planOrSearch({ ...req, file }, { ppid, cred, scope });
33327
+ const again = { ...planned, replan, recheck };
33328
+ const handle = await this.launch(again, {
33091
33329
  ...deciding ? { decided: deciding.promise } : {},
33092
33330
  cwd: req.cwd,
33093
33331
  cwdFrom: inheritedCwd(req.chdir, this.cwdSlots.get(ppid)),
@@ -33969,10 +34207,12 @@ var Launcher = class {
33969
34207
  nodes.changed = (path) => this.changes.publish(path);
33970
34208
  nodes.movedAway = (from, to) => this.moved(from, to, nodes);
33971
34209
  nodes.dirtied = (delta) => this.changes.dirtied(delta);
33972
- return unlinked.wrap(keepingOpen(fs, nodes, (path) => this.changes.publish(path)));
34210
+ const kept = keepingOpen(fs, nodes, (path) => this.changes.publish(path));
34211
+ return this.swaps.wrap(unlinked.wrap(kept));
33973
34212
  }
33974
34213
  async starting(argv, dir, account, tags, base) {
33975
34214
  const tag = tags?.[0];
34215
+ await this.swaps.settled();
33976
34216
  this.resetCatalogs();
33977
34217
  const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
33978
34218
  const start2 = dir ?? "/";
@@ -33981,9 +34221,13 @@ var Launcher = class {
33981
34221
  const file = await this.execFile(argv[0] ?? "", cwd, this.execGuard(credOf(account), 0, tag));
33982
34222
  if (!await this.base.exists(cwd))
33983
34223
  await this.stackFor(tags, base).fs.mkdir(cwd, { recursive: true });
33984
- const planned = await this.plan(file, argv, cwd, scope);
33985
- await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
33986
- return { cwd, file, planned };
34224
+ const plan = async () => {
34225
+ const found = await this.plan(file, argv, cwd, scope);
34226
+ await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, found);
34227
+ return found;
34228
+ };
34229
+ const planned = await plan();
34230
+ return { cwd, file, planned: planned && { ...planned, replan: plan } };
33987
34231
  }
33988
34232
  async openTerminal(argv, options) {
33989
34233
  const tagged = tagsOption(options);
@@ -34707,6 +34951,7 @@ function serveKernel(port, deps) {
34707
34951
  await fs.reconcile();
34708
34952
  const started = new Launcher({
34709
34953
  fs,
34954
+ ...name2 ? { packageLocks: name2 } : {},
34710
34955
  ...workers(deps),
34711
34956
  ...req.modules ? { modules: req.modules } : {},
34712
34957
  ...req.commands ? { commands: req.commands } : {},