@ai-ecoverse/slicc 1.44.0 → 1.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +14 -8
  2. package/package.json +7 -7
package/README.md CHANGED
@@ -765,7 +765,7 @@ A page RESET or a closed link kills the jerk it started.
765
765
 
766
766
  ### `cdp` (browser control)
767
767
 
768
- > **Warning:** a seven with the `cdp` capability has full control of every listed browser: its tabs, cookies, stored passwords and logged-in sessions, files it can download, and anything a page in it can reach. That includes the browser seven runs in (`seven: true`), whether slicc launched it or not: with `slicc` or `slicc attach` on your everyday browser, seven can drive all of that browser, without a further prompt. List only browsers you would let that seven use as you. For `follow` that is a seven on another machine. The one exception is seven's own origin, which the node keeps out of reach (see below).
768
+ > **Warning:** a seven with the `cdp` capability has full control of every listed browser: its tabs, cookies, stored passwords and logged-in sessions, files it can download, and anything a page in it can reach. That includes the browser seven runs in (`seven: true`), whether slicc launched it or not: with `slicc` or `slicc attach` on your everyday browser, seven can drive all of that browser, without a further prompt. List only browsers you would let that seven use as you. For `follow` that is a seven on another machine. The exceptions are seven's own origin and the browser's privileged pages (its settings and password manager, extension pages such as a password manager's or a wallet's, and `file://`), which the node keeps out of reach (see below).
769
769
 
770
770
  Only browsers the user named are listed: the one seven runs in, `--cdp` and `--cdp-extra` for `attach`, the extras picked with `--browser` or in the picker for `slicc`, and `--cdp` for `follow`. A browser discovery found but nobody picked is not.
771
771
 
@@ -788,6 +788,7 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
788
788
  - RESET from the page, or the link going down: the socket is closed at once.
789
789
 
790
790
  **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`).
791
+ - **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:`, 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`, `edge://newtab`, `brave://newtab`, `opera://startpage`). 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*).
791
792
  - **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.
792
793
  - **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.
793
794
  - **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.
@@ -815,11 +816,12 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
815
816
  - If the page asked for `waitForDebuggerOnStart`, the node runs `Runtime.runIfWaitingForDebugger` on it itself.
816
817
  - 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.
817
818
  - 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).
818
- - **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`, or `about:blank`). 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.
819
+ - **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 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.
819
820
  - **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. Other strings (cookie and storage values, DOM attributes) are the page's data and are left alone, and so is everything inside a `value`, `preview`, `customPreview`, `arguments` or `returnValue` (evaluation results and their previews).
820
821
  - **Limits.**
821
822
  - 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.
822
- - 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.
823
+ - 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).
824
+ - The network guard fails only `http` and `https` documents. Chrome itself keeps a web page from navigating to `chrome:`, `chrome-untrusted:`, `devtools:` and `file:` URLs, but not to an extension's web-accessible pages. A touched page that sends itself to one with a script or a link is detached after the commit, as above. The node detaches after the commit, as above, and commands that were already on their way may still have run in the new page.
823
825
  - If the node loses its own session (`ECONNRESET: slicc lost track of the browser's targets`), it refuses every command and resets the stream unless the browser closes within 2 s.
824
826
  - The node's own setup connection (see [Local setup](#local-setup-slicc-attach)) is not a `cdp` stream and isn't filtered.
825
827
 
