@ai-ecoverse/slicc-kernel 1.174.3 → 1.175.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.
@@ -2239,8 +2239,10 @@ var OpfsFs = class {
2239
2239
  for (let at = from; at < parts2.length; at++) {
2240
2240
  this.calls++;
2241
2241
  dir = await dir.getDirectoryHandle(parts2[at]);
2242
- this.dirs.set(`/${parts2.slice(0, at + 1).join("/")}`, dir);
2242
+ const path = `/${parts2.slice(0, at + 1).join("/")}`;
2243
+ this.dirs.set(path, dir);
2243
2244
  this.checked.set(dir, performance.now());
2245
+ this.absent.delete(path);
2244
2246
  }
2245
2247
  return dir;
2246
2248
  }
@@ -2322,6 +2324,7 @@ var OpfsFs = class {
2322
2324
  if (looked) {
2323
2325
  this.dirs.set(path, handle);
2324
2326
  this.checked.set(handle, performance.now());
2327
+ this.absent.delete(path);
2325
2328
  }
2326
2329
  return handle;
2327
2330
  }
@@ -2972,7 +2975,8 @@ var OpfsFs = class {
2972
2975
  }
2973
2976
  const [entry] = await this.meta.get([path]);
2974
2977
  const wanted = entry?.link === void 0 ? path : resolvePath(parentOf4(path), entry.link);
2975
- return [resolvePath((await this.locate(parentOf4(wanted), true)).path, baseOf(wanted)), false];
2978
+ const parent = await this.exactly(() => this.locate(parentOf4(wanted), true));
2979
+ return [resolvePath(parent.path, baseOf(wanted)), false];
2976
2980
  }
