diff --git a/.github/scripts/codecov-upload.sh b/.github/scripts/codecov-upload.sh new file mode 100755 index 00000000..56a0d19e --- /dev/null +++ b/.github/scripts/codecov-upload.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# Pin-download the official macOS Codecov CLI and upload one lcov file. +# codecov-action requires host `gpg` to verify its downloaded CLI; these Mac minis +# do not have gpg. SHA256 of codecovcli_macos v11.3.1 matches cli.codecov.io. +set -eu + +FILE=$1 +VERSION=v11.3.1 +EXPECTED_SHA256=ffb2b821551076b69d3267508b2d5dc10925c821c436521f2216f5ead62cb138 +DEST="$RUNNER_TEMP/codecov" +BIN="$DEST/codecov" + +mkdir -p "$DEST" +curl -fsSL "https://github.com/codecov/codecov-cli/releases/download/${VERSION}/codecovcli_macos" \ + -o "$BIN" +actual=$(shasum -a 256 "$BIN") +actual=${actual%% *} +if [ "$actual" != "$EXPECTED_SHA256" ]; then + echo "codecov CLI checksum mismatch: got $actual want $EXPECTED_SHA256" >&2 + exit 1 +fi +chmod +x "$BIN" + +"$BIN" upload-coverage \ + -t "$CODECOV_TOKEN" \ + --file "$FILE" \ + --disable-search \ + --fail-on-error \ + --git-service github diff --git a/.github/scripts/setup-bun-isolated.sh b/.github/scripts/setup-bun-isolated.sh new file mode 100755 index 00000000..d7f1debb --- /dev/null +++ b/.github/scripts/setup-bun-isolated.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Install Bun canary into $RUNNER_TEMP so parallel jobs on the same Mac mini +# do not overwrite a shared ~/.bun (oven-sh/setup-bun's default). The perf +# gate records Bun.version per revision; a mid-job overwrite looks like an +# environment mismatch and fails the compare. +set -eu + +DEST="$RUNNER_TEMP/bun" +mkdir -p "$DEST" +curl -fsSL "https://github.com/oven-sh/bun/releases/download/canary/bun-darwin-aarch64.zip" \ + -o "$DEST/bun.zip" +unzip -o -q "$DEST/bun.zip" -d "$DEST" +BIN="$DEST/bun-darwin-aarch64/bun" +if [ ! -f "$BIN" ]; then + echo "bun binary missing after extract" >&2 + exit 1 +fi +chmod +x "$BIN" +# The canary zip ships only `bun`. setup-bun also linked `bunx` (same binary). +ln -sf bun "$(dirname "$BIN")/bunx" +# Isolate bun's own home (install cache, etc.) from the shared ~/.bun. +echo "BUN_INSTALL=$DEST/home" >> "$GITHUB_ENV" +echo "$(dirname "$BIN")" >> "$GITHUB_PATH" +"$BIN" --revision diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 7bc4dd76..2ee40944 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -9,14 +9,16 @@ permissions: jobs: quality: - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64] steps: + # Job-level env cannot use the runner context on these Mac minis. + - name: Point TMPDIR at runner temp + run: echo "TMPDIR=$RUNNER_TEMP" >> "$GITHUB_ENV" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - # Bun 1.4 (the Rust rewrite) ships only as canary; the `canary` tag moves with each build. - bun-version: canary + # setup-bun writes ~/.bun, shared across runner instances on one Mac mini. + - name: Install Bun canary into runner temp + run: sh .github/scripts/setup-bun-isolated.sh - name: Install dependencies run: bun install --frozen-lockfile diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7bcbc47f..73f7ed10 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,6 +1,7 @@ name: Coverage on: + pull_request: push: branches: [main] workflow_dispatch: @@ -10,14 +11,16 @@ permissions: jobs: coverage: - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64] steps: + # Job-level env cannot use the runner context on these Mac minis. + - name: Point TMPDIR at runner temp + run: echo "TMPDIR=$RUNNER_TEMP" >> "$GITHUB_ENV" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - # Match the rest of CI while Bun 1.4 remains canary-only. - bun-version: canary + # setup-bun writes ~/.bun, shared across runner instances on one Mac mini. + - name: Install Bun canary into runner temp + run: sh .github/scripts/setup-bun-isolated.sh - name: Install dependencies run: bun install --frozen-lockfile @@ -30,12 +33,13 @@ jobs: bun test tests/ --coverage --coverage-reporter=lcov --coverage-dir=coverage test -s coverage/lcov.info # Bun writes SDK-relative source paths; coverage services need repository-relative paths. - sed -i 's|^SF:|SF:packages/hyperliquid/|' coverage/lcov.info + # Rewrite via $RUNNER_TEMP: BSD sed has no GNU `sed -i` in-place form. + sed 's|^SF:|SF:packages/hyperliquid/|' coverage/lcov.info > "$RUNNER_TEMP/lcov.info" + mv "$RUNNER_TEMP/lcov.info" coverage/lcov.info + # codecov-action requires host `gpg` for CLI integrity. These Mac minis do + # not have it; same pinned CLI + SHA256 check as lychee, same upload flags. - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./packages/hyperliquid/coverage/lcov.info - disable_search: true - fail_ci_if_error: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: sh .github/scripts/codecov-upload.sh ./packages/hyperliquid/coverage/lcov.info diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 04942176..2bd07ea0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,7 +40,7 @@ concurrency: jobs: docs: - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64] timeout-minutes: 20 env: # Project Pages is served from /; PR builds use the same paths. @@ -48,11 +48,13 @@ jobs: NEXT_TELEMETRY_DISABLED: "1" steps: + # Job-level env cannot use the runner context on these Mac minis. + - name: Point TMPDIR at runner temp + run: echo "TMPDIR=$RUNNER_TEMP" >> "$GITHUB_ENV" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - # Match the rest of CI while Bun 1.4 remains canary-only. - bun-version: canary + # setup-bun writes ~/.bun, shared across runner instances on one Mac mini. + - name: Install Bun canary into runner temp + run: sh .github/scripts/setup-bun-isolated.sh - name: Install dependencies run: bun install --frozen-lockfile @@ -72,28 +74,53 @@ jobs: working-directory: apps/docs run: bun run build + # `--with-deps` is apt/sudo on Linux. Chromium's macOS deps ship with the OS. - name: Install browser for documentation checks working-directory: apps/docs - run: bunx playwright install --with-deps chromium + run: bunx playwright install chromium - name: Test production documentation interactions working-directory: apps/docs run: bun run test:browser + # lychee-action downloads only `*-unknown-linux-gnu` and uses GNU `install -t`. + # Same lychee v0.24.2 scan, installed into $RUNNER_TEMP for the Mac mini. - name: Check documentation links - uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2 - with: - args: >- - --root-dir . - --no-progress - --max-retries 3 - --timeout 20 - README.md - packages/hyperliquid/README.md - 'apps/docs/content/docs/**/*.md' - fail: true - failIfEmpty: true - token: ${{ secrets.GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eu + LYCHEE_VERSION=v0.24.2 + ARCHIVE=lychee-aarch64-apple-darwin.tar.gz + EXPECTED_SHA256=c9d3740ea2d891854d37116c9fba840f37b6e7c89d330e7db84ac333631c4977 + DEST="$RUNNER_TEMP/lychee" + mkdir -p "$DEST" + curl -fsSL "https://github.com/lycheeverse/lychee/releases/download/lychee-${LYCHEE_VERSION}/${ARCHIVE}" \ + -o "$DEST/$ARCHIVE" + actual=$(shasum -a 256 "$DEST/$ARCHIVE") + actual=${actual%% *} + if [ "$actual" != "$EXPECTED_SHA256" ]; then + echo "lychee archive checksum mismatch: got $actual want $EXPECTED_SHA256" >&2 + exit 1 + fi + tar -xzf "$DEST/$ARCHIVE" -C "$DEST" + LYCHEE="$DEST/lychee-aarch64-apple-darwin/lychee" + chmod +x "$LYCHEE" + report="$RUNNER_TEMP/lychee-out.md" + "$LYCHEE" \ + --root-dir . \ + --no-progress \ + --max-retries 3 \ + --timeout 20 \ + --format markdown \ + README.md \ + packages/hyperliquid/README.md \ + "apps/docs/content/docs/**/*.md" \ + | tee "$report" + if grep -E 'Total[[:space:]]+\|[[:space:]]+0' "$report" >/dev/null; then + echo "No links were found. This usually indicates a configuration error." >&2 + exit 1 + fi - name: Upload GitHub Pages artifact if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' @@ -104,7 +131,7 @@ jobs: deploy: if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' needs: docs - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64] timeout-minutes: 10 permissions: pages: write @@ -116,6 +143,9 @@ jobs: group: github-pages cancel-in-progress: false steps: + # Job-level env cannot use the runner context on these Mac minis. + - name: Point TMPDIR at runner temp + run: echo "TMPDIR=$RUNNER_TEMP" >> "$GITHUB_ENV" # Enable Settings > Pages > Source: GitHub Actions before the first deployment. # Keeping Pages API calls here lets PR checks run before Pages is enabled. - name: Configure GitHub Pages diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index b89c6cec..3cbacc02 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -12,9 +12,9 @@ jobs: # improve it, never regress it. # # WHY THIS MEASURES TWICE INSTEAD OF USING THE SDK'S tests/perf/results/baseline.json: - # the scenarios measure between 9 ns and 600 µs per unit. A GitHub-hosted runner is several times - # slower than a developer machine, so comparing a committed baseline (recorded on whatever machine - # last touched it) against a runner would flag nearly every scenario as a regression on every PR. + # the scenarios measure between 9 ns and 600 µs per unit. A CI runner (even this Mac mini) is + # not the machine that last recorded the committed baseline, so comparing that file against a + # runner would flag nearly every scenario as a regression on every PR. # The SDK's `.dev/perf/compare.ts` refuses such a comparison outright. So this job measures the base # commit and the head commit back to back on the SAME runner and compares those two reports. # The committed baseline stays useful for local runs (`bun run perf:gate`) on one machine. @@ -35,18 +35,22 @@ jobs: # changes the workload being compared and is flagged for explicit review, not waved through. # If this job flaps, widen the threshold — do not add retries and do not drop the job. perf: - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64] steps: + # Job-level env cannot use the runner context on these Mac minis. + - name: Point TMPDIR at runner temp + run: echo "TMPDIR=$RUNNER_TEMP" >> "$GITHUB_ENV" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: # The base commit must be reachable so it can be checked out into a second worktree. fetch-depth: 0 - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - # Bun 1.4 (the Rust rewrite) ships only as canary; the `canary` tag moves with each build. - bun-version: canary + # setup-bun writes ~/.bun, shared across runner instances on one Mac mini. + # Parallel canary installs overwrite each other; the perf compare then + # sees two Bun versions in one job and refuses the comparison. + - name: Install Bun canary into runner temp + run: sh .github/scripts/setup-bun-isolated.sh - name: Install dependencies run: bun install --frozen-lockfile @@ -67,6 +71,11 @@ jobs: exit 1 } echo "Base: $BASE_REF -> $BASE_SHA" + # Self-hosted workspaces persist. A prior job can leave $RUNNER_TEMP/base + # registered after the directory was wiped; `worktree add` then fails. + git worktree prune + git worktree remove --force "$RUNNER_TEMP/base" 2>/dev/null || true + rm -rf "$RUNNER_TEMP/base" git worktree add "$RUNNER_TEMP/base" "$BASE_SHA" # Locate each revision independently: older commits keep the SDK at the repository # root, while workspace commits keep the same suite under packages/hyperliquid. diff --git a/.github/workflows/publish_npm.yml b/.github/workflows/publish_npm.yml index 868b10bc..57ea40c3 100644 --- a/.github/workflows/publish_npm.yml +++ b/.github/workflows/publish_npm.yml @@ -6,22 +6,24 @@ on: jobs: publish: - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64] permissions: contents: read id-token: write steps: + # Job-level env cannot use the runner context on these Mac minis. + - name: Point TMPDIR at runner temp + run: echo "TMPDIR=$RUNNER_TEMP" >> "$GITHUB_ENV" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # npm publishes via OIDC trusted publishing (no token here), which needs npm >= 11.5.1. - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: "24.x" - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - # Bun 1.4 (the Rust rewrite) ships only as canary; the `canary` tag moves with each build. - bun-version: canary + # setup-bun writes ~/.bun, shared across runner instances on one Mac mini. + - name: Install Bun canary into runner temp + run: sh .github/scripts/setup-bun-isolated.sh - name: Install dependencies run: bun install --frozen-lockfile @@ -34,15 +36,16 @@ jobs: id: tag run: | # The private workspace root has no release version; the SDK owns the published version. - VERSION=$(jq -r .version packages/hyperliquid/package.json) - # Extract tag (alpha, beta, rc) from version string if present - TAG=$(echo "$VERSION" | sed -n 's/.*-\(alpha\|beta\|rc\).*/\1/p') - # If no known tag found but version contains hyphen, use "prerelease" - if [[ -z "$TAG" && "$VERSION" == *-* ]]; then - TAG="prerelease" - fi - # Default to "latest" if no tag determined - echo "value=${TAG:-latest}" >> "$GITHUB_OUTPUT" + # bun instead of jq; case instead of GNU sed `\|` / bash `[[`. + VERSION=$(bun -e 'console.log(JSON.parse(await Bun.file("packages/hyperliquid/package.json").text()).version)') + case "$VERSION" in + *-alpha*) TAG=alpha ;; + *-beta*) TAG=beta ;; + *-rc*) TAG=rc ;; + *-*) TAG=prerelease ;; + *) TAG=latest ;; + esac + echo "value=$TAG" >> "$GITHUB_OUTPUT" - name: Publish package working-directory: ./packages/hyperliquid/dist diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 4d659d5f..f122ef7d 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -19,6 +19,10 @@ jobs: actions: read contents: read security-events: write + # Not migrated to the Mac mini runners. This reusable workflow pins + # `runs-on: ubuntu-latest` and a Linux container internally; we cannot set + # its runner. See docs/ci-runners.md. + # # SHA is the v2.3.8 tag target, verified via `gh api repos/google/osv-scanner-action/git/ref/tags/v2.3.8`. # The reusable workflow pins its own sub-actions and the osv-scanner container image by SHA internally. uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@9a498708959aeaef5ef730655706c5a1df1edbc2 # v2.3.8 diff --git a/.github/workflows/tests_offline.yml b/.github/workflows/tests_offline.yml index 89059891..cc93aa13 100644 --- a/.github/workflows/tests_offline.yml +++ b/.github/workflows/tests_offline.yml @@ -8,14 +8,16 @@ permissions: jobs: test: - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64] steps: + # Job-level env cannot use the runner context on these Mac minis. + - name: Point TMPDIR at runner temp + run: echo "TMPDIR=$RUNNER_TEMP" >> "$GITHUB_ENV" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - # Bun 1.4 (the Rust rewrite) ships only as canary; the `canary` tag moves with each build. - bun-version: canary + # setup-bun writes ~/.bun, shared across runner instances on one Mac mini. + - name: Install Bun canary into runner temp + run: sh .github/scripts/setup-bun-isolated.sh - name: Install dependencies run: bun install --frozen-lockfile @@ -25,7 +27,7 @@ jobs: # # Inside the SDK, `bun test tests/ -- --offline` also works: tests/_offline.ts recovers the raw command line # because `bun test` does not forward argv to test files. The env var is preferred here because - # it needs no such recovery — that path reads /proc/self/cmdline on Linux and is exercised only - # in CI, so relying on it would put the least-tested branch on the critical path. + # it needs no such recovery — that path reads /proc/self/cmdline on Linux or `ps` on Darwin + # and is the least-tested branch, so it stays off the CI critical path. - name: bun test (offline) run: bun run test:offline diff --git a/.github/workflows/tests_online.yml b/.github/workflows/tests_online.yml index b9e5f9ed..9dbebeaf 100644 --- a/.github/workflows/tests_online.yml +++ b/.github/workflows/tests_online.yml @@ -8,14 +8,16 @@ permissions: jobs: test: - runs-on: ubuntu-latest + runs-on: [self-hosted, macOS, ARM64] steps: + # Job-level env cannot use the runner context on these Mac minis. + - name: Point TMPDIR at runner temp + run: echo "TMPDIR=$RUNNER_TEMP" >> "$GITHUB_ENV" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - # Bun 1.4 (the Rust rewrite) ships only as canary; the `canary` tag moves with each build. - bun-version: canary + # setup-bun writes ~/.bun, shared across runner instances on one Mac mini. + - name: Install Bun canary into runner temp + run: sh .github/scripts/setup-bun-isolated.sh - name: Install dependencies run: bun install --frozen-lockfile @@ -29,15 +31,16 @@ jobs: bun test tests/ --coverage --coverage-reporter=lcov --coverage-dir=coverage test -s coverage/lcov.info # Bun writes SDK-relative source paths; coverage services need repository-relative paths. - sed -i 's|^SF:|SF:packages/hyperliquid/|' coverage/lcov.info + # Rewrite via $RUNNER_TEMP: BSD sed has no GNU `sed -i` in-place form. + sed 's|^SF:|SF:packages/hyperliquid/|' coverage/lcov.info > "$RUNNER_TEMP/lcov.info" + mv "$RUNNER_TEMP/lcov.info" coverage/lcov.info + # codecov-action requires host `gpg` for CLI integrity. These Mac minis do + # not have it; same pinned CLI + SHA256 check as lychee, same upload flags. - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./packages/hyperliquid/coverage/lcov.info - disable_search: true - fail_ci_if_error: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: sh .github/scripts/codecov-upload.sh ./packages/hyperliquid/coverage/lcov.info - name: Upload coverage to Coveralls.io uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2 diff --git a/docs/ci-runners.md b/docs/ci-runners.md new file mode 100644 index 00000000..2daf4bf9 --- /dev/null +++ b/docs/ci-runners.md @@ -0,0 +1,69 @@ +# CI runners + +Jobs in this repository run on self-hosted Apple Silicon Mac minis. + +```yaml +runs-on: [self-hosted, macOS, ARM64] +``` + +The runner must carry those three labels exactly. Workflows do not use `apt`, `sudo`, Homebrew, or Docker. + +## Not migrated + +`security.yml` calls Google's `osv-scanner-reusable.yml`. That reusable workflow pins `runs-on: ubuntu-latest` and a Linux container internally. This repository cannot set its runner, so the OSV scan stays on GitHub-hosted Ubuntu. + +## What the runner must already have + +Install these on the Mac mini before registering the GitHub Actions runner. Workflows never elevate privileges. + +| Tool | Why | +| --- | --- | +| macOS on Apple Silicon | Matches the `macOS` and `ARM64` labels. | +| GitHub Actions runner | Registered with labels `self-hosted`, `macOS`, `ARM64`. | +| Xcode Command Line Tools | `git`, `clang`, and the headers native packages (Bun, sharp, Playwright) expect. | +| `curl`, `tar`, `unzip`, `sed`, `shasum`, `bash`, `tee` | Stock macOS. Workflow scripts stay BSD-portable and write scratch files under `$RUNNER_TEMP` (exported as `TMPDIR` via `$GITHUB_ENV` in a setup step; job-level `env` cannot use the `runner` context). | + +Do not install CI tools with `apt` or `sudo` for these workflows. Homebrew is not required. + +The runner currently lacks **`gpg`**. `codecov/codecov-action` refuses to start without it (it GPG-checks the Codecov CLI). These workflows do not need host `gpg`: they pin-download `codecovcli_macos` v11.3.1 and SHA256-verify it, the same way they install lychee. Sibling repos that still call `codecov-action` directly will need `gpg` on the Mac mini (GnuPG, on `PATH`). This repository also does not need host `gh`, `gtar`, or `jq`. + +The runner user must be able to write the workspace and `$RUNNER_TEMP`. The machine needs enough free disk for a Bun install, a perf worktree, and Playwright's Chromium download (a few gigabytes in total). + +## What workflows install themselves + +These are not machine prerequisites. The jobs fetch them into the job environment or `$RUNNER_TEMP`: + +- **Bun canary** — downloaded into `$RUNNER_TEMP` (`bun-darwin-aarch64` zip) on every job that runs workspace scripts. `oven-sh/setup-bun` is not used: it installs into `~/.bun`, which is shared across runner instances on the same Mac mini, and a mid-job overwrite makes the perf gate see two Bun versions. +- **Node.js 24** — `actions/setup-node` on the npm publish job only (OIDC trusted publishing needs npm ≥ 11.5.1). +- **Playwright Chromium** — `bunx playwright install chromium` in the docs job. `--with-deps` is omitted; that flag installs Linux apt packages. Chromium's macOS libraries ship with the OS. The first docs run downloads the browser into Playwright's cache on the runner. +- **lychee v0.24.2** (`aarch64-apple-darwin`) — downloaded into `$RUNNER_TEMP`, checksum-verified, and used for the same Markdown link scan as before. The `lychee-action` composite only ships a Linux GNU binary. +- **Codecov CLI v11.3.1** (`codecovcli_macos`, universal x86_64/arm64) — downloaded into `$RUNNER_TEMP`, checksum-verified, and used for the same `upload-coverage` call as `codecov-action` (`--file`, `--disable-search`, `--fail-on-error`). The action is not used because it requires host `gpg`. + +## Network egress + +The runner needs outbound HTTPS to: + +- `github.com` (checkout, Actions APIs, Pages deploy, lychee release assets, OIDC) +- the Bun and npm registries +- Playwright's browser CDN +- `codecov.io` and `coveralls.io` (coverage uploads) +- Hyperliquid testnet (the online test job) +- `registry.npmjs.org` (publish) + +## Secrets + +Unchanged: `CODECOV_TOKEN`, `PRIVATE_KEY` (online tests), and the default `GITHUB_TOKEN`. npm publish uses OIDC trusted publishing (`id-token: write`), not an npm token. + +## Jobs + +| Workflow | Job | Runner | +| --- | --- | --- | +| `code_quality.yml` | `quality` | self-hosted Mac mini | +| `coverage.yml` | `coverage` | self-hosted Mac mini | +| `docs.yml` | `docs` | self-hosted Mac mini | +| `docs.yml` | `deploy` | self-hosted Mac mini | +| `perf.yml` | `perf` | self-hosted Mac mini | +| `publish_npm.yml` | `publish` | self-hosted Mac mini | +| `tests_offline.yml` | `test` | self-hosted Mac mini | +| `tests_online.yml` | `test` | self-hosted Mac mini | +| `security.yml` | `osv-scan` | GitHub-hosted Ubuntu (Google reusable workflow) |