@@ -827,7 +829,7 @@ Only browsers the user named are listed: the one seven runs in, `--cdp` and `--c
827
829
 
828
830
  **`browsers` updates** (page cap `browsers`), node → page. When the list changes while a link is up and the page's HELLO has `browsers` in `caps`, the node opens `OPEN { "kind": "browsers", "browsers": [ … ] }` on the control channel with the full current list in HELLO's shape. It replaces the previous list; there are no deltas. The page answers OPENED `{}` and END, and the node sends END. Updates are sent one at a time per link, a change that comes while one is in flight is sent after it, with the list as it is then, and an update the page doesn't finish within 10 s is reset with `ETIMEDOUT`. A change between the HELLO and the link coming up is sent as an update right away.
829
831
 
830
- `test/integration/testdata/link/link.js` is a page-side implementation of the link (framing, credit, both channel layouts, the violation rules, and `resolve` and raw-fetch helpers) that the integration tests run in Chrome against `slicc attach`. `cdp.js` next to it is a CDP client over `cdp` streams, `protect.js` probes the filter that keeps seven's origin out of reach, `guard.js` runs [slicc-browsers' guard vectors](https://github.com/ai-ecoverse/slicc-browsers#guard-vectors) through its `guard-runner.mjs`, with a second client straight to the test Chrome as the unguarded observer (a row still running after 3 minutes fails on its own with the CDP calls it was waiting for, and the test fails after 4 minutes without a finished row, naming each row in flight and its pending calls), `fs.js` an op-shaped client for `fs` streams (`call`, `list`, `read`, `write`, `watch`), `loopback.js` is the page side of [loopback signalling](#linking-seven-in-your-everyday-browser), and `page.js` answers `browsers` and `mounts` updates. Through `linkTest.loopback`, `page.js` pairs from the fragment (`confirm`), signals again (`signal`, `reconnect`), unlinks in-band (`unlink`) and posts raw requests (`post`), with an optional `?ports=<first>-<last>` scan range. `page.js` keeps one link per `node` id from the hello. For tray links (no hello `node`) it replaces an older link with the same fingerprint only when HELLO's `node` matches, as seven does. `agent.js` is a fake agent behind the page's `agent` cap: the tests queue scripted runs, emit transcript events, hold and release a run, reset watch streams, and turn the cap off. Loaded with `?kernel`, the test page lists `kernel-in` and answers `kernel` OPENs with `kernel.js`, a small HTTP and WebSocket echo server in the page.
832
+ `test/integration/testdata/link/link.js` is a page-side implementation of the link (framing, credit, both channel layouts, the violation rules, and `resolve` and raw-fetch helpers) that the integration tests run in Chrome against `slicc attach`. `cdp.js` next to it is a CDP client over `cdp` streams, `protect.js` probes the filter that keeps seven's origin and the browser's privileged pages out of reach, `guard.js` runs [slicc-browsers' guard vectors](https://github.com/ai-ecoverse/slicc-browsers#guard-vectors) through its `guard-runner.mjs`, with a second client straight to the test Chrome as the unguarded observer and the runner's `settleScale` at 3 on Windows, 2 on macOS and 1 on Linux, and its `refusalScale` at the default 1 (a row still running after 3 minutes fails on its own with the CDP calls it was waiting for, and the test fails after 4 minutes without a finished row, naming each row in flight and its pending calls), `fs.js` an op-shaped client for `fs` streams (`call`, `list`, `read`, `write`, `watch`), `loopback.js` is the page side of [loopback signalling](#linking-seven-in-your-everyday-browser), and `page.js` answers `browsers` and `mounts` updates. Through `linkTest.loopback`, `page.js` pairs from the fragment (`confirm`), signals again (`signal`, `reconnect`), unlinks in-band (`unlink`) and posts raw requests (`post`), with an optional `?ports=<first>-<last>` scan range. `page.js` keeps one link per `node` id from the hello. For tray links (no hello `node`) it replaces an older link with the same fingerprint only when HELLO's `node` matches, as seven does. `agent.js` is a fake agent behind the page's `agent` cap: the tests queue scripted runs, emit transcript events, hold and release a run, reset watch streams, and turn the cap off. Loaded with `?kernel`, the test page lists `kernel-in` and answers `kernel` OPENs with `kernel.js`, a small HTTP and WebSocket echo server in the page.
831
833
 
832
834
  ### Kernel services
833
835
 
@@ -1634,6 +1636,7 @@ On a [§1.3](#13-local-setup-without-cdp-loopback-signalling) loopback link, `un
1634
1636
  - **Protected ports:** every listed browser's debugging port is protected in the policy on both commands, so a `tcp` stream can't reach the raw port even under `--allow` or `--expose-debug-port` ([§6.5](#65-debugging-ports-every-mode)).
1635
1637
 
1636
1638
  - **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.
1639
+ - **Privileged browser pages** (≥ 1.8.0) count as protected origins: every URL in `protectedSchemes` (`chrome:`, `chrome-untrusted:`, `chrome-extension:`, `devtools:`, `file:`, `edge:`, `brave:`, `opera:`, `vivaldi:`, `arc:`) except the new tab pages in `schemeExceptions`; `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.
1637
1640
  - **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.
1638
1641
  - **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.
1639
1642
 
@@ -2010,7 +2013,7 @@ CI installs a pinned release: `go install github.com/ai-ecoverse/slicc-cli/cmd/s
2010
2013
 
2011
2014
  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`.
2012
2015
 
2013
- **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 runs two 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. That full matrix, all with `-race`, runs on pushes to `main`, which is the run the release waits for, so nothing ships without it. 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 full matrices. Pull requests and the merge queue (`merge_group`) run a lighter one, so a queue group needs only one macOS test runner: the two Linux shards with `-race`, and one leg each on macOS and Windows without `-race` (25 minutes). The [guard vectors](https://github.com/ai-ecoverse/slicc-browsers#guard-vectors) (`TestCDPProtectVectorsPrompt` and `TestCDPProtectVectorsLagging`, one per detach variant) run on Linux there, and on every OS in the full matrix: the 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. **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, 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 and the `fresh` check run on `vars.CERT_LIGHT_RUNNER`, 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.
2016
+ **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 two 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 two 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. **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.
2014
2017
 
2015
2018
  **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.
2016
2019
 
@@ -2049,7 +2052,9 @@ The real-Chrome tests in `test/integration/` launch headless, so they run the ex
2049
2052
 
2050
2053
  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`.
2051
2054
 
2052
- semantic-release cuts releases from `main` in `.github/workflows/release.yml`, on a macOS runner. The release doesn't run the tests again: the workflow starts when the CI workflow's run for a push to `main` completes (`workflow_run`), and the release job runs only when that run passed, sharded tests on all three OSes included, 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 release job when `main` has moved; when it can't tell, the release job runs 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. Only a run on `main` publishes: anywhere else (a pull request, a manual run from another branch) the release job is a dry run (`DRY_RUN` is true whenever `github.ref` isn't `refs/heads/main`), 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 release job then runs these steps:
2055
+ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, on a macOS runner. The release doesn't run the tests again: the workflow starts when the CI workflow's run for a push to `main` completes (`workflow_run`), and the release job runs only when that run passed (the light matrix: both Linux shards with `-race` and the guard vectors, and one leg each on macOS and Windows) on the commit that run tested, and the latest nightly passed. 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 release job when `main` has moved; when it can't tell, the release job runs 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.
2056
+
2057
+ **A red nightly blocks releases.** The full matrix runs in the Nightly workflow, once a day and on `workflow_dispatch` (see [Development](#development)). Before the release job, on `main` (after a CI push run that `fresh` let through, or a manual run), the `nightly` job on `vars.CERT_LIGHT_RUNNER` runs `scripts/nightly-gate.mjs`, which reads the latest completed `nightly.yml` run on `main` through the Actions API. It passes only when that run succeeded and started within the last 36 hours, printing `nightly: PASS <run URL> <sha> <age>`. A failed, cancelled or timed-out nightly, none at all, one older than 36 hours (so a broken schedule can't silently turn the gate off) or an API error refuses the release with `nightly: FAIL …`, and the release job, which needs the gate's success whenever `github.ref` is `refs/heads/main`, doesn't run. Dry runs (pull requests, manual runs from other branches) skip the gate. When a nightly ends red or cancelled, its `report` job (the only job with `issues: write`) runs `scripts/nightly-issue.mjs`: it opens an issue labelled `nightly-red` naming the run, the failed jobs and the commit, or comments on the open one; a green nightly comments on it and closes it. **To unblock:** fix `main` if the nightly found a real failure (or re-run its failed jobs if it was a flake), run the nightly (`gh workflow run nightly.yml --ref main`), and once it passes run the release again: `gh workflow run release.yml --ref main`, or re-run the refused Release run's failed jobs. The commits held back go out in that release, since a release takes every commit since the last tag. Only a run on `main` publishes: anywhere else (a pull request, a manual run from another branch) the release job is a dry run (`DRY_RUN` is true whenever `github.ref` isn't `refs/heads/main`), 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 release job then runs these steps:
2053
2058
  1. `scripts/release-plan.mjs` asks semantic-release (dry run) for the next version, and stops when there is none.
2054
2059
  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.
2055
2060
  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.
@@ -2067,6 +2072,7 @@ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, o
2067
2072
  - **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.
2068
2073
  - **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) and the verdict. A byte or reproducibility mismatch, 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) 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>; reproducible: …; ci: …; known: …; test gaps: …)`. **report** posts it as the status and a sticky comment.
2069
2074
  - **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`.
