@ai-ecoverse/slicc 1.47.3 → 1.49.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 +23 -68
  2. package/package.json +7 -7
package/README.md CHANGED
@@ -195,7 +195,7 @@ A profile that doesn't exist is an error (`SLICC six's Chrome profile doesn't ex
195
195
  - **Live and not debuggable:** slicc stops with exit 1: `six's Chrome is using this profile; close it, or use --browser/--profile to pick another`.
196
196
  - **Free** (no lock, or a stale one): slicc launches the browser six used. That is the installed Chromium browser whose version is the profile's `Last Version` (or, without one, the version in `Local State`'s `user_experience_metrics.stability.stats_version`), the first in `slicc browsers`' id order when several match; otherwise Chrome stable. `--browser <id>` overrides it (for `slicc`, the first `--browser`; the others are extras as usual).
197
197
 
198
- **Nothing is written to six's profile.** No seven prefs, no clean-exit marking, no removal of `DevToolsActivePort`, `Singleton*`, the session snapshot or any other file, no extensions added or removed, and no migration. The launch passes only runtime flags, which don't persist: `--remote-debugging-port=0 --user-data-dir=<six's profile> --no-first-run --no-default-browser-check`, the throwaway flags only when six's profile is in the temp dir (see [`slicc launch`](#slicc-launch---browser-id---profile-name---url-url---headless---no-seven-flags---json)), the seven flags (left out with `slicc launch --no-seven-flags`), `--headless=new` with `--headless`, and the start page. A `DevToolsActivePort` that was there before the launch is ignored until Chrome writes a new one.
198
+ **Nothing is written to six's profile.** No seven prefs, no clean-exit marking, no removal of `DevToolsActivePort`, `Singleton*`, the session snapshot or any other file, no extensions added or removed, and no migration. The launch passes only runtime flags, which don't persist: `--remote-debugging-port=0 --user-data-dir=<six's profile> --no-first-run --no-default-browser-check --enable-features=ForceForegroundPriorityForAllTabs`, the throwaway flags only when six's profile is in the temp dir (see [`slicc launch`](#slicc-launch---browser-id---profile-name---url-url---headless---no-seven-flags---json)), the seven flags (left out with `slicc launch --no-seven-flags`), `--headless=new` with `--headless`, and the start page. A `DevToolsActivePort` that was there before the launch is ignored until Chrome writes a new one.
199
199
 
200
200
  **What does persist** is only what Chrome itself keeps: browsing data from the session, and the seven tab in session restore, so six may reopen that tab the next time it starts Chrome.
201
201
 
@@ -210,10 +210,10 @@ A profile that doesn't exist is an error (`SLICC six's Chrome profile doesn't ex
210
210
  Put the binary and the release's `SHA256SUMS` in one folder, then check:
211
211
  - **Checksum on macOS and Linux:** `shasum -a 256 -c SHA256SUMS --ignore-missing`. On Linux `sha256sum -c SHA256SUMS --ignore-missing` does the same.
212
212
  - **Checksum on Windows (PowerShell):** `(Get-FileHash slicc-windows-amd64.exe -Algorithm SHA256).Hash.ToLower()` must equal the hash on that file's line in `SHA256SUMS` (`Select-String slicc-windows-amd64.exe SHA256SUMS`).
