@ai-ecoverse/slicc 1.44.7 → 1.45.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.
Files changed (2) hide show
  1. package/README.md +37 -19
  2. package/package.json +7 -7
package/README.md CHANGED
@@ -145,7 +145,7 @@ Six's `--version`, `version`, `--profile` and `--mount` are seven flags too, and
145
145
 
146
146
  | slicc-node | `slicc` |
147
147
  | --- | --- |
148
- | `npx @ai-ecoverse/slicc-node`, which opens seven in the default browser | `slicc open` (or `slicc --default-browser`) does that, in any browser and without CDP (see [Linking seven in your everyday browser](#linking-seven-in-your-everyday-browser)). `npx sliccy@next`, or `slicc` from `curl -fsSL https://get.sliccy.ai/slicc \| sh` (see [Install with curl](#install-with-curl)). It launches or attaches to a Chromium browser over CDP and opens seven there (see [The default](#the-default-slicc)) |
148
+ | `npx @ai-ecoverse/slicc-node`, which opens seven in the default browser | `slicc open` (or `slicc --default-browser`) does that, without CDP, in Chrome, Edge or Firefox; Safari is expected not to work, untested (see [Linking seven in your everyday browser](#linking-seven-in-your-everyday-browser)). `npx sliccy@next`, or `slicc` from `curl -fsSL https://get.sliccy.ai/slicc \| sh` (see [Install with curl](#install-with-curl)). It launches or attaches to a Chromium browser over CDP and opens seven there (see [The default](#the-default-slicc)) |
149
149
  | `--page <url>`, `--origin <url>` | `--page <url>`, `--origin <origin>` |
150
150
  | `--port`, `--rotate-key`, `--ephemeral`, `--no-open` | not needed: there is no proxy port and no key. `slicc open` listens on 17117 or the next free port up to 17126, `slicc unpair` forgets paired sevens, and `slicc pair` doesn't open a browser |
151
151
  | `--mount <path>[:<name>][:ro]` | the same `--mount` (see [Host folders](#host-folders)). In seven the folder is `<device>/<name>` (`mount -t hostfs lars-mbp/project /mnt/p`) rather than `<name>` |
@@ -159,7 +159,7 @@ Six's `--version`, `version`, `--profile` and `--mount` are seven flags too, and
159
159
  | `startProxy`, `launchUrl`, `persistentKey` (JS) | no JS API |
160
160
 
161
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 falls back to the first way below by itself, and `--no-fallback` turns that off (see [The default](#the-default-slicc)). `slicc` links seven in one of three ways:
162
- - **Your everyday browser, any vendor:** `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)).
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.
165
165
  - **or a browser that is already running with CDP** (`--cdp`). That includes your everyday Chrome once its remote debugging is turned on at `chrome://inspect/#remote-debugging`. It is then listed as `wsOnly` (see [Browsers](#browsers)).
@@ -203,7 +203,7 @@ A profile that doesn't exist is an error (`SLICC six's Chrome profile doesn't ex
203
203
 
204
204
  **Output.** `slicc --profile six` skips the picker and prints plain lines (or NDJSON with `--json`). When it attaches, the `foreign-browser` notice and event follow `attached`, with `profile` `six` (or `six:<port>`): `slicc: using Chrome (profile six) at http://127.0.0.1:9222, which slicc didn't launch; opened a new seven tab. …`. A launch reports `launched` with `browser` `six`, `profile` `six` and `profileDir`. `slicc launch --profile six` prints `attached` and `detached` events when it attaches (see [`slicc launch`](#slicc-launch---browser-id---profile-name---url-url---headless---no-seven-flags---json)). Errors about six's profile go to telemetry as fixed messages, never with a path.
205
205
 
206
- **In the picker** a `Chrome · SLICC six profile` row (`… six profile <port>` for a port variant) appears when that profile exists, with `SLICC six's own profile, used in place`, below every other row. It is never the best match and never preselected: Enter on it needs the cursor moved there, or the row selected with space. Picking it does the same as `--profile six`. A six endpoint in that profile is shown only through its row.
206
+ **In the picker** a `six` row (`six:<port>` for a port variant) appears under `SLICC six profiles` when that profile exists, after the other sections (unless it was the last launch); the section header says it might lose data. It is never the best match and never preselected: Enter on it needs the cursor moved there, or the row selected with space. Picking it does the same as `--profile six`. A six endpoint in that profile is shown only through its row.
207
207
 
208
208
  ## Verifying a download
209
209
 
@@ -318,7 +318,7 @@ Release builds (a plain `X.Y.Z` version, linked in or, for `go install`, from th
318
318
  | `SLICC_DISCOVERY` | test-only: limits browser discovery, see [Development](#development) |
319
319
  | `SLICC_OPEN_COMMAND` | test-only: `slicc open` runs this program with the pairing URL as its only argument instead of opening the default browser; the integration tests always set it |
320
320
  | `SLICC_PROTECTED_PORTS` | test-only: `<first>-<last>` (or one port), more [debugging ports](#network-policy) denied in every mode on top of 9222-9229 (never instead of them), so tests can prove the range on fake ports without touching the real ones. A malformed value exits 1 |
321
- | `SLICC_CDP_GUARD_IDLE`, `SLICC_CDP_GUARD_LIMIT` | test-only: Go durations (such as `2s`) that shorten the [`cdp`](#cdp-browser-control) guard's idle bound (30 s) and outer bound (5 min) on a target's setup. A longer value counts as the default, and so does 0, a negative value or one that doesn't parse, so the variables can never widen or turn off the guard |
321
+ | `SLICC_CDP_GUARD_IDLE`, `SLICC_CDP_GUARD_LIMIT` | test-only: Go durations (such as `2s`) that shorten the [`cdp`](#cdp-browser-control) guard's idle bound (30 s) and outer bound (5 min) on a target's setup and on a session it doesn't know yet. A longer value counts as the default, and so does 0, a negative value or one that doesn't parse, so the variables can never widen or turn off the guard |
322
322
  | `SLICC_LOOPBACK_PORTS` | test-only: `<first>-<last>`, the port range of the [loopback listener](#linking-seven-in-your-everyday-browser) instead of 17117-17126 |
323
323
  | `SLICC_LINK_URL` | inside seven only: the link facade's address, `tcp://127.0.0.1:<port>` (see [Inside seven](#inside-seven)) |
324
324
  | `SLICC_USER_DATA_DIR`, `XDG_STATE_HOME` | read, as SLICC six reads them, only to find six's Chrome profile (see [Using SLICC six's Chrome profile](#using-slicc-sixs-chrome-profile)) |
@@ -369,18 +369,21 @@ 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 lists the ranking best first, one row each, with its state: `CDP :<port>` and `seven open` for a running endpoint, `installed · profile <name>` (or `new SLICC profile <name>` when the profile doesn't exist yet) for a browser slicc would launch, and `installed · Electron` for an Electron app. The first row is marked `best match`. A list longer than the terminal scrolls with the cursor.
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) and `SLICC six profiles` (re-use existing SLICC profile (might lose data), the one warning, in yellow), in the order their best row ranks. 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`), `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)`), 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
  | --- | --- |
376
376
  | ↑ ↓ (`k`, `j`) | move |
377
- | space | select or unselect the row; the first selected hosts seven, and the rest are `extra cdp` browsers, in the order selected |
378
- | Enter | go: with the selected rows, or with the highlighted row when nothing is selected |
377
+ | space | select or unselect the row (`●1` hosts seven, `●2`, … are `extra cdp` browsers, in the order selected); on a folded row, open or close it |
378
+ | Enter | go: with the selected rows, or with the highlighted row when nothing is selected; on a folded row with nothing selected, open or close it |
379
379
  | `p` | choose the highlighted browser's SLICC profile: its default, an existing profile in the profiles dir, or `new profile…` with a name (letters, digits, `.`, `_`, `-`) |
380
+ | `e`, `h` | open or close the Electron apps and the headless browsers |
380
381
  | Esc | back, in the profile chooser |
381
382
  | `q`, Ctrl-C | quit; in the dashboard, close down as described above |
382
383
 
383
- **The dashboard** follows: the linked browsers (launched or attached, their CDP port, hosts seven or extra, and `gone` while one doesn't answer), the seven tab, the link state (`waiting for seven`, `connecting`, `linked` with its uptime, or `reconnecting` after a link is lost, while slicc waits for seven to offer again), the capabilities offered, the policy summary, `compute ssh via <runner>` with `--runner`, the kernel services URL, stream counts per kind (open and total, never contents), the last status lines, and the update notice, which then isn't printed on stderr. `NO_COLOR` turns colors off.
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
+
386
+ **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.
384
387
 
385
388
  **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.
386
389
 
@@ -390,7 +393,7 @@ The cdp list follows the browsers: an extra that stops answering is taken off th
390
393
  - `attached`: `browser`, `name`, `version?`, `role`, `launched?` (`true` when this run launched it), `cdp`, `webSocketDebuggerUrl?`. For `seven` slicc is connected to it over CDP; an `extra` is listed for `cdp`.
391
394
  - `foreign-browser`: `browser`, `name`, `profile?`, `cdp`, `openedTab` (`true` when slicc opened the seven tab, `false` when one was open). It follows `attached` when the browser hosting seven is an endpoint slicc didn't launch and the user didn't choose it (see above), and always when it is SLICC six's Chrome, with `profile` `six` or `six:<port>`. The same line goes to stderr.
392
395
  - `linked`: `peer?` (the name in seven's HELLO), `tab?` (the seven tab's URL), `caps`, `policy` (the summary).
393
- - `link-lost`: `reason?` when slicc or seven closed the link (`slicc is exiting`, seven's `bye` reason, `replaced by a new offer`), or `message?` for a failure. seven sets the link up again after a reload, and a new `linked` follows.
396
+ - `link-lost`: `reason?` when slicc or seven closed the link (`slicc is exiting`, `slicc is relinking`, seven's `bye` reason, `replaced by a new offer`), or `message?` for a failure. seven sets the link up again after a reload, and a new `linked` follows.
394
397
  - `browser-gone`, `browser-back`: `browser`, `name`, `role`. A browser stopped answering and left the cdp list, or answers again and is back on it.
395
398
  - `browser-slow`: `browser`, `name`, `role`, `elapsed` (seconds). A browser slicc launched hasn't written its `DevToolsActivePort` yet, every 10 s from 10 s on (see [`slicc launch`](#slicc-launch---browser-id---profile-name---url-url---headless---no-seven-flags---json)). The dashboard and plain mode show it as `<name> is still starting (slow disk?), <n> s so far`.
396
399
  - `closed`: `browsers`, the ids slicc closed (always a list). It is the last event of every run that got past choosing its browsers.
@@ -585,7 +588,7 @@ slicc --default-browser [the same flags]
585
588
  slicc unpair [<fingerprint> | --all]
586
589
  ```
587
590
 
588
- Link spec §1.3. These commands link a seven that runs in the browser you use every day, any vendor's, without CDP or remote debugging, as slicc-node could. Only the setup is new: once the peer connection is up, the link is the same as for `slicc attach` (see [Link](#link)).
591
+ Link spec §1.3. These commands link a seven that runs in the browser you use every day (Chrome, Edge or Firefox; Safari is expected not to work until tested, see [§1.3](#13-local-setup-without-cdp-loopback-signalling)), without CDP or remote debugging, as slicc-node could. Only the setup is new: once the peer connection is up, the link is the same as for `slicc attach` (see [Link](#link)).
589
592
 
590
593
  - **`slicc open`**, and `slicc --default-browser` (the same command), print the pairing URL on stdout and open it in the default browser: `open` on macOS, `xdg-open` on Linux, and `rundll32 url.dll,FileProtocolHandler` on Windows. If that fails, slicc says `could not open the default browser (…); open the URL above yourself` and goes on.
591
594
  - **`slicc pair`** only prints the pairing URL, for a seven that's already open. Paste it into seven's address bar, or into its *Link this computer* field.
@@ -791,6 +794,7 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
791
794
  **Seven's origin is off limits.** The node relays CDP through a filter, so seven's browser control never navigates to, opens or attaches to a target on seven's own origin, and never reads or writes its cookies or storage. That holds for every listed browser, not only the one seven runs in. What is protected comes from [slicc-browsers](https://github.com/ai-ecoverse/slicc-browsers#protected-origins)' `protectedOrigins`: every `sliccy.ai` host (the apex and `www` included, any port, `https` and `http`), `vault.sliccy.ai` by name (the vault is never a CDP target for seven's tools), `blob:`, `filesystem:` and `view-source:` URLs by the URL inside them, `devtools:` URLs, and the extra origins: `--origin` for `attach`, and `--origin` and the `--page` origin for `slicc`. `follow` has none. `*.kernel.localhost` is not protected. URLs are parsed the way the browser parses them (WHATWG), and the node forwards its own re-encoding of each command, so the browser runs exactly what was checked: a key given twice counts once, as its last value, and a message that isn't a JSON object with an integer `id` and a string `method` is refused (or dropped, without an `id`).
792
795
  - **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*).
793
796
  - **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
+ - **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)).
794
798
  - **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; `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*).
795
799
  - **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.
796
800
  - **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`.
@@ -813,6 +817,10 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
813
817
  - **Outer bound:** a setup still under way 5 min after it started fails, whatever its progress.
814
818
  - **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`.
815
819
  - **Caps:** more than 4096 waiting commands, or 64 MiB of them, on one stream reset it with `ENOBUFS`.
820
+ - **Sessions the node doesn't know.** A page command with a `sessionId` the node hasn't seen on this stream is never forwarded as it stands. The node knows a session from a `Target.attachedToTarget` it relayed or held, from the answer to the page's `Target.attachToTarget` (for a target it knows) or `Target.attachToBrowserTarget`, and from its own sessions. Commands on an unknown session wait, in order, until the session appears, then any guard setup on its target, and are then decided as usual. So a browser that answers an attach before it announces the session can't open a gap.
821
+ - **Progress** for an unknown session is only a message from the browser on that session id. The browser answering other commands doesn't count, because it says nothing about this session. The bounds are a target's setup bounds (30 s idle, 5 min outer, checked every quarter of the shorter), and so are the caps.
822
+ - **Refusals:** a session that never appears is refused with `slicc: refused: slicc doesn't know which target this session belongs to: its guard made no progress in 30s` (or `…: its guard wasn't ready in 5m0s`). A session the node saw detach (`Target.detachedFromTarget`; it remembers the last 4096) is refused at once with `slicc: refused: slicc doesn't know which target this session belongs to`. Another stream's session is unknown on this one, so it waits for the idle bound before it's refused.
823
+ - Commands without a `sessionId` don't wait.
816
824
  - In touched targets, service workers are bypassed for every request, not only for protected ones.
817
825
  - The guard covers every frame that shares the touched target's process. A cross-site iframe's navigation pauses in its parent's target, before the iframe becomes a target of its own, with the frame's id as `frameId` (the id the iframe target gets later). A popup's request pauses in the popup's target, with its `targetId` as `frameId`. A redirect into a protected origin pauses again at the request stage, with `redirectedRequestId`, even when the first URL didn't match.
818
826
  - The page's own `Fetch.enable` on a page session still works. Chrome chains the interceptors of a target's sessions, the most recently enabled first, so the page's interceptor could see a protected document's request before the node's does, or after the node failed it, at the response stage. The node fails such a request itself, and the page never sees it (see *Paused requests to seven* above).
@@ -981,7 +989,7 @@ The watcher per OS:
981
989
  - **macOS:** one FSEvents stream on the folder (`FSEventStreamCreate` with file events, through purego, so the binary stays free of cgo). It holds no descriptor on anything in the tree and never follows symlinks, so tree size doesn't matter. A dropped or coalesced batch (`MustScanSubDirs`) is an `all`, and a folder that is moved or removed (`RootChanged`) is lost until it comes back.
982
990
  - **Windows:** one `ReadDirectoryChangesW` on the folder with `bWatchSubtree`, which is what libuv does. A handle on every directory would stop Windows from renaming any of their parents.
983
991
 
984
- **Mount updates** (page cap `mounts`). When folders are added or removed while a link is up (`fs.Server.Add` and `Remove`, for the TUI), the node opens `OPEN { "kind": "mounts", "mounts": [ … ] }` on the control channel, with the full list in HELLO's shape. It replaces the previous list, and there are no deltas. The page answers OPENED `{}` and END, and the node sends END. Updates go one at a time per link, and one the page doesn't finish within 10 s is reset with `ETIMEDOUT`. A removed folder's handles close (`EBADF` if the name comes back), its operations are `ENOENT`, and a watch stream covering it ends with `RESET ENOENT: no such folder`. `--mount` alone never changes the set.
992
+ **Mount updates** (page cap `mounts`). When folders are added or removed while a link is up (`fs.Server.Add` and `Remove`, from the TUI's capabilities screen; `fs.Server.Live` says whether a linked page takes them), the node opens `OPEN { "kind": "mounts", "mounts": [ … ] }` on the control channel, with the full list in HELLO's shape. It replaces the previous list, and there are no deltas. The page answers OPENED `{}` and END, and the node sends END. Updates go one at a time per link, and one the page doesn't finish within 10 s is reset with `ETIMEDOUT`. A removed folder's handles close (`EBADF` if the name comes back), its operations are `ENOENT`, and a watch stream covering it ends with `RESET ENOENT: no such folder`. `--mount` alone never changes the set.
985
993
 
986
994
  **Errors.** The code is the POSIX name, `EIO` when there is none. The message is libuv's lower-case text for it (`no such file or directory`), or slicc's own for the cases it names (`path escapes the folder`, `read-only folder`, `file changed since it was opened`, `the folder root cannot be moved or removed`). It never contains a host path. Windows errors are mapped the way libuv maps them, for example `ERROR_ACCESS_DENIED` to `EPERM` and `ERROR_SHARING_VIOLATION` to `EBUSY`.
987
995
 
@@ -1387,7 +1395,7 @@ The scenario: `slicc` on a laptop launches Chrome there. seven runs on a desktop
1387
1395
 
1388
1396
  #### 1.3 Local setup without CDP: loopback signalling
1389
1397
 
1390
- The scenario: seven is open in the user's **everyday browser** (any vendor, no CDP, no remote debugging), on the same machine as `slicc`, as slicc-node allowed. Only the signalling and trust setup are new. Once the peer connection is up, the link is [§2](#2-channels)–[§5](#5-stream-kinds) unchanged.
1398
+ The scenario: seven is open in the user's **everyday browser** (Chrome, Edge or Firefox; no CDP, no remote debugging), on the same machine as `slicc`, as slicc-node allowed. Only the signalling and trust setup are new. Once the peer connection is up, the link is [§2](#2-channels)–[§5](#5-stream-kinds) unchanged.
1391
1399
 
1392
1400
  **Pairing and trust**
1393
1401
  - **What anchors trust:** two pinned certificates.
@@ -1416,7 +1424,7 @@ The scenario: seven is open in the user's **everyday browser** (any vendor, no C
1416
1424
  **Local Network Access**
1417
1425
  - Chrome's LNA prompt appears on seven's first request to `127.0.0.1`. seven only makes loopback requests after a pairing [Link] click, or on load when it has a pin, so users who never pair are never prompted.
1418
1426
  - If the permission is denied, the page says how to allow it in site settings.
1419
- - **Mixed content:** `http://127.0.0.1` is potentially trustworthy, so Chrome, Edge, Firefox and Safari allow it from https pages. bios CI tests Chrome and Firefox; Safari is checked by hand before release. Where a browser blocks it, the page offers the tray path ([§1.2](#12-remote-setup-over-the-tray-hub)).
1427
+ - **Mixed content:** `http://127.0.0.1` is potentially trustworthy, so Chrome, Edge and Firefox allow it from https pages; bios CI tests Chrome and Firefox. WebKit as tested (Playwright's build) blocks `http://127.0.0.1` and `http://localhost` from an https page as mixed content, so the request never reaches slicc and this setup doesn't work there. Safari is assumed to behave the same until it is checked on a real Safari. In such a browser the page says it can't pair there and offers the tray path ([§1.2](#12-remote-setup-over-the-tray-hub)), or Chrome, Edge or Firefox.
1420
1428
 
1421
1429
  **The link in this mode**
1422
1430
  - **ICE:** host candidates on loopback only, as in [§1.1](#11-local-setup-over-cdp).
@@ -1645,7 +1653,17 @@ On a [§1.3](#13-local-setup-without-cdp-loopback-signalling) loopback link, `un
1645
1653
  - **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.
1646
1654
  - **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.
1647
1655
  - **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 (any answer from the browser on the stream's socket, the node's own `Browser.getVersion` probe included, or any event for that target or its sessions) 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`.
1656
+ - **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.
1648
1657
  - **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.
1658
+ - **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.
1659
+ - **Hidden:** the node learns every context of a touched target on its own guard session, whatever the page enabled. It drops these events from the stream: the world's `Runtime.executionContextCreated` and `executionContextDestroyed`, its `consoleAPICalled`, `exceptionThrown` and `bindingCalled`, and `Debugger.scriptParsed`/`scriptFailedToParse` for scripts in it. It also drops `Debugger.breakpointResolved` in those scripts. A `Debugger.paused` whose top frame is in the world is dropped, and the node resumes the target. The world's frames lower in the stack are removed from the call frames.
1660
+ - **Refused** with `slicc: refused: that execution context belongs to a browser extension`: any command that names the world through a `contextId` or `executionContextId`, `uniqueContextId`, an `executionContextName` the world has, a remote `objectId` or `*ObjectId` (call arguments included) or `callFrameId` whose injected-script part is the world, or the `scriptId` of a script in it. So `Runtime.evaluate`, `callFunctionOn`, `compileScript`, `runScript`, `globalLexicalScopeNames`, `queryObjects`, `getProperties`, `addBinding`, `Debugger.evaluateOnCallFrame`, `setVariableValue`, `setScriptSource` and `DOM.resolveNode` there are all refused. The default world, and isolated worlds the page creates itself, are unaffected. On page-like sessions, a `Runtime.addBinding` with neither `executionContextId` nor `executionContextName` is sent with `executionContextName: ""`, so it reaches every frame's main world now and after reloads, but no isolated world.
1661
+ - **Lifetime:** a hidden world is forgotten only when Chrome reports it gone (`executionContextDestroyed`, or `executionContextsCleared` on a session that reported it), never to make room. A target with more than 4096 live hidden worlds is refused every command there that names an execution context, object, call frame, script or context name, until its contexts are cleared.
1662
+ - **Heap profiling:** a page's heap snapshot covers its whole renderer, other worlds included. So on page, iframe, background page and webview sessions every `HeapProfiler` command except `enable`, `disable`, `collectGarbage` and `getHeapObjectId` is refused with `slicc: refused: HeapProfiler reads the page's whole renderer, browser extensions' worlds included`, whether or not an extension is there.
1663
+ - **Breakpoint conditions** run in whatever world the script belongs to, and a breakpoint by URL outlives reloads. So on those sessions a `Debugger.setBreakpointByUrl` with a `condition` needs a non-empty `url`, which can't name a protected script (see above). One by `urlRegex` or `scriptHash` or with an empty `url` is refused with `slicc: refused: a breakpoint condition that could run in a browser extension's world needs a url naming the page's own script`. Breakpoints without a condition are allowed; a pause they cause in a hidden world is resumed out of sight.
1664
+ - **Profiles, coverage, stacks and initiators** can name any script in the isolate. So the node scrubs every message it sends the page, events and command results alike, outside opaque values (`value`, `preview`, `customPreview`, `arguments`, `returnValue`). Any `callFrames` list loses the frames on a protected URL or in a hidden world's script. That covers console, exception, pause, async, node-creation and initiator stacks, whatever the method. A coverage or type-profile list (entries with `scriptId` and `functions` or `entries`) also loses its entries for scripts without a URL. A profile node's `callFrame` in such a script becomes `(program)` and loses its `positionTicks`. An `initiator` on a protected `url` loses that `url` and its position.
1665
+ - **Messages without a context:** on page-like sessions, the node drops `Console.messageAdded` whose `url` is empty or protected. It drops `Log.entryAdded` on a protected `url` or with a stack through such a frame. It drops `Profiler.consoleProfileStarted` and `consoleProfileFinished` altogether: they name no context, so the node can't tell whose `console.profile` they are.
1666
+ - **Requests and sockets a hidden world starts:** a `Network.requestWillBeSent`, `webSocketCreated` or `webTransportCreated` whose initiator stack runs through a frame on a protected URL or in a hidden script, or whose initiator `url` is protected, loses those frames and that URL. The node treats it like a request to a protected origin: cookie headers are stripped, and its bodies are refused.
1649
1667
 
1650
1668
  #### 5.8.1 `browsers` updates (page cap `browsers`), node → page
1651
1669
  - **When:** the browser list changes while a link is up (a browser quits, or the TUI adds one). The node then opens `OPEN { "kind": "browsers", "browsers": [ … ] }` on the control channel, carrying the **full** current list in HELLO's shape. It replaces the previous list; there are no deltas.
@@ -2020,11 +2038,11 @@ CI installs a pinned release: `go install github.com/ai-ecoverse/slicc-cli/cmd/s
2020
2038
 
2021
2039
  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`.
2022
2040
 
2023
- **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 one leg each on macOS and Windows (shard `1/1`) without `-race` (about 25 minutes), so a queue group or a push needs only one macOS test runner. **The full matrix** runs in the Nightly workflow (`.github/workflows/nightly.yml`), once a day (03:17 UTC) and on `workflow_dispatch`, on the head of `main` only: its `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 12 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, so a push run that waited for it was stale by the time it passed and nothing released; a red nightly instead blocks the next release (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. 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`. 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); commit the result when tests get much slower or faster. `TestEachCIShardFitsItsTimeout` fails when a shard that runs with `-race` and the guard vectors adds up to more than three quarters of its `-timeout` by those times: then add a shard. Linux has three 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 nightly (`schedule` 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 `nightly` gate run on `vars.CERT_LIGHT_RUNNER`, as does the nightly's `report` job, 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.
2041
+ **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 one leg each on macOS and Windows (shard `1/1`) without `-race` (about 25 minutes), so a queue group or a push needs only one macOS test runner. **The full matrix** runs in the Nightly workflow (`.github/workflows/nightly.yml`), once a day (03:17 UTC) and on `workflow_dispatch`, on the head of `main` only: its `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 12 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, so a push run that waited for it was stale by the time it passed and nothing released; a red nightly instead blocks the next release (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`. 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); commit the result when tests get much slower or faster. `TestEachCIShardFitsItsTimeout` fails when a shard that runs with `-race` and the guard vectors adds up to more than three quarters of its `-timeout` by those times: then add a shard. Linux has three 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 nightly (`schedule` 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 `nightly` gate run on `vars.CERT_LIGHT_RUNNER`, as does the nightly's `report` job, 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.
2024
2042
 
2025
2043
  **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.
2026
2044
 
2027
- Front ends and capabilities follow a node's links with `node.Node.Subscribe(func(node.Event)) (unsubscribe func())`. Events: `connecting` (an offer was answered), `linked` (the peer's HELLO arrived, in `Peer`), `link-down` (`Reason` when the node closed the link: seven's `bye` reason, `replaced by a new offer` or `slicc is exiting`; otherwise `Err` for a failure; a failed answer has no `Link`), and `stream-open` and `stream-close` for each page-opened stream, with its kind. Every event but a failed answer carries its `*link.Link`. A link's `link-down` comes before the next link's `connecting`, but its streams' `stream-close` can come after its `link-down`. Events are delivered synchronously on the node's and the link's goroutines, so a subscriber must return quickly and never block; one that needs to wait hands the event to its own goroutine.
2045
+ Front ends and capabilities follow a node's links with `node.Node.Subscribe(func(node.Event)) (unsubscribe func())`. Events: `connecting` (an offer was answered), `linked` (the peer's HELLO arrived, in `Peer`), `link-down` (`Reason` when the node closed the link: seven's `bye` reason, `replaced by a new offer`, or the reason of its own `bye`: `slicc is exiting`, or the `node.Bye` that cancelled `Serve`'s context as its cause, such as `slicc is relinking`; otherwise `Err` for a failure; a failed answer has no `Link`), and `stream-open` and `stream-close` for each page-opened stream, with its kind. Every event but a failed answer carries its `*link.Link`. A link's `link-down` comes before the next link's `connecting`, but its streams' `stream-close` can come after its `link-down`. Events are delivered synchronously on the node's and the link's goroutines, so a subscriber must return quickly and never block; one that needs to wait hands the event to its own goroutine.
2028
2046
 
2029
2047
  **Test isolation: `SLICC_DISCOVERY`** (test-only). When it is set, browser discovery (`slicc browsers`, the default flow, and `browser.Discover` in the same process) never probes ports 9222–9229, never reads the process list, and finds only `--cdp` URLs, plus:
2030
2048
  - `isolated`, or any value not listed here: nothing else;
@@ -2066,7 +2084,7 @@ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, o
2066
2084
  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.
2067
2085
  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.
2068
2086
  4. `actions/attest-build-provenance` attests every binary, the `.wasm` included, through `SHA256SUMS`.
2069
- 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, so a `sliccy` version never points at missing platform packages.
2087
+ 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.
2070
2088
  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.
2071
2089
 
2072
2090
  **Certification in the pull request** (`.github/workflows/cert-review-pr.yml`, `tools/cert-review/`). Every same-repo pull request gets the commit status `cert-review` on its head, which the merge queue requires beside `ci`. Fork pull requests get none: only Lars merges one. Every tool runs from the pull request's base commit, main's copy, never from the code under test, which is only built and tested; the review uses `refs/pull/N/merge` and records its first parent and the head.
@@ -2076,9 +2094,9 @@ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, o
2076
2094
  - **rebuild** (heavy runner) runs [the recipe](#reproducing-a-release) on a `git archive` of the merge commit, in another path on another machine; every release file must be byte-identical to build's.
2077
2095
  - **delta** names the Go tests the pull request adds or changes in `test/integration/*_test.go` (the `func Test…` a hunk falls in). A changed helper counts as changing every test in its file; a helper in a file with no tests is a test gap (`helper <name>`), never a guess.
2078
2096
  - **suite** (heavy runner): the candidate leg runs the whole integration suite on the merge commit on Linux (`go test -json`); the base leg is the NEGATIVE: main's tree with the pull request's whole `test/` diff applied, running only its tests, against main's own binary. A fix whose test fails there and passes on the candidate is `negative`; passing on both is `no-negative`; not compiling or skipping on main is `prev-not-run`. Gaps are known issues, never a FAIL by themselves.
2079
- - **bios** runs seven's own canary on the candidate wasm when classify sees `slicc-wasip1-wasm.wasm` or the `@ai-ecoverse/wasi-slicc` tarball differ from main's build: it calls slicc-bios's `canary.yml` as a reusable workflow, pinned to the commit in `tools/cert-review/bios.json`, with build's `cert-build-wasi-slicc-<v>` artifact as the candidate (strict binding: its run, artifact id, artifact digest and tarball sha256, never its name alone) and the shards that install the in-kernel slicc (`follow-chat`, whose `follow-slicc.test.mjs` follows another seven). It passes no secret. The canary's shards run on hosted runners.
2097
+ - **bios** runs seven's own canary on the candidate wasm when classify sees `slicc-wasip1-wasm.wasm` or the `@ai-ecoverse/wasi-slicc` tarball differ from main's build: it calls slicc-bios's `canary.yml` as a reusable workflow, pinned to the commit in `tools/cert-review/bios.json`, with build's `cert-build-wasi-slicc-<v>` artifact as the candidate (strict binding: its run, artifact id, artifact digest and tarball sha256, never its name alone) and the shards that install the in-kernel slicc (`follow-chat`, whose `follow-slicc.test.mjs` follows another seven). It passes no secret. The canary's shards run on hosted runners. It runs only while the repository variable `CERT_BIOS_CANARY` is `on`, and that variable stays unset until [slicc-bios#599](https://github.com/ai-ecoverse/slicc-bios/issues/599) is fixed: the canary binds an artifact candidate's tarball, but the in-kernel pnpm still resolves its version on the npm registry, so every new wasm would read as a regression. While it's off, record gets `--bios-off ai-ecoverse/slicc-bios#599`, and the line says `bios: off (ai-ecoverse/slicc-bios#599), wasm changed` (an unchanged wasm still says `bios: not needed, wasm unchanged`).
2080
2098
  - **review** (light runner) runs `claude-code-action/base-action` on that evidence with `AWS_BEARER_TOKEN_BEDROCK`, the workflow's only secret, in that step only; without the key the step skips and the line says `INCOMPLETE: no reviewer key`. Its read-only `gh` calls use the runner's own `gh`; nothing downloads one, and a runner without it makes the review INCOMPLETE. The reviewer writes only `verdict.json` and `comment.md`, in a folder of its own: a `fail` can add a defect, but nothing it writes can clear one decided in code.
2081
- - **record** (code only) recomputes the findings from the code-made evidence (delta, suite and rebuild artifacts), re-hashes the build artifact against build's `SHA256SUMS` hash, checks the rebuild, reads CI's results on the head (the macOS, Windows and Linux test legs and `ci`, waiting up to an hour), bios's canary report (`bios-canary`, by the id and digest the call returned, its pin, this run, this build's tarball and every pinned shard) and the verdict. A byte or reproducibility mismatch, a failing canary test, or the reviewer's fail, is `cert-ci: FAIL` (failure); missing evidence (a known issue that can't be read or doesn't exist included, and a canary that left no result, missed a shard, or was flaky) is `cert-ci: INCOMPLETE` (error: re-run the failed jobs); otherwise `certified ai-ecoverse/slicc-cli PR #N at <head>, build <v> SHA256SUMS sha256=<hex> (wasm sha256=<hex>; bios: follow-chat pass at slicc-bios@<sha> or bios: not needed, wasm unchanged; reproducible: …; ci: …; known: …; test gaps: …)`. **report** posts it as the status and a sticky comment.
2099
+ - **record** (code only) recomputes the findings from the code-made evidence (delta, suite and rebuild artifacts), re-hashes the build artifact against build's `SHA256SUMS` hash, checks the rebuild, reads CI's results on the head (the macOS, Windows and Linux test legs and `ci`, waiting up to an hour), bios's canary report (`bios-canary`, by the id and digest the call returned, its pin, this run, this build's tarball and every pinned shard) and the verdict. A byte or reproducibility mismatch, a failing canary test, or the reviewer's fail, is `cert-ci: FAIL` (failure); missing evidence (a known issue that can't be read or doesn't exist included, and a canary that left no result, missed a shard, or was flaky) is `cert-ci: INCOMPLETE` (error: re-run the failed jobs); otherwise `certified ai-ecoverse/slicc-cli PR #N at <head>, build <v> SHA256SUMS sha256=<hex> (wasm sha256=<hex>; bios: follow-chat pass at slicc-bios@<sha>, bios: not needed, wasm unchanged, or bios: off (<issue>), wasm changed; reproducible: …; ci: …; known: …; test gaps: …)`. **report** posts it as the status and a sticky comment.
2082
2100
  - **No silent pass.** Every step that runs a cert tool requires that tool's verdict: plan's `build=`, a posted status from classify and the lookup, a `certified …` line and a `success` record before report posts success, and in `cert-verify.yml` the reproduction's and the darwin check's verdict lines, verify's `certified slicc-cli@<version> binaries …` line and the notes job's `notes: …` line. A tool that exits 0 and prints nothing fails its step, and the status is `error`.
2083
2101
  - **Evidence by artifact id and digest.** Every job that uploads evidence hands on the artifact's id and digest as job outputs, one pair per matrix side. Every consumer downloads by `artifact-ids` with `digest-mismatch: error`, never by name or pattern, and no upload overwrites another. A download that failed counts as no evidence. The review and record take from each producer's artifact only its own files (`gather.mjs`: the candidate suite gives `candidate.*`, main's gives `prev.*`), so one job can't plant another's evidence. classify, record and cert-verify's verify list the run's artifacts: an artifact under a declared id with another name or digest, or a `cert-*` artifact from this attempt that no producer declared, is `cert-ci: INCOMPLETE` (classify posts `error`). Artifacts left by an earlier attempt of the run are ignored.
2084
2102
  - **merge group**: CI's deterministic suites, plus a code-only lookup that each pull request in the group has a green status on the head being merged; otherwise red, naming it. No build, review or model.
@@ -2110,7 +2128,7 @@ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, o
2110
2128
 
2111
2129
  **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.
2112
2130
 
2113
- 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 `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 nightly'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/nightly-gate.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 on the release's macOS runner, and each binary run with `--version` on its own OS and CPU.
2131
+ 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 nightly'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/nightly-gate.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 on the release's macOS runner, and each binary run with `--version` on its own OS and CPU.
2114
2132
 
2115
2133
  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:
2116
2134
  - 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.44.7",
3
+ "version": "1.45.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.44.7",
22
- "@ai-ecoverse/slicc-darwin-amd64": "1.44.7",
23
- "@ai-ecoverse/slicc-linux-arm64": "1.44.7",
24
- "@ai-ecoverse/slicc-linux-amd64": "1.44.7",
25
- "@ai-ecoverse/slicc-windows-arm64": "1.44.7",
26
- "@ai-ecoverse/slicc-windows-amd64": "1.44.7",
21
+ "@ai-ecoverse/slicc-darwin-arm64": "1.45.0",
22
+ "@ai-ecoverse/slicc-darwin-amd64": "1.45.0",
23
+ "@ai-ecoverse/slicc-linux-arm64": "1.45.0",
24
+ "@ai-ecoverse/slicc-linux-amd64": "1.45.0",
25
+ "@ai-ecoverse/slicc-windows-arm64": "1.45.0",
26
+ "@ai-ecoverse/slicc-windows-amd64": "1.45.0",
27
27
  "@ai-ecoverse/slicc-jerk": "^1.0.0"
28
28
  }
29
29
  }