2075
+ - **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.
2070
2076
  - **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.
2071
2077
 
2072
2078
  **Verify on npm** (`.github/workflows/cert-verify.yml`, `tools/cert-review/`). `release.yml` stays plain semantic-release. When a `Release` run on `main` succeeds, `cert-verify` takes the `v*` tag on the commit it built (or the version given to a manual run) and checks it, code only, with no secret and no `id-token`, in a concurrency group of its own, so the next release never waits for it:
@@ -2092,11 +2098,11 @@ semantic-release cuts releases from `main` in `.github/workflows/release.yml`, o
2092
2098
  - **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.
2093
2099
  - **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.
2094
2100
 
2095
- **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`, and `install-check: PASS all installer checks passed`. 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.
2101
+ **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`, `nightly: PASS <run URL> <sha> <age>`, and `nightly-issue: PASS red; opened #N` (or `commented on #N`, `green; closed #N`). Every caller in `ci.yml`, `release.yml`, `nightly.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.
2096
2102
 
2097
2103
  **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.
2098
2104
 
2099
- 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 and the merge queue, so a queue group needs only one macOS runner, and runs on every push to `main`. A pull request that changes `release.yml`, `.releaserc.json`, or a script only the release runs (`scripts/package.sh`, `scripts/npm-*.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.
2105
+ 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.
2100
2106
 
2101
2107
  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:
2102
2108
  - 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.0",
3
+ "version": "1.44.1",
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.0",
22
- "@ai-ecoverse/slicc-darwin-amd64": "1.44.0",
23
- "@ai-ecoverse/slicc-linux-arm64": "1.44.0",
24
- "@ai-ecoverse/slicc-linux-amd64": "1.44.0",
25
- "@ai-ecoverse/slicc-windows-arm64": "1.44.0",
26
- "@ai-ecoverse/slicc-windows-amd64": "1.44.0",
21
+ "@ai-ecoverse/slicc-darwin-arm64": "1.44.1",
22
+ "@ai-ecoverse/slicc-darwin-amd64": "1.44.1",
23
+ "@ai-ecoverse/slicc-linux-arm64": "1.44.1",
24
+ "@ai-ecoverse/slicc-linux-amd64": "1.44.1",
25
+ "@ai-ecoverse/slicc-windows-arm64": "1.44.1",
26
+ "@ai-ecoverse/slicc-windows-amd64": "1.44.1",
27
27
  "@ai-ecoverse/slicc-jerk": "^1.0.0"
28
28
  }
29
29
  }