213
- - **Provenance:** `gh attestation verify <file> --repo ai-ecoverse/slicc-cli` checks the GitHub build-provenance attestation (Sigstore) that the release workflow made for that exact file. `slicc update` checks the same attestation itself (see [Attestations](#attestations)).
213
+ - **Provenance:** `gh attestation verify SHA256SUMS --repo ai-ecoverse/slicc-cli --signer-workflow ai-ecoverse/slicc-shared-web/.github/workflows/pipeline.yml` checks the GitHub build-provenance attestation (Sigstore) that the release made for `SHA256SUMS`, which then covers each file through its checksum. `slicc update` checks the same attestation itself (see [Attestations](#attestations)).
214
214
 
215
215
  Signing:
216
- - **macOS:** the binaries are signed with a Developer ID, with the hardened runtime, and notarized. A bare binary can't carry a stapled ticket (only apps, disk images and installer packages can), so Gatekeeper checks the notarization online the first time it runs. Inside Sliccstart.app the binary is stapled with the app.
216
+ - **macOS:** the binaries are signed with a Developer ID, with the hardened runtime, and notarized, by `rcodesign` on the release's Linux runner. A bare binary can't carry a stapled ticket (only apps, disk images and installer packages can), so Gatekeeper checks the notarization online the first time it runs. Inside Sliccstart.app the binary is stapled with the app.
217
217
  - **Windows:** Windows binaries are unsigned; SmartScreen may warn on first run. AppLocker, WDAC or Intune policies can block them.
218
218
 
219
219
  ## Reproducing a release
@@ -229,7 +229,7 @@ gh release download v<version> --repo ai-ecoverse/slicc-cli -p SHA256SUMS -O - |
229
229
  - It uses `-trimpath`, so the checkout's path doesn't matter.
230
230
  - It runs the Go release that `go.mod` names (its `toolchain` line, else its `go` line, which then needs a patch version such as `go 1.26.5`; without one `package.sh` refuses to build) through `GOTOOLCHAIN`, downloading it when another Go is installed. It clears `GOFLAGS`, `GOEXPERIMENT`, `GOAMD64`, `GOARM64`, `GOWASM` and `GOFIPS140`, sets `GOENV=off` so no `go env -w` setting applies, and sets `GOWORK=off`, so the environment doesn't change the build. Its log names the Go it used.
231
231
 
232
- The darwin binaries differ only by their signature: the release signs the same unsigned bytes. The integration test `TestReleaseBuildIsReproducibleFromAnyCheckout` (on macOS, where the release builds) builds a checkout tagged after its commit, and a `git archive` of it with `GOAMD64`, `GOARM64`, `GOFIPS140`, `GOFLAGS` and a `go env -w` file set, and requires identical `SHA256SUMS`.
232
+ The darwin binaries differ only by their signature: the release signs the same unsigned bytes. The integration test `TestReleaseBuildIsReproducibleFromAnyCheckout` (everywhere but Windows, so on Linux in CI, where the release builds too) builds a checkout tagged after its commit, and a `git archive` of it with `GOAMD64`, `GOARM64`, `GOFIPS140`, `GOFLAGS` and a `go env -w` file set, and requires identical `SHA256SUMS`.
233
233
 
234
234
  Releases whose `package.sh` lacks `-buildvcs=false` were built with Go's VCS stamping, so their bytes depend on the tags present at build time: rebuild one from its commit with that tag and every later one deleted locally (`git tag -d`), as the release saw it, with the Go that `go.mod` names at that tag (`GOTOOLCHAIN=go<version>`) and a clean Go environment (`GOENV=off`, and none of the variables above set).
235
235
 
@@ -243,7 +243,7 @@ fnox (secrets on a real OS) is upstream jdx/fnox: install it from https://fnox.j
243
243
 
244
244
  **`slicc install jerk`** installs jerk into the data folder's `bin` (any other name, or none, exits 2 with `slicc install: name jerk` and the usage):
245
245
  - `jerk` from the latest release of `ai-ecoverse/slicc-jerk` (0.6.0 or later, the first with the `--via-link` calls the link needs; an older latest release is refused with `slicc install: ai-ecoverse/slicc-jerk <version> is older than 0.6.0, the first release slicc works with`);
246
- - **the release contract** (the same as slicc-cli's own): a plain binary `<name>-<os>-<arch>` (`.exe` on Windows; `darwin`, `linux`, `windows` × `amd64`, `arm64`), and `SHA256SUMS` with a line for it, with a build provenance attestation by the repo's `release.yml` over the binary or over `SHA256SUMS`;
246
+ - **the release contract** (the same as slicc-cli's own, but for who attests): a plain binary `<name>-<os>-<arch>` (`.exe` on Windows; `darwin`, `linux`, `windows` × `amd64`, `arm64`), and `SHA256SUMS` with a line for it, with a build provenance attestation over the binary or over `SHA256SUMS` by one of the identities that step 5 of [Attestations](#attestations) lists for it;
247
247
  - slicc checks the download against `SHA256SUMS` and verifies its attestation (see [Attestations](#attestations)), then installs it only once its `--version` names the release's version, through a rename next to the old one (as `slicc update` does);
248
248
  - it says `installed <name> <version> at <path> (SHA256SUMS and the build provenance attestation by <identity>)`, or `<name> <version> is already installed at <path>`;
249
249
  - `--checksum-only` skips the attestation, with a warning, and says `SHA256SUMS only; the attestation wasn't checked`;
@@ -262,24 +262,24 @@ Only a user runs `slicc install`. Nothing on a link starts an install.
262
262
  2. its single DSSE signature verifies with the certificate's key (ECDSA P-256 or P-384) over the DSSE pre-authentication encoding;
263
263
  3. the certificate chains, as a code-signing certificate, to a Fulcio root in the trusted root that is valid at the entry's integrated time, and the certificate itself was valid then;
264
264
  4. one of the certificate's embedded SCTs (`1.3.6.1.4.1.11129.2.4.2`) verifies with a certificate transparency log key in the trusted root, valid at the SCT's time, over the precertificate (the certificate body without its SCT list) and its issuer's key hash (RFC 6962);
265
- 5. the certificate's identity: issuer (Fulcio extension `1.3.6.1.4.1.57264.1.8`) `https://token.actions.githubusercontent.com`, and exactly one subject alternative name, the URI `https://github.com/<repo>/.github/workflows/release.yml@refs/heads/main`, pinned per repo: `ai-ecoverse/slicc-cli` for `update`, `ai-ecoverse/slicc-jerk` for `install`;
265
+ 5. the certificate's identity: issuer (Fulcio extension `1.3.6.1.4.1.57264.1.8`) `https://token.actions.githubusercontent.com`, and exactly one subject alternative name. For `update` (`ai-ecoverse/slicc-cli`) that is slicc-shared-web's release pipeline, `https://github.com/ai-ecoverse/slicc-shared-web/.github/workflows/pipeline.yml@refs/tags/v2.<minor>.<patch>`, and the certificate's build configuration (Fulcio extension `1.3.6.1.4.1.57264.1.18`) must be the caller, `https://github.com/ai-ecoverse/slicc-cli/.github/workflows/ci.yml@refs/heads/main`, so the pipeline run by another repo or from a branch doesn't count. For `install` (`ai-ecoverse/slicc-jerk`) it is one of `https://github.com/ai-ecoverse/slicc-jerk/.github/workflows/release.yml@refs/heads/main` (its releases before the shared pipeline), `https://github.com/ai-ecoverse/slicc-jerk/.github/workflows/ci.yml@refs/heads/main`, or the shared pipeline as for `update`, called from slicc-jerk's `ci.yml` at `main`; any other workflow, ref or repo is refused;
266
266
  6. the Rekor entry is a `dsse` 0.0.1 entry whose payload hash, signature and verifier are the envelope's and the certificate's;
267
267
  7. its signed entry timestamp verifies with the key of a Rekor log in the trusted root, valid at the integrated time;
268
268
  8. its inclusion proof leads from the entry to the proof's root hash (RFC 6962), and the checkpoint carries that tree size and root hash and is signed by the same log.
269
269
 
270
270
  Errors name the step that failed (`attestation identity: …`). A bundle from a Rekor v2 log, or any other shape, fails closed.
271
271
 
272
+ **Releases before the move to the shared pipeline** were attested by `ai-ecoverse/slicc-cli/.github/workflows/release.yml@refs/heads/main`. A binary from one of them refuses the later releases: update it once by hand, with `slicc update --checksum-only`, `install.sh`, or npm.
273
+
272
274
  **Unsupported: Rekor v2.** Bundles from Rekor v2 logs (Ed25519 log keys, `dsse` 0.0.2 entries, RFC 3161 timestamps instead of signed entry timestamps) aren't supported. They fail closed, so `update` and `install` refuse such a release until the verifier learns them; `--checksum-only` is the way around it meanwhile.
273
275
 
274
276
  **The trusted root.** `internal/attest/trusted_root.json` is Sigstore's public-good trusted root (Fulcio CAs, Rekor and CT log keys) (the `trusted_root.json` target of `https://tuf-repo-cdn.sigstore.dev`), embedded in the binary. `SLICC_SIGSTORE_TRUSTED_ROOT` replaces it, for tests.
275
277
 
276
- **Rotating the trusted root.** The *Sigstore* workflow runs daily. Its `trusted-root` job runs `node scripts/sigstore-root.mjs`, which follows the TUF repository's `timestamp.json`, snapshot and targets to the current `trusted_root.json` and compares its SHA-256 with the embedded file; when they differ, it opens an issue. To rotate:
278
+ **Rotating the trusted root.** The integration test `TestTheEmbeddedSigstoreTrustedRootIsCurrent` runs `node scripts/sigstore-root.mjs`, which follows the TUF repository's `timestamp.json`, snapshot and targets to the current `trusted_root.json` and compares its SHA-256 with the embedded file; when they differ, the test fails, so every pull request stays red until the root is re-embedded (it skips when the TUF repository doesn't answer, exit code 3). To rotate:
277
279
  1. run `node scripts/sigstore-root.mjs --write`, which downloads the new file and checks it against the targets hash;
278
280
  2. review the diff (new Fulcio certificates or Rekor keys, `validFor` ranges) against Sigstore's announcement; the script doesn't check TUF signatures, so this review is the trust decision;
279
281
  3. open a `fix:` PR, so a release ships it. Binaries released before it keep the old root, and fail closed on attestations that need the new one until they update with `--checksum-only` or by hand.
280
282
 
281
- **Parity.** The workflow's `parity` job runs `TestAttestParityWithGH`: for the latest release, `gh attestation verify --repo ai-ecoverse/slicc-cli --signer-workflow …/release.yml` and slicc's verifier must both accept the binary, and both refuse it after one byte is appended.
282
-
283
283
  ## Updates
284
284
 
285
285
  `slicc update [--check] [--checksum-only]` replaces the running binary (symlinks resolved), wherever it is, such as `~/.local/bin/slicc` from `install.sh`, with the latest GitHub release of `ai-ecoverse/slicc-cli`. It downloads the asset for this OS and CPU, checks it against the release's `SHA256SUMS`, and verifies its build provenance attestation (see [Attestations](#attestations)); `--checksum-only` skips the attestation, with a warning. The new binary has to answer `--version` with the release's version before anything is replaced. The swap is a rename next to the old binary; on Windows the running binary is first moved aside to `<name>.old`, which the next run deletes. `slicc update --check` only reports.
@@ -511,7 +511,7 @@ Launches a browser with CDP and keeps running until interrupted (Ctrl-C, `SIGINT
511
511
  - Before each launch, the seven prefs are merged into the profile's `Default/Preferences`, which is created if missing: `tab_freezing_enabled: false`, `performance_tuning.tab_freezing.enabled: false`, Memory Saver off (`performance_tuning.high_efficiency_mode.state: 0`), and `[*.]sliccy.ai` plus each extra allowed origin's host as tab-discard exceptions. The exceptions go into the list `performance_tuning.tab_discarding.exceptions` (six's form) and into the map `performance_tuning.tab_discarding.exceptions_with_time`, which Chrome 154 reads and shows under "Always keep these sites active". Existing keys, exceptions and timestamps are kept, and a file that already has everything isn't rewritten. A Preferences file that isn't a JSON object is left alone. `--no-seven-flags` skips these prefs too.
512
512
  - Chromium flags: `--remote-debugging-port=0 --user-data-dir=<profile> --no-first-run --no-default-browser-check`, the throwaway flags for a throwaway profile, the seven flags below, `--headless=new` with `--headless`, then `--url`.
513
513
  - A throwaway profile is one inside the system temp dir (Go's `os.TempDir()`: `TMPDIR` on macOS and Linux, `TMP` or `TEMP` on Windows; symlinks resolved, case ignored on macOS and Windows). It gets `--use-mock-keychain --password-store=basic` (`browser.ThrowawaySwitches`), so Chrome never asks for the macOS keychain or a Linux password store; without a default keychain, as under a temporary `HOME`, macOS would show "Keychain Not Found". Every other profile, SLICC's and six's included, headless or not, keeps the real keychain, so its saved cookies and passwords stay readable. `browser.Throwaway(dir)` tells which is which.
514
- - The seven flags keep a seven tab's kernel running while the tab is in the background: `--disable-background-timer-throttling`, `--disable-backgrounding-occluded-windows`, `--disable-renderer-backgrounding`, `--disable-background-tracing`, and `--disable-features=LocalNetworkAccessChecks,LocalNetworkAccessChecksWebSockets,IntensiveWakeUpThrottling,HighEfficiencyModeAvailable,InfiniteTabsFreezing,InfiniteTabsFreezingOnMemoryPressure,CPUMeasurementInFreezingPolicy,MemoryMeasurementInFreezingPolicy,AllowDevtoolsConnectedDiscard`. Chromium ignores feature names it doesn't know. `--no-seven-flags` (`LaunchOptions.NoSevenFlags`) leaves them all out, along with the seven prefs above. Chromium keeps only one `--disable-features` value, so every `--disable-features` from the seven flags and from `LaunchOptions.ExtraArgs` is merged into one switch, in order and without duplicates. The CDP port is read from the profile's `DevToolsActivePort` once `/json/version` answers with the same websocket path. slicc waits for that as long as the browser it started is running, up to 2 min, since a first start from a cold or slow disk can take well over 30 s. Every 10 s of the wait it says so: `slicc: Google Chrome is still starting (slow disk?), 10 s so far` on stderr, or the `browser-slow` event with `--json`. A browser that exits first fails the launch at once, with its exit code and the end of its output (`Google Chrome exited with code 21 after 0.4s, before writing DevToolsActivePort; its output: …`). One still running after 2 min is stopped, and the launch fails with `… is still running after 2m0s but has not written <profile>/DevToolsActivePort`. Ctrl-C, `SIGINT` or `SIGTERM` during the wait stops the browser at once, and `slicc launch` says `slicc: interrupted while <name> was starting; closed it` (`{"event":"closed"}` with `--json`) and exits 0. A browser that `open` started on macOS isn't a child of slicc, so a launch that fails or is interrupted also stops the processes that hold the profile: the `SingletonLock` owner and every process started with its `--user-data-dir`. For an Electron app, which has no SLICC profile, it stops the process running the app's executable; the launch refuses an app that is already running, so that process is the one slicc started. One that is still running 15 s later is named in the error (`… is still running as process [<pid>]`), and `slicc launch` exits 1 instead of reporting `closed`. Electron apps get the same wait for their CDP port. On macOS a browser with a window starts through `open -n -W -a <app> --args …`, so camera and microphone permissions belong to the browser, not the terminal; `--headless` runs the executable directly. A flatpak starts with `flatpak run --filesystem=<profile> <id>`.
514
+ - The seven flags keep a seven tab's kernel running while the tab is in the background: `--disable-background-timer-throttling`, `--disable-backgrounding-occluded-windows`, `--disable-renderer-backgrounding`, `--disable-background-tracing`, and `--disable-features=LocalNetworkAccessChecks,LocalNetworkAccessChecksWebSockets,IntensiveWakeUpThrottling,HighEfficiencyModeAvailable,InfiniteTabsFreezing,InfiniteTabsFreezingOnMemoryPressure,CPUMeasurementInFreezingPolicy,MemoryMeasurementInFreezingPolicy,AllowDevtoolsConnectedDiscard`. Chromium ignores feature names it doesn't know. Every Chromium browser slicc launches, with or without the seven flags, also gets `--enable-features=ForceForegroundPriorityForAllTabs`, merged with any other `--enable-features` into one switch since Chromium keeps only the last, so a loaded Mac doesn't move a hidden tab's renderer into the background priority band, where the guard can't hear from it within 30 s and refuses the tab. `--no-seven-flags` (`LaunchOptions.NoSevenFlags`) leaves them all out, along with the seven prefs above, but not `--enable-features=ForceForegroundPriorityForAllTabs`, because the guard watches every browser slicc launches, not only seven's. Chromium keeps only one `--disable-features` value, so every `--disable-features` from the seven flags and from `LaunchOptions.ExtraArgs` is merged into one switch, in order and without duplicates. The CDP port is read from the profile's `DevToolsActivePort` once `/json/version` answers with the same websocket path. slicc waits for that as long as the browser it started is running, up to 2 min, since a first start from a cold or slow disk can take well over 30 s. Every 10 s of the wait it says so: `slicc: Google Chrome is still starting (slow disk?), 10 s so far` on stderr, or the `browser-slow` event with `--json`. A browser that exits first fails the launch at once, with its exit code and the end of its output (`Google Chrome exited with code 21 after 0.4s, before writing DevToolsActivePort; its output: …`). One still running after 2 min is stopped, and the launch fails with `… is still running after 2m0s but has not written <profile>/DevToolsActivePort`. Ctrl-C, `SIGINT` or `SIGTERM` during the wait stops the browser at once, and `slicc launch` says `slicc: interrupted while <name> was starting; closed it` (`{"event":"closed"}` with `--json`) and exits 0. A browser that `open` started on macOS isn't a child of slicc, so a launch that fails or is interrupted also stops the processes that hold the profile: the `SingletonLock` owner and every process started with its `--user-data-dir`. For an Electron app, which has no SLICC profile, it stops the process running the app's executable; the launch refuses an app that is already running, so that process is the one slicc started. One that is still running 15 s later is named in the error (`… is still running as process [<pid>]`), and `slicc launch` exits 1 instead of reporting `closed`. Electron apps get the same wait for their CDP port. On macOS a browser with a window starts through `open -n -W -a <app> --args …`, so camera and microphone permissions belong to the browser, not the terminal; `--headless` runs the executable directly. A flatpak starts with `flatpak run --filesystem=<profile> <id>`.
515
515
  - Electron apps keep their own data and get `--remote-debugging-port=<port>`, the first free port from 9333 up, so they never take 9222–9229. An app that is already running is refused, since a second instance would hand off to it; quit it first. Apps with `debugSupport: "disabled"` are refused.
516
516
  - Shutdown: `Browser.close` over CDP; if the browser is still running 5 s later, a signal (`SIGTERM` to the browser's process group and main process; `taskkill /T` on Windows), then after 3 s a kill (`SIGKILL`; `taskkill /F /T`). The browser runs in its own process group (`CREATE_NEW_PROCESS_GROUP` on Windows), so a terminal Ctrl-C reaches only `slicc`.
517
517
 
@@ -2109,9 +2109,9 @@ CI installs a pinned release: `go install github.com/ai-ecoverse/slicc-cli/cmd/s
2109
2109
 
2110
2110
  ## Development
2111
2111
 
2112
- 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`.
2112
+ 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` (also with `GOOS=darwin` and `GOOS=windows`, since CI tests on Linux only) and golangci-lint; `npm run lint:sh` runs shellcheck on `install.sh` and the scripts. `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`.
2113
2113
 
2114
- **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 the merge queue (`merge_group`) and every push to `main`, which is the run the release waits for: the three Linux shards with `-race`, and two shards each on macOS and Windows (`1/2` and `2/2`, each with a 20-minute `-timeout`) without `-race`, so a queue group or a push needs two macOS and two Windows test runners. **A pull request** runs only the light matrix's Linux shards, beside `lint`, `installer-assets` and the installer checks: hosted macOS runners are the scarce ones, and the pull request's own merge group runs every OS before anything lands on `main`. The `all-os` label gives a pull request the whole light matrix, for changes to platform code: the `runner` job reads the pull request's labels when it runs (its `os` output, `linux` or `all`), so after adding the label, push or re-run all of the CI run's jobs. The aggregate `ci` prints which matrix ran (`test matrix: Linux only …` or `every OS`). With one leg each, both ran 19 to 23 of their 25 minutes, and a macOS leg timed out in a pull request. **The full matrix** runs in every release from `main`, on the commit it releases, beside the release build: `release.yml`'s `full` job calls `ci.yml` (`workflow_call` with `full: true`), which then runs three shards on Linux, three on Windows and four on macOS, whose runners vary the most (the same shard took from 400 s to 570 s) and run the guard vectors slowest, so each stays well inside its 15 minutes, every leg with `-race`, and the installer's macOS curl check too. On the org's hosted macOS pool the full matrix takes an hour or more, longer than merges often arrive: when `main` moves on meanwhile, that run's semantic-release skips as behind, and the newer commit's run releases (see [Releases](#releases)). Only one push run on `main` runs at a time, and only the newest push waits: a newer push replaces a pending one, never the one running, so a steady stream of merges queues at most two light matrices. **Node:** the `test` job runs on Node 26, in both matrices. On Node 24.21.0, V8 crashed the WASI kernel harness (`run.mjs`, the `TestWasi*` tests) about once in a hundred runs on Windows (`exit status 0xc0000005`, 15 crashes in 1600 runs against none in 1600 on Node 26), and occasionally on Linux and macOS (`Check failed: jit_page…`). Node 24, which npm users still run, stays covered by `installer-assets`: it stages and dry-runs the npm packages on Node 24, and runs the npm launcher tests (`TestNPM*` and `TestSliccy*`, `npx sliccy@next` among them) on it. The [guard vectors](https://github.com/ai-ecoverse/slicc-browsers#guard-vectors) (`TestCDPProtectVectorsPrompt` and `TestCDPProtectVectorsLagging`, one per detach variant) run on Linux in the light matrix, and on every OS in the full matrix: the light matrix's macOS and Windows legs skip them with `SLICC_GUARD_VECTORS=0`. On macOS both leave out the C9 vectors (extension worlds; the `C9.` prefix that `guardSkippedOn` in `guardrunner_test.go` returns for `darwin`, and the run logs why): there their unguarded control hung in Chrome itself (`Runtime.getProperties` for minutes, [#344](https://github.com/ai-ecoverse/slicc-cli/issues/344)), so no row passed and the full matrix went red on a guard that held. On Linux and Windows the C9 vectors run like the rest, and `TestCDPGuardVectorsSkipC9OnlyOnMacOS` keeps the skip to macOS. `TestCDPHidesExtensionWorlds` (`protectworld_test.go`) covers the extension-world guard on every OS. CI runs them with `-v`, so `node scripts/shard-times.mjs --run <CI run id>` refreshes the file from a finished `main` run's logs (`<platform>=<log file>` reads local `go test -v` output instead); a test the logs only show as skipped (the guard vectors on macOS and Windows, in a `main` run) keeps the time it had. Commit the result when tests get much slower or faster. `TestEachCIShardFitsItsTimeout` fails when a shard of either matrix adds up to more than three quarters of its `-timeout` by those times (without the guard vectors where it skips them), or when its job's `timeout-minutes` leaves less than 5 minutes past its `-timeout`: then add a shard. The `/3` and `/4` shards have 15 minutes: refreshed in October 2026, the times put each at 540 to 610 s, past three quarters of 12. Linux has three shards for that reason: with two, a shard ran 719 s of its 12 minutes, and the next run hit the timeout. **Retries.** In the release's full matrix only, each leg runs `test-shard.mjs --retry 1`: when `go test` fails, it runs the failed top-level tests once more (`-run` with their names, the other flags unchanged), and the leg passes when they all pass then. A test that passed on its retry is named in the PASS line (`…; TestX passed on retry`) and as a `flaky tests` warning on the run. A shard that hit its `-timeout`, or failed without naming a failed test (a build failure), isn't retried, and pull requests, the merge queue and pushes never retry, so a flaky test still shows there. The full matrix's jobs get 45 minutes, room for a shard and its retry. When a shard does hit its `-timeout`, `go test` panics naming whichever test was running then, so `test-shard.mjs` adds a line with how long that test had run, what the finished tests took against the times' prediction, and the slowest of them. **Runners.** CI's first job, `runner`, picks where the Linux jobs run with `.github/runner.mjs`: the pool named by `vars.CERT_RUNNER` (now `cloud-run-job`, one Cloud Run task per job; unset, `ubuntu-latest`) for pushes, the release's full build (`workflow_run` and `workflow_dispatch`), same-repo pull requests and merge groups whose entries are all same-repo, and GitHub's hosted `ubuntu-latest` for fork pull requests, merge groups with a fork entry, and when the decider fails. It gives two labels through the same rule: `runner` for the heavy jobs (the Linux test shards and `installer-assets`) and `light` (`vars.CERT_LIGHT_RUNNER`, unset `ubuntu-latest`) for `lint`, `installer (ubuntu-curl)` and the aggregate `ci`, so where a fork gets `ubuntu-latest` it gets it for both. `vars.CI_RUNNER` forces one runner, for both labels, for every event, fork pull requests and merge groups included, so it is never a rollback; `vars.CERT_RUNNER` is the one pool variable, and changing it is the rollback, with every rule here still applied. The `runner` job itself runs hosted for fork pull requests, otherwise on `vars.CERT_LIGHT_RUNNER` (or `vars.CERT_RUNNER`); it loads `runner.mjs` from `main`, never from the code under test, which may be a fork's: a pull request's base commit, and for a merge group the head of `main` (`merge_group.base_ref`), not the group's parent commit (`base_sha`), which can be the speculative commit of the group ahead of it in the queue, with that group's pull requests in it. That keeps the decider's own answer trusted; a fork pull request that edits `ci.yml` itself is stopped only by the repository's approval rule for outside contributors' workflows and the pool's runner-group access. The Alpine installer check stays hosted (it needs Docker), and macOS and Windows always run hosted. In `release.yml`, the dispatch-only `ci` wait, the `fresh` check and the `report` job run on `vars.CERT_LIGHT_RUNNER`, as do the Linux legs of `smoke` (hosted for fork pull requests) and `install-smoke` on `vars.CERT_RUNNER`, each falling back to `ubuntu-latest`, and every job with `id-token: write` (`release`, `sliccy-next`) on a GitHub-hosted runner. No workflow names a pool literally.
2114
+ **CI.** `.github/workflows/ci.yml` is the only workflow: one caller of [slicc-shared-web's pipeline](https://github.com/ai-ecoverse/slicc-shared-web#pipeline), pinned at the release of the `@ai-ecoverse/slicc-shared-web` devDependency, with `secrets: inherit`. On pull requests and in the merge queue it runs `lint` (`npm run lint` with golangci-lint, pinned in `ci.yml` and installed into `$RUNNER_TEMP`, then `npm run lint:sh`), then `npm test` on four Linux shards of the pool, then on a pull request the Claude and Codex reviews with this repo's `review-prompt`. The checks to require are `ci / integration` and `ci / review`. A push to `main` runs lint and the release (see [Releases](#releases)); it runs no tests, since what reaches `main` passed the merge queue. macOS and Windows aren't tested in CI: lint vets both, and platform code needs a local run there. **Test shards.** `npm test` runs `scripts/test-shard.mjs ci/4 -race -count=1 -timeout 15m -v ./test/integration/...`. On the pipeline's shard `SLICC_TEST_SHARD` (0 to 3) it runs that shard's share of the top-level tests; without it (a local run) it runs them all. The split is greedy by measured time: `test/integration/shard-times.json` holds each top-level test's seconds on `linux`, each test goes to the shard with the least time so far, a test without a time counts as the median, and the last shard runs everything the others don't, so a new test still runs. `go test` gets the pipeline's own `NODE_OPTIONS` back (without its shard preload and JUnit reporter) and none of its `SLICC_TEST_*` variables. The script writes each top-level test's result to the shard's JUnit report (`SLICC_TEST_JUNIT`), which the pipeline's `integration` check counts: it fails when no test ran or one failed. It also fails a shard that selects no tests or whose `go test -v` runs none, and, when a shard hits its `-timeout`, names the test that was running and how long the finished ones took. `node scripts/shard-times.mjs --run <CI run id>` refreshes the times from a finished run's `test (<n>)` logs (`linux=<log file>` reads local `go test -v` output instead); commit the result when tests get much slower or faster. `TestEachCIShardFitsItsTimeout` fails when a shard adds up to more than three quarters of its `-timeout`, or when `timeout-minutes` leaves less than 5 minutes past it: then raise `shards` in `ci.yml` and the count in `npm test` together. The [guard vectors](https://github.com/ai-ecoverse/slicc-browsers#guard-vectors) run on every shard; `SLICC_GUARD_VECTORS=0` skips them locally. On macOS they leave out the C9 vectors (extension worlds; the `C9.` prefix that `guardSkippedOn` in `guardrunner_test.go` returns for `darwin`): there their unguarded control hung in Chrome itself ([#344](https://github.com/ai-ecoverse/slicc-cli/issues/344)). `TestCDPGuardVectorsSkipC9OnlyOnMacOS` keeps the skip to macOS, and `TestCDPHidesExtensionWorlds` covers the extension-world guard everywhere. **Node:** CI runs Node 26: on Node 24.21.0, V8 crashed the WASI kernel harness (`TestWasi*`) about once in a hundred runs on Windows and occasionally on Linux.
2115
2115
 
2116
2116
  **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.
2117
2117
 
@@ -2142,71 +2142,26 @@ Chrome 156 no longer has `Network.setRequestInterception`, so the deprecated `Ne
2142
2142
 
2143
2143
  **Real browsers in tests.** The Chrome the harness starts itself is `SLICC_TEST_CHROME` (test-only: the path of a Chrome executable), else the machine's Chrome or Chromium, with the real environment, a temporary profile and `--use-mock-keychain --password-store=basic`. The tests that have `slicc` itself launch a real Chrome run it under a temporary home, where its profiles are throwaway ones. Off CI they never launch the machine's own Chrome: they need `SLICC_TEST_CHROME` set to a Chrome for Testing inside its `.app`, which they link into the temporary home's `Applications` folder on macOS and launch by its id (`chrome-for-testing`), and they skip with a notice without it, or on other systems. On CI they launch the runner's Google Chrome. An older Chrome for Testing can fail the tests that read Chrome's own prefs.
2144
2144
 
2145
- On GitHub's Windows runners the system drive `C:` is slow: with Chrome profiles there, Chrome sometimes waited seconds before a new tab's first request (up to past the 15 s page-load limit) or before exiting (`did not exit`). With them on `D:`, the runner's temp drive, those stalls weren't seen and the suite ran about a quarter faster. So CI points `TEMP` and `TMP` at the runner's temp dir, and the tests' temporary homes and Chrome profiles live there. When a test page doesn't load, the failure says where it stopped: the tab's URL and `readyState`, the resources it fetched, and every request the page server saw.
2145
+ On GitHub's Windows runners the system drive `C:` is slow: with Chrome profiles there, Chrome sometimes waited seconds before a new tab's first request (up to past the 15 s page-load limit) or before exiting (`did not exit`). With them on `D:`, the runner's temp drive, those stalls weren't seen and the suite ran about a quarter faster. So on a Windows runner, point `TEMP` and `TMP` at its temp drive: the tests' temporary homes and Chrome profiles live there. When a test page doesn't load, the failure says where it stopped: the tab's URL and `readyState`, the resources it fetched, and every request the page server saw.
2146
2146
 
2147
2147
  The real-Chrome tests in `test/integration/` launch headless, so they run the executable directly. A windowed launch on macOS goes through `open -n -W -a`, which CI doesn't cover: on GitHub's macOS runners, Chrome started that way never writes `DevToolsActivePort`.
2148
2148
 
2149
2149
  ## Releases
2150
2150
 
2151
- 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`.
2152
-
2153
- semantic-release cuts releases from `main` in `.github/workflows/release.yml`. The workflow starts when the CI workflow's run for a push to `main` completes (`workflow_run`), and builds only when that run passed (the light matrix: the Linux shards with `-race` and the guard vectors, and two shards each on macOS and Windows) on the commit that run tested. A CI run that failed or was cancelled releases nothing; when a re-run of its failed jobs passes, the release follows by itself. When `main` has moved on by then, the checkout is behind `main`, so semantic-release (in `release-plan.mjs` and again in the release) skips without failing, and the newer commit's run releases. So that such a run doesn't wait for a macOS runner first, its `fresh` job on `cloud-run-bench` compares the tested commit with `git ls-remote` of `main` and skips the build, the full matrix and the release when `main` has moved; when it can't tell, they run and semantic-release decides. A manual run instead waits in the `ci` job for the CI workflow's `push` run on the same commit, and fails unless it passed. A pull request's dry run doesn't wait for CI; it runs alongside it.
2154
-
2155
- **Each release passes its own full build.** Nothing that failed earlier holds a release up: a release needs only its own commit's results. On `main` (after a CI push run that `fresh` let through, or a manual run) two jobs start together: `full`, the full matrix on that commit (see [Development](#development)), whose test matrix fails fast (`fail-fast` is `inputs.full`), so the first failed leg cancels the others and frees their runners; and `build` on macOS, which plans, builds, signs and stages the release and uploads it as the `slicc-release` artifact (one tarball, so file modes survive). Nothing polls the run or cancels it: the build is short beside the matrix, and the light matrix's legs never fail fast, so a pull request still reports every failing leg. `smoke` runs each binary from the build on its own OS and CPU. The `release` job, on GitHub-hosted `ubuntu-latest`, publishes only when the build, the full matrix and every smoke test passed, so a failure in the full matrix fails the release. Then `report` (the only job besides `release` with `issues: write`, which semantic-release needs) runs `scripts/release-issue.mjs` whenever the full matrix ran, even in a cancelled run: when a job failed it opens an issue labelled `release-red` naming the run, the failed jobs and the commit, or comments on the one already open, so failures never pile up as duplicates; a run that only got cancelled (superseded or stopped by hand) reports nothing; a green run comments on the open issue and closes it. The next push to `main` builds and releases from scratch with every commit since the last tag; to retry the same commit, re-run the failed jobs or `gh workflow run release.yml --ref main`. Only a run on `main` publishes: anywhere else (a pull request, a manual run from another branch) the build is a dry run (`DRY_RUN` is true whenever `github.ref` isn't `refs/heads/main`), the full matrix, `release` and `report` don't run, and the attestation, semantic-release (the GitHub release and the npm packages), `go-warm.sh`, `install-smoke` and the `sliccy-next` recovery each also require `refs/heads/main` themselves. The build and the release run these steps:
2156
- 1. `scripts/release-plan.mjs` asks semantic-release (dry run) for the next version, and stops when there is none.
2157
- 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.
2158
- 3. (still in `build`) `scripts/npm-stage.mjs` stages `@ai-ecoverse/slicc` and the six platform packages into `dist/npm/`. It also stages the same launcher as `sliccy` (with two bins, `sliccy` and `slicc`, and a short preview README) into `dist/npm-next/`, as version `7.0.0-next.<version>`, with exact `optionalDependencies` on this release's platform packages. `npx sliccy@next` runs the bin named after the package, `sliccy`, and both bins run the same launcher. `--bin <name>` (repeatable) overrides the bins of any staged launcher. `--wasi` also stages `@ai-ecoverse/wasi-slicc` into `dist/npm/wasi-slicc/` from `slicc-wasip1-wasm.wasm` and `npm/wasi-slicc/host/slicc-host.mjs`, with README.md's [Inside seven](#inside-seven) section as its README; `--wasi-only` stages only that package.
2159
- 4. The `release` job unpacks the build, checking its `dist/VERSION` against the planned version, and `actions/attest-build-provenance` attests every binary, the `.wasm` included, through `SHA256SUMS`.
2160
- 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. Then the `release` job asks `git ls-remote` whether `v<version>` is on `origin` at the commit it built, and only then outputs the version. When semantic-release published nothing (for example because `main` moved on during the full matrix, so the checkout is behind), there is no output: `install-smoke` and `sliccy-next` skip, and the run stays green with no `release-red` issue. An `origin` it can't read fails the job.
2161
- 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.
2162
-
2163
- **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.
2164
- - **plan** (light runner) runs on opened, synchronize, reopened, ready_for_review, converted_to_draft, and `cert-review-approved` being added or removed, and plans the whole run again, since such an event cancels a run in progress; other labels neither start nor cancel anything. **Draft status replaces the old `merge-ok` label:** a draft is neither built nor reviewed and its `cert-review` is red `draft: not reviewed, mark it ready for review`, so an older result on the same head can't satisfy the check; **mark ready = reviewed, and merges on green**. With the hold rule (held = draft) the order for a PR that needs the coordinator's security or link-spec read is: the coordinator reviews the draft, it is marked ready, the cert runs (with its Claude review), then the queue; a cert finding means a new head, back to draft and a re-read. Renovate's pull requests aren't drafts, so they're reviewed by themselves. A `pending` status left on the head by a run that ended without a result becomes `error` (re-run the failed jobs), so the check never waits forever. The shared self-guard (`cert-self-guard`, pinned) holds a change to the reviewer's own paths (`REVIEWER_GLOBS` in `tools/cert-review/lib.mjs`: all of `.github/**`, every workflow and action, so no pull request's own workflow can post `cert-review`, and `tools/cert-review/**`) until a coordinator puts `cert-review-approved` on that head. Such pull requests always go to the coordinator before queueing.
2165
- - **build** (hosted `ubuntu-latest`, no secrets) builds the merge commit and its first parent the release's way, each in a full clone with its tags: `SLICC_SIGN=0 scripts/package.sh <v>`, `npm-stage.mjs` for every package, and `npm pack`, with one version `X.Y.(Z+1)-pr.N.<run>` on both sides. `outputs.mjs` hashes every release file and tarball.
2166
- - **classify** decides by output where the build can see it: a pull request is behaviour when a binary, the wasm, `SHA256SUMS` or an npm tarball differs from main's build. Paths whose effect the unsigned build never shows are behaviour whatever it outputs: `scripts/**` (signing and notarization, publishing, the release plan, `go-*`, `sliccy-next.mjs`), `install.sh`, `npm/**`, `package.json` and `package-lock.json` (they choose the semantic-release code the release runs), `.releaserc*`, and the reviewer's paths. Otherwise the status is `not behaviour: identical build outputs` (success), so a docs or test change that ships nothing new needs nothing more. Renovate's pull requests aren't drafts, so a dev-pin bump is reviewed by itself and merges on green. A behaviour change on a draft is `draft: not reviewed, mark it ready for review` (failure); once it's ready, the review starts.
2167
- - **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.
2168
- - **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.
2169
- - **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.
2170
- - **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`).
2171
- - **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.
2172
- - **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; a pull request's CI run whose `runner` decided Linux only, read from its `test matrix` annotation `os=linux` in the same check suite as a green `ci`, and whose workflow run is a `pull_request` one, needs no macOS or Windows leg, and the comment says `not run on this PR (Linux-only; the merge queue runs every OS)`; on any other run every OS is still required, and a leg that ran must pass), 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.
2173
- - **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`.
2174
- - **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.
2175
- - **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.
2176
-
2177
- **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:
2178
- - `release / reproduce from the tag` (`vars.CERT_RUNNER`, no token): [the recipe](#reproducing-a-release) on a `git archive` of `v<version>` on Linux. Every `linux`, `windows` and `.wasm` asset and `install.sh` must match the release's `SHA256SUMS` line, built on macOS.
2179
- - `release / darwin signature and notarization` (hosted `macos-latest`) checks both darwin binaries:
2180
- - codesign first: `codesign -dv` (identifier `ai.sliccy.slicc`, a `Developer ID Application` authority, team `vars.CERT_APPLE_TEAM`, default `S8LB56P782`, the hardened runtime) and `codesign --verify --strict`. A failure there is FAIL whatever Gatekeeper says, and a codesign that didn't finish is INCOMPLETE.
2181
- - On a codesign pass, `spctl -a -vv -t install` asks Gatekeeper online. Its first answer for a new binary is sometimes `Unnotarized`, so it asks up to three times with a growing pause and keeps every answer in the evidence. It passes only on `source=Notarized Developer ID`.
2182
- - `release / verify on npm` (`vars.CERT_LIGHT_RUNNER`, read-only) covers each package a publish variable lets out: `@ai-ecoverse/slicc`, its six platform packages, `@ai-ecoverse/wasi-slicc` and `sliccy@7.0.0-next.<version>`. For each, it:
2183
- - waits up to an hour for npm;
2184
- - checks `dist.integrity` against the registry tarball;
2185
- - checks the SLSA provenance: subject = the tarball's sha512, `release.yml` at `refs/heads/main`, a GitHub-hosted builder, `gitCommit` = `gitHead` = the tag's commit, which is on `main`;
2186
- - checks that each platform package's binary and `wasi-slicc`'s `bin/slicc.wasm` are the release's assets, and that `@ai-ecoverse/slicc` and `sliccy` pin this version's platform packages;
2187
- - then runs `npm audit signatures`.
2188
-
2189
- On GitHub, the release must have exactly the eight assets and `SHA256SUMS`, each must re-hash to its line, and `gh attestation verify` must accept each one for `release.yml` at `refs/heads/main`. `get.sliccy.ai/slicc` must redirect to `releases/latest/download/install.sh`, whose bytes are this release's while it is the latest. The job also reads the rebuild and darwin evidence and the release notes. A mismatch anywhere, or notes that already certify another sha, is `supply-chain alert: …`. Missing evidence (no rebuild or darwin result, a check that didn't finish) is `cert-ci: INCOMPLETE …`. Its record (`verify.json`) is written last, with the final state.
2190
- - `release / write the certified lines` (hosted `ubuntu-latest`, the only job with `contents: write`, after a verify that passed) does only the notes edit, from the lines verify handed over. Between `<!-- cert-verify -->` markers it writes one line per package and one for the binaries:
2191
- - `certified <package>@<version> sha256=<the registry tarball's sha256> (covers …; ci: …; pr-certified: …; known: …)`;
2192
- - `certified slicc-cli@<version> binaries SHA256SUMS sha256=<the SHA256SUMS file's sha256> (…)`.
2193
-
2194
- `pr-certified` lists each pull request since the previous tag with its `cert-review` status on its head. When one of them has none (a pull request from before the check was required), the lines start with `verified` instead of `certified`, since they then cover only the supply chain; readers take only `certified` as a cert. A `verified` line with the same sha counts as written. Notes that already carry the same lines stay unchanged, and lines in the block for packages outside this run's set are kept. Another sha writes nothing and fails with a `supply-chain alert`.
2151
+ PRs merge through the merge queue, which requires `ci / integration` and `ci / review`. semantic-release cuts a release from every push to `main` in the pipeline's `release` job (GitHub-hosted `ubuntu-latest`, since npm's trusted publishing takes OIDC tokens only from GitHub-hosted runners), in its own concurrency group that never cancels; `main` runs lint before it and no tests, since the merge queue tested the commit. The job gets the Apple secrets named in `ci.yml`'s `release-secrets` and nothing else. Nothing is released by hand: a failed release is fixed by the next push to `main`, which releases every commit since the last tag. `.releaserc.json` runs:
2152
+ 1. `scripts/go-major.mjs <version>` (verify, see below).
2153
+ 2. `scripts/release-build.sh <version>` (prepare): `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 the darwin binaries with [rcodesign](https://github.com/indygreg/apple-platform-rs) (pinned by version and SHA-256, downloaded unless `RCODESIGN` names one), identifier `ai.sliccy.slicc`, hardened runtime, Developer ID with a timestamp, notarizes both in one zip with Apple's Notary API, and writes `SHA256SUMS`. The windows and linux binaries are not signed. In the release (`SLICC_SIGN=required`) a missing secret fails the build; elsewhere (`SLICC_SIGN` unset) it warns and leaves the darwin binaries unsigned, and `SLICC_SIGN=0` skips signing. The script then runs the runner's own binary with `--version`, which must answer `slicc <version>`, and `scripts/npm-stage.mjs` stages `@ai-ecoverse/slicc` and the six platform packages into `dist/npm/`, with `--wasi` also `@ai-ecoverse/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), and the same launcher as `sliccy` (two bins, `sliccy` and `slicc`, and a short preview README) into `dist/npm-next/` as `7.0.0-next.<version>`, with exact `optionalDependencies` on this release's platform packages. `npx sliccy@next` runs the bin named after the package, and both bins run the same launcher. `--bin <name>` (repeatable) overrides the bins of any staged launcher.
2154
+ 3. `@semantic-release/github` tags and creates the GitHub release with every binary, `install.sh` and `SHA256SUMS`.
2155
+ 4. `scripts/npm-publish.mjs` (publish) publishes the platform packages, then the launcher and `wasi-slicc`, through npm trusted publishing from `ci.yml` (OIDC, with provenance), then `sliccy` with `--tag next`. Versions already on npm are skipped. Before publishing `sliccy`, it checks that every exact-pinned `optionalDependencies` version is on the registry, waits up to `SLICC_REGISTRY_WAIT_SECONDS` (default 900) for freshly published ones, and fails otherwise (exit code 3), so a `sliccy` version never points at missing platform packages.
2156
+
2157
+ After a release, the pipeline attests the build provenance of `artifacts/release/SHA256SUMS` (`attest` in `ci.yml`), which `slicc update` checks (see [Attestations](#attestations)), and `after-release` runs `scripts/go-warm.sh <version>`: it 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. Both only warn.
2195
2158
 
2196
2159
  **Go module versions.** In Go a pushed tag is the release: `proxy.golang.org` caches it, `sum.golang.org` records its hash for good, and `pkg.go.dev` indexes it.
2197
2160
  - **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.
2198
- - **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.
2199
-
2200
- **Gate verdicts.** A gate's silence never reads as a pass. Each script that gates a merge or a release ends a pass with a `<name>: PASS …` line: `test-shard: PASS linux 1/2 ran 143 tests`, `go-major: PASS 1.4.0 (major 1) fits module path …`, `release-plan: PASS release 1.4.0` (or `no release due`), `npm-publish: PASS latest: published 7, 1 already published, 0 held by their gates, of 8 packages`, `sliccy-next: PASS sliccy@7.0.0-next.1.4.0`, `install-check: PASS all installer checks passed`, and `release-issue: PASS red; opened #N` (or `commented on #N`, `green; closed #N`, `cancelled (…) without a failure; nothing to report`). Every caller in `ci.yml`, `release.yml` and `.releaserc.json` runs the gate through `sh scripts/verdict.sh <name> <command…>`, which fails with the gate's exit code when it fails, and fails when it exits 0 without its `<name>: PASS` line, so an early exit, a skipped main block or empty output fails the step. `test-shard.mjs` also fails a shard that selects no tests, or whose `go test -v` runs none, and `npm-publish.mjs` fails on a directory with no packages. The `fresh` job sets `verdict` to `current`, `stale`, or `unknown` when `git ls-remote` can't read `main` (`fresh: PASS unknown (semantic-release decides)`, which refuses a release behind the remote anyway); a release after CI needs `current` or `unknown`, so a missing verdict releases nothing. The scripts find their main block with `realpath` on both `process.argv[1]` and the module's own path (`scripts/is-main.mjs`), so they also run through a symlinked checkout or script.
2201
-
2202
- **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.
2203
-
2204
- Every pull request's CI checks the packaging in its `installer-assets` job: an unsigned build of all six targets, the npm packages and `sliccy` (with `--tag next`) packed with `--dry-run`, and `scripts/sliccy-next.mjs --dry-run` (the recovery path against the latest real release, registry check included). The dry run doesn't wait for the latest release: when its platform packages aren't all served yet, it is still publishing (npm has taken 25 minutes), so it checks the release before it instead, with the usual wait, and fails when that one is missing too; only the real publish waits for the latest. The `installer` jobs then run `install.sh` against those assets with curl on Linux and with wget on Alpine; the macOS curl run is left out of pull requests, the merge queue and pushes to `main`, so each needs only one macOS runner, and runs in the release's full matrix. A pull request that changes `release.yml`, `.releaserc.json`, or a script only the release runs (`scripts/package.sh`, `scripts/npm-*.mjs`, `scripts/release-issue.mjs`, `scripts/release-plan.mjs`, `scripts/sliccy-next.mjs`, `scripts/go-*`, `scripts/is-main.mjs`, `scripts/verdict.sh`) also runs the release workflow as a dry run: the same checks in the `build` job on macOS, and each binary run with `--version` on its own OS and CPU.
2161
+ - **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). A `BREAKING CHANGE` commit therefore needs a deliberate module-path change first.
2205
2162
 
2206
- 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:
2207
- - macOS signing: `APPLE_CERTIFICATE_BASE64`, `APPLE_DEVELOPER_CERTIFICATE_PASSWORD`;
2208
- - notarization: `APPLE_API_KEY_ID`, `APPLE_API_KEY_ISSUER_ID`, `APPLE_API_KEY_P8_BASE64`.
2163
+ **Gate verdicts.** A gate's silence never reads as a pass. Each script that gates a release ends a pass with a `<name>: PASS …` line: `go-major: PASS 1.4.0 (major 1) fits module path …`, `release-build: PASS 1.4.0, slicc-linux-amd64 runs, 8 files in SHA256SUMS` and `npm-publish: PASS latest: published 8, 0 already published of 8 packages`. `.releaserc.json` runs each 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. `npm-publish.mjs` also fails on a directory with no packages. 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.
2209
2164
 
2210
- npm needs no token. `npm run trust` (fledgling) configures trusted publishing for the eight packages listed under `fledgling.include` in `package.json`, `@ai-ecoverse/wasi-slicc` included. Publishing to npm is off until the repository variable `SLICC_NPM_PUBLISH` is `true`, `sliccy` stays off until `SLICCY_NEXT_PUBLISH` is `true`, and `@ai-ecoverse/wasi-slicc` until `WASI_SLICC_PUBLISH` is `true`; until then a release skips them with a notice. Its trusted publishing has to be set up with `npm run trust` before that variable is turned on.
2165
+ npm needs no token. `npm run trust` (fledgling) configures trusted publishing from `ci.yml` for the nine packages under `fledgling.include` in `package.json`, `@ai-ecoverse/wasi-slicc` and `sliccy` included.
2211
2166
 
2212
2167
  `sliccy` only ever goes to the `next` dist-tag, so `npx sliccy` keeps running SLICC six and `npx sliccy@next` runs this binary. `npm-publish.mjs` refuses, even in a dry run, to publish `sliccy` with any tag but `next` or with a version that doesn't match `^7\.0\.0-next\.`. Nothing here changes `sliccy`'s other dist-tags.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc",
3
- "version": "1.47.3",
3
+ "version": "1.49.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.47.3",
22
- "@ai-ecoverse/slicc-darwin-amd64": "1.47.3",
23
- "@ai-ecoverse/slicc-linux-arm64": "1.47.3",
24
- "@ai-ecoverse/slicc-linux-amd64": "1.47.3",
25
- "@ai-ecoverse/slicc-windows-arm64": "1.47.3",
26
- "@ai-ecoverse/slicc-windows-amd64": "1.47.3",
21
+ "@ai-ecoverse/slicc-darwin-arm64": "1.49.0",
22
+ "@ai-ecoverse/slicc-darwin-amd64": "1.49.0",
23
+ "@ai-ecoverse/slicc-linux-arm64": "1.49.0",
24
+ "@ai-ecoverse/slicc-linux-amd64": "1.49.0",
25
+ "@ai-ecoverse/slicc-windows-arm64": "1.49.0",
26
+ "@ai-ecoverse/slicc-windows-amd64": "1.49.0",
27
27
  "@ai-ecoverse/slicc-jerk": "^1.2.2"
28
28
  }
29
29
  }