@ai-ecoverse/slicc-kernel 1.67.1 → 1.68.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.
@@ -2019,6 +2019,7 @@ function kernelFdKind(file) {
2019
2019
  if (file.held) return "held";
2020
2020
  if (file.heldMeta && "device" in file.heldMeta) return "device";
2021
2021
  if (file.tty) return "tty";
2022
+ if (file.sealed) return "sealed";
2022
2023
  return file.seek ? "file" : "stream";
2023
2024
  }
2024
2025
  var FdTable = class _FdTable {
@@ -11407,6 +11408,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
11407
11408
  "fd-open-tty",
11408
11409
  "fd-tty-names",
11409
11410
  "fd-tty-owner",
11411
+ "fd-open-sealed",
11410
11412
  "tty-get",
11411
11413
  "tty-set",
11412
11414
  "tty-winsz",
@@ -11462,6 +11464,7 @@ var WasmProcess = class {
11462
11464
  ignored = 0;
11463
11465
  inherited = 0;
11464
11466
  interrupt = new AbortController();
11467
+ gone = new AbortController();
11465
11468
  dying = false;
11466
11469
  umask = DEFAULT_UMASK;
11467
11470
  cred;
@@ -11684,6 +11687,7 @@ var WasmProcess = class {
11684
11687
  }
11685
11688
  async write(fd, body, nonblock = false, cancelled) {
11686
11689
  const file = this.fds.get(fd).file;
11690
+ if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
11687
11691
  if (!file.write) throw new KernelError("EBADF");
11688
11692
  if (!pollFile(file).writable) {
11689
11693
  if (nonblock) throw new KernelError("EAGAIN");
@@ -11765,6 +11769,24 @@ var WasmProcess = class {
11765
11769
  if (name2 !== void 0) ttyOpenable(this.guarded(), tty);
11766
11770
  return this.fds.install(tty.file(), 3);
11767
11771
  }
11772
+ openSealed() {
11773
+ const broker = this.options.sealed;
11774
+ if (!broker) throw new KernelError("ENOENT");
11775
+ return this.fds.install(broker.open(), 3);
11776
+ }
11777
+ async sealedCaller() {
11778
+ const shown = await this.options.identity?.();
11779
+ const broker = this.options.sealed;
11780
+ const tag = broker.tagOf(this.pid);
11781
+ return {
11782
+ pid: shown?.pid ?? this.pid,
11783
+ ppid: shown?.ppid ?? 0,
11784
+ uid: this.cred.euid,
11785
+ gid: this.cred.egid,
11786
+ ...tag !== void 0 ? { tag } : {},
11787
+ users: broker.users
11788
+ };
11789
+ }
11768
11790
  guarded() {
11769
11791
  const { guard: guard2 } = this.options;
11770
11792
  return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid };
@@ -11885,6 +11907,8 @@ var WasmProcess = class {
11885
11907
  return { ok: true, kind: "json", json: this.openTty(req.name) };
11886
11908
  case "fd-tty-names":
11887
11909
  return { ok: true, kind: "json", json: this.options.jobs?.terminalNames() ?? [] };
11910
+ case "fd-open-sealed":
11911
+ return { ok: true, kind: "json", json: this.openSealed() };
11888
11912
  case "fd-tty-owner":
11889
11913
  return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
11890
11914
  case "fd-select": {
@@ -12320,6 +12344,7 @@ var WasmProcess = class {
12320
12344
  async exit() {
12321
12345
  if (this.exited) return;
12322
12346
  this.exited = true;
12347
+ this.gone.abort();
12323
12348
  this.die();
12324
12349
  this.clearAlarm();
12325
12350
  this.interrupt.abort();
@@ -12507,6 +12532,7 @@ function processOptions(opts, header3, hooks, created3) {
12507
12532
  return {
12508
12533
  ...inherited(opts),
12509
12534
  ...opts.owners ? { owners: true } : {},
12535
+ ...opts.sealed ? { sealed: opts.sealed } : {},
12510
12536
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3 } } : {},
12511
12537
  ...opts.identity ? { identity: opts.identity } : {},
12512
12538
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -12989,6 +13015,178 @@ function replayingHops(inner, heldMs = HELD_MS) {
12989
13015
  };
12990
13016
  }
12991
13017
 
13018
+ // src/kernel/sealed.ts
13019
+ var SEALED_MAX_BYTES = 64 * 1024;
13020
+ var SEALED_TIMEOUT_MS = 1e4;
13021
+ var SEALED_OPS = /* @__PURE__ */ new Set([
13022
+ "seal",
13023
+ "placeholder",
13024
+ "list",
13025
+ "test",
13026
+ "request",
13027
+ "status",
13028
+ "cancel",
13029
+ "materialize"
13030
+ ]);
13031
+ var encoder3 = new TextEncoder();
13032
+ var decoder2 = new TextDecoder("utf-8", { fatal: true });
13033
+ function parseSealed(bytes) {
13034
+ try {
13035
+ if (bytes.length > SEALED_MAX_BYTES) throw new KernelError("EMSGSIZE");
13036
+ let parsed2;
13037
+ try {
13038
+ parsed2 = JSON.parse(decoder2.decode(bytes));
13039
+ } catch {
13040
+ throw new KernelError("EINVAL");
13041
+ }
13042
+ if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2))
13043
+ throw new KernelError("EINVAL");
13044
+ const req = parsed2;
13045
+ if (req.v !== 1 || typeof req.op !== "string" || !SEALED_OPS.has(req.op))
13046
+ throw new KernelError("EINVAL");
13047
+ return req;
13048
+ } finally {
13049
+ bytes.fill(0);
13050
+ }
13051
+ }
13052
+ function errorReply(code, message3) {
13053
+ return { v: 1, error: { code, message: message3 } };
13054
+ }
13055
+ function isRecord(value) {
13056
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13057
+ }
13058
+ function serialized(reply) {
13059
+ let plain;
13060
+ try {
13061
+ const text = JSON.stringify(reply);
13062
+ plain = text === void 0 ? void 0 : JSON.parse(text);
13063
+ } catch (err) {
13064
+ return JSON.stringify(errorReply("EIO", lineOf(err)));
13065
+ }
13066
+ if (!isRecord(plain)) return JSON.stringify(errorReply("EIO", "the vault gave no reply"));
13067
+ const { v: _, ...rest2 } = plain;
13068
+ return JSON.stringify({ v: 1, ...rest2 });
13069
+ }
13070
+ function lineOf(err) {
13071
+ const text = err instanceof Error ? err.message : String(err);
13072
+ return (text.split("\n")[0] ?? "").slice(0, 200);
13073
+ }
13074
+ var SealedChannel = class {
13075
+ reply;
13076
+ offset = 0;
13077
+ inflight;
13078
+ broker;
13079
+ constructor(broker) {
13080
+ this.broker = broker;
13081
+ }
13082
+ send(bytes, caller, gone) {
13083
+ const length2 = bytes.length;
13084
+ const fields = parseSealed(bytes);
13085
+ this.reply = void 0;
13086
+ this.offset = 0;
13087
+ this.inflight?.control.abort("replaced");
13088
+ const control = new AbortController();
13089
+ const withdraw = () => control.abort("withdrawn");
13090
+ if (gone.aborted) withdraw();
13091
+ else gone.addEventListener("abort", withdraw, { once: true });
13092
+ const timer = setTimeout(() => control.abort("timeout"), this.broker.timeoutMs);
13093
+ const req = { ...fields, v: 1, op: fields.op, caller };
13094
+ const flight = { control, done: Promise.resolve() };
13095
+ flight.done = this.answer(req, control.signal).then((reply) => {
13096
+ clearTimeout(timer);
13097
+ gone.removeEventListener("abort", withdraw);
13098
+ if (this.inflight !== flight) return;
13099
+ this.inflight = void 0;
13100
+ if (reply) this.reply = encoder3.encode(serialized(reply));
13101
+ });
13102
+ this.inflight = flight;
13103
+ return length2;
13104
+ }
13105
+ async answer(req, signal) {
13106
+ const hook = this.broker.hook;
13107
+ if (!hook) return errorReply("ENOTCONN", "no vault is connected");
13108
+ const stopped = new Promise((resolve) => {
13109
+ signal.addEventListener("abort", () => resolve(void 0), { once: true });
13110
+ });
13111
+ if (signal.aborted) return void 0;
13112
+ const asked = Promise.resolve().then(() => hook(req, { signal })).then(
13113
+ (reply2) => isRecord(reply2) ? reply2 : errorReply("EIO", "the vault gave no reply"),
13114
+ (err) => errorReply("EIO", lineOf(err))
13115
+ );
13116
+ const reply = await Promise.race([asked, stopped]);
13117
+ if (!signal.aborted) return reply;
13118
+ return signal.reason === "timeout" ? errorReply("ETIMEDOUT", "the vault did not answer within 10 s") : void 0;
13119
+ }
13120
+ async read(max, signal) {
13121
+ for (; ; ) {
13122
+ if (this.reply) {
13123
+ const out = this.reply.slice(this.offset, this.offset + max);
13124
+ this.offset += out.length;
13125
+ if (this.offset >= this.reply.length) {
13126
+ this.reply = void 0;
13127
+ this.offset = 0;
13128
+ }
13129
+ return out;
13130
+ }
13131
+ const flight = this.inflight;
13132
+ if (!flight) return new Uint8Array(0);
13133
+ await this.until(flight, signal);
13134
+ }
13135
+ }
13136
+ async until(flight, signal) {
13137
+ if (!signal) return flight.done;
13138
+ if (signal.aborted) throw new KernelError("EINTR");
13139
+ let stop = () => {
13140
+ };
13141
+ const interrupted2 = new Promise((_, reject) => {
13142
+ stop = () => reject(new KernelError("EINTR"));
13143
+ signal.addEventListener("abort", stop, { once: true });
13144
+ });
13145
+ try {
13146
+ await Promise.race([flight.done, interrupted2]);
13147
+ } finally {
13148
+ signal.removeEventListener("abort", stop);
13149
+ }
13150
+ }
13151
+ poll() {
13152
+ return { readable: this.reply !== void 0 || !this.inflight, writable: true, hangup: false };
13153
+ }
13154
+ async changed(signal) {
13155
+ const flight = this.inflight;
13156
+ if (flight) await this.until(flight, signal);
13157
+ }
13158
+ close() {
13159
+ this.inflight?.control.abort("withdrawn");
13160
+ this.inflight = void 0;
13161
+ this.reply = void 0;
13162
+ }
13163
+ };
13164
+ var SealedBroker = class {
13165
+ hook;
13166
+ users;
13167
+ timeoutMs;
13168
+ tagOf;
13169
+ constructor(hook, users, timeoutMs = SEALED_TIMEOUT_MS, tagOf = () => void 0) {
13170
+ this.hook = hook;
13171
+ this.users = users;
13172
+ this.timeoutMs = timeoutMs;
13173
+ this.tagOf = tagOf;
13174
+ }
13175
+ open() {
13176
+ const channel = new SealedChannel(this);
13177
+ return new OpenFile({
13178
+ read: (max, signal) => channel.read(max, signal),
13179
+ write: async () => {
13180
+ throw new KernelError("EINVAL");
13181
+ },
13182
+ poll: () => channel.poll(),
13183
+ changed: (signal) => channel.changed(signal),
13184
+ close: () => channel.close(),
13185
+ sealed: channel
13186
+ });
13187
+ }
13188
+ };
13189
+
12992
13190
  // src/kernel/settling.ts
12993
13191
  var SETUP_OPS = /* @__PURE__ */ new Set([
12994
13192
  "proc-list",
@@ -13636,13 +13834,13 @@ var HostfsClient = class {
13636
13834
  const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
13637
13835
  onOpen();
13638
13836
  const reader = response.body.getReader();
13639
- const decoder2 = new TextDecoder();
13837
+ const decoder3 = new TextDecoder();
13640
13838
  let buffered = "";
13641
13839
  try {
13642
13840
  for (; ; ) {
13643
13841
  const { done: done4, value } = await reader.read();
13644
13842
  if (done4) return;
13645
- buffered += decoder2.decode(value, { stream: true });
13843
+ buffered += decoder3.decode(value, { stream: true });
13646
13844
  let end = buffered.indexOf("\n");
13647
13845
  while (end >= 0) {
13648
13846
  const line = buffered.slice(0, end).trim();
@@ -15020,7 +15218,7 @@ var LAYOUT = [
15020
15218
  ];
15021
15219
  var LOAD_SAMPLE_MS = 5e3;
15022
15220
  var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
15023
- var encoder3 = new TextEncoder();
15221
+ var encoder4 = new TextEncoder();
15024
15222
  var USERS_MODES = ["off", "report", "enforce"];
15025
15223
  function usersMode(mode = "off") {
15026
15224
  if (USERS_MODES.includes(mode)) return mode;
@@ -15170,12 +15368,19 @@ var Launcher = class {
15170
15368
  fstab = Promise.resolve([]);
15171
15369
  usersMode;
15172
15370
  dac;
15371
+ sealed;
15173
15372
  nextPid = 1e3;
15174
15373
  boot = Date.now();
15175
15374
  terminals = 0;
15176
15375
  constructor(options) {
15177
15376
  this.usersMode = usersMode(options.users);
15178
15377
  this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
15378
+ this.sealed = new SealedBroker(
15379
+ options.sealed,
15380
+ this.usersMode,
15381
+ void 0,
15382
+ (pid) => this.tagOf(pid)
15383
+ );
15179
15384
  if (this.owners) options.fs.trackOwners?.();
15180
15385
  this.mounts = new MountTable({
15181
15386
  open: (type, spec) => this.driver(type, spec),
@@ -15575,6 +15780,7 @@ var Launcher = class {
15575
15780
  ...req.umask !== void 0 ? { umask: req.umask } : {},
15576
15781
  cred: req.cred,
15577
15782
  ...this.owners ? { owners: true } : {},
15783
+ sealed: this.sealed,
15578
15784
  ...this.dac ? { dac: this.dac } : {},
15579
15785
  program: req.program,
15580
15786
  argv0: req.argv0,
@@ -16213,7 +16419,7 @@ var Launcher = class {
16213
16419
  },
16214
16420
  account
16215
16421
  );
16216
- const report = (message3) => options.onData(encoder3.encode(`${message3}\r
16422
+ const report = (message3) => options.onData(encoder4.encode(`${message3}\r
16217
16423
  `));
16218
16424
  const handle = await this.launch(planned, {
16219
16425
  env,
@@ -16250,7 +16456,7 @@ var Launcher = class {
16250
16456
  };
16251
16457
  const stdout = collect(out, options.onStdout);
16252
16458
  const stderr = collect(err, options.onStderr);
16253
- const report = (message3) => stderr(encoder3.encode(`${message3}
16459
+ const report = (message3) => stderr(encoder4.encode(`${message3}
16254
16460
  `));
16255
16461
  if (!planned) {
16256
16462
  report(`${file}: command not found`);
@@ -16275,6 +16481,33 @@ var Launcher = class {
16275
16481
  }
16276
16482
  };
16277
16483
 
16484
+ // src/sealed-relay.ts
16485
+ function sealedAsker(port) {
16486
+ const asks = /* @__PURE__ */ new Map();
16487
+ let next2 = 0;
16488
+ const hook = (req, { signal }) => {
16489
+ const id2 = ++next2;
16490
+ const waiting = Promise.withResolvers();
16491
+ asks.set(id2, waiting);
16492
+ const abort = () => {
16493
+ if (!asks.delete(id2)) return;
16494
+ port.postMessage({ sealedAbort: { id: id2, reason: signal.reason } });
16495
+ waiting.reject(new Error(String(signal.reason)));
16496
+ };
16497
+ signal.addEventListener("abort", abort, { once: true });
16498
+ port.postMessage({ sealedAsk: { id: id2, req } });
16499
+ return waiting.promise.finally(() => signal.removeEventListener("abort", abort));
16500
+ };
16501
+ const answer = ({ id: id2, reply, error }) => {
16502
+ const waiting = asks.get(id2);
16503
+ if (!waiting) return;
16504
+ asks.delete(id2);
16505
+ if (reply) waiting.resolve(reply);
16506
+ else waiting.reject(new Error(error ?? "no reply"));
16507
+ };
16508
+ return { hook, answer };
16509
+ }
16510
+
16278
16511
  // src/serve.ts
16279
16512
  async function inOpfs(deps, root) {
16280
16513
  if (deps.syncAccess !== true) return false;
@@ -16389,11 +16622,31 @@ function usersOp(l, req) {
16389
16622
  if (req.op === "groups-remove-member") return l.users.removeMember(req.group, req.user);
16390
16623
  return l.users.list();
16391
16624
  }
16392
- function hostOptions(req, port) {
16625
+ function hostOptions(req, port, sealed) {
16393
16626
  return {
16394
16627
  ...req.hostname !== void 0 ? { hostname: req.hostname } : {},
16395
16628
  ...req.users !== void 0 ? { users: req.users } : {},
16396
- ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {}
16629
+ ...req.denials ? { onDenial: (denial) => port.postMessage({ denial }) } : {},
16630
+ ...req.sealed ? { sealed } : {}
16631
+ };
16632
+ }
16633
+ function policyAsker(port) {
16634
+ const policies = /* @__PURE__ */ new Map();
16635
+ let nextPolicy = 0;
16636
+ const askPolicy = (req) => new Promise((resolve) => {
16637
+ const id2 = ++nextPolicy;
16638
+ policies.set(id2, resolve);
16639
+ port.postMessage({ mountPolicy: { id: id2, req } });
16640
+ });
16641
+ return {
16642
+ mountPolicy: (policy) => policy === void 0 ? {} : { processMounts: policy === "ask" ? askPolicy : policy },
16643
+ allowed(data) {
16644
+ const answer = data.mountAllowed;
16645
+ if (!answer) return false;
16646
+ policies.get(answer.id)?.(answer.allowed);
16647
+ policies.delete(answer.id);
16648
+ return true;
16649
+ }
16397
16650
  };
16398
16651
  }
16399
16652
  function serveKernel(port, deps) {
@@ -16403,6 +16656,7 @@ function serveKernel(port, deps) {
16403
16656
  const terminals = /* @__PURE__ */ new Map();
16404
16657
  const reply = (id2, body, transfer) => port.postMessage({ id: id2, ...body }, transfer);
16405
16658
  const clients = /* @__PURE__ */ new Set();
16659
+ const sealed = sealedAsker(port);
16406
16660
  const approvals = sudoApprovals(port);
16407
16661
  const locks = deps.locks === null ? void 0 : deps.locks ?? locksOf();
16408
16662
  let holding;
@@ -16467,7 +16721,7 @@ function serveKernel(port, deps) {
16467
16721
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
16468
16722
  ...req.modules ? { modules: req.modules } : {},
16469
16723
  ...req.env ? { env: req.env } : {},
16470
- ...hostOptions(req, port),
16724
+ ...hostOptions(req, port, sealed.hook),
16471
16725
  ...req.approve ? { approve: approvals.ask } : {},
16472
16726
  ...net,
16473
16727
  ...media ? { media } : {},
@@ -16530,23 +16784,13 @@ function serveKernel(port, deps) {
16530
16784
  if (grant) waiting.resolve(grant);
16531
16785
  else waiting.reject(Object.assign(new Error(error ?? "no grant"), { code: "EACCES" }));
16532
16786
  }
16533
- const policies = /* @__PURE__ */ new Map();
16534
- let nextPolicy = 0;
16535
- const askPolicy = (req) => new Promise((resolve) => {
16536
- const id2 = ++nextPolicy;
16537
- policies.set(id2, resolve);
16538
- port.postMessage({ mountPolicy: { id: id2, req } });
16539
- });
16540
- const mountPolicy = (policy) => policy === void 0 ? {} : { processMounts: policy === "ask" ? askPolicy : policy };
16787
+ const { mountPolicy, allowed } = policyAsker(port);
16541
16788
  port.addEventListener("message", (event) => {
16789
+ const sealedReply = event.data.sealedReply;
16790
+ if (sealedReply) return sealed.answer(sealedReply);
16542
16791
  const grant = event.data.hostfsGrant;
16543
16792
  if (grant) return granted2(grant);
16544
- const answer = event.data.mountAllowed;
16545
- if (answer) {
16546
- policies.get(answer.id)?.(answer.allowed);
16547
- policies.delete(answer.id);
16548
- return;
16549
- }
16793
+ if (allowed(event.data)) return;
16550
16794
  if (answered(event.data, remote, uplink, approvals)) return;
16551
16795
  const req = event.data;
16552
16796
  handle(req).then(