@ai-ecoverse/slicc-kernel 1.71.1 → 1.72.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.
@@ -141,11 +141,18 @@ var IndexedDbMeta = class {
141
141
  cache = new PathMap();
142
142
  channel;
143
143
  generation = 0;
144
+ listeners = /* @__PURE__ */ new Set();
145
+ listen(listener) {
146
+ this.listeners.add(listener);
147
+ }
144
148
  constructor(db) {
145
149
  this.db = db;
146
150
  db.onversionchange = () => db.close();
147
151
  this.channel = new BroadcastChannel(`slicc-kernel-meta:${db.name}`);
148
- this.channel.onmessage = ({ data }) => this.drop(data);
152
+ this.channel.onmessage = ({ data }) => {
153
+ this.drop(data);
154
+ for (const listener of this.listeners) listener(data);
155
+ };
149
156
  this.channel.unref?.();
150
157
  }
151
158
  drop({ paths = [], trees = [] }) {
@@ -530,6 +537,121 @@ var ProtectedFs = class {
530
537
  }
531
538
  };
532
539
 
540
+ // src/fs/remote.ts
541
+ var ANNOUNCE_WAIT_MS = 500;
542
+ var PRUNE_EVERY_MS = 3e4;
543
+ function lockManager() {
544
+ return globalThis.navigator?.locks;
545
+ }
546
+ var RemoteWatch = class {
547
+ channel;
548
+ name;
549
+ tab = crypto.randomUUID();
550
+ peers = /* @__PURE__ */ new Set();
551
+ others = /* @__PURE__ */ new Map();
552
+ union = /* @__PURE__ */ new Set();
553
+ listeners = /* @__PURE__ */ new Set();
554
+ waiting = /* @__PURE__ */ new Map();
555
+ locks;
556
+ mine = [];
557
+ seq = 0;
558
+ pruned = 0;
559
+ constructor(name2, options = {}) {
560
+ this.name = name2;
561
+ this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
562
+ this.channel = new BroadcastChannel(name2);
563
+ this.channel.onmessage = ({ data }) => this.receive(data);
564
+ this.channel.unref?.();
565
+ void this.locks?.request(this.lockOf(this.tab), () => new Promise(() => {
566
+ }));
567
+ this.channel.postMessage({ hello: this.tab });
568
+ }
569
+ lockOf(tab) {
570
+ return `${this.name}:${tab}`;
571
+ }
572
+ receive(data) {
573
+ if (data.hello !== void 0) this.greeted(data.hello);
574
+ if (data.here !== void 0 && data.to === this.tab) this.peers.add(data.here);
575
+ if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
576
+ if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
577
+ this.acked(data.ack, data.from);
578
+ }
579
+ if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [] });
580
+ }
581
+ greeted(peer) {
582
+ this.peers.add(peer);
583
+ this.channel.postMessage({ here: this.tab, to: peer });
584
+ if (this.mine.length > 0) void this.announce(this.mine);
585
+ }
586
+ announced({ tab, paths, seq: seq2 }) {
587
+ const from = tab;
588
+ this.peers.add(from);
589
+ if (paths.length > 0) this.others.set(from, paths);
590
+ else this.others.delete(from);
591
+ this.rebuild();
592
+ this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
593
+ }
594
+ acked(seq2, from) {
595
+ const wait2 = this.waiting.get(seq2);
596
+ if (!wait2) return;
597
+ wait2.left.delete(from);
598
+ if (wait2.left.size === 0) wait2.done();
599
+ }
600
+ rebuild() {
601
+ this.union.clear();
602
+ for (const paths of this.others.values()) for (const path of paths) this.union.add(path);
603
+ }
604
+ async prune() {
605
+ if (!this.locks) return;
606
+ const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
607
+ for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
608
+ if (!held.has(this.lockOf(tab))) {
609
+ this.others.delete(tab);
610
+ this.peers.delete(tab);
611
+ }
612
+ }
613
+ this.rebuild();
614
+ }
615
+ watched() {
616
+ return this.union.size > 0;
617
+ }
618
+ wrote(path) {
619
+ if (!this.union.has(path)) return;
620
+ this.channel.postMessage({ changed: [path] });
621
+ const now = Date.now();
622
+ if (now - this.pruned < PRUNE_EVERY_MS) return;
623
+ this.pruned = now;
624
+ void this.prune();
625
+ }
626
+ announce(paths) {
627
+ this.mine = [...paths];
628
+ const seq2 = ++this.seq;
629
+ this.channel.postMessage({ tab: this.tab, paths: this.mine, seq: seq2 });
630
+ if (this.peers.size === 0) return Promise.resolve();
631
+ return new Promise((resolve) => {
632
+ const done4 = () => {
633
+ clearTimeout(timer);
634
+ this.waiting.delete(seq2);
635
+ resolve();
636
+ };
637
+ const timer = setTimeout(() => {
638
+ done4();
639
+ void this.prune();
640
+ }, ANNOUNCE_WAIT_MS);
641
+ this.waiting.set(seq2, { left: new Set(this.peers), done: done4 });
642
+ });
643
+ }
644
+ emit(change) {
645
+ for (const listener of this.listeners) listener(change);
646
+ }
647
+ listen(listener) {
648
+ this.listeners.add(listener);
649
+ return () => {
650
+ this.listeners.delete(listener);
651
+ };
652
+ }
653
+ };
654
+
533
655
  // src/fs/opfs.ts
