@ai-ecoverse/slicc 1.46.4 → 1.47.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 +44 -31
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
| command | what it does |
|
|
10
10
|
| --- | --- |
|
|
11
|
-
| `slicc` | pick browsers in a small TUI; Enter launches or attaches to the best match, opens seven and links it. With no SLICC profile and no CDP endpoint,
|
|
11
|
+
| `slicc` | pick browsers in a small TUI; Enter launches or attaches to the best match, opens seven and links it. With no SLICC profile and no CDP endpoint, its best match links seven in the default browser instead, and without a terminal slicc does that by itself (works now; see [The default](#the-default-slicc)) |
|
|
12
12
|
| `slicc attach --cdp <url>` | link a seven that is open in an already-running CDP browser (works now; see [Local setup](#local-setup-slicc-attach)) |
|
|
13
13
|
| `slicc open`, `slicc --default-browser`, `slicc pair` | link a seven in your everyday browser, without CDP: open or print a pairing URL (works now; see [Linking seven in your everyday browser](#linking-seven-in-your-everyday-browser)) |
|
|
14
14
|
| `slicc unpair [<fingerprint> \| --all]` | list or forget the sevens paired with `open` or `pair` |
|
|
@@ -158,7 +158,7 @@ Six's `--version`, `version`, `--profile` and `--mount` are seven flags too, and
|
|
|
158
158
|
| `/auth/callback`, `/api/oauth-state`, `/api/oauth-result` | not needed on `https://<label>.sliccy.ai`, where seven signs in through the relay itself. A seven on another origin needs slicc-extension to sign in |
|
|
159
159
|
| `startProxy`, `launchUrl`, `persistentKey` (JS) | no JS API |
|
|
160
160
|
|
|
161
|
-
**Which browser.** slicc-node opened seven in the default browser, in any profile. `slicc` does that too when it finds no SLICC profile and no CDP endpoint: plain `slicc` then
|
|
161
|
+
**Which browser.** slicc-node opened seven in the default browser, in any profile. `slicc` does that too when it finds no SLICC profile and no CDP endpoint: plain `slicc` then offers the first way below as the picker's best match, or falls back to it by itself without a terminal, and `--no-fallback` turns that off (see [The default](#the-default-slicc)). `slicc` links seven in one of three ways:
|
|
162
162
|
- **Your everyday browser (Chrome, Edge or Firefox):** `slicc open` (or `slicc --default-browser`) opens seven in the default browser and pairs it over loopback, with no CDP and no remote debugging. `slicc pair` prints the pairing URL for a seven that's already open. Once paired, that seven links again by itself after a reload or a restart of `slicc`. The link has the local policy, folders, kernel services and compute, but no `cdp`, since slicc controls no browser there (see [Linking seven in your everyday browser](#linking-seven-in-your-everyday-browser)). Safari is expected not to work, though untested: Playwright's WebKit blocks the loopback request as mixed content, and there seven offers the tray path below instead (see [§1.3](#13-local-setup-without-cdp-loopback-signalling)).
|
|
163
163
|
- **A browser `slicc` controls over CDP**, with `slicc` itself or `slicc attach`, which also offers seven `cdp` for that browser:
|
|
164
164
|
- **by default,** a browser in a SLICC profile (see [`slicc launch`](#slicc-launch---browser-id---profile-name---url-url---headless---no-seven-flags---json)). Its seven is separate from the one in your everyday profile, since seven keeps its files per browser profile.
|
|
@@ -334,9 +334,9 @@ slicc [--browser <id>]... [--cdp <url>]... [--page <url>] [--origin <origin>]...
|
|
|
334
334
|
|
|
335
335
|
`slicc` with no command (`npx sliccy`) finds browsers, launches or attaches to the best one, opens seven in it and links it. It serves the link until it is told to stop: `q` or Ctrl-C in the dashboard, or `SIGINT` or `SIGTERM`. Then it says `bye` to seven, closes the browsers this run launched (as `slicc launch` does), leaves the browsers it attached to running, and exits 0. If the browser that hosts seven goes away, `slicc` closes the rest and exits 0 too. Setup failures (no browser, a launch or attach that fails) exit 1, and bad flags exit 2. With `--no-fallback`, a run where nothing can host seven says ``no browser to control: run `slicc open` to link seven in your everyday browser`` and exits 1.
|
|
336
336
|
|
|
337
|
-
**The fallback to your default browser.** Plain `slicc` with no SLICC profile and no CDP endpoint falls back to [linking seven in your everyday browser](#linking-seven-in-your-everyday-browser), as `slicc pair` does:
|
|
338
|
-
- **When:** no `--browser`, `--cdp` or `--profile` was given and `--no-fallback` wasn't
|
|
339
|
-
- **What it says:** `No SLICC browser or debugging endpoint found, so seven opens in your default browser for pairing. Use --profile or --cdp to choose.` on stderr
|
|
337
|
+
**The fallback to your default browser.** Plain `slicc` with no SLICC profile and no CDP endpoint falls back to [linking seven in your everyday browser](#linking-seven-in-your-everyday-browser), as `slicc pair` does. On a terminal it doesn't fall back by itself: the picker comes up, with the `Default browser` row first and the best match (see *The picker* below).
|
|
338
|
+
- **When:** stdin or stdout isn't a terminal, or `--yes`, `--plain` or `--json` was given; no `--browser`, `--cdp` or `--profile` was given and `--no-fallback` wasn't; and discovery found no running CDP endpoint (SLICC six's running Chrome counts too) and no installed Chromium browser with a profile in the SLICC profiles dir. A profile or endpoint, found or given, always wins, so the flow above runs instead. Since slicc never picks six's Chrome by itself, a run whose only endpoint is six's launches an installed browser in a new SLICC profile, or exits 1 with `no browser to control` when there is none.
|
|
339
|
+
- **What it says:** `No SLICC browser or debugging endpoint found, so seven opens in your default browser for pairing. Use --profile or --cdp to choose.` on stderr. With `--json` it adds `{"schema":1,"event":"mode","mode":"loopback","reason":"no-browser"}` after `discovered`.
|
|
340
340
|
- **The pairing URL:** when stdin and stdout are both terminals, slicc opens it in the default browser. Otherwise it only prints it, unless `--open` is given. Without `--json` it goes to stdout. With `--json` it comes as `{"schema":1,"event":"pairing","url":"<pairing URL>"}`, and so does each new one.
|
|
341
341
|
- **`--json` events** in this mode, with the shapes of the other modes plus `mode: "loopback"` and `device` (the page's certificate fingerprint):
|
|
342
342
|
- `linked`: `peer?`, `caps` and `policy`, for each link that comes up;
|
|
@@ -353,7 +353,7 @@ slicc [--browser <id>]... [--cdp <url>]... [--page <url>] [--origin <origin>]...
|
|
|
353
353
|
|
|
354
354
|
Without `--browser`, the first row of the ranking that can host seven is chosen, an endpoint slicc didn't launch included, but never one in SLICC six's profile. When that happens without the picker, slicc says so on stderr, for an endpoint that isn't in a SLICC profile and that no `--cdp` named: `slicc: using Google Chrome (profile default) at http://127.0.0.1:9222, which slicc didn't launch; opened a new seven tab. Use --browser or --cdp to choose another.` It says `found a seven tab` when one was open already. The profile is `default` for a browser's default user-data dir, the base name of the user-data dir otherwise, and left out (with its parentheses) when unknown. With `--json` the line comes as the `foreign-browser` event too. Picking a row in the picker, `--browser`, or a `--cdp` for that endpoint means the user chose it, and there is no notice. An Electron app can't host seven. Each further browser is an extra: an installed one is launched without a start page, and an endpoint is used as it is. Then the link is set up as for `slicc attach` (see [Local setup](#local-setup-slicc-attach)): over CDP with the browser that hosts seven, the first page tab on an allowed origin.
|
|
355
355
|
|
|
356
|
-
**What the link offers:** `net` (tcp, resolve), `http`, `ws`, `udp`, and `cdp` with the browser that hosts seven (`seven: true`) and every extra, under the local policy with all of their debugging ports protected, and this machine's other debugging ports too (see [Network policy](#network-policy)); and kernel services on `--kernel-port` (default 80; `--no-kernel` turns them off) when seven offers `kernel-in` (see [Kernel services](#kernel-services)); and `ssh` only with `--runner` (see [SSH server](#ssh-server)); and `fs` with each `--mount` folder (see [Host folders](#host-folders)). seven gets full control of every listed browser, the one it runs in too. The `cdp` list (HELLO `browsers`) has only the browser that hosts seven and the extras picked with `--browser` or in the picker; the other browsers discovery found are never offered.
|
|
356
|
+
**What the link offers:** `net` (tcp, resolve), `http`, `ws`, `udp`, and `cdp` with the browser that hosts seven (`seven: true`) and every extra, under the local policy with all of their debugging ports protected, and this machine's other debugging ports too (see [Network policy](#network-policy)); and kernel services on `--kernel-port` (default 80, or a spare port when this user can't have 80; `--no-kernel` turns them off) when seven offers `kernel-in` (see [Kernel services](#kernel-services)); and `ssh` only with `--runner` (see [SSH server](#ssh-server)); and `fs` with each `--mount` folder (see [Host folders](#host-folders)). seven gets full control of every listed browser, the one it runs in too. The `cdp` list (HELLO `browsers`) has only the browser that hosts seven and the extras picked with `--browser` or in the picker; the other browsers discovery found are never offered.
|
|
357
357
|
|
|
358
358
|
The cdp list follows the browsers: an extra that stops answering is taken off the list (`browser-gone`), and listed again with the same id when it answers on the same endpoint again (`browser-back`).
|
|
359
359
|
|
|
@@ -369,7 +369,7 @@ The cdp list follows the browsers: an extra that stops answering is taken off th
|
|
|
369
369
|
- `--default-browser`: don't look for browsers; link a seven in the default browser instead, exactly as `slicc open` does (see [Linking seven in your everyday browser](#linking-seven-in-your-everyday-browser)).
|
|
370
370
|
- A flag of SLICC six's points back to six and exits 2 (see [Coming from SLICC six](#coming-from-slicc-six)). Any other flag, or an argument, is an error.
|
|
371
371
|
|
|
372
|
-
**The picker** comes up when stdin and stdout are both terminals and none of `--yes`, `--browser`, `--plain` or `--json` is given. It shows the ranking in sections, each under a header that says what picking a row does: `Running` (CDP endpoints, attached over CDP), `Launch` (installed browsers slicc launches in a SLICC profile of its own, for testing SLICC seven)
|
|
372
|
+
**The picker** comes up when stdin and stdout are both terminals and none of `--yes`, `--browser`, `--plain` or `--json` is given. It shows the ranking in sections, each under a header that says what picking a row does: `Running` (CDP endpoints, attached over CDP), `Launch` (installed browsers slicc launches in a SLICC profile of its own, for testing SLICC seven), `SLICC six profiles` (re-use existing SLICC profile (might lose data), the one warning, in yellow) and `Pair` (seven in your everyday browser, linked over loopback without cdp), in the order their best row ranks. `Pair` has one row, `Default browser`, which links seven as `slicc open` does: it comes first and is the best match when plain `slicc` would otherwise fall back (see above), and follows the installed browsers when not. `--no-fallback`, `--cdp` and `--profile` leave it out, and it can't be selected together with other browsers. Electron apps and headless browsers follow as folded summary rows (`▸ 15 Electron apps extras only · 1 blocks debugging`, `▸ 2 headless browsers`), opened and closed with Enter or space on the row, or with `e` and `h`; they start open when nothing else is listed. A row shows the name, the version (the major version for Chromium, major.minor otherwise; none on six's rows), its labels and, faint on the right, the port, or the browser a six profile opens in: `best match` on the row Enter takes, `last launched` (green), `seven is open`, `profile "<name>"` or `new profile "<name>"` when that SLICC profile doesn't exist yet, when a six profile was last used (`used 2 days ago`, from its `Local State`), or `running` and `seven is open` with the browser and port (`Google Chrome 155 at :9222`) when its Chrome runs, `headless`, `websocket only` and `blocks debugging`. Below the list a detail line starts with what Enter does with the highlighted row (`↵ Opens seven in Google Chrome 155 (create new Google Chrome profile "chrome" for testing SLICC seven)`, `↵ Links the seven tab in Google Chrome at :9222`, `↵ Re-uses existing SLICC profile six:51852 in Google Chrome 155 (might lose data)`, `↵ Links the seven tab in SLICC profile six, running in Google Chrome 155 at :9222`, `↵ Opens seven in your default browser and pairs it over loopback (no cdp: slicc controls no browser)`), with the profile or user-data path under it; with a selection it adds the extras (`…, with Slack and bb as extra browsers`). A browser that can't be selected says so in faint text below the list (`1Password blocks remote debugging, so it can't be picked`). Selected rows are marked `●1` (hosts seven), `●2`, … in selection order. The keys are listed at the bottom. With colour on, the highlighted row has a background bar, and the palette follows the terminal's light or dark background; on a light one faint text is a grey with at least 4.5:1 contrast. A running endpoint found in the process list is named after the app it runs from: the catalog name for a known browser, also for its helper processes (`Google Chrome for Testing` rather than `Chrome`), otherwise the macOS app bundle or the executable (`chrome-headless-shell`). A headless endpoint is one whose user agent says `HeadlessChrome` or whose process has `--headless`; unless it was named with `--cdp` (then it stays under `Running`, marked `headless`), the picker folds it into the dimmed headless rows after the installed browsers and never makes it the best match while anything else can host seven. `--yes`, `--plain` and `slicc browsers --json` keep the ranking below. The browser or SLICC six profile slicc last launched to host seven comes first, marked `last launched`, with the SLICC profile it used; slicc remembers it in `last-launch.json` next to `seven-profiles`. `--yes`, `--plain` and `--json` don't use it. A list longer than the terminal scrolls with the cursor.
|
|
373
373
|
|
|
374
374
|
| key | what it does |
|
|
375
375
|
| --- | --- |
|
|
@@ -383,6 +383,8 @@ The cdp list follows the browsers: an extra that stops answering is taken off th
|
|
|
383
383
|
|
|
384
384
|
**The dashboard** follows, in sections: BROWSERS (launched or attached, their CDP port, hosts seven or extra, and `gone` while one doesn't answer); SEVEN (the tab, the link state (`waiting for seven`, `connecting`, `linked` with its uptime, `reconnecting` after a link is lost, while slicc waits for seven to offer again, or `relinking` while new capabilities are applied), the policy summary, and stream counts per kind, open and total, never contents); CAPABILITIES (the network capabilities always offered, then `jerk`, `shell` with `ssh via <runner>`, `kernel` with its URL, or why it is off, and the shared folders); RECENT (the last status lines, links in green, losses in yellow, errors in red); and the update notice, which then isn't printed on stderr. Keys: `c` capabilities, `m` share a folder, `q` quit. `NO_COLOR` turns colors off.
|
|
385
385
|
|
|
386
|
+
**The dashboard after `Default browser`** links seven over loopback, as `slicc open` does, and opens the pairing URL in the default browser. In place of BROWSERS it shows `Default browser paired over loopback · no cdp hosts seven`; SEVEN starts with the pairing URL (a link where the terminal supports it, wrapped to the terminal's width, good once, for 10 minutes) until seven links, then `↵ for a URL to pair another seven`. The other sections are as above. Its capabilities are those of the command line: the capabilities screen and `m` aren't offered, so changing them means restarting slicc. Keys: `↵` a new pairing URL, `y` copies the URL to the clipboard (OSC 52), `q` quit.
|
|
387
|
+
|
|
386
388
|
**Capabilities at runtime.** `c` opens the capabilities screen: `jerk` (when slicc-jerk is installed), `shell` (the `--runner`; turning it on uses `$SHELL -c`, or `pwsh -Command` / `powershell -Command` on Windows), `kernel` (the `--kernel-port`, or off) and the shared folders, each `path[:name][:ro]` as for `--mount`, with tab completing folders. `space` turns a row on or off, `↵` edits it, `a` adds a folder, `r` makes a folder read-only, `x` removes it. Changes collect as pending until `s` applies them (`u` undoes them; leaving with `esc` asks first). Folder changes go live as a mount update (see [Host folders](#host-folders)) when folders are already shared and seven takes `mounts` updates. Every other change relinks: the node says `bye` with the reason `slicc is relinking`, the browsers and the CDP connection stay, and a new node with the new capabilities sends seven's tab a fresh `hello` with the same `node` id, so seven offers again and replaces the link. Streams open on the old link close. A runner changed in the TUI drops `--shell`, `--eval`, `--eval-quiet` and `--sftp` / `--no-sftp`, which belong to the runner given on the command line.
|
|
387
389
|
|
|
388
390
|
**Plain mode** (no terminal, `--yes`, `--browser` or `--plain`): no picker and no dashboard, just the default, with status lines on stderr (`slicc: <name> is still starting (slow disk?), <n> s so far`, `slicc: launched …`, `slicc: attached to …`, `slicc: offering …; policy: …; compute: ssh via <runner>`, `slicc: linked with "<name>" in <tab>`, `slicc: link lost: …`, `slicc: <name> quit …`, `slicc: <name> is back …`, `slicc: closed <ids>`). It never writes ANSI escapes or cursor codes. `SLICC_DEBUG=1` adds diagnostics.
|
|
@@ -544,7 +546,7 @@ slicc attach --cdp <url> [--cdp-extra <url>]... [--origin <origin>]... [--signal
|
|
|
544
546
|
- `--eval <argv…>` and `--eval-quiet <duration>`: the REPL behind the `slicc-eval` subsystem (for example `--eval python3 -i`), and the silence that ends one response (default `500ms`).
|
|
545
547
|
- `--sftp`, `--no-sftp`: serve the `sftp` subsystem or not. The default is on only for a plain local shell as the runner.
|
|
546
548
|
- `--mount <path>[:<name>][:ro]`: share this folder with seven's kernel (cap `fs`, repeatable); see [Host folders](#host-folders).
|
|
547
|
-
- `--kernel-port`: the local port for [kernel services](#kernel-services) (1–65535, default `80`).
|
|
549
|
+
- `--kernel-port`: the local port for [kernel services](#kernel-services) (1–65535, default `80`; with `80`, a spare port in 17180–17189 when `80` can't be bound).
|
|
548
550
|
- `--no-kernel`: no kernel services; nothing listens.
|
|
549
551
|
- `--no-jerk`: don't offer this machine's slicc-jerk (caps `jerk` and `jerk-seal`, see [`jerk`](#jerk-slicc-jerk-over-the-link)).
|
|
550
552
|
|
|
@@ -663,7 +665,7 @@ One WebRTC peer connection. seven offers and the node answers. The node:
|
|
|
663
665
|
- **Resets with `EOPNOTSUPP`:** an OPEN for a kind it doesn't serve.
|
|
664
666
|
- **Closes the link:** an unknown frame type, a short or malformed frame (END with a payload, CREDIT that isn't 4 bytes), stream id 0, a reused id, an id with the wrong parity (page streams are odd, node streams even), or a second OPEN on a stream channel.
|
|
665
667
|
|
|
666
|
-
HELLO from `attach`: `{ "v": 1, "role": "node", "name", "host", "version", "mode": "local", "node", "caps": ["net", "http", "credentialed", "ws", "udp", "cdp"], "routes": { "prefixes": [], "exit": true }, "policy": { "allow", "deny", "summary" }, "browsers": [ … ] }`. `cdp` and `browsers` are there when at least one browser is listed (see [`cdp`](#cdp-browser-control)). With `--runner`, `caps` adds `"ssh"` and HELLO adds `"sshHostKey": "ssh-ed25519 AAAA…"`, the host key in `authorized_keys` form, so the page can write `<host>.slicc.internal <key>` into the kernel's `known_hosts`. With `--mount`, `caps` adds `"fs"` and HELLO adds `"mounts"` (see [Host folders](#host-folders)). With host jerk, `caps` adds `"jerk"` and `"jerk-seal"` and HELLO adds `"jerk": { "version" }` (see [`jerk`](#jerk-slicc-jerk-over-the-link)).
|
|
668
|
+
HELLO from `attach`: `{ "v": 1, "role": "node", "name", "host", "version", "mode": "local", "node", "caps": ["net", "http", "credentialed", "ws", "udp", "cdp"], "routes": { "prefixes": [], "exit": true }, "policy": { "allow", "deny", "summary" }, "browsers": [ … ] }`. `cdp` and `browsers` are there when at least one browser is listed (see [`cdp`](#cdp-browser-control)). With `--runner`, `caps` adds `"ssh"` and HELLO adds `"sshHostKey": "ssh-ed25519 AAAA…"`, the host key in `authorized_keys` form, so the page can write `<host>.slicc.internal <key>` into the kernel's `known_hosts`. With `--mount`, `caps` adds `"fs"` and HELLO adds `"mounts"` (see [Host folders](#host-folders)). With host jerk, `caps` adds `"jerk"` and `"jerk-seal"` and HELLO adds `"jerk": { "version" }` (see [`jerk`](#jerk-slicc-jerk-over-the-link)). Unless `--no-kernel`, HELLO adds `"kernel": { "port" }`, the port the kernel listener uses (see [Kernel services](#kernel-services)).
|
|
667
669
|
|
|
668
670
|
**`ssh`** (cap `ssh`), page → node, with `--runner` only:
|
|
669
671
|
- OPEN `{ "kind": "ssh" }`, OPENED `{}` at once. The stream's bytes are one SSH connection to the node's SSH server.
|
|
@@ -795,17 +797,17 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
|
|
|
795
797
|
- **Privileged pages** (slicc-browsers ≥ 1.8.0) are protected the same way, as if they were seven's: every URL whose scheme is in `protectedSchemes` (`chrome:`, `chrome-untrusted:`, `chrome-extension:`, `devtools:`, `file:`, from 1.8.5 also `chrome-search:`, `chrome-native:`, `chrome-distiller:`, `isolated-app:`, `chrome-experimental-site-token-provider:`, `externalfile:` and `content:`, and the UI schemes of the catalog's other Chromium browsers, `edge:`, `brave:`, `opera:`, `vivaldi:` and `arc:`), on any host and path, except the new tab pages in `schemeExceptions` (`chrome://newtab`, `chrome://new-tab-page` and its `chrome-untrusted://new-tab-page` frames, `edge://newtab`, `brave://newtab`, `opera://startpage`), matched by the parsed scheme and exact host, so `chrome-untrusted://new-tab-page.evil/` stays protected. `chrome-error:` is not protected: Chrome's network and frame error pages stay readable, snapshottable, reattachable and navigable away from, like the ordinary pages they stand in for. So `chrome://settings`, `chrome://password-manager`, an extension's pages, frames and workers (a password manager's or a wallet's vault included) and any local file are refused, hidden from auto-attach and detached like a seven tab. A wrapper is judged by what it wraps: `view-source:chrome://settings/` and `view-source:file:///…` by their inner URL, `blob:` and `filesystem:` by their origin. An `about:` URL other than `about:blank` and `about:srcdoc` is judged as the `chrome://` URL Chrome makes of it, so `about:settings` is `chrome://settings`. A target whose `type` is in `targetTypes` (`browser_ui`, Chrome's own UI such as `chrome://omnibox-popup.top-chrome/`) is protected whatever its URL, and hidden altogether (see *Listing*).
|
|
796
798
|
- **Targets.** Before the stream opens, the node attaches its own session to the browser target and calls `Target.setDiscoverTargets` and `Target.getTargets` on it with ids of its own. Neither the session nor its messages ever reach the page. From then on it knows every target's URL and opener. A target on an inheriting URL (`about:blank`, `about:srcdoc`, none) is protected when it was protected before or when its opener is protected or unknown, so a blank popup that seven opens counts as seven. On a page session, the main frame's `securityOrigin` (`Page.frameNavigated`) and the default context's `origin` (`Runtime.executionContextCreated`) count too.
|
|
797
799
|
- **Extension worlds.** Any other execution context on a protected origin, such as a password manager's content-script isolated world on an ordinary page, is hidden rather than cutting the page. Seven never sees it, and commands, objects and call frames that name it are refused with `slicc: refused: that execution context belongs to a browser extension` ([§5.8](#58-cdp-cap-cdp-page--node)).
|
|
798
|
-
- **URLs slicc can't parse** count as protected wherever the node judges a URL the browser will load or has loaded. Chrome's parser and the node's (WHATWG) disagree at the edges: Chrome loads `http://exa mple.sliccy.ai/` as `http://exa%20mple.sliccy.ai/`, a `sliccy.ai` host that gets seven's cookies, and, in some versions, `http://<U+200D>.sliccy.ai/` as `http://xn--1ug.sliccy.ai/`, and the node's parser rejects all of those forms. So a command whose destination doesn't parse (`url` of `Target.createTarget`, `Page.navigate`, `Fetch.continueRequest`, `Network.continueInterceptedRequest`, `Network.loadNetworkResource` and `PWA.launch`, `installUrlOrOrigin` of `PWA.install`, the `url` of a cookie command or any of the `urls` of `Network.getCookies` and `Page.getCookies`, and a history entry's `url` or `userTypedURL`) is refused with `slicc: refused: slicc can't parse this URL, so it could be a protected page`. An empty URL is fine (`Target.createTarget` with `url: ""` opens `about:blank`). On the browser's side, a target, a main frame's `securityOrigin`, an execution context's `origin`, a request or WebSocket, and a service worker whose URL doesn't parse are protected like seven's: left out of listings (see Listing), not attachable, detached, hidden, their document loads failed and their requests stripped. The opaque origins `null` and `://` are not affected. A `blob:null/…` URL (a blob made on an opaque origin, such as a `data:` page or a sandboxed frame) is: its inner `null/…` doesn't parse, and slicc can't name the blob's creator, so it is protected too. URLs the node only reads, such as storage keys
|
|
800
|
+
- **URLs slicc can't parse** count as protected wherever the node judges a URL the browser will load or has loaded. Chrome's parser and the node's (WHATWG) disagree at the edges: Chrome loads `http://exa mple.sliccy.ai/` as `http://exa%20mple.sliccy.ai/`, a `sliccy.ai` host that gets seven's cookies, and, in some versions, `http://<U+200D>.sliccy.ai/` as `http://xn--1ug.sliccy.ai/`, and the node's parser rejects all of those forms. So a command whose destination doesn't parse (`url` of `Target.createTarget`, `Page.navigate`, `Fetch.continueRequest`, `Network.continueInterceptedRequest`, `Network.loadNetworkResource` and `PWA.launch`, `installUrlOrOrigin` of `PWA.install`, the `url` of a cookie command or any of the `urls` of `Network.getCookies` and `Page.getCookies`, and a history entry's `url` or `userTypedURL`) is refused with `slicc: refused: slicc can't parse this URL, so it could be a protected page`. An empty URL is fine (`Target.createTarget` with `url: ""` opens `about:blank`). On the browser's side, a target, a main frame's `securityOrigin`, an execution context's `origin`, a request or WebSocket, and a service worker whose URL doesn't parse are protected like seven's: left out of listings (see Listing), not attachable, detached, hidden, their document loads failed and their requests stripped. The opaque origins `null` and `://` are not affected. A `blob:null/…` URL (a blob made on an opaque origin, such as a `data:` page or a sandboxed frame) is: its inner `null/…` doesn't parse, and slicc can't name the blob's creator, so it is protected too. URLs the node only reads, such as storage keys and script URLs, are unaffected. In what the page gets, a URL that doesn't parse is cut whole (to an empty string), because it has no origin to keep. The cost: a page on a host that Chrome accepts and WHATWG doesn't (an invalid `xn--` label, a joiner such as U+200D, a space or another forbidden code point), and Chrome's error page for one, is cut too, and navigating to it is refused. URLs Chrome rejects as well (`http://[::1`, `http://a:99999/`, `http://1.2.3.4.5/`) lose nothing: Chrome answers `Page.navigate` with `Cannot navigate to invalid URL`, and `Target.createTarget` with a tab that never loads a document.
|
|
799
801
|
- **Refused**, with `{"id":…,"sessionId"?:…,"error":{"code":-32000,"message":"slicc: refused: target is on seven's origin"}}`, never forwarded: `Target.createTarget`, `Page.navigate` and anything else whose `url` is protected (one that doesn't parse is refused with `slicc: refused: slicc can't parse this URL, so it could be a protected page`, see above); `Page.navigateToHistoryEntry` to an entry whose URL is protected (the node looks it up with `Page.getNavigationHistory`); `Target.attachToTarget`, `activateTarget`, `closeTarget`, `autoAttachRelated`, `Browser.getWindowForTarget` and any other command whose `targetId` or `frameId` is a protected target; and every command on a session whose target is protected, or became so. `Target.getTargetInfo` and `Target.detachFromTarget` stay allowed, except `Target.getTargetInfo` on a hidden target (see *Listing*).
|
|
800
802
|
- **Origins and storage.** A command whose `origin`, `securityOrigin`, `ownerOrigin`, `issuerOrigin`, `topLevelOrigin`, `topLevelSite`, `embeddedOrigin`, `requestingOrigin`, `scopeURL` or `manifestId` is protected, or whose `storageKey` or `cacheId` names seven's storage, is refused the same way. This covers `Storage.*`, `IndexedDB.*`, `CacheStorage.*`, `DOMStorage.*`, `FileSystem.*`, `ServiceWorker.*`, `PWA.*`, `Browser.setPermission` and `Browser.grantPermissions`. Free-form values (`expression`, `arguments`, `value`, `headers`, `postData` and the like) aren't searched.
|
|
801
803
|
- **Cookies.** `Network.getCookies`, `Network.getAllCookies`, `Storage.getCookies` and `Page.getCookies` answer without the cookies whose domain is protected (`sliccy.ai` and every subdomain, and each extra origin's host and its parents: cookies ignore ports), and without partitioned cookies whose `partitionKey.topLevelSite` is such a site. `Network.setCookie`, `setCookies`, `deleteCookies`, `Storage.setCookies` and `Page.deleteCookie` for such a domain, a URL on such a host, or a partition key under such a site, are refused with `slicc: refused: cookies on seven's origin`. So are `Network.clearBrowserCookies` and `Storage.clearCookies`, except in a browser context this stream created with `Target.createBrowserContext`.
|
|
802
|
-
- **Requests a page makes to seven.** For a request whose URL is protected, `Network.*` and `Fetch.*` events lose their `Cookie` and `
|
|
804
|
+
- **Requests a page makes to seven.** For a request whose URL is protected, `Network.*` and `Fetch.*` events lose their `Cookie`, `Set-Cookie`, `Authorization` and `Proxy-Authorization` headers (in the header maps and as lines of the raw header texts, which otherwise stay, their URLs cut as everywhere) and their cookie lists, and `requestWillBeSentExtraInfo` drops seven's cookies from `associatedCookies` for any request. Chrome can send `requestWillBeSentExtraInfo` and `responseReceivedExtraInfo` before the event that names the request's URL, so the node holds them until the request is found to be protected, gets its response, finishes or fails (a WebSocket or WebTransport once it is created); one still held after 2 seconds is passed on stripped, as for a protected request. For a WebSocket, WebTransport or EventSource on a protected origin (`ws:` and `wss:` count as `http:` and `https:`), `webSocketWillSendHandshakeRequest`, `webSocketHandshakeResponseReceived`, `webSocketFrameError`, `eventSourceMessageReceived` and `dataReceived` are dropped. Reading such a request's body (`Network.getResponseBody`, `Fetch.getResponseBody`, `takeResponseBody…AsStream`, `Audits.getEncodedResponse` and the like) is refused. A request paused at the response stage whose response headers name a protected URL (an ordinary redirect into seven, say) counts as such a request too. `Fetch.continueRequest` and `Network.continueInterceptedRequest` with a `url` that the node has seen redirect into a protected origin on that target are refused with `slicc: refused: that URL redirects to seven's origin`. So is reading a stylesheet or script that came from a protected URL, directly or through a redirect: `Page.getResourceContent` and `Page.searchInResource` for its URL, and any command naming its `styleSheetId` or `scriptId`. CSS rules and styles from it reach the page with only their `styleSheetId` and `origin`. In a dedicated, shared or service worker, the node keeps its own guard session (Network only), and the worker's commands, `Runtime.runIfWaitingForDebugger` included, wait until that session's `Network.enable` answers, within the same bounds as a page's guard. A worker script other than the worker's own is readable only if that guard saw its whole redirect chain and none of it was protected; an import from before the guard attached, such as in a worker that was already running, is refused. In a page, a stylesheet or script loaded before the node attached to the tab has no redirect chain the node saw, so it stays readable even if it came through a redirect from a protected origin (a known residual). The node also checks the answer, because the page may ask before the node has seen the request. When `Network.getResponseBody`, `searchInResponseBody`, `streamResourceContent`, `getResponseBodyForInterception`, `takeResponseBodyForInterceptionAsStream`, `getRequestPostData`, `Fetch.getResponseBody`, `Fetch.takeResponseBodyAsStream` or `Audits.getEncodedResponse` answers, the node holds the answer until the request has a response (for `getRequestPostData`, until it is sent; for an intercepted request, until it is paused). If the request is protected by then, the answer is refused, and a stream it handed out is closed. If the node still hasn't seen the request after 2 seconds, the answer is refused with `slicc: refused: slicc hasn't seen this request, so it could be seven's`. `Network.replayXHR` is refused with that message too, unless the node has already seen the request; for a protected request it is refused like the body getters. `IO.read` is refused, with `slicc: refused: slicc didn't hand out this stream, so it could be seven's`, unless the node passed that handle to the page in an allowed answer (`take…AsStream`, `Page.printToPDF`, or `IO.resolveBlob` as `blob:<uuid>`). - **Paused requests to seven.** A page session's own interception never gets to touch a request whose URL is protected, whatever its kind (a document, `fetch()`, XHR, an image or any other subresource), at either stage. The node keeps `Fetch.requestPaused`, `Fetch.authRequired` and the deprecated `Network.requestIntercepted` for such a request from the page and settles it itself:
|
|
803
805
|
- a document request is failed (`Fetch.failRequest` with `BlockedByClient`, or `Network.continueInterceptedRequest` with `errorReason: BlockedByClient`);
|
|
804
806
|
- any other request continues untouched (`Fetch.continueRequest`, or `Network.continueInterceptedRequest` with only its `interceptionId`);
|
|
805
807
|
- an auth challenge gets the browser's own handling (`Fetch.continueWithAuth`, or `Network.continueInterceptedRequest`, with `authChallengeResponse: {"response": "Default"}`).
|
|
806
808
|
|
|
807
809
|
`Fetch.continueRequest`, `continueResponse`, `failRequest`, `fulfillRequest`, `continueWithAuth` and `Network.continueInterceptedRequest` are refused with `slicc: refused: slicc didn't pass this paused request to the page, so it could be seven's` for any `requestId` or `interceptionId` the node hasn't passed to that session in a paused event, and for one whose pause the page has already answered successfully (until the next paused event for it). So a guessed id can't fulfil, change or fail a protected request, even one that is still paused. The node remembers the last 16 384 ids it passed on; an id older than that is refused too.
|
|
808
|
-
- **Browser-wide commands**, refused with their own reason after `slicc: refused: `: `Target.exposeDevToolsProtocol` (it would give a page the whole browser) and `Target.sendMessageToTarget` (attach with `flatten: true` instead), on any target; `Fetch.enable` on the browser itself (a page session is fine); `Tracing.start`; `Extensions.loadUnpacked`; and `Browser.setPermission`, `Browser.grantPermissions` without an `origin` and `Browser.resetPermissions`, except in a browser context this stream created.
|
|
810
|
+
- **Browser-wide commands**, refused with their own reason after `slicc: refused: `: `Target.exposeDevToolsProtocol` (it would give a page the whole browser) and `Target.sendMessageToTarget` (attach with `flatten: true` instead), on any target; `Fetch.enable` on the browser itself (a page session is fine); `Tracing.start`; `Extensions.loadUnpacked`; `Network.loadNetworkResource` on any session (Chrome follows its redirects itself, with the user's cookies, so an ordinary URL that redirects to seven would hand the page seven's page; a destination that is protected or doesn't parse keeps its usual refusal); `Page.captureSnapshot` (it serialises every frame, Chrome's error pages for seven's URLs included); and `Browser.setPermission`, `Browser.grantPermissions` without an `origin` and `Browser.resetPermissions`, except in a browser context this stream created.
|
|
809
811
|
- **Auto-attach.** With `Target.setAutoAttach`, a session to a protected target is detached by the node at once (`Target.detachFromTarget` on its parent session), and the page never sees its `attachedToTarget` or `detachedFromTarget`.
|
|
810
812
|
- **Navigation by other means.** A script's `location = …`, a link, `window.open` or a server redirect isn't a command the node can refuse, so the node guards the network instead.
|
|
811
813
|
- A target the stream attached to (`Target.attachToTarget`, or auto-attach that reached the page) or created (`Target.createTarget`) is *touched*, for as long as the stream is open, even after the page detached. That includes every tab the browser opens while the stream auto-attaches to new tabs.
|
|
@@ -813,7 +815,7 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
|
|
|
813
815
|
- The node fails each such request into a protected origin with `Fetch.failRequest` (`BlockedByClient`), and the target shows Chrome's error page instead. Every other request continues untouched. So a touched target never loads a protected document, and nothing the stream installed on it (`Page.addScriptToEvaluateOnNewDocument`, `Fetch`, `Network`, `Page.setBypassCSP`, emulation, overrides) ever runs there.
|
|
814
816
|
- Seven's own tab and its vault frame are never touched, because attaching to them is refused.
|
|
815
817
|
- Page commands on a touched target's session wait, in order, until the node's own session there is ready; commands on other sessions of the stream don't wait. No page command on that session reaches the browser before the node's session is ready. The node's `Network.enable` and `Network.setBypassServiceWorker` answer only once the target's renderer does, which a loaded host can take a minute or more to start, so the wait has no fixed length. It fails closed on no progress, or at an outer bound:
|
|
816
|
-
- **Progress** for a target whose setup is under way is
|
|
818
|
+
- **Progress** for a target whose setup is under way is the answer to the node's attach to it, or any answer or event on the node's own guard session for that target. Answers and events on the page's sessions, and on other targets, don't count, so a guard that stays silent fails at the 30 s bound even while the browser and the page stay busy. The node checks the bounds every quarter of the shorter one (7.5 s by default).
|
|
817
819
|
- **Idle bound:** a setup with no progress for 30 s fails, within one check of it (37.5 s).
|
|
818
820
|
- **Outer bound:** a setup still under way 5 min after it started fails, whatever its progress.
|
|
819
821
|
- **Refusals:** when a setup call fails, the node's session is lost or the target closes, the waiting commands, and any after them, are refused at once with `slicc: refused: slicc can't keep seven's origin out of this target`. When a bound fires, the message names it: `…: its guard made no progress in 30s` or `…: its guard wasn't ready in 5m0s`.
|
|
@@ -832,7 +834,7 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
|
|
|
832
834
|
- Once the target has a URL (`Target.targetInfoChanged`), a protected target's session is detached without the page seeing it. Otherwise the page gets `attachedToTarget` with the current `targetInfo` and `waitingForDebugger: false`. The target has already started, so a script the page adds for new documents in such a frame or popup misses its first one.
|
|
833
835
|
- A target that has no URL after 10 s is treated as protected. A session that the node does forward makes the target touched (see above).
|
|
834
836
|
- **Listing.** `Target.getTargets`, `Target.getTargetInfo`, `Target.targetCreated` and `Target.targetInfoChanged` still show protected targets, so seven knows its tab exists, but their `url` and `title` are only the origin (`https://seven.sliccy.ai`, `chrome://settings`, `chrome-extension://<id>`, or `about:blank`), or only the scheme for a URL without a host (`file:`). A target whose URL slicc can't parse isn't listed at all, since it has no origin to show: `Target.getTargets` leaves it out, and its `targetCreated` and `targetInfoChanged` are dropped. A target of a `targetTypes` type (`browser_ui`) is never shown: `Target.getTargets` leaves it out, its `targetCreated`, `targetInfoChanged`, `targetDestroyed` and `targetCrashed` are dropped, and `Target.getTargetInfo` on its id is refused like any other command that names it. Service worker registrations and versions on a protected origin are left out of `ServiceWorker` events, and `Storage`, `DOMStorage` and `BackgroundService` events that name a protected origin are dropped.
|
|
835
|
-
- **Protected URLs in what the page gets.** In every event and result the page gets, a URL field that holds a protected URL is cut to its origin, at any depth: every key ending in `url`, `urls`, `origin` or `origins` (any case, so `documentURL`, `unreachableUrl` and `securityOrigin` too), `referrer`, `location`, `href` and `src`, and every header value in `headers`, `requestHeaders` and `responseHeaders`. That covers `Page.frameRequestedNavigation`, `Page.frameNavigated`, `Network.requestWillBeSent`, `Page.windowOpen`, `Page.getFrameTree` and the rest.
|
|
837
|
+
- **Protected URLs in what the page gets.** In every event and result the page gets, a URL field that holds a protected URL is cut to its origin, at any depth: every key ending in `url`, `urls`, `origin` or `origins` (any case, so `documentURL`, `unreachableUrl` and `securityOrigin` too), `referrer`, `location`, `href` and `src`, and every header value in `headers`, `requestHeaders` and `responseHeaders`. That covers `Page.frameRequestedNavigation`, `Page.frameNavigated`, `Network.requestWillBeSent`, `Page.windowOpen`, `Page.getFrameTree` and the rest. Every other string (header values such as `Link` and `Refresh`, `headersText` and `requestHeadersText`, cookie and storage values, DOM attributes) keeps its text, but each protected URL inside it (a `scheme://` run up to whitespace, a quote, `<` or `>`, a URL that doesn't parse included, and a run whose `://` is percent-encoded in whole or in part, such as `scheme%3A%2F%2F` or `scheme%3A//`, up to those or a raw `&`, decoded once; a doubly encoded one isn't decoded) is cut to its origin, and next to a protected URL field every field ending in `Fragment` (`urlFragment` of a request or frame) is blanked to `""`, while ordinary URLs keep theirs, so a redirect from an ordinary page to seven's `/auth/callback?code=…` shows the page only seven's origin, wherever it names that URL. Unprotected URLs stay intact. Everything inside a `value`, `preview`, `customPreview`, `arguments` or `returnValue` (evaluation results and their previews) is left alone.
|
|
836
838
|
- **Limits.**
|
|
837
839
|
- The filter relies on Chrome's site isolation. A seven frame inside a page of another site is a target of its own (`iframe`), which the node protects. A page the stream never touched can still frame seven, though, and once the stream touches that page, a screenshot of it shows the frame. A same-site frame shares its parent's process and isn't a target. Every `sliccy.ai` host is protected, so this only matters for an extra origin, such as one port of `127.0.0.1` framed by a page on another.
|
|
838
840
|
- A document that arrives without a request isn't failed: a page restored from the back/forward cache, and a `blob:` or `filesystem:` URL of a protected origin (which only that origin can make). The node detaches after the commit, as above, and commands that were already on their way may still have run in the new page.
|
|
@@ -851,8 +853,10 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
|
|
|
851
853
|
seven's kernel runs servers (vite, `python -m http.server`), and kernel port `N` is `http://N.kernel.localhost/`. seven's service worker carries subresources itself, but not top-level navigations or WebSockets. `*.kernel.localhost` resolves to loopback, so `attach` runs a listener that carries those connections into the page as `kernel` streams.
|
|
852
854
|
|
|
853
855
|
**Listener.**
|
|
854
|
-
- It binds `127.0.0.1:<kernel port>` (default `80
|
|
855
|
-
-
|
|
856
|
+
- It binds `127.0.0.1:<kernel port>` (default `80`) when the first page whose HELLO lists `kernel-in` links up, and keeps it until `attach` exits. With `--kernel-port 8080` the URLs carry the port: `http://5173.kernel.localhost:8080/`. `--no-kernel` turns it off, and remote nodes never listen.
|
|
857
|
+
- **macOS** refuses a normal user `127.0.0.1:80` (`permission denied`) but lets any user bind `0.0.0.0:80`: it exempts only the wildcard address from the privileged-port rule. So on macOS, when `127.0.0.1:<port>` gets `permission denied`, the listener binds `0.0.0.0:<port>` instead and refuses every connection whose peer isn't a loopback address: it resets it before reading a byte, and the page never hears of it. Only this machine reaches kernel services, as with `127.0.0.1`. The macOS firewall, when it's on, may ask once whether slicc may accept incoming connections; either answer works, since the firewall never blocks loopback. Another user can't take `127.0.0.1:80` in front of it, for the same rule.
|
|
858
|
+
- **Port 80 out of reach.** Linux refuses an unprivileged bind on `80` at any address (unless `net.ipv4.ip_unprivileged_port_start` allows it or slicc has `CAP_NET_BIND_SERVICE`), and something else may hold `80` on any system (`http.sys` on Windows, a local web server). So with the port `80` (the default, or `--kernel-port 80`), slicc checks at startup whether it can bind it as above, and otherwise takes the first port in 17180–17189 it can bind on `127.0.0.1`, and says `cannot listen on <address> (<reason>), so kernel services use port <n>`. The URLs then carry that port (`http://5173.kernel.localhost:17180/`), and HELLO's `kernel.port` tells seven. Any other `--kernel-port` is used as given.
|
|
859
|
+
- If the port can't be bound when the page links up (something took it since startup, or none of 17180–17189 was free), `attach` prints `kernel services are off: cannot listen on <address> (<reason>); try --kernel-port` and runs on without it. It tries once.
|
|
856
860
|
- The kernel port is denied in the [network policy](#network-policy) from startup, like the browser's debugging port, unless `--no-kernel` is given.
|
|
857
861
|
|
|
858
862
|
**Each connection.**
|
|
@@ -878,7 +882,7 @@ seven's kernel runs servers (vite, `python -m http.server`), and kernel port `N`
|
|
|
878
882
|
|
|
879
883
|
The browser's connection waits unread for `OPENED`.
|
|
880
884
|
|
|
881
|
-
**Security.** The `Host` check defends against DNS rebinding. Kernel services are reachable like any dev server on localhost: by any local process, and by any web page Chrome lets reach loopback. Use `--no-kernel` to keep them inside the page.
|
|
885
|
+
**Security.** The `Host` check defends against DNS rebinding. Kernel services are reachable like any dev server on localhost: by any local process, and by any web page Chrome lets reach loopback. They are never reachable from another machine: the macOS wildcard bind resets every peer that isn't loopback, which a connection to this machine's own LAN address isn't either. Use `--no-kernel` to keep them inside the page.
|
|
882
886
|
|
|
883
887
|
## Host folders
|
|
884
888
|
|
|
@@ -1463,11 +1467,13 @@ The scenario: seven is open in the user's **everyday browser** (Chrome, Edge or
|
|
|
1463
1467
|
- **Several sevens:** the node holds one link per paired page fingerprint, and each is an exit on its own.
|
|
1464
1468
|
|
|
1465
1469
|
**Entry points** (automatic fallback):
|
|
1466
|
-
- **Plain `slicc
|
|
1467
|
-
|
|
1470
|
+
- **Plain `slicc` on a terminal** offers this mode as the picker's `Default browser` row, first and the best match when there is no SLICC profile and no CDP endpoint (none launched, none found, none given).
|
|
1471
|
+
- **Plain `slicc` without a terminal**, or with `--yes`, `--plain` or `--json`, falls back to this mode by itself when there is no SLICC profile and no CDP endpoint: it prints the pairing URL (and opens it in the default browser with `--open`, or when on a terminal) and links over loopback.
|
|
1472
|
+
- A status line on stderr (with `--json` also an event `{"event":"mode","mode":"loopback","reason":"no-browser"}`, then `{"event":"pairing","url"}` for each pairing URL, and the same `linked` and `closed` events as other modes) says: "No SLICC browser or debugging endpoint found, so seven opens in your default browser for pairing. Use --profile or --cdp to choose."
|
|
1473
|
+
- **Picking `Default browser`** runs this mode in the dashboard (see [The default](#the-default-slicc)): the pairing URL, a new one with Enter, the link state and the capabilities.
|
|
1468
1474
|
- **`--no-fallback`** keeps the pre-fallback behaviour, for scripts: launch an installed Chromium into a new SLICC profile when one exists, and exit with "no browser to control" only when nothing can host seven. Without it, non-interactive runs (no TTY) still fall back, but print the pairing URL instead of opening a browser unless `--open` is given. With it, nothing falls back, TTY or not.
|
|
1469
1475
|
- **`slicc open`, `slicc --default-browser` and `slicc pair`** stay as the explicit forms.
|
|
1470
|
-
- **A found or given profile or CDP endpoint always wins
|
|
1476
|
+
- **A found or given profile or CDP endpoint always wins,** SLICC six's running Chrome included; the fallback only triggers when both are absent.
|
|
1471
1477
|
|
|
1472
1478
|
#### 1.4 A seven as follower: leader to leader
|
|
1473
1479
|
|
|
@@ -1546,7 +1552,8 @@ Page-opened streams are odd and node-opened streams even, never 0, and never reu
|
|
|
1546
1552
|
"mode": "local",
|
|
1547
1553
|
"caps": ["net", "http"],
|
|
1548
1554
|
"routes": { "prefixes": [], "exit": true },
|
|
1549
|
-
"policy": { "allow": [], "deny": ["127.0.0.1/32:9222"], "summary": "every public address, this machine, and the local network" }
|
|
1555
|
+
"policy": { "allow": [], "deny": ["127.0.0.1/32:9222"], "summary": "every public address, this machine, and the local network" },
|
|
1556
|
+
"kernel": { "port": 80 }
|
|
1550
1557
|
}
|
|
1551
1558
|
```
|
|
1552
1559
|
|
|
@@ -1559,6 +1566,7 @@ Page-opened streams are odd and node-opened streams even, never 0, and never reu
|
|
|
1559
1566
|
- page: `kernel-in` ([§5.4](#54-kernel-page-cap-kernel-in-node--page)).
|
|
1560
1567
|
- **`routes`** (node only): the prefixes it offers as specific routes, plus `exit: true` when it's willing to be a default exit.
|
|
1561
1568
|
- **`policy`:** for display only; the node enforces it ([§6](#6-policy-node)).
|
|
1569
|
+
- **`kernel`** (local node only, with kernel services): the port of the node's kernel listener ([§5.9](#59-kernel-listener-on-the-node-local-only)).
|
|
1562
1570
|
- **Versions:** each side ignores fields it doesn't know. If `v` differs, the side with the lower `v` decides, or closes the link when it can't.
|
|
1563
1571
|
- **Before HELLO:** neither side sends OPEN until it has received the peer's HELLO.
|
|
1564
1572
|
- **Unsupported kinds:** OPEN for a kind the receiver didn't advertise gets RESET `EOPNOTSUPP`.
|
|
@@ -1679,9 +1687,11 @@ On a [§1.3](#13-local-setup-without-cdp-loopback-signalling) loopback link, `un
|
|
|
1679
1687
|
|
|
1680
1688
|
- **Protected origins (PRE-1):** the node filters every `cdp` stream against `protectedOrigins` from slicc-browsers (≥ 1.4.0: `*.sliccy.ai` at any depth, any port, http and https; `vault.sliccy.ai` by name; plus the setup's extra origins). It refuses navigating to, attaching to, or driving targets on those origins, and storage, cookie and permission access for them, as **CDP errors** `{"error":{"code":-32000,"message":"slicc: refused: …"}}`, not RESETs. It detaches sessions that land on them and redacts their `url`/`title` in listings to the origin.
|
|
1681
1689
|
- **Privileged browser pages** (≥ 1.8.0) count as protected origins: every URL in `protectedSchemes` (`chrome:`, `chrome-untrusted:`, `chrome-extension:`, `devtools:`, `file:`, from 1.8.5 `chrome-search:`, `chrome-native:`, `chrome-distiller:`, `isolated-app:`, `chrome-experimental-site-token-provider:`, `externalfile:` and `content:`, `edge:`, `brave:`, `opera:`, `vivaldi:`, `arc:`) except the new tab pages in `schemeExceptions`, and never `chrome-error:`; `view-source:`, `blob:` and `filesystem:` by what they wrap; `about:` aliases as the `chrome://` URL they become (`aboutScheme`). Targets of a `targetTypes` type (`browser_ui`) are protected whatever their URL, and never listed or announced to the page. The refusal text is the same.
|
|
1682
|
-
- **URLs that don't parse** (WHATWG) count as protected wherever the node judges a URL the browser will load or has loaded: a command's destination (`Target.createTarget`, `Page.navigate`, `Fetch.continueRequest`, `Network.continueInterceptedRequest`, `Network.loadNetworkResource`, `PWA.launch`, `PWA.install`, cookie commands' `url` or `urls`, history entries) is refused with `slicc: refused: slicc can't parse this URL, so it could be a protected page`, and a target, main frame, execution context, request, WebSocket or service worker the browser reports on such a URL is redacted, refused, detached or hidden like seven's. An empty URL and the opaque origins `null` and `://` are not affected (a `blob:null/…` URL is: its inner `null/…` doesn't parse), and a target on such a URL isn't listed at all, and URLs the node only reads (storage keys, script URLs
|
|
1690
|
+
- **URLs that don't parse** (WHATWG) count as protected wherever the node judges a URL the browser will load or has loaded: a command's destination (`Target.createTarget`, `Page.navigate`, `Fetch.continueRequest`, `Network.continueInterceptedRequest`, `Network.loadNetworkResource`, `PWA.launch`, `PWA.install`, cookie commands' `url` or `urls`, history entries) is refused with `slicc: refused: slicc can't parse this URL, so it could be a protected page`, and a target, main frame, execution context, request, WebSocket or service worker the browser reports on such a URL is redacted, refused, detached or hidden like seven's. An empty URL and the opaque origins `null` and `://` are not affected (a `blob:null/…` URL is: its inner `null/…` doesn't parse), and a target on such a URL isn't listed at all, and URLs the node only reads (storage keys, script URLs) keep "unparseable isn't protected", and in what the page gets such a URL is cut whole. Chrome canonicalises some URLs WHATWG rejects (`http://exa mple.sliccy.ai/` → `http://exa%20mple.sliccy.ai/`, U+200D → `xn--1ug`, invalid `xn--` labels), so this is what keeps those `sliccy.ai` hosts protected. The cost: pages on such exotic hosts, and Chrome's error pages for them, are cut even when they aren't protected. The rule and its refusal come from slicc-browsers' catalog (`protectedOrigins.unparseable` and `unparseableRefusal`, ≥ 1.10.0). [`cdp` (browser control)](#cdp-browser-control) has the details.
|
|
1683
1691
|
- **In targets the stream touched** (attached to or created): the node fails every protected document load with `net::ERR_BLOCKED_BY_CLIENT`, so instrumentation installed before a navigation can never run in a protected document. That covers redirects, service-worker-served navigations and the page's own Fetch interception.
|
|
1684
|
-
- **
|
|
1692
|
+
- **Redirects into protected URLs:** a protected URL that reaches the page inside any string it gets, such as a redirect's `Location`, `Link` or `Refresh` header or a `headersText` line, plain or percent-encoded once (doubly encoded nesting isn't decoded), is cut to its origin, and its `urlFragment` is blanked; unprotected URLs stay intact. A stylesheet or script whose URL, or any hop of whose redirect chain (as the node's own guard session saw it on that target, compared in WHATWG form without the fragment), is protected can't be read: `Page.getResourceContent` and `Page.searchInResource` for such a URL (or one that doesn't parse), and any command naming such a `styleSheetId` or `scriptId` (`CSS.getStyleSheetText`, `Debugger.getScriptSource`, `searchInContent` and the rest), are refused with `slicc: refused: the response came from seven's origin`, and the CSS rules and styles from such a stylesheet reach the page with only their `styleSheetId` and `origin`. `Network.loadNetworkResource` (Chrome follows its redirects with the user's cookies) and `Page.captureSnapshot` (it serialises every frame) are refused on every session. [`cdp` (browser control)](#cdp-browser-control) has the details.
|
|
1693
|
+
- **Workers:** a touched dedicated, shared or service worker gets a guard session too (Network only, to see its imports' redirect chains), and its commands wait for it the same way, except `Runtime.runIfWaitingForDebugger` on a shared or service worker, which goes ahead so the worker can't stall on its own guard; its imports from before the guard was ready then count as unseen and can't be read. A worker script other than the worker's own whose chain that guard didn't see, or which touched a protected URL, can't be read. In pages, scripts and stylesheets loaded before the node attached are a known residual.
|
|
1694
|
+
- **Waiting for that guard:** page commands on a touched target's session wait while the node sets its guard up there. The wait fails closed after 30 s without progress (an answer or event on that target's own guard session, or the answer to the node's attach to it; answers and events elsewhere on the stream don't count) or 5 min in all, whichever comes first, and the refusal names the bound. At most 4096 commands or 64 MiB wait on a stream; more is `RESET ENOBUFS`.
|
|
1685
1695
|
- **Sessions the node doesn't know:** a page command whose `sessionId` the node hasn't seen on this stream (in a `Target.attachedToTarget`, in the answer to the page's `Target.attachToTarget` or `Target.attachToBrowserTarget`, or its own) is held, not forwarded, until the session appears, and is then decided as usual. Only messages on that session id count as progress. The same bounds and caps apply, and the refusal is `slicc: refused: slicc doesn't know which target this session belongs to: …`, naming the bound. A session that detached is refused at once, without the suffix. Commands without a `sessionId` are unaffected.
|
|
1686
1696
|
- **Auto-attached targets with no URL yet** (OOPIFs, popups) are held until the URL is known, then detached if protected. After 10 s with no URL they are treated as protected. The node's own setup connection ([§1.1](#11-local-setup-over-cdp)) isn't a `cdp` stream and is the one exception. [`cdp` (browser control)](#cdp-browser-control) has the details.
|
|
1687
1697
|
- **Protection follows the target's own effective origin. Execution contexts on a protected origin are hidden, not cut,** unless the context is the target's own main-frame default context; that one still cuts the target as above. The typical case is an extension's content-script isolated world (`chrome-extension://<id>`) on an ordinary page. The page stays usable, and the hidden world is invisible and out of reach.
|
|
@@ -1715,7 +1725,10 @@ On a [§1.3](#13-local-setup-without-cdp-loopback-signalling) loopback link, `un
|
|
|
1715
1725
|
- **Per connection:** the node opens `OPEN { "kind": "kernel", "port": N }` to the page, sends the bytes it has read, then pipes both ways.
|
|
1716
1726
|
- The node refuses port 9222 itself, before any OPEN.
|
|
1717
1727
|
- Its listener port joins the policy's protected ports.
|
|
1718
|
-
- **Binding:**
|
|
1728
|
+
- **Binding:** on macOS, where an unprivileged user may bind port 80 only on the wildcard address, a `permission denied` on `127.0.0.1` makes the node bind `0.0.0.0:<kernel port>` and reset every connection from a peer that isn't loopback, before reading from it.
|
|
1729
|
+
- With the default port 80 out of reach (Linux without `CAP_NET_BIND_SERVICE`, or taken), the node picks the first port in 17180–17189 it can bind, at startup.
|
|
1730
|
+
- **HELLO:** a local node with kernel services adds `"kernel": { "port": <n> }`, the port it listens on (80, a spare port or `--kernel-port`). The page should build kernel URLs from it: `http://<kernel port>.kernel.localhost/` for 80, `http://<kernel port>.kernel.localhost:<n>/` otherwise. A node without the field listens on 80, if at all.
|
|
1731
|
+
- If the port can't be bound, the node prints `kernel services are off: … try --kernel-port` and goes on without them.
|
|
1719
1732
|
- **Remote nodes** never listen.
|
|
1720
1733
|
|
|
1721
1734
|
#### 5.10 `fs` (cap `fs`), page → node: host folders over the link
|
|
@@ -2088,7 +2101,7 @@ CI installs a pinned release: `go install github.com/ai-ecoverse/slicc-cli/cmd/s
|
|
|
2088
2101
|
|
|
2089
2102
|
Go 1.26 and Node ≥ 24 (for the lint tools). `npm run build` writes `bin/slicc`. `npm run lint` runs the slicc lint tools, `gofmt`, `go mod tidy -diff`, `go vet` and golangci-lint. `npm test` runs the integration tests in `test/integration/`, which build the binary and drive it; run `npm ci` first, since the [Inside seven](#inside-seven) tests need the pinned `@ai-ecoverse/slicc-kernel`. Unit tests stay next to the code and out of git (`npm run test:unit`). `go build` and `npm run build` link no version, so these binaries report `dev`. `npm run dist` cross-builds all six targets into `artifacts/release/` with `SHA256SUMS`, unsigned. `npm run npm:stage` then stages the npm packages into `dist/npm/`, and `npm run npm:check` packs them with `npm publish --dry-run`.
|
|
2090
2103
|
|
|
2091
|
-
**Test shards.** CI splits the integration tests into shards with `scripts/test-shard.mjs <index>/<count> <go test args>`. The split is greedy by measured time: `test/integration/shard-times.json` holds each top-level test's seconds per platform (`darwin`, `linux`, `win32`), each test goes to the shard with the least time so far, and a test without a time counts as the platform's median. The last shard runs everything the others don't, so a test that isn't in any list yet still runs. CI has two matrices. **The light matrix** runs on pull requests, the merge queue (`merge_group`) and every push to `main`, which is the run the release waits for: the three Linux shards with `-race`, and
|
|
2104
|
+
**Test shards.** CI splits the integration tests into shards with `scripts/test-shard.mjs <index>/<count> <go test args>`. The split is greedy by measured time: `test/integration/shard-times.json` holds each top-level test's seconds per platform (`darwin`, `linux`, `win32`), each test goes to the shard with the least time so far, and a test without a time counts as the platform's median. The last shard runs everything the others don't, so a test that isn't in any list yet still runs. CI has two matrices. **The light matrix** runs on pull requests, the merge queue (`merge_group`) and every push to `main`, which is the run the release waits for: the three Linux shards with `-race`, and two shards each on macOS and Windows (`1/2` and `2/2`, each with a 20-minute `-timeout`) without `-race`, so a queue group or a push needs two macOS and two Windows test runners. With one leg each, both ran 19 to 23 of their 25 minutes, and a macOS leg timed out in a pull request. **The full matrix** runs in every release from `main`, on the commit it releases, beside the release build: `release.yml`'s `full` job calls `ci.yml` (`workflow_call` with `full: true`), which then runs three shards on Linux, three on Windows and four on macOS, whose runners vary the most (the same shard took from 400 s to 570 s) and run the guard vectors slowest, so each stays well inside its 15 minutes, every leg with `-race`, and the installer's macOS curl check too. On the org's hosted macOS pool the full matrix takes an hour or more, longer than merges often arrive: when `main` moves on meanwhile, that run's semantic-release skips as behind, and the newer commit's run releases (see [Releases](#releases)). Only one push run on `main` runs at a time, and only the newest push waits: a newer push replaces a pending one, never the one running, so a steady stream of merges queues at most two light matrices. **Node:** the `test` job runs on Node 26, in both matrices. On Node 24.21.0, V8 crashed the WASI kernel harness (`run.mjs`, the `TestWasi*` tests) about once in a hundred runs on Windows (`exit status 0xc0000005`, 15 crashes in 1600 runs against none in 1600 on Node 26), and occasionally on Linux and macOS (`Check failed: jit_page…`). Node 24, which npm users still run, stays covered by `installer-assets`: it stages and dry-runs the npm packages on Node 24, and runs the npm launcher tests (`TestNPM*` and `TestSliccy*`, `npx sliccy@next` among them) on it. The [guard vectors](https://github.com/ai-ecoverse/slicc-browsers#guard-vectors) (`TestCDPProtectVectorsPrompt` and `TestCDPProtectVectorsLagging`, one per detach variant) run on Linux in the light matrix, and on every OS in the full matrix: the light matrix's macOS and Windows legs skip them with `SLICC_GUARD_VECTORS=0`. Both leave out the C9 vectors (extension worlds; the `C9.` prefix in `guardSkipped` in `guardrunner_test.go`): on macOS their unguarded control often failed to reproduce the attack or hung in Chrome itself (`Runtime.getProperties` for minutes), so no row passed and the full matrix went red on a guard that held. `TestCDPHidesExtensionWorlds` (`protectworld_test.go`) still covers the extension-world guard. CI runs them with `-v`, so `node scripts/shard-times.mjs --run <CI run id>` refreshes the file from a finished `main` run's logs (`<platform>=<log file>` reads local `go test -v` output instead); a test the logs only show as skipped (the guard vectors on macOS and Windows, in a `main` run) keeps the time it had. Commit the result when tests get much slower or faster. `TestEachCIShardFitsItsTimeout` fails when a shard of either matrix adds up to more than three quarters of its `-timeout` by those times (without the guard vectors where it skips them), or when its job's `timeout-minutes` leaves less than 5 minutes past its `-timeout`: then add a shard. The `/3` and `/4` shards have 15 minutes: refreshed in October 2026, the times put each at 540 to 610 s, past three quarters of 12. Linux has three shards for that reason: with two, a shard ran 719 s of its 12 minutes, and the next run hit the timeout. When a shard does hit its `-timeout`, `go test` panics naming whichever test was running then, so `test-shard.mjs` adds a line with how long that test had run, what the finished tests took against the times' prediction, and the slowest of them. **Runners.** CI's first job, `runner`, picks where the Linux jobs run with `.github/runner.mjs`: the pool named by `vars.CERT_RUNNER` (now `cloud-run-job`, one Cloud Run task per job; unset, `ubuntu-latest`) for pushes, the release's full build (`workflow_run` and `workflow_dispatch`), same-repo pull requests and merge groups whose entries are all same-repo, and GitHub's hosted `ubuntu-latest` for fork pull requests, merge groups with a fork entry, and when the decider fails. It gives two labels through the same rule: `runner` for the heavy jobs (the Linux test shards and `installer-assets`) and `light` (`vars.CERT_LIGHT_RUNNER`, unset `ubuntu-latest`) for `lint`, `installer (ubuntu-curl)` and the aggregate `ci`, so where a fork gets `ubuntu-latest` it gets it for both. `vars.CI_RUNNER` forces one runner, for both labels, for every event, fork pull requests and merge groups included, so it is never a rollback; `vars.CERT_RUNNER` is the one pool variable, and changing it is the rollback, with every rule here still applied. The `runner` job itself runs hosted for fork pull requests, otherwise on `vars.CERT_LIGHT_RUNNER` (or `vars.CERT_RUNNER`); it loads `runner.mjs` from `main`, never from the code under test, which may be a fork's: a pull request's base commit, and for a merge group the head of `main` (`merge_group.base_ref`), not the group's parent commit (`base_sha`), which can be the speculative commit of the group ahead of it in the queue, with that group's pull requests in it. That keeps the decider's own answer trusted; a fork pull request that edits `ci.yml` itself is stopped only by the repository's approval rule for outside contributors' workflows and the pool's runner-group access. The Alpine installer check stays hosted (it needs Docker), and macOS and Windows always run hosted. In `release.yml`, the dispatch-only `ci` wait, the `fresh` check and the `report` job run on `vars.CERT_LIGHT_RUNNER`, as do the Linux legs of `smoke` (hosted for fork pull requests) and `install-smoke` on `vars.CERT_RUNNER`, each falling back to `ubuntu-latest`, and every job with `id-token: write` (`release`, `sliccy-next`) on a GitHub-hosted runner. No workflow names a pool literally.
|
|
2092
2105
|
|
|
2093
2106
|
**Test names: `--hosts <file>`** (test-only, left out of `--help`). `slicc attach`, `slicc open`, `slicc pair` and `slicc <join-url> follow` take a hosts file, so tests get deterministic names without public DNS. Each line is `<ip> <name> [<name>…]`, and `#` starts a comment. Names match case-insensitively, with or without a trailing dot, and a name on several lines gets every address, in file order. Wherever the node resolves a name (`resolve`, `tcp` by name, `http`, and the policy's [browser endpoints](#network-policy)), a name in the file gets the file's addresses of the requested family, possibly none, and never the system resolver's. Other names go to the system resolver. The policy applies afterwards as usual: `resolve` drops refused addresses, and a dial to a refused address is `EACCES`. `tcp` and `http` try the addresses in file order. A file that can't be read or parsed exits 2. A name mapped to a loopback address only helps the node's own dials, `http` and `tcp` by name: a seven kernel that gets a loopback address from `resolve` dials its own loopback, because loopback never goes out through an exit. For end-to-end exit tests, map test names to a non-loopback address of the machine.
|
|
2094
2107
|
|
|
@@ -2127,13 +2140,13 @@ The real-Chrome tests in `test/integration/` launch headless, so they run the ex
|
|
|
2127
2140
|
|
|
2128
2141
|
PRs merge through the merge queue. Only a pull request's newer push cancels its older CI run. Pushes to `main` share one concurrency group (`ci-push-refs/heads/main`) without `cancel-in-progress`: the running push run always finishes, and a newer push replaces the one still pending, which is cancelled and releases nothing. The newer run releases instead, and since a release takes every commit since the last tag, it still includes the replaced run's commits. Merge-queue runs each have their own group, per queue ref, and are never cancelled or replaced by another run. CI also runs on `merge_group`, and the queue requires one check, the `ci` job, which fails unless every other CI job passed. Merge-queue runs never start or satisfy a release: the release starts only from CI's `push` run on `main`.
|
|
2129
2142
|
|
|
2130
|
-
semantic-release cuts releases from `main` in `.github/workflows/release.yml
|
|
2143
|
+
semantic-release cuts releases from `main` in `.github/workflows/release.yml`. The workflow starts when the CI workflow's run for a push to `main` completes (`workflow_run`), and builds only when that run passed (the light matrix: the Linux shards with `-race` and the guard vectors, and two shards each on macOS and Windows) on the commit that run tested. A CI run that failed or was cancelled releases nothing; when a re-run of its failed jobs passes, the release follows by itself. When `main` has moved on by then, the checkout is behind `main`, so semantic-release (in `release-plan.mjs` and again in the release) skips without failing, and the newer commit's run releases. So that such a run doesn't wait for a macOS runner first, its `fresh` job on `cloud-run-bench` compares the tested commit with `git ls-remote` of `main` and skips the build, the full matrix and the release when `main` has moved; when it can't tell, they run and semantic-release decides. A manual run instead waits in the `ci` job for the CI workflow's `push` run on the same commit, and fails unless it passed. A pull request's dry run doesn't wait for CI; it runs alongside it.
|
|
2131
2144
|
|
|
2132
|
-
**
|
|
2145
|
+
**Each release passes its own full build.** Nothing that failed earlier holds a release up: a release needs only its own commit's results. On `main` (after a CI push run that `fresh` let through, or a manual run) two jobs start together: `full`, the full matrix on that commit (see [Development](#development)), whose test matrix fails fast (`fail-fast` is `inputs.full`), so the first failed leg cancels the others and frees their runners; and `build` on macOS, which plans, builds, signs and stages the release and uploads it as the `slicc-release` artifact (one tarball, so file modes survive). Nothing polls the run or cancels it: the build is short beside the matrix, and the light matrix's legs never fail fast, so a pull request still reports every failing leg. `smoke` runs each binary from the build on its own OS and CPU. The `release` job, on GitHub-hosted `ubuntu-latest`, publishes only when the build, the full matrix and every smoke test passed, so a failure in the full matrix fails the release. Then `report` (the only job besides `release` with `issues: write`, which semantic-release needs) runs `scripts/release-issue.mjs` whenever the full matrix ran, even in a cancelled run: when a job failed it opens an issue labelled `release-red` naming the run, the failed jobs and the commit, or comments on the one already open, so failures never pile up as duplicates; a run that only got cancelled (superseded or stopped by hand) reports nothing; a green run comments on the open issue and closes it. The next push to `main` builds and releases from scratch with every commit since the last tag; to retry the same commit, re-run the failed jobs or `gh workflow run release.yml --ref main`. Only a run on `main` publishes: anywhere else (a pull request, a manual run from another branch) the build is a dry run (`DRY_RUN` is true whenever `github.ref` isn't `refs/heads/main`), the full matrix, `release` and `report` don't run, and the attestation, semantic-release (the GitHub release and the npm packages), `go-warm.sh`, `install-smoke` and the `sliccy-next` recovery each also require `refs/heads/main` themselves. The build and the release run these steps:
|
|
2133
2146
|
1. `scripts/release-plan.mjs` asks semantic-release (dry run) for the next version, and stops when there is none.
|
|
2134
2147
|
2. `scripts/package.sh <version>` builds `darwin`, `linux` and `windows` × `amd64`, `arm64` with `CGO_ENABLED=0`, `-trimpath`, `-buildvcs=false` and `-X main.version=<version>`, and `slicc-wasip1-wasm.wasm` (`GOOS=wasip1 GOARCH=wasm`) the same way, with the Go toolchain `go.mod` names (see [Reproducing a release](#reproducing-a-release)). It signs and notarizes the darwin binaries and writes `SHA256SUMS`. The windows and linux binaries are not signed. Without the Apple signing secrets (forks, pull requests) it warns and leaves the darwin binaries unsigned.
|
|
2135
|
-
3. `scripts/npm-stage.mjs` stages `@ai-ecoverse/slicc` and the six platform packages into `dist/npm/`. It also stages the same launcher as `sliccy` (with two bins, `sliccy` and `slicc`, and a short preview README) into `dist/npm-next/`, as version `7.0.0-next.<version>`, with exact `optionalDependencies` on this release's platform packages. `npx sliccy@next` runs the bin named after the package, `sliccy`, and both bins run the same launcher. `--bin <name>` (repeatable) overrides the bins of any staged launcher. `--wasi` also stages `@ai-ecoverse/wasi-slicc` into `dist/npm/wasi-slicc/` from `slicc-wasip1-wasm.wasm` and `npm/wasi-slicc/host/slicc-host.mjs`, with README.md's [Inside seven](#inside-seven) section as its README; `--wasi-only` stages only that package.
|
|
2136
|
-
4. `actions/attest-build-provenance` attests every binary, the `.wasm` included, through `SHA256SUMS`.
|
|
2148
|
+
3. (still in `build`) `scripts/npm-stage.mjs` stages `@ai-ecoverse/slicc` and the six platform packages into `dist/npm/`. It also stages the same launcher as `sliccy` (with two bins, `sliccy` and `slicc`, and a short preview README) into `dist/npm-next/`, as version `7.0.0-next.<version>`, with exact `optionalDependencies` on this release's platform packages. `npx sliccy@next` runs the bin named after the package, `sliccy`, and both bins run the same launcher. `--bin <name>` (repeatable) overrides the bins of any staged launcher. `--wasi` also stages `@ai-ecoverse/wasi-slicc` into `dist/npm/wasi-slicc/` from `slicc-wasip1-wasm.wasm` and `npm/wasi-slicc/host/slicc-host.mjs`, with README.md's [Inside seven](#inside-seven) section as its README; `--wasi-only` stages only that package.
|
|
2149
|
+
4. The `release` job unpacks the build, checking its `dist/VERSION` against the planned version, and `actions/attest-build-provenance` attests every binary, the `.wasm` included, through `SHA256SUMS`.
|
|
2137
2150
|
5. semantic-release checks the version again (`dist/VERSION`, and `scripts/go-major.mjs`, see below), tags, creates the GitHub release with every binary and `SHA256SUMS`, then `scripts/npm-publish.mjs` publishes the platform packages and the launcher through npm trusted publishing (OIDC, with provenance), and then `sliccy` with `--tag next`. Versions already on npm are skipped. Before publishing `sliccy`, the script checks that every exact-pinned `optionalDependencies` version is on the registry, waits up to `SLICC_REGISTRY_WAIT_SECONDS` (default 900) for freshly published ones to appear, and fails otherwise (exit code 3), so a `sliccy` version never points at missing platform packages.
|
|
2138
2151
|
6. `scripts/go-warm.sh <version>`, once the tag is on `origin`, asks `proxy.golang.org` for the module at `v<version>` (`go list -m` with a scratch module cache) and asks `pkg.go.dev` to fetch it. The proxy fetch is retried briefly; the `pkg.go.dev` fetch is one attempt of at most 10 s, since the proxy fetch already gets the version indexed. Both only warn, and the step can't fail the release.
|
|
2139
2152
|
|
|
@@ -2174,11 +2187,11 @@ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, o
|
|
|
2174
2187
|
- **Never move or delete a pushed tag.** The checksum database would then reject that version for every user. A bad version gets a `retract` directive in `go.mod`, and a new release.
|
|
2175
2188
|
- **Major versions.** A module at v2 or above needs `/vN` at the end of its module path, or `go install …@latest` never sees it. `scripts/go-major.mjs <version>` fails the release when the version's major doesn't fit the module path in `go.mod` (no suffix allows v0 and v1, `/vN` allows only vN). `release-plan.mjs` runs the same check, so it fails before the build. A `BREAKING CHANGE` commit therefore needs a deliberate module-path change first.
|
|
2176
2189
|
|
|
2177
|
-
**Gate verdicts.** A gate's silence never reads as a pass. Each script that gates a merge or a release ends a pass with a `<name>: PASS …` line: `test-shard: PASS linux 1/2 ran 143 tests`, `go-major: PASS 1.4.0 (major 1) fits module path …`, `release-plan: PASS release 1.4.0` (or `no release due`), `npm-publish: PASS latest: published 7, 1 already published, 0 held by their gates, of 8 packages`, `sliccy-next: PASS sliccy@7.0.0-next.1.4.0`, `install-check: PASS all installer checks passed`,
|
|
2190
|
+
**Gate verdicts.** A gate's silence never reads as a pass. Each script that gates a merge or a release ends a pass with a `<name>: PASS …` line: `test-shard: PASS linux 1/2 ran 143 tests`, `go-major: PASS 1.4.0 (major 1) fits module path …`, `release-plan: PASS release 1.4.0` (or `no release due`), `npm-publish: PASS latest: published 7, 1 already published, 0 held by their gates, of 8 packages`, `sliccy-next: PASS sliccy@7.0.0-next.1.4.0`, `install-check: PASS all installer checks passed`, and `release-issue: PASS red; opened #N` (or `commented on #N`, `green; closed #N`, `cancelled (…) without a failure; nothing to report`). Every caller in `ci.yml`, `release.yml` and `.releaserc.json` runs the gate through `sh scripts/verdict.sh <name> <command…>`, which fails with the gate's exit code when it fails, and fails when it exits 0 without its `<name>: PASS` line, so an early exit, a skipped main block or empty output fails the step. `test-shard.mjs` also fails a shard that selects no tests, or whose `go test -v` runs none, and `npm-publish.mjs` fails on a directory with no packages. The `fresh` job sets `verdict` to `current`, `stale`, or `unknown` when `git ls-remote` can't read `main` (`fresh: PASS unknown (semantic-release decides)`, which refuses a release behind the remote anyway); a release after CI needs `current` or `unknown`, so a missing verdict releases nothing. The scripts find their main block with `realpath` on both `process.argv[1]` and the module's own path (`scripts/is-main.mjs`), so they also run through a symlinked checkout or script.
|
|
2178
2191
|
|
|
2179
2192
|
**Recovering `sliccy@next`.** When a release tagged and published its platform packages but `sliccy` didn't go out (a registry that lagged past the wait, say), `gh workflow run release.yml --ref main -f sliccy_next_only=true` publishes it without a new release. On `main` only, that run skips the release job and runs `scripts/sliccy-next.mjs`. The script takes the latest `vX.Y.Z` tag reachable from `HEAD`, stages `sliccy@7.0.0-next.<version>` with the launcher (`npm/slicc.js`) from that tag and exact `optionalDependencies` on that version's platform packages, and publishes it through `npm-publish.mjs` with `--tag next`. The same guards apply: the `SLICCY_NEXT_PUBLISH` gate, the tag and version checks, the registry check, and a version that is already published is skipped. Only the latest release can be recovered, so `next` never moves backwards.
|
|
2180
2193
|
|
|
2181
|
-
Every pull request's CI checks the packaging in its `installer-assets` job: an unsigned build of all six targets, the npm packages and `sliccy` (with `--tag next`) packed with `--dry-run`, and `scripts/sliccy-next.mjs --dry-run` (the recovery path against the latest real release, registry check included). The dry run doesn't wait for the latest release: when its platform packages aren't all served yet, it is still publishing (npm has taken 25 minutes), so it checks the release before it instead, with the usual wait, and fails when that one is missing too; only the real publish waits for the latest. The `installer` jobs then run `install.sh` against those assets with curl on Linux and with wget on Alpine; the macOS curl run is left out of pull requests, the merge queue and pushes to `main`, so each needs only one macOS runner, and runs in the
|
|
2194
|
+
Every pull request's CI checks the packaging in its `installer-assets` job: an unsigned build of all six targets, the npm packages and `sliccy` (with `--tag next`) packed with `--dry-run`, and `scripts/sliccy-next.mjs --dry-run` (the recovery path against the latest real release, registry check included). The dry run doesn't wait for the latest release: when its platform packages aren't all served yet, it is still publishing (npm has taken 25 minutes), so it checks the release before it instead, with the usual wait, and fails when that one is missing too; only the real publish waits for the latest. The `installer` jobs then run `install.sh` against those assets with curl on Linux and with wget on Alpine; the macOS curl run is left out of pull requests, the merge queue and pushes to `main`, so each needs only one macOS runner, and runs in the release's full matrix. A pull request that changes `release.yml`, `.releaserc.json`, or a script only the release runs (`scripts/package.sh`, `scripts/npm-*.mjs`, `scripts/release-issue.mjs`, `scripts/release-plan.mjs`, `scripts/sliccy-next.mjs`, `scripts/go-*`, `scripts/is-main.mjs`, `scripts/verdict.sh`) also runs the release workflow as a dry run: the same checks in the `build` job on macOS, and each binary run with `--version` on its own OS and CPU.
|
|
2182
2195
|
|
|
2183
2196
|
The workflow reads these repository secrets. Each group is optional: without the first the darwin binaries stay unsigned, without the second they are signed but not notarized:
|
|
2184
2197
|
- macOS signing: `APPLE_CERTIFICATE_BASE64`, `APPLE_DEVELOPER_CERTIFICATE_PASSWORD`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/slicc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.47.0",
|
|
4
4
|
"description": "SLICC's command line for seven: links a machine's network, compute and browsers to seven over WebRTC",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"node": ">=18"
|
|
19
19
|
},
|
|
20
20
|
"optionalDependencies": {
|
|
21
|
-
"@ai-ecoverse/slicc-darwin-arm64": "1.
|
|
22
|
-
"@ai-ecoverse/slicc-darwin-amd64": "1.
|
|
23
|
-
"@ai-ecoverse/slicc-linux-arm64": "1.
|
|
24
|
-
"@ai-ecoverse/slicc-linux-amd64": "1.
|
|
25
|
-
"@ai-ecoverse/slicc-windows-arm64": "1.
|
|
26
|
-
"@ai-ecoverse/slicc-windows-amd64": "1.
|
|
27
|
-
"@ai-ecoverse/slicc-jerk": "^1.
|
|
21
|
+
"@ai-ecoverse/slicc-darwin-arm64": "1.47.0",
|
|
22
|
+
"@ai-ecoverse/slicc-darwin-amd64": "1.47.0",
|
|
23
|
+
"@ai-ecoverse/slicc-linux-arm64": "1.47.0",
|
|
24
|
+
"@ai-ecoverse/slicc-linux-amd64": "1.47.0",
|
|
25
|
+
"@ai-ecoverse/slicc-windows-arm64": "1.47.0",
|
|
26
|
+
"@ai-ecoverse/slicc-windows-amd64": "1.47.0",
|
|
27
|
+
"@ai-ecoverse/slicc-jerk": "^1.1.0"
|
|
28
28
|
}
|
|
29
29
|
}
|