2977
2981
  async fileFor(path, create = true) {
2978
2982
  const [target, existed] = await this.writeTarget(path);
@@ -3079,10 +3083,12 @@ var OpfsFs = class {
3079
3083
  await this.mkdir(parentOf4(path), { recursive: true, ...above ? { owner: above } : {} });
3080
3084
  const parent = await this.locate(parentOf4(path), true);
3081
3085
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
3082
- this.refuse(resolvePath(parent.path, baseOf(path)));
3083
- await this.io(parent.handle.getDirectoryHandle(baseOf(path), { create: true }));
3086
+ const made2 = resolvePath(parent.path, baseOf(path));
3087
+ this.forget(made2);
3088
+ this.refuse(made2);
3089
+ const handle = await this.io(parent.handle.getDirectoryHandle(baseOf(path), { create: true }));
3090
+ this.remember(made2, handle, true);
3084
3091
  const now = Date.now();
3085
- const made2 = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
3086
3092
  await this.stampNew(made2, owner, true);
3087
3093
  this.touch(now, parent.path, made2);
3088
3094
  }
@@ -8740,7 +8746,7 @@ function satisfies(version, range2) {
8740
8746
  }
8741
8747
 
8742
8748
  // src/version.ts
8743
- var KERNEL_VERSION = true ? "1.174.3" : "0.0.0-dev";
8749
+ var KERNEL_VERSION = true ? "1.175.1" : "0.0.0-dev";
8744
8750
  var OS_RELEASE = "6.0.0-slicc";
8745
8751
  function procVersion() {
8746
8752
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -19063,7 +19069,12 @@ function sessionValues(reply, asked) {
19063
19069
  // src/kernel/sealed.ts
19064
19070
  var SEALED_MAX_BYTES = 64 * 1024;
19065
19071
  var SEALED_TIMEOUT_MS = 1e4;
19072
+ var SEALED_ASK_TIMEOUT_MS = 305e3;
19073
+ var SEALED_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
19074
+ var SEALED_NAME_MAX = 128;
19075
+ var SEALED_PROMPT_MAX = 200;
19066
19076
  var SEALED_OPS = /* @__PURE__ */ new Set([
19077
+ "ask",
19067
19078
  "seal",
19068
19079
  "placeholder",
19069
19080
  "list",
@@ -19078,6 +19089,16 @@ function tagFields(tags) {
19078
19089
  if (tag === void 0) return {};
19079
19090
  return tags.length > 1 ? { tag, tags: [...tags] } : { tag };
19080
19091
  }
19092
+ var ASK_FIELDS = /* @__PURE__ */ new Set([
19093
+ "v",
19094
+ "op",
19095
+ "name",
19096
+ "prompt",
19097
+ "reveal",
19098
+ "replace",
19099
+ "caller",
19100
+ "requester"
19101
+ ]);
19081
19102
  var encoder6 = new TextEncoder();
19082
19103
  var decoder3 = new TextDecoder("utf-8", { fatal: true });
19083
19104
  function parseSealed(bytes) {
@@ -19095,6 +19116,7 @@ function parseSealed(bytes) {
19095
19116
  if (req.v !== 1 || typeof req.op !== "string" || !SEALED_OPS.has(req.op))
19096
19117
  throw new KernelError("EINVAL");
19097
19118
  if (req.op === "materialize" && !materializable(req)) throw new KernelError("EINVAL");
19119
+ if (req.op === "ask" && !askable(req)) throw new KernelError("EINVAL");
19098
19120
  return req;
19099
19121
  } finally {
19100
19122
  bytes.fill(0);
@@ -19104,6 +19126,27 @@ function materializable(req) {
19104
19126
  if (req.shellGrants !== true && !isStrings(req.names)) return false;
19105
19127
  return isStrings(req.argv) && typeof req.path === "string" && typeof req.cwd === "string";
19106
19128
  }
19129
+ function askable(req) {
19130
+ if (Object.keys(req).some((key) => !ASK_FIELDS.has(key))) return false;
19131
+ const { name: name2, prompt, reveal, replace } = req;
19132
+ if (typeof name2 !== "string" || name2.length > SEALED_NAME_MAX || !SEALED_NAME.test(name2))
19133
+ return false;
19134
+ if (prompt !== void 0 && (typeof prompt !== "string" || [...prompt].length > SEALED_PROMPT_MAX))
19135
+ return false;
19136
+ return [reveal, replace].every((flag) => flag === void 0 || typeof flag === "boolean");
19137
+ }
19138
+ function askRequest(fields, caller, requester) {
19139
+ return {
19140
+ v: 1,
19141
+ op: "ask",
19142
+ name: fields.name,
19143
+ ...fields.prompt !== void 0 ? { prompt: fields.prompt } : {},
19144
+ reveal: fields.reveal === true,
19145
+ replace: fields.replace === true,
19146
+ caller,
19147
+ requester
19148
+ };
19149
+ }
19107
19150
  function errorReply(code, message3) {
19108
19151
  return { v: 1, error: { code, message: message3 } };
19109
19152
  }
@@ -19136,7 +19179,7 @@ var SealedChannel = class {
19136
19179
  this.broker = broker;
19137
19180
  this.access = access2;
19138
19181
  }
19139
- send(bytes, caller, gone) {
19182
+ send(bytes, caller, gone, requester = async () => void 0) {
19140
19183
  if (this.access === "read") {
19141
19184
  bytes.fill(0);
19142
19185
  throw new KernelError("EBADF");
@@ -19149,10 +19192,15 @@ var SealedChannel = class {
19149
19192
  const withdraw = () => control.abort("withdrawn");
19150
19193
  if (gone.aborted) withdraw();
19151
19194
  else gone.addEventListener("abort", withdraw, { once: true });
19152
- const timer = setTimeout(() => control.abort("timeout"), this.broker.timeoutMs);
19195
+ const ask = fields.op === "ask";
19196
+ const timer = setTimeout(
19197
+ () => control.abort(ask ? "expired" : "timeout"),
19198
+ ask ? this.broker.askTimeoutMs : this.broker.timeoutMs
19199
+ );
19153
19200
  const req = { ...fields, v: 1, op: fields.op, caller };
19201
+ const asked = ask ? this.asking(fields, caller, requester, control.signal) : void 0;
19154
19202
  const flight = { control, done: Promise.resolve() };
19155
- flight.done = this.answer(req, control.signal).then(async (reply) => {
19203
+ flight.done = (asked ?? this.answer(req, control.signal)).then(async (reply) => {
19156
19204
  clearTimeout(timer);
19157
19205
  gone.removeEventListener("abort", withdraw);
19158
19206
  if (reply && req.op === "materialize")
@@ -19164,6 +19212,17 @@ var SealedChannel = class {
19164
19212
  this.inflight = flight;
19165
19213
  return length2;
19166
19214
  }
19215
+ async asking(fields, caller, requester, signal) {
19216
+ const facts = await requester().catch(() => void 0);
19217
+ if (signal.aborted) return this.stopped(signal);
19218
+ if (!facts) return errorReply("EIO", "the kernel has no record of this process");
19219
+ return this.answer(askRequest(fields, caller, facts), signal);
19220
+ }
19221
+ stopped(signal) {
19222
+ if (signal.reason === "timeout")
19223
+ return errorReply("ETIMEDOUT", "the vault did not answer within 10 s");
19224
+ return signal.reason === "expired" ? { v: 1, state: "expired" } : void 0;
19225
+ }
19167
19226
  async answer(req, signal) {
19168
19227
  const hook = this.broker.hook;
19169
19228
  if (!hook) return errorReply("ENOTCONN", "no vault is connected");
@@ -19176,8 +19235,7 @@ var SealedChannel = class {
19176
19235
  (err) => errorReply("EIO", lineOf3(err))
19177
19236
  );
19178
19237
  const reply = await Promise.race([asked, stopped]);
19179
- if (!signal.aborted) return reply;
19180
- return signal.reason === "timeout" ? errorReply("ETIMEDOUT", "the vault did not answer within 10 s") : void 0;
19238
+ return signal.aborted ? this.stopped(signal) : reply;
19181
19239
  }
19182
19240
  drop() {
19183
19241
  this.reply?.fill(0);
@@ -19239,7 +19297,9 @@ var SealedBroker = class {
19239
19297
  tagOf;
19240
19298
  tickets = new TicketBook();
19241
19299
  sessions = new SessionScrubs();
19300
+ askTimeoutMs = SEALED_ASK_TIMEOUT_MS;
19242
19301
  userOf = async () => void 0;
19302
+ factsOf = async () => void 0;
19243
19303
  constructor(hook, users, timeoutMs = SEALED_TIMEOUT_MS, tagOf = () => void 0) {
19244
19304
  this.hook = hook;
19245
19305
  this.users = users;
@@ -27626,7 +27686,10 @@ var WasmProcess = class {
27626
27686
  }
27627
27687
  async write(fd, body, nonblock = false, cancelled) {
27628
27688
  const file = this.fds.get(fd).file;
27629
- if (file.sealed) return file.sealed.send(body, await this.sealedCaller(), this.gone.signal);
27689
+ if (file.sealed) {
27690
+ const caller = await this.sealedCaller();
27691
+ return file.sealed.send(body, caller, this.gone.signal, () => this.sealedRequester());
27692
+ }
27630
27693
  if (!file.write) throw new KernelError("EBADF");
27631
27694
  if (file.embedder) return this.writeEmbedder(file, body, nonblock, cancelled);
27632
27695
  if (!pollFile(file).writable) {
@@ -27831,6 +27894,10 @@ var WasmProcess = class {
27831
27894
  const ids = { pid: shown2?.pid ?? this.pid, ppid: shown2?.ppid ?? 0 };
27832
27895
  return broker.stamp(ids, this.cred, broker.tagOf(this.pid));
27833
27896
  }
27897
+ async sealedRequester() {
27898
+ const facts = await this.options.sealed.factsOf(this.pid);
27899
+ return facts && { ...facts, stdin: this.stdinKind() };
27900
+ }
27834
27901
  guarded() {
27835
27902
  const { guard: guard2 } = this.options;
27836
27903
  return guard2 && { fs: guard2.fs, dac: guard2.dac, cred: this.cred, pid: this.pid, links: guard2.links };
@@ -32738,6 +32805,122 @@ function refusing(command) {
32738
32805
  };
32739
32806
  }
32740
32807
 
32808
+ // src/sealed-command.ts
32809
+ var SEALED_BIN = "/usr/bin/sealed";
32810
+ var SEALED_COMMAND = {
32811
+ name: "sealed",
32812
+ abi: "js",
32813
+ glue: SEALED_BIN,
32814
+ wasm: SEALED_BIN,
32815
+ argv0: "sealed"
32816
+ };
32817
+ var SEALED_SOURCE = `export default async function sealed(ctx) {
32818
+ const USAGE = "usage: sealed ask NAME [--prompt TEXT] [--reveal] [--replace]\\n sealed placeholder NAME\\n sealed list [--json]\\n";
32819
+ const NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
32820
+ const EXIT = { EINVAL: 2, EACCES: 3, EAGAIN: 4, ETIMEDOUT: 124 };
32821
+ const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);
32822
+ const fail = async (status, text) => {
32823
+ await say(2, "sealed: " + text + "\\n");
32824
+ return status;
32825
+ };
32826
+ const usage = async (text) => {
32827
+ if (text !== undefined) await say(2, "sealed: " + text + "\\n");
32828
+ await say(2, USAGE);
32829
+ return 2;
32830
+ };
32831
+ const [action, ...args] = ctx.argv.slice(1);
32832
+ if (action === undefined || action === "-h" || action === "--help") return usage();
32833
+ let name;
32834
+ let prompt;
32835
+ let reveal = false;
32836
+ let replace = false;
32837
+ let asJson = false;
32838
+ for (let i = 0; i < args.length; i++) {
32839
+ const arg = args[i];
32840
+ if (action === "ask" && arg === "--prompt") {
32841
+ prompt = args[++i];
32842
+ if (prompt === undefined) return usage("--prompt needs a text");
32843
+ } else if (action === "ask" && arg.startsWith("--prompt=")) prompt = arg.slice(9);
32844
+ else if (action === "ask" && arg === "--reveal") reveal = true;
32845
+ else if (action === "ask" && arg === "--replace") replace = true;
32846
+ else if (action === "list" && arg === "--json") asJson = true;
32847
+ else if (action !== "list" && name === undefined && !arg.startsWith("-")) name = arg;
32848
+ else return usage("unexpected argument " + JSON.stringify(arg));
32849
+ }
32850
+ let request;
32851
+ if (action === "ask" || action === "placeholder") {
32852
+ if (name === undefined) return usage("a NAME is needed");
32853
+ if (name.length > 128 || !NAME.test(name)) return fail(2, JSON.stringify(name) + " is not a valid name");
32854
+ if (prompt !== undefined && [...prompt].length > 200) return fail(2, "the prompt is longer than 200 characters");
32855
+ request = action === "ask"
32856
+ ? { v: 1, op: "ask", name, ...(prompt !== undefined ? { prompt } : {}), reveal, replace }
32857
+ : { v: 1, op: "placeholder", name };
32858
+ } else if (action === "list") request = { v: 1, op: "list" };
32859
+ else return usage("unknown command " + JSON.stringify(action));
32860
+ let reply;
32861
+ try {
32862
+ reply = await exchange(request);
32863
+ } catch (err) {
32864
+ const code = err && err.code ? err.code : String(err);
32865
+ return fail(EXIT[code] ?? 1, "/dev/sealed: " + code);
32866
+ }
32867
+ if (reply.error !== undefined) {
32868
+ const code = String(reply.error && reply.error.code);
32869
+ const message = reply.error && reply.error.message ? reply.error.message : code;
32870
+ return fail(EXIT[code] ?? 1, (name !== undefined ? name + ": " : "") + message);
32871
+ }
32872
+ const newline = (await ctx.isatty(1).catch(() => false)) ? "\\n" : "";
32873
+ if (action === "list") {
32874
+ const secrets = Array.isArray(reply.secrets) ? reply.secrets : [];
32875
+ if (asJson) {
32876
+ await say(1, JSON.stringify(secrets) + "\\n");
32877
+ return 0;
32878
+ }
32879
+ await say(1, secrets.map((s) => s.name + "\\t" + s.placeholder + "\\t" + s.state + "\\n").join(""));
32880
+ return 0;
32881
+ }
32882
+ if (action === "placeholder") {
32883
+ if (typeof reply.placeholder !== "string") return fail(1, name + ": the vault gave no placeholder");
32884
+ await say(1, reply.placeholder + newline);
32885
+ return 0;
32886
+ }
32887
+ if (reply.state === "cancelled") return fail(1, name + ": cancelled");
32888
+ if (reply.state === "expired") return fail(124, name + ": the request expired");
32889
+ if (reply.state !== "done") return fail(1, name + ": the vault gave no answer");
32890
+ const out = reveal ? reply.value : reply.placeholder;
32891
+ if (typeof out !== "string") return fail(1, name + ": the vault gave no " + (reveal ? "value" : "placeholder"));
32892
+ await say(1, out + newline);
32893
+ return 0;
32894
+
32895
+ async function exchange(req) {
32896
+ const file = await ctx.open("/dev/sealed", { read: true, write: true });
32897
+ try {
32898
+ await ctx.write(file.fd, JSON.stringify(req));
32899
+ const parts = [];
32900
+ for (;;) {
32901
+ const chunk = await ctx.read(file.fd, 65536);
32902
+ if (chunk.length === 0) break;
32903
+ parts.push(chunk);
32904
+ }
32905
+ const bytes = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
32906
+ let at = 0;
32907
+ for (const part of parts) {
32908
+ bytes.set(part, at);
32909
+ at += part.length;
32910
+ part.fill(0);
32911
+ }
32912
+ const text = new TextDecoder().decode(bytes);
32913
+ bytes.fill(0);
32914
+ const parsed = JSON.parse(text);
32915
+ if (typeof parsed !== "object" || parsed === null) throw Object.assign(new Error("EIO"), { code: "EIO" });
32916
+ return parsed;
32917
+ } finally {
32918
+ await file.close().catch(() => undefined);
32919
+ }
32920
+ }
32921
+ }
32922
+ `;
32923
+
32741
32924
  // src/sudo-command.ts
32742
32925
  var SUDO_PATH = "/usr/bin/sudo";
32743
32926
  var SUDO_COMMAND = {
@@ -32951,7 +33134,7 @@ var LAYOUT = [
32951
33134
  ["/usr", 493],
32952
33135
  ["/usr/local", 493]
32953
33136
  ];
32954
- var IN_TREE = /* @__PURE__ */ new Set([SUDO_PATH, VISUDO_PATH, CRONTAB_PATH]);
33137
+ var IN_TREE = /* @__PURE__ */ new Set([SUDO_PATH, VISUDO_PATH, CRONTAB_PATH, SEALED_BIN]);
32955
33138
  var LOAD_SAMPLE_MS = 5e3;
32956
33139
  var LOAD_WINDOWS_MS = [6e4, 3e5, 9e5];
32957
33140
  var encoder10 = new TextEncoder();
@@ -33080,6 +33263,7 @@ function expandDefaults(defaults2, env) {
33080
33263
  function targetOf(command) {
33081
33264
  return {
33082
33265
  abi: command.abi,
33266
+ command: command.name,
33083
33267
  glue: command.glue,
33084
33268
  wasm: command.wasm,
33085
33269
  argv0: command.argv0,
@@ -33176,6 +33360,8 @@ var Launcher = class {
33176
33360
  orphans = /* @__PURE__ */ new Set();
33177
33361
  described = /* @__PURE__ */ new Map();
33178
33362
  creds = /* @__PURE__ */ new Map();
33363
+ programs = /* @__PURE__ */ new Map();
33364
+ cwdPaths = /* @__PURE__ */ new Map();
33179
33365
  tags = /* @__PURE__ */ new Map();
33180
33366
  sessions = /* @__PURE__ */ new Map();
33181
33367
  tagBases = /* @__PURE__ */ new Map();
@@ -33503,7 +33689,7 @@ var Launcher = class {
33503
33689
  this.refusedOf.set(commands, refused4.map(refusing));
33504
33690
  return commands;
33505
33691
  }).then(
33506
- (commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND).set(VISUDO_COMMAND.name, VISUDO_COMMAND).set(CRONTAB_COMMAND.name, CRONTAB_COMMAND)
33692
+ (commands) => this.refusals(commands).set(SUDO_COMMAND.name, SUDO_COMMAND).set(VISUDO_COMMAND.name, VISUDO_COMMAND).set(CRONTAB_COMMAND.name, CRONTAB_COMMAND).set(SEALED_COMMAND.name, SEALED_COMMAND)
33507
33693
  );
33508
33694
  this.catalogs.set(scope, found);
33509
33695
  }
@@ -34276,6 +34462,9 @@ var Launcher = class {
34276
34462
  if (target.abi === "js" && target.wasm === CRONTAB_PATH) {
34277
34463
  return { abi: "js", glue: CRONTAB_SOURCE, path: CRONTAB_PATH };
34278
34464
  }
34465
+ if (target.abi === "js" && target.wasm === SEALED_BIN) {
34466
+ return { abi: "js", glue: SEALED_SOURCE, path: SEALED_BIN };
34467
+ }
34279
34468
  if (target.abi === "js" && target.wasm === REFUSED_PATH) {
34280
34469
  return { abi: "js", glue: REFUSED_SOURCE, path: REFUSED_PATH };
34281
34470
  }
@@ -34334,6 +34523,7 @@ var Launcher = class {
34334
34523
  argv0: target.argv0,
34335
34524
  exe: target.copy ?? target.glue,
34336
34525
  ...exeId,
34526
+ ...target.command !== void 0 && target.copy === void 0 ? { command: target.command } : {},
34337
34527
  args,
34338
34528
  env,
34339
34529
  cwdSlot
@@ -34450,6 +34640,8 @@ var Launcher = class {
34450
34640
  if (session) this.sessions.set(pid, session);
34451
34641
  if (req.tags?.length) this.tagBases.set(pid, req.tagBase ?? req.tags.length);
34452
34642
  if (req.links !== void 0) this.links.set(pid, req.links);
34643
+ if (req.command !== void 0) this.programs.set(pid, req.command);
34644
+ this.cwdPaths.set(pid, req.cwd);
34453
34645
  }
34454
34646
  async admitted(wasm, fds, start2) {
34455
34647
  for (; ; ) {
@@ -34618,6 +34810,7 @@ var Launcher = class {
34618
34810
  void handle.exited.then(() => {
34619
34811
  this.cwds.release(this.cwdSlots.get(pid));
34620
34812
  this.cwdSlots.delete(pid);
34813
+ this.cwdPaths.delete(pid);
34621
34814
  this.freshModules.forget(pid);
34622
34815
  const spent = handle.times?.();
34623
34816
  if (spent) this.spent.set(pid, spent);
@@ -34651,6 +34844,7 @@ var Launcher = class {
34651
34844
  }
34652
34845
  this.jobs.remove(pid);
34653
34846
  this.described.delete(pid);
34847
+ this.programs.delete(pid);
34654
34848
  const spent = this.spent.get(pid);
34655
34849
  if (spent) {
34656
34850
  this.reapedTimes.utime += spent.utime;
@@ -34950,7 +35144,21 @@ var Launcher = class {
34950
35144
  cwdChdir: (path) => this.chdirHeld(pid, path)
34951
35145
  };
34952
35146
  }
35147
+ async processFacts(pid) {
35148
+ const described2 = this.described.get(pid);
35149
+ const cwd = this.cwdPaths.get(pid);
35150
+ if (!described2 || described2.exe === void 0 || cwd === void 0) return void 0;
35151
+ const real = (path) => physicalPathAsync(this.base, path).catch(() => path);
35152
+ const command = this.programs.get(pid);
35153
+ return {
35154
+ exe: command !== void 0 ? `/usr/bin/${command}` : await real(described2.exe),
35155
+ argv: [...described2.argv],
35156
+ cwd: await real(cwd),
35157
+ tty: described2.tty
35158
+ };
35159
+ }
34953
35160
  async chdirHeld(pid, path) {
35161
+ this.cwdPaths.set(pid, path);
34954
35162
  this.cwds.release(this.cwdSlots.get(pid));
34955
35163
  const slot2 = await this.cwds.hold(path);
34956
35164
  this.cwdSlots.set(pid, slot2);
@@ -35014,6 +35222,7 @@ var Launcher = class {
35014
35222
  wireSealed() {
35015
35223
  this.sealed.tickets.normalise = (path) => physicalPathAsync(this.base, path);
35016
35224
  this.sealed.userOf = async (uid) => (await this.users.lookup(uid))?.name;
35225
+ this.sealed.factsOf = (pid) => this.processFacts(pid);
35017
35226
  }
35018
35227
  embedderBroker(hook) {
35019
35228
  const broker = new EmbedderBroker(