534
656
  var S_IFDIR = 16384;
535
657
  var S_IFREG = 32768;
@@ -657,6 +779,7 @@ var OpfsFs = class {
657
779
  dirs = new PathMap();
658
780
  checked = /* @__PURE__ */ new WeakMap();
659
781
  channel;
782
+ remote;
660
783
  ranged;
661
784
  owners = false;
662
785
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
@@ -664,9 +787,15 @@ var OpfsFs = class {
664
787
  this.meta = sidecar(meta);
665
788
  this.ranged = options.ranged === true;
666
789
  this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
790
+ this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`);
667
791
  if (this.channel) {
792
+ const remote = this.remote;
793
+ meta.listen?.(
794
+ (c) => remote?.emit({ paths: c.paths ?? [], trees: c.trees ?? [] })
795
+ );
668
796
  this.channel.onmessage = ({ data }) => {
669
797
  for (const path of data) this.forget(path);
798
+ remote?.emit({ paths: data, trees: [] });
670
799
  };
671
800
  this.channel.unref?.();
672
801
  }
@@ -675,6 +804,16 @@ var OpfsFs = class {
675
804
  for (const path of paths) this.forget(path);
676
805
  this.channel?.postMessage(paths);
677
806
  }
807
+ remoteChanges(listener) {
808
+ return this.remote?.listen(listener) ?? (() => {
809
+ });
810
+ }
811
+ announceWatch(paths) {
812
+ return this.remote?.announce(paths) ?? Promise.resolve();
813
+ }
814
+ committed(target) {
815
+ if (this.remote?.watched()) this.remote.wrote(target);
816
+ }
678
817
  resolvePath(base, path) {
679
818
  return resolvePath(base, path);
680
819
  }
@@ -1026,15 +1165,18 @@ var OpfsFs = class {
1026
1165
  );
1027
1166
  }
1028
1167
  async pwrite(path, offset, bytes) {
1029
- const handle = (await this.fileFor(path))[0];
1168
+ const [handle, , target] = await this.fileFor(path);
1030
1169
  await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
1170
+ this.committed(target);
1031
1171
  }
1032
1172
  async truncate(path, size) {
1033
1173
  const handle = await this.existing(path, true);
1174
+ const target = this.remote?.watched() ? (await this.locate(path, true)).path : path;
1034
1175
  await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
1176
+ this.committed(target);
1035
1177
  }
1036
1178
  async writeSession(path, truncate) {
1037
- const handle = (await this.fileFor(path))[0];
1179
+ const [handle, , target] = await this.fileFor(path);
1038
1180
  const writable2 = await this.retrying(
1039
1181
  path,
1040
1182
  () => handle.createWritable({ keepExistingData: !truncate }),
@@ -1047,9 +1189,12 @@ var OpfsFs = class {
1047
1189
  truncate: (size) => writable2.truncate(size).catch((err) => {
1048
1190
  throw translate(err, path, "write", false);
1049
1191
  }),
1050
- close: () => writable2.close().catch((err) => {
1051
- throw translate(err, path, "commit", false);
1052
- })
1192
+ close: async () => {
1193
+ await writable2.close().catch((err) => {
1194
+ throw translate(err, path, "commit", false);
1195
+ });
1196
+ this.committed(target);
1197
+ }
1053
1198
  };
1054
1199
  }
1055
1200
  async readFile(path) {
@@ -1098,6 +1243,7 @@ var OpfsFs = class {
1098
1243
  await writable2?.abort().catch(() => void 0);
1099
1244
  throw translate(err, path, "commit", false);
1100
1245
  }
1246
+ this.committed(target);
1101
1247
  }
1102
1248
  async listing(path) {
1103
1249
  const found = await this.locate(path, true);
@@ -1119,12 +1265,12 @@ var OpfsFs = class {
1119
1265
  const base = dir === "/" ? "" : dir;
1120
1266
  const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
1121
1267
  const handed2 = this.owners ? await this.handedTo(dir) : void 0;
1122
- const owned3 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
1268
+ const owned4 = (stat, entry) => handed2 ? { ...stat, ...ownOf(entry) ?? handed2 } : stat;
1123
1269
  return Promise.all(
1124
1270
  children2.map(async ([name2, child], i) => {
1125
- if (!isHandle(child)) return [name2, owned3(this.linkStat(child), child)];
1271
+ if (!isHandle(child)) return [name2, owned4(this.linkStat(child), child)];
1126
1272
  const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
1127
- return [name2, stat && owned3(stat, metas[i])];
1273
+ return [name2, stat && owned4(stat, metas[i])];
1128
1274
  })
1129
1275
  );
1130
1276
  }
@@ -1733,6 +1879,7 @@ var Dac = class {
1733
1879
  log;
1734
1880
  seen = /* @__PURE__ */ new Set();
1735
1881
  suppressed = false;
1882
+ tagOf = () => void 0;
1736
1883
  constructor(mode, hook, log = (line) => console.warn(line)) {
1737
1884
  this.mode = mode;
1738
1885
  this.hook = hook;
@@ -1741,7 +1888,9 @@ var Dac = class {
1741
1888
  get enforcing() {
1742
1889
  return this.mode === "enforce";
1743
1890
  }
1744
- denied(denial) {
1891
+ denied(given) {
1892
+ const tag = given.tag ?? (given.pid ? this.tagOf(given.pid) : void 0);
1893
+ const denial = tag === void 0 ? given : { ...given, tag };
1745
1894
  try {
1746
1895
  this.hook?.({ ...denial });
1747
1896
  } catch {
@@ -1759,7 +1908,7 @@ var Dac = class {
1759
1908
  }
1760
1909
  this.seen.add(key);
1761
1910
  this.log(
1762
- `slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid})`
1911
+ `slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid}${d.tag === void 0 ? "" : `, tag ${d.tag}`})`
1763
1912
  );
1764
1913
  }
1765
1914
  };
@@ -1775,7 +1924,8 @@ var Created = class {
1775
1924
  }
1776
1925
  };
1777
1926
  function deny(g, op, path, errno = "EACCES") {
1778
- g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno });
1927
+ const tag = g.tag === void 0 ? {} : { tag: g.tag };
1928
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
1779
1929
  }
1780
1930
  var TTY_MODE = 400;
1781
1931
  function ttyOpenable(g, tty) {
@@ -3745,9 +3895,9 @@ function ownerFor(l, self) {
3745
3895
  if (!l.owners || !self) return void 0;
3746
3896
  return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
3747
3897
  }
3748
- function guardFor(l, self) {
3898
+ function guardFor(l, self, tag) {
3749
3899
  if (!l.dac || !self) return void 0;
3750
- return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac };
3900
+ return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac, tag };
3751
3901
  }
3752
3902
  async function accountOf(l, user) {
3753
3903
  const found = await l.users.lookup(user);
@@ -3851,6 +4001,7 @@ async function watchOp(l, req, watches, send, g) {
3851
4001
  function serveClient(port, host) {
3852
4002
  let state = "new";
3853
4003
  let self;
4004
+ const guard2 = (l) => guardFor(l, self, host.tag);
3854
4005
  const terminals = /* @__PURE__ */ new Map();
3855
4006
  const groups = /* @__PURE__ */ new Set();
3856
4007
  const sessions = /* @__PURE__ */ new Set();
@@ -3960,10 +4111,10 @@ function serveClient(port, host) {
3960
4111
  if (!FS_METHODS.includes(req.method)) {
3961
4112
  throw new Error(`unknown file system call ${req.method}`);
3962
4113
  }
3963
- return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
4114
+ return fsCall(l.processFs, req.method, req.args, ownerFor(l, self), guard2(l));
3964
4115
  case "watch":
3965
4116
  case "unwatch":
3966
- return watchOp(l, req, watches, send, guardFor(l, self));
4117
+ return watchOp(l, req, watches, send, guard2(l));
3967
4118
  case "mount":
3968
4119
  return { result: await l.mountAs(clientCred(self), req.spec) };
3969
4120
  case "umount":
@@ -6624,6 +6775,136 @@ function binfmtOf(binfmts, path) {
6624
6775
  return best === void 0 ? void 0 : binfmts.get(best);
6625
6776
  }
6626
6777
 
6778
+ // src/fs/attest.ts
6779
+ var ATTEST_MAX = 1024 * 1024;
6780
+ var UNOWNED = 1e3;
6781
+ function refuse(code, path, why) {
6782
+ throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
6783
+ }
6784
+ function chainOf(path) {
6785
+ const parts2 = path.split("/").filter(Boolean);
6786
+ return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
6787
+ }
6788
+ function under2(path, target) {
6789
+ return path === target || path.startsWith(`${target}/`);
6790
+ }
6791
+ function owned(path, st) {
6792
+ return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
6793
+ }
6794
+ function unchanged(a, b, bytes) {
6795
+ return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
6796
+ }
6797
+ async function sha256(bytes) {
6798
+ const digest = new Uint8Array(
6799
+ await crypto.subtle.digest("SHA-256", bytes)
6800
+ );
6801
+ return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
6802
+ }
6803
+ function attested(path, st, bytes, digest, o, parents) {
6804
+ const { uid, gid, mode } = owned(path, st);
6805
+ return {
6806
+ path,
6807
+ bytes,
6808
+ uid,
6809
+ gid,
6810
+ mode,
6811
+ ino: st.ino,
6812
+ mtimeMs: st.mtime.getTime(),
6813
+ sha256: digest,
6814
+ users: o.users,
6815
+ onRootFs: true,
6816
+ parents
6817
+ };
6818
+ }
6819
+ async function trustedParents(fs, path, o) {
6820
+ const parents = [];
6821
+ for (const dir of chainOf(path).slice(0, -1)) {
6822
+ const st = await fs.lstat(dir);
6823
+ if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
6824
+ if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
6825
+ const perm = owned(dir, st);
6826
+ if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
6827
+ refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
6828
+ }
6829
+ parents.push(perm);
6830
+ }
6831
+ return parents;
6832
+ }
6833
+ async function attest(fs, asked, o) {
6834
+ if (typeof asked !== "string" || !asked.startsWith("/")) {
6835
+ refuse("EINVAL", String(asked), "not an absolute path");
6836
+ }
6837
+ const path = normalizePath(asked);
6838
+ const mount = o.mounts.find((target) => {
6839
+ const at = normalizePath(target);
6840
+ return at === "/" || under2(path, at);
6841
+ });
6842
+ if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
6843
+ const parents = await trustedParents(fs, path, o);
6844
+ for (let tries = 0; ; tries++) {
6845
+ const before = await fs.lstat(path);
6846
+ if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
6847
+ if (!before.isFile)
6848
+ refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
6849
+ if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
6850
+ const bytes = (await fs.readFileBuffer(path)).slice();
6851
+ const after = await fs.lstat(path);
6852
+ if (unchanged(before, after, bytes)) {
6853
+ return attested(path, after, bytes, await sha256(bytes), o, parents);
6854
+ }
6855
+ if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
6856
+ }
6857
+ }
6858
+ var WATCH_DEBOUNCE_MS = 50;
6859
+ var FileWatches = class {
6860
+ live = /* @__PURE__ */ new Set();
6861
+ local;
6862
+ remote;
6863
+ constructor(local, remote) {
6864
+ this.local = local;
6865
+ this.remote = remote;
6866
+ }
6867
+ announce() {
6868
+ return this.remote.announceWatch?.([...new Set([...this.live].flat())]) ?? Promise.resolve();
6869
+ }
6870
+ async watch(paths, notify) {
6871
+ const files = paths.map(normalizePath);
6872
+ const timers = /* @__PURE__ */ new Map();
6873
+ let active = true;
6874
+ const fire = (changed, trees) => {
6875
+ for (const file of files) {
6876
+ const hit = [...changed, ...trees].some((p) => under2(file, normalizePath(p)));
6877
+ if (!hit) continue;
6878
+ clearTimeout(timers.get(file));
6879
+ timers.set(
6880
+ file,
6881
+ setTimeout(() => {
6882
+ timers.delete(file);
6883
+ if (active) notify(file);
6884
+ }, WATCH_DEBOUNCE_MS)
6885
+ );
6886
+ }
6887
+ };
6888
+ const unwatch = this.local.watch(
6889
+ files,
6890
+ { recursive: false },
6891
+ (change) => fire("overflow" in change ? files : change.paths, [])
6892
+ );
6893
+ const unlisten = this.remote.remoteChanges?.((change) => fire(change.paths, change.trees));
6894
+ this.live.add(files);
6895
+ await this.announce();
6896
+ return () => {
6897
+ if (!active) return;
6898
+ active = false;
6899
+ for (const timer of timers.values()) clearTimeout(timer);
6900
+ unwatch();
6901
+ unlisten?.();
6902
+ this.live.delete(files);
6903
+ void this.announce();
6904
+ };
6905
+ }
6906
+ };
6907
+
6627
6908
  // src/fs/commands.ts
6628
6909
  var S_IFDIR2 = 16384;
6629
6910
  var S_IFREG2 = 32768;
@@ -6662,7 +6943,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6662
6943
  if (st || path === "/") return st ?? {};
6663
6944
  return nearest(path.slice(0, path.lastIndexOf("/")) || "/");
6664
6945
  }
6665
- async function owned3(stat2, dir) {
6946
+ async function owned4(stat2, dir) {
6666
6947
  if (!owners) return stat2;
6667
6948
  const { uid = 0, gid = 0 } = dir === void 0 ? {} : await nearest(dir);
6668
6949
  return { ...stat2, uid, gid };
@@ -6676,17 +6957,17 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
6676
6957
  }
6677
6958
  async function virtual(path) {
6678
6959
  if (path === "/usr" || path === "/bin" || path === "/usr/bin") {
6679
- return owned3(synthetic(true, path));
6960
+ return owned4(synthetic(true, path));
6680
6961
  }
6681
6962
  const name2 = COMMAND.exec(path)?.[1];
6682
6963
  if (name2 !== void 0) {
6683
- return (await names()).has(name2) ? owned3(synthetic(false, path)) : void 0;
6964
+ return (await names()).has(name2) ? owned4(synthetic(false, path)) : void 0;
6684
6965
  }
6685
- if ((await userDir(path))?.size) return owned3(synthetic(true, path), path);
6966
+ if ((await userDir(path))?.size) return owned4(synthetic(true, path), path);
6686
6967
  const slash = path.lastIndexOf("/");
6687
6968
  const dir = path.slice(0, slash);
6688
6969
  const own = await userDir(dir);
6689
- return own?.has(path.slice(slash + 1)) ? owned3(synthetic(false, path), dir) : void 0;
6970
+ return own?.has(path.slice(slash + 1)) ? owned4(synthetic(false, path), dir) : void 0;
6690
6971
  }
6691
6972
  async function linked(path) {
6692
6973
  return await virtual(path) ?? virtual(await followLinks(fs, path));
@@ -7035,11 +7316,11 @@ var UnlinkedKeeper = class {
7035
7316
 
7036
7317
  // src/fs/watch.ts
7037
7318
  var WATCH_BATCH_LIMIT = 1e3;
7038
- var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
7319
+ var under3 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
7039
7320
  var parentOf6 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
7040
7321
  function touches(watcher, changed) {
7041
7322
  return watcher.paths.some(
7042
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf6(changed) === watched)
7323
+ (watched) => changed === watched || under3(watched, changed) || (watcher.recursive ? under3(changed, watched) : parentOf6(changed) === watched)
7043
7324
  );
7044
7325
  }
7045
7326
  var FsWatchers = class {
@@ -8758,7 +9039,7 @@ async function keptStat(entry, req) {
8758
9039
  return { ok: false, errno: "ENOENT", message: "the handle keeps no file" };
8759
9040
  return json2(statJson(await entry.fs.stat(hidden)));
8760
9041
  }
8761
- async function owned(entry, own, req, path, target) {
9042
+ async function owned2(entry, own, req, path, target) {
8762
9043
  const source = target === path ? void 0 : path;
8763
9044
  if (UNLINKING_OPS.has(req.op) && await linkedElsewhere(entry.fs, target, source)) {
8764
9045
  return run(entry, path, req);
@@ -8813,7 +9094,7 @@ async function dispatchOne(entry, req) {
8813
9094
  }
8814
9095
  if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
8815
9096
  const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
8816
- return await owned(entry, entry.own, req, path, target);
9097
+ return await owned2(entry, entry.own, req, path, target);
8817
9098
  } catch (err) {
8818
9099
  return toErrno(err);
8819
9100
  }
@@ -11668,6 +11949,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11668
11949
  "proc-sudo",
11669
11950
  "proc-run",
11670
11951
  "proc-exit",
11952
+ "proc-kill-self",
11671
11953
  "proc-fork",
11672
11954
  "proc-kill",
11673
11955
  "thread-kill",
@@ -11795,6 +12077,20 @@ var WasmProcess = class {
11795
12077
  else this.adoptExec(pid);
11796
12078
  return pid;
11797
12079
  }
12080
+ async killSelf(sig, target, probe) {
12081
+ if (!this.options.exitSignal) throw new KernelError("ENOSYS");
12082
+ if (!isSignal(sig) || defaultAction(sig) !== "terminate") throw new KernelError("EINVAL");
12083
+ const { caught, ignored } = this.signalMasks();
12084
+ const to = target === 0 ? -this.pgid() : target ?? this.pid;
12085
+ if (sig !== SIG.KILL && (caught | ignored) & sigbit(sig)) {
12086
+ if (!await this.options.kill?.(to, sig)) throw new KernelError("ESRCH");
12087
+ return { ok: true, kind: "void" };
12088
+ }
12089
+ if (probe) return { ok: true, kind: "json", json: "terminate" };
12090
+ if (to !== this.pid) await this.options.kill?.(to, sig);
12091
+ this.options.exitSignal(sig);
12092
+ return DYING;
12093
+ }
11798
12094
  run(req) {
11799
12095
  return runChild(req, {
11800
12096
  children: this.children,
@@ -12392,6 +12688,8 @@ var WasmProcess = class {
12392
12688
  if (!this.options.exitNow) throw new KernelError("ENOSYS");
12393
12689
  this.options.exitNow(req.status & 255);
12394
12690
  return DYING;
12691
+ case "proc-kill-self":
12692
+ return this.killSelf(req.sig, req.target, req.probe);
12395
12693
  case "proc-identity":
12396
12694
  return { ok: true, kind: "json", json: await this.options.identity?.() ?? null };
12397
12695
  case "proc-exec": {
@@ -12826,6 +13124,7 @@ function processOptions(opts, header3, hooks, created3) {
12826
13124
  raise: hooks.raise,
12827
13125
  threadKill: hooks.threadKill,
12828
13126
  ...hooks.exitNow ? { exitNow: hooks.exitNow } : {},
13127
+ ...hooks.exitSignal ? { exitSignal: hooks.exitSignal } : {},
12829
13128
  ...opts.program.abi === "wasi" || opts.program.abi === "js" ? { pendingBits: () => Atomics.load(header3, SAB_I_SIGNALS) } : {}
12830
13129
  };
12831
13130
  }
@@ -12859,7 +13158,10 @@ function spawnWasmProcess(opts) {
12859
13158
  {
12860
13159
  raise: (sig) => signal(sig),
12861
13160
  threadKill: (tid, sig) => killThread(threadSignals.get(tid), process.signalMasks().caught, sig),
12862
- ...opts.program.abi === "js" ? { exitNow: (code) => exitNow(code) } : {}
13161
+ ...opts.program.abi === "js" ? {
13162
+ exitNow: (code) => exitNow(code),
13163
+ exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
13164
+ } : {}
12863
13165
  },
12864
13166
  created3
12865
13167
  )
@@ -14664,8 +14966,8 @@ var ChunkedBytes = class {
14664
14966
  }
14665
14967
  return out;
14666
14968
  }
14667
- write(offset, bytes, owned3 = false) {
14668
- const whole = owned3 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
14969
+ write(offset, bytes, owned4 = false) {
14970
+ const whole = owned4 && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
14669
14971
  for (let done4 = 0; done4 < bytes.length; ) {
14670
14972
  const at = offset + done4;
14671
14973
  const index = Math.floor(at / CHUNK3);
@@ -14715,7 +15017,7 @@ function created2(ino, kind, mode, from, owner) {
14715
15017
  gid: owner?.gid ?? from?.gid ?? 0
14716
15018
  };
14717
15019
  }
14718
- function owned2(node, change) {
15020
+ function owned3(node, change) {
14719
15021
  if (change.uid !== void 0) node.uid = change.uid;
14720
15022
  if (change.gid !== void 0) node.gid = change.gid;
14721
15023
  }
@@ -14886,7 +15188,7 @@ function tmpfs() {
14886
15188
  node.mtime = Date.now();
14887
15189
  }
14888
15190
  if (change.mtime !== void 0) node.mtime = change.mtime;
14889
- owned2(node, change);
15191
+ owned3(node, change);
14890
15192
  },
14891
15193
  async statfs() {
14892
15194
  return { bsize: 4096, blocks: 262144, bfree: 262144 };
@@ -15635,12 +15937,16 @@ var Launcher = class {
15635
15937
  usersMode;
15636
15938
  dac;
15637
15939
  sealed;
15940
+ rootFs;
15941
+ fileWatches;
15638
15942
  nextPid = 1e3;
15639
15943
  boot = Date.now();
15640
15944
  terminals = 0;
15641
15945
  constructor(options) {
15642
15946
  this.usersMode = usersMode(options.users);
15947
+ this.rootFs = options.fs;
15643
15948
  this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
15949
+ if (this.dac) this.dac.tagOf = (pid) => this.tagOf(pid);
15644
15950
  this.sealed = new SealedBroker(
15645
15951
  options.sealed,
15646
15952
  this.usersMode,
@@ -15890,7 +16196,7 @@ var Launcher = class {
15890
16196
  async mayExec(cred, req, planned) {
15891
16197
  const { dac } = this;
15892
16198
  if (!dac) return;
15893
- const g = { fs: this.fs, cred, pid: req.pid, dac };
16199
+ const g = { fs: this.fs, cred, pid: req.pid, dac, tag: req.tag };
15894
16200
  const byPath = (file) => file.includes("/") && !COMMAND2.test(file);
15895
16201
  if (byPath(req.file)) {
15896
16202
  const path = this.fs.resolvePath(req.cwd, req.file);
@@ -16393,6 +16699,13 @@ var Launcher = class {
16393
16699
  kill(pid, sig, sender, from) {
16394
16700
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
16395
16701
  const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target) || from !== void 0 && this.granted.get(this.jobs.shown(target)) === from;
16702
+ if (pid === -1) {
16703
+ const sender2 = from === void 0 ? void 0 : this.imageOf(from);
16704
+ return this.killAll(
16705
+ sig,
16706
+ (member) => this.imageOf(member) !== sender2 && may(this.imageOf(member))
16707
+ );
16708
+ }
16396
16709
  if (pid < 0) return this.jobs.killGroup(-pid, sig, (member) => may(this.imageOf(member)));
16397
16710
  const handle = this.processes.get(pid);
16398
16711
  if (!handle) return false;
@@ -16400,6 +16713,15 @@ var Launcher = class {
16400
16713
  if (sig !== 0) handle.signal(sig);
16401
16714
  return true;
16402
16715
  }
16716
+ killAll(sig, may) {
16717
+ let any = false;
16718
+ for (const [pid, handle] of [...this.processes]) {
16719
+ if (pid === 1 || !may(pid)) continue;
16720
+ any = true;
16721
+ if (sig !== 0) handle.signal(sig);
16722
+ }
16723
+ return any;
16724
+ }
16403
16725
  async driver(type, spec) {
16404
16726
  if (type === "fsa") return this.fsaDriver(spec);
16405
16727
  if (type === "hostfs") {
@@ -16489,6 +16811,16 @@ var Launcher = class {
16489
16811
  }
16490
16812
  };
16491
16813
  }
16814
+ attest(path) {
16815
+ return attest(this.rootFs, path, {
16816
+ users: this.usersMode,
16817
+ mounts: this.mounts.list().map((entry) => entry.target).filter((target) => this.mounts.mounted(target) !== void 0)
16818
+ });
16819
+ }
16820
+ watchFiles(paths, notify) {
16821
+ this.fileWatches ??= new FileWatches(this.watchers, this.rootFs);
16822
+ return this.fileWatches.watch(paths, notify);
16823
+ }
16492
16824
  async mount(spec) {
16493
16825
  this.guardMount(spec.target);
16494
16826
  const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
@@ -16669,7 +17001,7 @@ var Launcher = class {
16669
17001
  ...account.umask !== void 0 ? { umask: account.umask } : {}
16670
17002
  };
16671
17003
  }
16672
- async starting(argv, dir, account) {
17004
+ async starting(argv, dir, account, tag) {
16673
17005
  this.resetCatalogs();
16674
17006
  const scope = account.uid === 0 ? "" : pnpmHomeOf(account, this.pnpmHome);
16675
17007
  const start2 = dir ?? "/";
@@ -16677,13 +17009,13 @@ var Launcher = class {
16677
17009
  const [file = ""] = argv;
16678
17010
  if (!await this.base.exists(cwd)) await this.processFs.mkdir(cwd, { recursive: true });
16679
17011
  const planned = await this.plan(file, argv, cwd, scope);
16680
- await this.mayExec(credOf(account), { file, cwd, pid: 0 }, planned);
17012
+ await this.mayExec(credOf(account), { file, cwd, pid: 0, tag }, planned);
16681
17013
  return { cwd, file, planned };
16682
17014
  }
16683
17015
  async openTerminal(argv, options) {
16684
17016
  tagOption(options.tag);
16685
17017
  const { account, tty: ttyGroup } = await this.terminalAccount(options.user);
16686
- const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
17018
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
16687
17019
  if (!planned) throw new Error(`${file}: command not found`);
16688
17020
  let leader = 0;
16689
17021
  const tty = new KernelTty(
@@ -16734,7 +17066,7 @@ var Launcher = class {
16734
17066
  async run(argv, options = {}) {
16735
17067
  tagOption(options.tag);
16736
17068
  const account = await this.account(options.user);
16737
- const { cwd, file, planned } = await this.starting(argv, options.cwd, account);
17069
+ const { cwd, file, planned } = await this.starting(argv, options.cwd, account, options.tag);
16738
17070
  const env = this.environment(cwd, options.env, account);
16739
17071
  const out = [];
16740
17072
  const err = [];
@@ -16893,9 +17225,31 @@ var USERS_OPS = /* @__PURE__ */ new Set([
16893
17225
  "groups-add-member",
16894
17226
  "groups-remove-member"
16895
17227
  ]);
17228
+ var ATTEST_OPS = /* @__PURE__ */ new Set(["attest", "watch-files", "unwatch-files"]);
17229
+ function isAttestRequest(req) {
17230
+ return ATTEST_OPS.has(req.op);
17231
+ }
16896
17232
  function isUsersRequest(req) {
16897
17233
  return USERS_OPS.has(req.op);
16898
17234
  }
17235
+ function transferOf2(req, result) {
17236
+ if (result instanceof MessagePort) return [result];
17237
+ if (req.op === "attest") return [result.bytes.buffer];
17238
+ return void 0;
17239
+ }
17240
+ function attestOps(port) {
17241
+ const watches = /* @__PURE__ */ new Map();
17242
+ return async (l, req) => {
17243
+ if (req.op === "attest") return l.attest(req.path);
17244
+ if (req.op === "unwatch-files") {
17245
+ watches.get(req.watch)?.();
17246
+ return watches.delete(req.watch);
17247
+ }
17248
+ const notify = (path) => port.postMessage({ fileChanged: { watch: req.id, path } });
17249
+ watches.set(req.id, await l.watchFiles(req.paths, notify));
17250
+ return req.id;
17251
+ };
17252
+ }
16899
17253
  function failure(err) {
16900
17254
  const code = err?.code;
16901
17255
  return {
@@ -17034,8 +17388,10 @@ function serveKernel(port, deps) {
17034
17388
  if (req.op === "insert") return l.insert(req.target, req.handle, req.source);
17035
17389
  return l.mounts.list();
17036
17390
  }
17391
+ const attestOp = attestOps(port);
17037
17392
  async function handle(req) {
17038
17393
  if (req.op === "init") return init(req);
17394
+ if (isAttestRequest(req)) return attestOp(await ready2(), req);
17039
17395
  if (req.op === "open-terminal") return terminal(req);
17040
17396
  if (req.op === "connect") return connect(req.user, req.tag);
17041
17397
  if (isUsersRequest(req)) return usersOp(await ready2(), req);
@@ -17084,7 +17440,7 @@ function serveKernel(port, deps) {
17084
17440
  if (answered(event.data, remote, uplink, approvals)) return;
17085
17441
  const req = event.data;
17086
17442
  handle(req).then(
17087
- (result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),
17443
+ (result) => reply(req.id, { result }, transferOf2(req, result)),
17088
17444
  (err) => reply(req.id, failure(err))
17089
17445
  );
17090
17446
  });