@ai-ecoverse/slicc-kernel 1.136.9 → 1.138.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.
- package/README.md +21 -5
- package/dist/index.js +3 -1
- package/dist/index.js.map +2 -2
- package/dist/kernel-worker.js +675 -168
- package/dist/kernel-worker.js.map +4 -4
- package/dist/meta.js.map +1 -1
- package/dist/node-process-worker.js +58 -5
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +662 -164
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +58 -5
- package/dist/process-worker.js.map +4 -4
- package/dist/testing.js +1 -0
- package/dist/testing.js.map +2 -2
- package/dist/types/fs/notice.d.ts +1 -0
- package/dist/types/fs/opfs.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/kernel/net/dns-stub.d.ts +65 -0
- package/dist/types/kernel/net/resolver.d.ts +4 -1
- package/dist/types/kernel/net/uplink.d.ts +3 -1
- package/dist/types/kernel/proc-info.d.ts +2 -0
- package/dist/types/kernel/socket.d.ts +3 -1
- package/dist/types/launcher.d.ts +2 -0
- package/dist/types/node.d.ts +1 -0
- package/dist/types/process/procfs.d.ts +1 -1
- package/dist/types/process/wasi/wasi-procfs.d.ts +2 -1
- package/dist/types/serve.d.ts +2 -0
- package/dist/types/testing.d.ts +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ TypeScript declarations ship in `dist/types/`, and each entry (`.`, `./node`, `.
|
|
|
33
33
|
| `ca` | `'<metadata>-ca'`, or `'slicc-kernel-ca'` | the IndexedDB database for the proxy's CA (see [Network](#network)); `false` keeps it in memory |
|
|
34
34
|
| `media` | `'<metadata>:media'` | the IndexedDB database for the folder handles of `fsa` drives (see [Removable media](#removable-media-fsa)); `false` keeps them in memory, as `metadata: false` does |
|
|
35
35
|
| `worker` | `new URL('./kernel-worker.js', import.meta.url)` | the kernel worker script |
|
|
36
|
-
| `network` | none | `{ transport, uplink }`: how programs reach the outside world (see [Network](#network)
|
|
36
|
+
| `network` | none | `{ transport, uplink, doh }`: how programs reach the outside world (see [Network](#network), [Uplink](#uplink) and [DNS](#dns)) |
|
|
37
37
|
| `requestDirectory` | none | `() => Promise<FileSystemDirectoryHandle>`, typically `showDirectoryPicker`: how an `fsa` mount gets its folder (see [Removable media](#removable-media-fsa)) |
|
|
38
38
|
| `hostfs` | none | `(source, { readonly }) => Promise<{ url, token, capabilities? } \| { port, token?, capabilities? }>`: a grant for a `hostfs` mount from the local proxy, or a `MessagePort` the page serves the folder on (see [Host folders](#host-folders-hostfs)) |
|
|
39
39
|
| `onMountPending` | none | `({ target, source, insert }) => void`: an `fsa` mount needs a folder; call `insert()` from a user gesture |
|
|
@@ -231,6 +231,8 @@ A command's name is any single path component without whitespace or control char
|
|
|
231
231
|
|
|
232
232
|
**Running a glue by path:** executing a package's Emscripten glue by its path runs the command that names it (the one named like the file, when several share it), even with no `.wasm` beside it. A byte-identical copy of the glue in the same directory, named for one of the package's commands, runs as that command too ([#899](https://github.com/ai-ecoverse/slicc-kernel/issues/899)). wasm-clang ships `bin/clang`, `bin/wasm-ld` and the rest as copies of `bin/llvm`, and emcc executes `$LLVM_ROOT/clang`. The caller must be able to read the copy. A copy elsewhere, or a same-named file whose bytes differ, is not the command. The comparison is cached by path, size, mtime and inode.
|
|
233
233
|
|
|
234
|
+
**A `.wasm` run by path:** a WebAssembly file that no package command owns, executed by its path (for example a venv's copied interpreter), runs as a WASI program with `argv[0]` as the caller passed it, as `execve` does: `/tmp/v/bin/python` or `./python`, or `python` when found through `PATH` (#1264). Earlier kernels gave it the file's base name without `.wasm`. Its `/proc/self/exe` names the file.
|
|
235
|
+
|
|
234
236
|
**Variants by environment** ([homescoop#357](https://github.com/ai-ecoverse/homescoop/issues/357)): one package can carry more than one build of a wasm command and choose between them by the environment. `"variants": [{ "env": { "PYTHON_FORK": "true" }, "wasm": "bin/python-fork.wasm" }]` on a command entry (Emscripten or WASI) is matched in order against the environment the new process gets, at spawn and at exec. Every key must equal its value exactly, and the first match runs its `wasm`; no match runs the command's own `wasm`. This holds however the kernel reaches the entry: by name, by its path (`/usr/bin/python3`, as `sys.executable` gives it to `subprocess` and `multiprocessing`), or through a symlink to that path (a venv's `bin/python`). So `PYTHON_FORK=true python3`, `PYTHON_FORK=true exec /usr/bin/python3` and a child that inherits `PYTHON_FORK=true` all get the variant, and `PYTHON_FORK=1` doesn't. Commands of one package that share a build (`python`, `python3`, `pip`, `pip3`) each carry the same `variants`. A variant replaces only the module: the `abi`, the glue, `env`, `argv0`, `argv0Path` and the `/usr/bin` entry stay the command's. For an Emscripten command that means a variant swaps only the `.wasm` and keeps the command's JS glue, so it must be built against that same glue; a WASI or WASIX command has no glue, so there's no such constraint. Only the chosen module is read, checked and compiled, and compiled modules are cached by its path. A read or `stat` of `/usr/bin/<name>` still shows the default `wasm`. A `variants` that isn't an array, or has an entry without an `env` object of strings and a `wasm` inside the package, is ignored as a whole, with one warning per command, and the command runs its `wasm`. An older kernel ignores the field.
|
|
235
237
|
|
|
236
238
|
A package can also name a command that runs files by their suffix, as `binfmt_misc` does: `"slicc": { "binfmt": { ".jsh": "jsh" } }`. Executing a file that has no `#!` line and is not a wasm module, whose name ends in a mapped suffix, runs that command with the file's path as invoked and the arguments after it, as a `#!` line naming the command would; a `#!` line still wins. The longest matching suffix wins, the whole name is never a suffix (`.jsh` alone is not one), and of two packages that map one suffix, the one whose name sorts first wins, as for commands; the kernel warns once (`console.warn`) when another package maps that suffix to a different command. A suffix starts with a dot and the command must be a name the catalog resolves when the file runs.
|
|
@@ -594,6 +596,15 @@ WASIX programs get `AF_INET`/`AF_INET6` `SOCK_DGRAM` sockets, and `AF_UNIX` ones
|
|
|
594
596
|
- **Flow control:** a receiver queue holds 512 messages or 256 KiB. A full one blocks a sender, or fails a non-blocking send with `EAGAIN`, as on Linux.
|
|
595
597
|
- **Not supported:** passing descriptors or credentials (`SCM_RIGHTS`, `SCM_CREDENTIALS`) over datagrams, and `AF_UNIX` datagrams for Emscripten and JS programs.
|
|
596
598
|
|
|
599
|
+
#### DNS
|
|
600
|
+
|
|
601
|
+
The kernel answers DNS itself, so DNS clients (BIND's `dig`, `host` and `nslookup`) see the same names as `getaddrinfo` (#1244).
|
|
602
|
+
- **Where it listens:** UDP and TCP port 53 on the kernel's loopback; `/etc/resolv.conf` names `127.0.0.53`, as systemd-resolved does, but any loopback address reaches it. TCP takes several queries on one connection (2-byte length framing), at most 64 connections at once, each closed after 10 s without a query or when an answer can't be written within 10 s (a client that stops reading). Over UDP at most 64 questions are answered at once; while that many wait (on the forwarder, say), more are dropped, as by a full receive queue. Port 53 is the kernel's, as the proxy's and CDP's ports are: a program's `bind` to it, root's included, is `EADDRINUSE` on UDP and TCP, so no program can answer other users' questions, and `kernel.dial` refuses it.
|
|
603
|
+
- **What it answers itself,** from the [kernel's resolver](#node-name): `A` and `AAAA` for `/etc/hosts` names, the node name, `localhost` and `*.localhost`, `host.slicc.internal`, and the [uplink](#uplink)'s names (a tray's `<device>.slicc.internal`, MagicDNS); `PTR` from `/etc/hosts` and the built-in entries (`127.0.0.0/8` is `localhost`). TTLs are 30 s. Answers for `localhost` and `slicc.internal` are authoritative (`aa`), and a name there that nothing knows is `NXDOMAIN`. Other types for a name the kernel knows (`MX` for `host.slicc.internal`) get an empty answer.
|
|
604
|
+
- **Everything else** (other names, other types, other classes, address literals, an `AAAA` the uplink has no answer to, a name whose uplink lookup failed) goes to a forwarder, and there is none by default: those questions are `SERVFAIL`, and nothing leaves the kernel. An [uplink](#uplink) whose `nameserver` names its DNS server (an address it routes, such as Tailscale's `100.100.100.100`) gets them over its own TCP, length-framed as RFC 1035 has it, so they go where that uplink and its exit send them. Without one, `network: { doh }` names a DNS over HTTPS endpoint (RFC 8484, `POST`, id 0) that the [transport](#network) is asked; `doh: false` or none is no forwarder. A forwarder's answer must repeat the question (name, type and class) or it is `SERVFAIL`, as are a failed connection, an HTTP error or a non-`application/dns-message` answer, and no answer within 5 s. The answer goes back as the forwarder gave it, with the query's id. Over UDP, an answer longer than 512 bytes (or the query's EDNS size, at most 4096) comes back truncated (`tc`, no records), so the client asks again over TCP.
|
|
605
|
+
- **What a forwarder sees:** the question's bytes as the program wrote them (id set to 0), EDNS options included, so a program can carry data to the forwarder in DNS names, as anywhere DNS is resolved. That gives no process more than it has: every process may already use the proxy and the transport, and connect to what the uplink routes; the kernel has no per-user network policy that the stub would bypass.
|
|
606
|
+
- **`/etc/resolv.conf`:** at boot, the kernel writes `nameserver 127.0.0.53` and `options edns0` there when there is no such file (a symlink counts as one), owned by root with mode `0644`. A file root wrote stays, and so does none at all when `/etc/resolv.conf` is a [protected path](#protected-paths). One that root removes comes back at the next boot. It has no `search` line, because musl's `getaddrinfo` (WASIX programs) would then try every short name in that domain first.
|
|
607
|
+
|
|
597
608
|
### Node name
|
|
598
609
|
|
|
599
610
|
The kernel has one node name, `slicc` unless `createKernel({ hostname })` (or `createNodeKernel`) names another; it must be a valid host name, and not `localhost`, a `*.localhost` name, `host.slicc.internal`, `wasmer.sh` or an IPv4 address, or `createKernel` rejects. `no_proxy` lists it (and `emscripten`), so HTTP clients reach the kernel's own servers by it directly.
|
|
@@ -609,6 +620,7 @@ The kernel has one node name, `slicc` unless `createKernel({ hostname })` (or `c
|
|
|
609
620
|
|
|
610
621
|
- `traits`: `{ tcp: true, udp, ipv6 }`. Without `ipv6: true`, the kernel asks only for, and keeps only, IPv4 addresses. With `udp: true` and a `udp` function, routed datagrams go through it.
|
|
611
622
|
- `routes`: the first route table, `{ prefixes, exit }`, which `kernel.setRoutes` replaces when it changes. The kernel matches it itself, so a destination outside it costs no round trip.
|
|
623
|
+
- `nameserver` (optional): an address the uplink routes where a DNS server answers on TCP port 53 (Tailscale: `100.100.100.100`). The kernel's [DNS](#dns) stub forwards the questions it can't answer there.
|
|
612
624
|
- `resolve(name, family, signal)`: resolves with the name's addresses, as a list or as `{ addresses, ttl }` (seconds). `[]` means the name is not the uplink's. `family` is `4`, `6` or `0` (either), and `signal` aborts when the kernel stops waiting.
|
|
613
625
|
- `dial({ network: 'tcp', host, port, signal })`: opens a TCP connection and resolves with `{ localAddr, remoteAddr, read(), write(bytes), closeWrite(), close() }`.
|
|
614
626
|
- `read()` resolves with the next bytes, or `null` at the end.
|
|
@@ -648,7 +660,7 @@ An uplink connection is an ordinary socket fd: it survives `dup`, `fork` and `ex
|
|
|
648
660
|
|
|
649
661
|
In the browser, the page serves the uplink to the kernel worker over the same port as the transport, with buffers copied and transferred. `terminate()` closes every connection and cancels whatever is pending, and the page refuses to dial loopback, `10.0.2.2` or reserved addresses itself as well.
|
|
650
662
|
|
|
651
|
-
`@ai-ecoverse/slicc-kernel/testing` is test support, kept out of the main entry so it never reaches a page bundle. It runs with the Node kernel and needs no wasm of its own. `fakeUplink({ names, routes, ipv6, peers, address })` is an uplink to pass as `network.uplink`:
|
|
663
|
+
`@ai-ecoverse/slicc-kernel/testing` is test support, kept out of the main entry so it never reaches a page bundle. It runs with the Node kernel and needs no wasm of its own. `fakeUplink({ names, routes, ipv6, peers, address, nameserver })` is an uplink to pass as `network.uplink`:
|
|
652
664
|
- **Names:** `resolve` answers from `names` (a list or `{ addresses, ttl }` per name) and records each question in `asked`.
|
|
653
665
|
- **Routes:** `routes` is the first table; `kernel.setRoutes` changes it as for a real uplink.
|
|
654
666
|
- **Dials:** each dial is recorded in `dialled`. `localAddr` is `address` (default `100.100.100.100`) with a fresh port. `peers`, keyed `'host:port'` (or `'[v6]:port'`), decides how a dial ends:
|
|
@@ -1107,7 +1119,7 @@ A character device (`0666`), there in every `users` mode, through which programs
|
|
|
1107
1119
|
|
|
1108
1120
|
### `/proc`
|
|
1109
1121
|
|
|
1110
|
-
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. `/proc/version` names the kernel, Linux-shaped, with the slicc-kernel version that built it: `Linux version 6.0.0-slicc (slicc-kernel <version>) #1 SMP PREEMPT_DYNAMIC`. `/proc/sys/kernel/osrelease` is `6.0.0-slicc` and `/proc/sys/kernel/ostype` is `Linux`. The version is the one npm publishes, set when the package is built (`0.0.0-dev` when run from the sources). A JS program reads it as `ctx.kernel.version`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`; a process with a tag, see `kernel.run`, also has `Tag:\t<tag>` after `Gid:`, shown to root and to the same uid only, with newlines, tabs and carriage returns written `\n`, `\t` and `\r`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times are measured per process, its threads and the images it exec'd included: time a worker spends waiting for the kernel's answer is system time (at most 20 ms of each call; the rest of a longer one, such as a write into a full pipe, counts as blocked), time it spends blocked (a read, a `poll` or `select` that waits, a sleep, `wait`, a futex, while stopped) is neither, from the moment the call actually starts to wait (a call that could wait but answers at once, such as a `select` with a zero timeout or a ready descriptor, a `waitpid` with `WNOHANG`, or a futex wait whose value has already changed, is system time), and the rest is user time; a process's times stop when it ends, a call still in flight then adds nothing, so a loop that never calls the kernel shows as user time while it runs; they show in `/proc/<pid>/stat`, `ps` and `ps()`, and the times of the children a process has waited for (theirs and their own waited-for children's, as POSIX counts them) show as its `cutime` and `cstime` in `/proc/<pid>/stat`; a stopped process is `T` there, in `/proc/<pid>/status` (`T (stopped)`) and in `ps()`; a WASI or WASIX program's `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID` (`clock_time_get`) count only the time its thread runs, its own waits and blocked kernel calls left out (a sleep is not CPU time), per thread for now, so a threaded program's process clock is its calling thread's; and `/proc/stat` counts every process of the kernel, the reaped ones included, and `/proc/loadavg` averages the processes not blocked, sampled every 5 seconds. A JS program's time isn't measured yet. `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) is a placeholder. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). WASI and WASIX programs also have `/proc/<pid>/task/<tid>/stat` for each of a process's threads (the main thread is tid 1), whose state is `S` while the thread is blocked in the kernel (a `sem_wait`, a blocking read, a sleep, a futex) and `R` otherwise (`T` and `Z` as for the process). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
|
|
1122
|
+
Every process of the kernel has `/proc/<pid>/` with `cmdline`, `comm`, `stat`, `statm` and `status`, in the formats procps reads, and `exe`, a link to the file the process was started from: the glue or `.wasm` that ran, as resolved (a copy run by its own path names the copy, a symlink its target) (#1264). As on Linux without ptrace access, reading another process's `exe` link is `EACCES` unless the reader is root or its uid and gid match all of the target's real, effective and saved ids; `cmdline` and `status` stay readable, and `/proc/self` is a link to the `/proc/<pid>` of the process that reads it, as on Linux (`readlink /proc/self` is its pid, the one it was exec'd under included). They come from the kernel's process table when they are opened, so a terminal sees the processes of every client. A process that exits while a program reads the `/proc` directory doesn't fail or cut short the listing: its pid is still listed, and opening its files fails with `ENOENT`, which `ps`, `pgrep` and `pidof` skip, as on Linux; an exec'd program shows under the pid its parent knows. `/proc/uptime`, `/proc/loadavg`, `/proc/stat` and `/proc/meminfo` are there too, and `/proc/mounts`. `/proc/version` names the kernel, Linux-shaped, with the slicc-kernel version that built it: `Linux version 6.0.0-slicc (slicc-kernel <version>) #1 SMP PREEMPT_DYNAMIC`. `/proc/sys/kernel/osrelease` is `6.0.0-slicc` and `/proc/sys/kernel/ostype` is `Linux`. The version is the one npm publishes, set when the package is built (`0.0.0-dev` when run from the sources). A JS program reads it as `ctx.kernel.version`. A process with no parent in the kernel (one a page, a client or the Node entry started) has parent 1, as children of init do, in `getppid()`, `/proc/<pid>/stat` and `ps` alike, and so does a process whose parent has ended, waited for or not (`getppid()` asks the kernel each time; a shell's `$PPID` keeps the value it started with, as on Linux). Pids, parents, process groups, sessions, command lines, terminals, start and boot times are real, and so are the credentials in `status` (`Uid:`, `Gid:`, `Groups:`; a process with a tag, see `kernel.run`, also has `Tag:\t<tag>` after `Gid:`, shown to root and to the same uid only, with newlines, tabs and carriage returns written `\n`, `\t` and `\r`) and each process's memory: the size of its wasm memory (`VSZ` and `RSS` alike, as wasm has no paging), which `/proc/meminfo` counts as used. CPU times are measured per process, its threads and the images it exec'd included: time a worker spends waiting for the kernel's answer is system time (at most 20 ms of each call; the rest of a longer one, such as a write into a full pipe, counts as blocked), time it spends blocked (a read, a `poll` or `select` that waits, a sleep, `wait`, a futex, while stopped) is neither, from the moment the call actually starts to wait (a call that could wait but answers at once, such as a `select` with a zero timeout or a ready descriptor, a `waitpid` with `WNOHANG`, or a futex wait whose value has already changed, is system time), and the rest is user time; a process's times stop when it ends, a call still in flight then adds nothing, so a loop that never calls the kernel shows as user time while it runs; they show in `/proc/<pid>/stat`, `ps` and `ps()`, and the times of the children a process has waited for (theirs and their own waited-for children's, as POSIX counts them) show as its `cutime` and `cstime` in `/proc/<pid>/stat`; a stopped process is `T` there, in `/proc/<pid>/status` (`T (stopped)`) and in `ps()`; a WASI or WASIX program's `CLOCK_PROCESS_CPUTIME_ID` and `CLOCK_THREAD_CPUTIME_ID` (`clock_time_get`) count only the time its thread runs, its own waits and blocked kernel calls left out (a sleep is not CPU time), per thread for now, so a threaded program's process clock is its calling thread's; and `/proc/stat` counts every process of the kernel, the reaped ones included, and `/proc/loadavg` averages the processes not blocked, sampled every 5 seconds. A JS program's time isn't measured yet. `MemTotal` (from `navigator.deviceMemory`, else 4 GiB) is a placeholder. A process that isn't root sees only its own user's processes (see [Users](#users)). WASI and WASIX programs see the same `/proc`, read-only (opening a file in it for writing, or linking one of its files elsewhere, fails with `EROFS`), from every thread, also through a symlink that points into it (where every change, `mkdir`, `rename`, `unlink`, `chmod`, a hard link from it or into it, and so on, fails too: with `EROFS` when the root file system has a physical `/proc`, which stays hidden and untouched, and otherwise with the `ENOENT` of the missing directory, since that check is only paid for when a physical `/proc` exists; with one, a symlink whose target passes `..` after a file or a missing name, such as `/f/../proc`, fails with `ENOTDIR` or `ENOENT`, as on Linux), and `/` lists it, so a WASI program gets a preopen for it; `/proc/self` is the program's own pid there too (the pid an exec'd program took over). WASI and WASIX programs also have `/proc/<pid>/task/<tid>/stat` for each of a process's threads (the main thread is tid 1), whose state is `S` while the thread is blocked in the kernel (a `sem_wait`, a blocking read, a sleep, a futex) and `R` otherwise (`T` and `Z` as for the process). JS programs read the same `/proc` through `ctx.fs` and `ctx.open` (see [JS programs](#js-programs)). Neither has `/proc/<pid>/fd` yet.
|
|
1111
1123
|
|
|
1112
1124
|
The kernel writes `/etc/passwd` and `/etc/group` when it starts (see [Users](#users)). While the root has none of them, or no `/etc/mtab`, reading them gets per-process ones: root is uid 0 and the realm user, uid 1000, is named after `USER`, or `web_user` (Emscripten's default) without one, so it matches `$USER`, with `HOME` as its home, so `id`, `whoami`, `ls -l` and `ps` show names. Nothing is written to OPFS, and a real file there wins.
|
|
1113
1125
|
|
|
@@ -1137,7 +1149,7 @@ OPFS stays authoritative: an entry is consulted only for a path that exists in O
|
|
|
1137
1149
|
|
|
1138
1150
|
**Stamping files written to OPFS:** `import { writeMetadata } from '@ai-ecoverse/slicc-kernel/meta'` and `await writeMetadata(root, [{ path, mode?, uid?, gid? }], { metadata?, indexedDB? })` is the supported way for an embedder to give files it wrote straight to OPFS a mode and an owner, also while no kernel runs (an installer before the first boot). `root` is the directory handle that becomes `/`, each `path` is absolute (`EINVAL` otherwise) and must exist there (`ENOENT` otherwise), `mode` is the permission bits (`0` to `0o7777`), and `uid` and `gid` come together or not at all (`EINVAL` otherwise). Entries for the same path (after normalization) are merged in order, a later field winning. All entries are written in one IndexedDB transaction, to the database a kernel on that root reads (`metadata`, default `slicc-kernel`), keeping what else a record holds and setting its change time (`ctime`) to now, and a running kernel on the same origin drops what it cached of them. The record format is the kernel's own and changes with it, so use this rather than writing the database. The kernel gives its layout paths (see [Permissions](#permissions)) an owner and mode only when they have no owner yet, so a path stamped here with `uid` and `gid` keeps them.
|
|
1139
1151
|
|
|
1140
|
-
**Telling running kernels about files written to OPFS:** a writer outside the kernel (an installer worker, a service worker, the page) that changes files on a root a kernel may be running on calls `import { postFsNotice } from '@ai-ecoverse/slicc-kernel/meta'` and `postFsNotice({ paths?, trees? }, { metadata? })` **after** each write has committed: after `close()` of its writable, `removeEntry` or `move()` has resolved. `paths` are absolute paths written, created or removed. List a created or removed name's parent directory too, so listings refresh. `trees` are absolute directories whose whole subtree changed, for a recursive replace or a sweep. The notice goes on the BroadcastChannel `slicc-kernel-dirs:<metadata>` (default `slicc-kernel-dirs:slicc-kernel`), the one kernels on that root already use between themselves, as `{ paths, trees }`. One notice per batch of writes is enough, if it's posted after the last of them. A kernel that receives it drops its cached lookups, handles and misses for those paths (for a tree, everything under it), tells its open files to reread, and publishes the paths to its programs' caches. A tree marks every path changed for them, so a running program's next look at any path goes to the kernel. **The residual window:** a reader can still see the old state between the write's commit and the notice's delivery (a message, usually well under a millisecond, but not bounded). A writer that runs while no kernel is up needs no notice: a kernel's caches start empty when it boots. **Without a notice** a kernel that is alone on its root keeps what it cached: a running program's stats of a path stay as they were for as long as the program runs, and the kernel's own directory handles for up to a second. So a writer that doesn't post notices must not change files a running kernel uses ([#1156](https://github.com/ai-ecoverse/slicc-kernel/issues/1156)).
|
|
1152
|
+
**Telling running kernels about files written to OPFS:** a writer outside the kernel (an installer worker, a service worker, the page) that changes files on a root a kernel may be running on calls `import { postFsNotice } from '@ai-ecoverse/slicc-kernel/meta'` and `postFsNotice({ paths?, trees? }, { metadata? })` **after** each write has committed: after `close()` of its writable, `removeEntry` or `move()` has resolved. `paths` are absolute paths written, created or removed. List a created or removed name's parent directory too, so listings refresh. `trees` are absolute directories whose whole subtree changed, for a recursive replace or a sweep. The notice goes on the BroadcastChannel `slicc-kernel-dirs:<metadata>` (default `slicc-kernel-dirs:slicc-kernel`), the one kernels on that root already use between themselves, as `{ paths, trees }`. One notice per batch of writes is enough, if it's posted after the last of them. A kernel that receives it drops its cached lookups, handles and misses for those paths (for a tree, everything under it), tells its open files to reread, and publishes the paths to its programs' caches. A tree marks every path changed for them, so a running program's next look at any path goes to the kernel. **The residual window:** a reader can still see the old state between the write's commit and the notice's delivery (a message, usually well under a millisecond, but not bounded). A writer that runs while no kernel is up needs no notice: a kernel's caches start empty when it boots. A notice for a path that exists as a regular file, listed in `paths` (a `trees` entry refreshes caches but stamps no times, since a tree can hold thousands of files), also gives it a new modification time, before running programs are told of the change: the notice's arrival, with the change time along, unless its modification time is already later (an explicit future one is kept, with its change time), as a write does on Linux, recorded once per burst of notices for the same file ([#1275](https://github.com/ai-ecoverse/slicc-kernel/issues/1275)). That matters because Chrome can keep a file's OPFS `lastModified` across an append through `createWritable({ keepExistingData: true })`, and the kernel reports `lastModified` for files it didn't write, so `-nt` and make wouldn't see the change. A notice without a real change still moves the time, so a make may rebuild once. An outside write that posts no notice keeps OPFS's time. Notices between kernels (the array form they post for their own writes) don't stamp: those kernels record their times themselves. That's part of the channel's contract: kernels post their own change notices on the dirs channel as a plain array of paths, and only the `{ paths, trees }` object form that `postFsNotice` sends counts as an outside writer's. A kernel must keep posting arrays there, which `fs-notice-page.test.mjs`'s peer row checks. **Without a notice** a kernel that is alone on its root keeps what it cached: a running program's stats of a path stay as they were for as long as the program runs, and the kernel's own directory handles for up to a second. So a writer that doesn't post notices must not change files a running kernel uses ([#1156](https://github.com/ai-ecoverse/slicc-kernel/issues/1156)).
|
|
1141
1153
|
|
|
1142
1154
|
The database is `slicc-kernel` (or the `metadata` option), schema version 1, with one object store, `entries`, keyed by absolute path (`keyPath: 'path'`) and an index `dir` on the parent directory of symlinks. A record looks like `{ path, mode?, atimeMs?, mtimeMs?, mtimeFor?, ctimeMs?, ino?, link?, dir?, uid?, gid?, heir?, writes?, epoch? }`, where `mtimeFor` is the OPFS `lastModified` an explicit `mtimeMs` belongs to, `uid`, `gid` and `heir` (`[uid, gid]`) are the owners of [File owners](#file-owners), `writes` counts the writes kernels with `integrity` made to a protected file (see [Protected paths](#protected-paths)), whose `lastModified` OPFS leaves alone for a write through a sync access handle, and `epoch`, on the entry of `/`, counts their renames and symlinks at or above a protected path. A future schema bumps the version and migrates in `onupgradeneeded`; an older kernel then closes its connection. The sidecar is keyed by path from the `root` handle, so use a different `metadata` name for each distinct root.
|
|
1143
1155
|
|
|
@@ -1265,7 +1277,7 @@ The tools in `tools/cert-review/`:
|
|
|
1265
1277
|
- and record, which re-hashes the candidate's artifact against the pack job's sha256.
|
|
1266
1278
|
- **The verdict** goes into the commit status on the pull request's head and a comment on the pull request. Certified is green. A FAIL is red with the line, and the pull request needs a fix. **INCOMPLETE** (a job that didn't succeed, no verdict, a suite without results) is red with `re-run the failed jobs`, never a silent pass. Main moving on after a review doesn't trigger another one; a push does.
|
|
1267
1279
|
- **Merge groups only look up.** On `merge_group`, the job lists the group's pull requests (its merge commits) and requires, for each one, a green status on the head being merged, or no behaviour change. Otherwise it's red, naming the pull request. Then it adds the group's downstream verdict. The group's `ci / test` covers the combined tree.
|
|
1268
|
-
- **Downstream, once per merge group:** the `merge group pack` job packs the group's tree with `main`'s tools (`x.y.z-group.<run>`), and `downstream bios canary` calls slicc-bios's `canary.yml` at a pinned full sha (`
|
|
1280
|
+
- **Downstream, once per merge group:** the `merge group pack` job packs the group's tree with `main`'s tools (`x.y.z-group.<run>`), and `downstream bios canary` calls slicc-bios's `canary.yml` at a pinned full sha (`fcaa9a23…`, never a tag) with that tarball, bound by artifact id, digest and sha256, on the `sweep`, `guard-prompt`, `guard-lagging`, `k4-upgrade`, `k4-agent-modes` and `link-serve` shards, each run once, on the runner the `canary runner` job picks: `.github/runner.mjs`'s `decide()` from `main`, as `ci.yml` uses it, gives `vars.CERT_CANARY_RUNNER` only to a merge group whose entries are all same-repo PRs, and `ubuntu-latest` otherwise, without the variable, or if the decider fails (bios honours a runner only for merge groups and pushes to main in ai-ecoverse, so rolling back is unsetting the variable). The verdict line names the shards, or their count past 32 characters (`6 shards`), so a status description (140 characters) keeps the reason. `downstream.mjs` judges its result (`downstreamOf` in `lib.mjs`), and the lookup requires it. The `merge group` job reads that result as `result.json` from the canary's own artifact, downloaded by the call's `artifact-id` with `digest-mismatch: error`, not from the `result` output: that output can be up to 512 KB, and one environment variable holds 128 KB, so passing it that way stopped the step from starting at all (`Argument list too long`, runs 39166647175 and 39168823799). A missing or unreadable artifact leaves no result, so the group is INCOMPLETE:
|
|
1269
1281
|
- **PASS:** the candidate passes, `downstream: PASS bios … N pass`;
|
|
1270
1282
|
- **FAIL:** a regression, or a failing test (by the counts, even without its rows) is **not certified**, and the group is red;
|
|
1271
1283
|
- **INCOMPLETE** blocks too: no result, a result from another bios than the pinned one, a missing shard, a shard that exited without a failing test, a refused candidate, 0 tests run, or no test passed;
|
|
@@ -1278,6 +1290,10 @@ Not part of the run yet: the rest of bios's setup (the `rest` shard, the upgrade
|
|
|
1278
1290
|
|
|
1279
1291
|
The WASIX fixtures of `test/integration/epoll.test.mjs` (#674), under `test/integration/fixtures/`, are committed with their sources, build scripts and built wasm.
|
|
1280
1292
|
|
|
1293
|
+
### `dnstest`
|
|
1294
|
+
|
|
1295
|
+
`test/integration/fixtures/dnstest`: a C WASIX program, built by `build.sh` with `wasixcc`, for the [DNS](#dns) rows (#1244). `dnstest udp|tcp|tcp2 <server> <name> <type> [edns]` sends one question to `<server>:53` (`tcp2` sends it twice on one connection), then prints the reply's id check, rcode and `aa`/`tc`/`ra` flags and section counts, and each answer as `<name> <ttl> <type> <data>`. `dnstest flood <server> <name> <type> <n>` sends `n` questions over UDP without waiting, `dnstest bind` tries to bind `127.0.0.1:53` over UDP and TCP and prints each result, and `dnstest stall <server> <name> <type> <s>` writes questions over TCP until the connection is full, waits `s` seconds, then counts the answers it can read (`closed` when the stub hung up before answering them all).
|
|
1296
|
+
|
|
1281
1297
|
### `pg-fixture` with `plus`
|
|
1282
1298
|
|
|
1283
1299
|
`test/integration/pg-fixture.mjs`, `pgFixture(t, cert, password, { plus: true })`: pg-gateway's SCRAM-SHA-256 server, which offers `SCRAM-SHA-256-PLUS` too and refuses as PostgreSQL's `auth-scram.c` does (jsh#531):
|
package/dist/index.js
CHANGED
|
@@ -2278,7 +2278,8 @@ function dialer(call) {
|
|
|
2278
2278
|
function uplinkInit(uplink) {
|
|
2279
2279
|
if (!uplink) return {};
|
|
2280
2280
|
const traits = { tcp: true, udp: carriesUdp(uplink), ipv6: uplink.traits.ipv6 === true };
|
|
2281
|
-
|
|
2281
|
+
const nameserver = typeof uplink.nameserver === "string" ? { nameserver: uplink.nameserver } : {};
|
|
2282
|
+
return { uplink: { traits, ...uplink.routes ? { routes: uplink.routes } : {}, ...nameserver } };
|
|
2282
2283
|
}
|
|
2283
2284
|
function initCall(options, transport) {
|
|
2284
2285
|
return {
|
|
@@ -2292,6 +2293,7 @@ function initCall(options, transport) {
|
|
|
2292
2293
|
...options.ca !== void 0 ? { ca: options.ca } : {},
|
|
2293
2294
|
...transport ? { transport: transport.traits } : {},
|
|
2294
2295
|
...uplinkInit(options.network?.uplink),
|
|
2296
|
+
...options.network?.doh !== void 0 ? { doh: options.network.doh } : {},
|
|
2295
2297
|
...options.hostfs ? { hostfs: true } : {},
|
|
2296
2298
|
...options.cdp ? { cdp: true } : {},
|
|
2297
2299
|
...facadesInit(options.facades),
|