@ai-ecoverse/slicc-kernel 1.115.0 → 1.116.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.
package/README.md CHANGED
@@ -377,6 +377,15 @@ The module's default export (or its `main` export) is called with a context and
377
377
  - `sudo(args)`, what the [`sudo`](#sudo) command calls: `args` are its arguments after `sudo`, and it resolves with `{ pid }`, the command the kernel started as the program's child, to `wait` for; `{ run }`, an argv to run as the caller (with `users: 'off'`); `{ list }`, the text of `sudo -l`; or `{ error, errno }`, the message to print;
378
378
  - `crontab({ action, user?, text? })`, what the [`crontab`](#cron) command calls: `action` is `'list'`, `'remove'` or `'install'` (with the table as `text`), on the caller's own table or, for an effective uid of 0, on `user`'s. It resolves with `{ text }` (the table for `list`, `''` otherwise) or `{ error }`, the message to print. A malformed call is `EINVAL`;
379
379
  - `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), and `close()`. The loopback is IPv4 (`127.0.0.1` by default, or `0.0.0.0`), so an IPv6 host such as `::1` fails with `EADDRNOTAVAIL` for both `listen` and `connect`, and binding and connecting fail as they would for a WASIX program (`EADDRINUSE`, `ECONNREFUSED`, `EADDRNOTAVAIL`). A blocked accept, read or write waits without holding anything in the kernel, so aborting it or closing the socket leaves nothing behind. A write to a connection the peer closed fails with `EPIPE` and no `SIGPIPE`;
380
+ - `net.connect({ host, port, tls: { servername, alpn, rejectUnauthorized, ca } })` ([#914](https://github.com/ai-ecoverse/slicc-kernel/issues/914)) is **TLS through the transport, not a TLS handshake.**
381
+ - **What it does:** there's no handshake in the kernel. The connection carries plain HTTP/1.1 (and WebSocket upgrades) to the realm proxy's HTTP path for the origin `https://<servername or host>[:port]`, exactly as a `CONNECT` through `127.0.0.1:3128` does after the kernel terminates its TLS. The transport (the page's `fetch` or WebSocket, or the uplink's) makes the real TLS connection and verifies it. So `https.request` over a socket, keep-alive agents, `agent-base` and `wss://` clients work with the transport's semantics. That includes sealed placeholders in headers, which reach the transport exactly as from `ctx.fetch`.
382
+ - **The `tls` property:** the connection's `tls` is `{ protocol: 'transport', alpnProtocol: 'http/1.1', authorized: true, servername }`. `authorized` means the transport verified the server. There's no peer certificate chain and no TLS version of the kernel's own, so don't read it as a real handshake's result.
383
+ - **Options:** the transport always verifies, so `rejectUnauthorized: false` is accepted and changes nothing. A custom `ca` is refused (`ENOTSUP`), and so is an `alpn` list without `http/1.1` (`EPROTONOSUPPORT`). The kernel's own loopback (`localhost`, `*.localhost`, `127/8`, `0.0.0.0`, `::1`, as `servername` or as `host`) is `ENOTSUP` at connect, as `ctx.fetch` refuses `https:` and `wss:` there. Connect in plain text to a kernel server. `host.slicc.internal` goes to the transport under its policy, as for `ctx.fetch`.
384
+ - **Non-HTTP protocols aren't carried:**
385
+ - the well-known TLS ports of other protocols (465 SMTPS, 993 IMAPS, 995 POP3S, 636 LDAPS, 853, 8883 MQTT, 6697 IRC, 5671 AMQP and the like) are refused at `connect` with `EPROTONOSUPPORT`;
386
+ - non-HTTP first bytes on any other port get a `400` and a close;
387
+ - a server-speaks-first protocol on another port (the client sends nothing) waits for the proxy's 120 s idle limit.
388
+ - A real client handshake for raw TLS protocols, over an uplink that routes TCP, is [#922](https://github.com/ai-ecoverse/slicc-kernel/issues/922).
380
389
  - `net.resolve(name, { family, signal })` asks the kernel's resolver, which WASIX `resolve` and Emscripten `getaddrinfo` use too: entries in a real `/etc/hosts`, the kernel's own names (`localhost`, the node name, `host.slicc.internal`) and then the uplink's DNS ([#913](https://github.com/ai-ecoverse/slicc-kernel/issues/913)). It answers the addresses, in the resolver's order, for `family` `4`, `6` or `0` (both, IPv4 first; the default). A name with no answer rejects with a `JsCallError` of code `ENOTFOUND`, as Node's `dns.lookup` does, another family with `EAI_FAMILY`, and an aborted `signal` with its reason.
381
390
  - `websocket(url, { protocols, headers, signal })`, a WebSocket over the kernel's transport (its `websocket`, so `fetchTransport({ webSocket: true })` in a browser), shaped like `WebSocketStream`. It resolves once the socket is open, with `url`, the `protocol` the far end chose (always one the program offered: a transport that hands back a socket with another fails the open with `EPROTO`, while browsers and Node's `WebSocket`, which `fetchTransport()` uses, fail such a handshake themselves without saying why, so the open fails with `ECONNREFUSED`), a `readable` of messages (`string` for text, `Uint8Array` for binary), a `writable` for sending them, `closed` (the `{ code, reason }` of the close, from either side, whether or not the program reads) and `close({ code, reason })`. An `http(s):` URL is taken as `ws(s):`. Sending waits while more than 1 MiB is buffered. A signal aborts the open, and after it the socket. A transport without WebSockets rejects with a `TypeError` whose `cause.code` is `ENOTSUP`. One that cannot connect rejects with the `code` its error carries (an errno, such as `EAI_NONAME`, `ECONNREFUSED`, `ETIMEDOUT` or `EPROTO`), else `ECONNREFUSED`. `ws://host.slicc.internal:port` reaches the real machine's loopback, as it does through the proxy;
382
391
  - `fetch(input, init)`, the web's `fetch` over the kernel's transport, the one the proxy uses, so the embedder's transport, its WebSocket and proxy settings and the kernel's routes apply. The response body streams as the program reads it. On a transport with `traits.streamingBodies` (below), a `ReadableStream` body (with `duplex: 'half'`, which `Request` requires) streams to it as the program produces it, with no size limit of the kernel's, and a redirect that would send it again (a 307 or 308) rejects with a `TypeError`, as the fetch standard has it. Other bodies (a string, a buffer, a `Blob`, a `FormData`) can be sent again, so they go whole even there. Otherwise the request body is read whole first, and one larger than the transport's `maxRequestBody` is answered `413`, as through the proxy. A signal aborts the request at any stage. Redirects follow the fetch rules: at most 20, a 301 or 302 after a `POST` and a 303 turn into a `GET` without a body, `Authorization` is dropped across origins, and `redirect: 'manual'` returns the redirect itself. On a transport that follows redirects on its own (`fetchTransport()` in a browser) only `'follow'` works: `'manual'` and `'error'` reject with a `TypeError` whose `cause.code` is `ENOTSUP`, since such a transport never shows the program a redirect. A status the transport chose comes back as a response whose body is the reason. An unreachable host, or no transport at all, rejects with a `TypeError` whose `cause.code` is `ECONNREFUSED` or `ENETUNREACH`, as Node's `fetch` does. A `data:` URL is answered by `fetch` itself, as the fetch standard decodes it, without a transport: for any method, a `HEAD` without a body, and its type parsed and serialized as a MIME type (lowercase, no spaces, invalid parameters dropped), as Node's `fetch` answers; any other scheme than `http:` and `https:` rejects with a `TypeError` whose `cause.code` is `EPROTONOSUPPORT`, at the start as for a redirect target. A URL on one of the fetch standard's bad ports (25, 6000 and the rest) rejects too, as Node's `fetch` does: a `TypeError` `fetch failed` whose `cause` is an `Error` `bad port` (no `code`), at the start and as a redirect target, on the kernel's own loopback too ([#822](https://github.com/ai-ecoverse/slicc-kernel/issues/822)). `redirect: 'error'` rejects with a `TypeError` whose `cause` is an `Error` `unexpected redirect`, and a body whose connection ends early fails its read with `TypeError` `terminated` whose `cause` is undici's: `name` `SocketError`, `message` `other side closed`, `code` `UND_ERR_SOCKET`. The request goes out with its headers in the order the program gave them (an object's or an array's order; a `Headers` object's is sorted), lowercase as `Headers` keeps them. The global `fetch` stays withheld;
@@ -793,7 +802,7 @@ The kernel runs scheduled jobs itself, with no daemon process: a cron service st
793
802
  - **`crontab`** is a command of the kernel itself, in `/usr/bin` (a package's `crontab` never wins over it), with Vixie's options: `crontab -l` prints the caller's table, `crontab -r` removes it, `crontab <file>` and `crontab -` (standard input) install one, and `crontab -e` copies the table to `/tmp`, runs `$VISUAL` (or `$EDITOR`) on the copy through `/bin/bash -c '<editor> "$1"'`, and installs it if it changed and the editor exited with 0. If the edit doesn't parse, the copy is kept and its path is printed. `-u <user>` works on another user's table and needs an effective uid of 0 (`sudo crontab -u cone -l`). An install is checked first: a table with a line that doesn't parse is refused with every error (`"-":<line>: <message>`), and the old one stays. The table becomes the user's `0600` file in the spool, written to a dotfile of a random name and renamed, so the service, which skips dotfiles, never reads half a table. The command only reads the file it is given and passes it on (`ctx.crontab`, see [JS programs](#js-programs)). The kernel decides whose table it is from the caller's real uid, so a user reaches only their own table, and `/etc/crontab` stays root's to edit.
794
803
  - **Format:** Vixie's. Five fields (minute, hour, day of month, month, weekday) with lists, ranges, steps and the names `jan`–`dec` and `sun`–`sat` (7 is Sunday too), or one of `@yearly`/`@annually`, `@monthly`, `@weekly`, `@daily`/`@midnight`, `@hourly` and `@reboot`. As in cronie, when the day of month or the weekday starts with `*`, a day must match both fields; when both are restricted, it may match either. `/etc/crontab` has a user field after the schedule; a user's table doesn't. `NAME=value` lines set the environment for the jobs after them, `#` lines are comments, and `# name: <id>` on the line above a job gives it a stable id (letters, digits, `_`, `.`, `-`), which names its log. Without one, the id is a hash of the table, the user, the schedule and the command. `%` is not special. Times are the kernel's local time.
795
804
  - **Environment:** only what a login of that user gets, and nothing from the page or the embedder's `env`. That is `HOME`, `USER`, `LOGNAME`, `PATH`, `PNPM_HOME`, `NPM_CONFIG_PREFIX`, the kernel's network settings (`http_proxy`, `SSL_CERT_FILE`, …), `SHELL`, `PWD`, the table's `NAME=value` lines, and `SLICC_CRON=1`, `SLICC_CRON_JOB=<id>`, `SLICC_CRON_DUE=<ISO time of the due run>` and `SLICC_CRON_MISSED=<runs folded into this one>`. An `agent send` from a job therefore knows it was scheduled.
796
- - **Missed runs:** when the kernel wasn't running (a closed tab) or time jumped forward, every due run since the last one is folded into **one** run, with `SLICC_CRON_MISSED` saying how many more were due (counted up to 100 000). The next run is then computed from now. A clock set back more than two minutes runs nothing until the next due time after the new time.
805
+ - **Missed runs:** when the kernel wasn't running (a closed tab) or time jumped forward, every due run since the last one is folded into **one** run, with `SLICC_CRON_MISSED` saying how many more were due (counted up to 100 000) and `SLICC_CRON_DUE` naming the latest due time, even past the count's cap ([#843](https://github.com/ai-ecoverse/slicc-kernel/issues/843)). The next run is then computed from now. A clock set back more than two minutes runs nothing until the next due time after the new time.
797
806
  - **At most once:** the service writes each due run to `/var/spool/cron/stamp` before it starts the job. A kernel that ends mid-job (a reload, a crash) never runs that due again; a stamp that can't be read counts every job as run just now.
798
807
  - **`@reboot`** runs once per kernel boot, when that kernel first fires jobs.
799
808
  - **Overlap:** a job's runs don't wait for each other, so a slow job can still be running when it is due again. At most 16 jobs of one user run at once; a run past that is skipped, with a line in the job's log and in `/var/log/cron/cron.err`.