Repository navigation
docs(release): record 3.141.592657 B06/B07 and correct action count #962
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # Every job only reads the repository; keep the token from defaulting to write. | |
| permissions: | |
| contents: read | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| branches: [master] | |
| # Manual mutation sweep (Actions -> CI -> Run workflow). Deliberately NOT on the PR path: | |
| # each mutation re-runs its spec's whole test suite, so all 85 specs is hours, and a gate | |
| # nobody can afford to run is worse than no gate — that is the warning in the specs job's | |
| # own comment. Until 2026-09-13 nothing in this repo ever executed `npm run mutate`. | |
| workflow_dispatch: | |
| inputs: | |
| specs: | |
| description: 'Spec files to run (space separated). Blank = every test/mutations/*.json' | |
| required: false | |
| default: '' | |
| shard: | |
| description: 'Zero-based shard i/N (0/1 = all selected specs)' | |
| required: false | |
| default: '0/1' | |
| jobs: | |
| mutations: | |
| name: Mutation specs (manual) | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 350 | |
| steps: | |
| # fetch-depth 0: the mutation harness materializes its workspace with | |
| # `git clone --local` and reads history objects, so a shallow checkout fails it. | |
| # Measured 2026-09-13: with the default depth 1 the sweep died on the FIRST spec | |
| # with exit 2 and produced no verdict lines at all. | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node 24 | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 | |
| with: | |
| node-version: '24' | |
| - name: Install dependencies | |
| run: npm ci | |
| # PR/push jobs intentionally do not execute mutation specs: this is a cost boundary. | |
| # Optional parallel form (replace the input shard with matrix.shard in Sweep): | |
| # strategy: | |
| # fail-fast: false | |
| # matrix: | |
| # shard: ['0/4', '1/4', '2/4', '3/4'] | |
| # Put strategy at the mutations job level, not inside steps. Each job has its own | |
| # checkout, manifest and artifact; never run two shards in one owner worktree. | |
| - name: Sweep | |
| env: | |
| REQUESTED_SPECS: ${{ inputs.specs }} | |
| REQUESTED_SHARD: ${{ inputs.shard }} | |
| run: | | |
| args=(--shard "${REQUESTED_SHARD:-0/1}" --report-dir "$RUNNER_TEMP/mutation-report") | |
| if [ -n "$REQUESTED_SPECS" ]; then args+=(--specs "$REQUESTED_SPECS"); fi | |
| node scripts/mutation-sweep.mjs "${args[@]}" | |
| - name: Assert verdict coverage | |
| if: always() | |
| run: node scripts/mutation-sweep.mjs --verify "$RUNNER_TEMP/mutation-report" | |
| - name: Publish mutation evidence | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: mutation-report-${{ github.run_id }}-${{ strategy.job-index || 0 }} | |
| path: ${{ runner.temp }}/mutation-report/ | |
| if-no-files-found: error | |
| syntax: | |
| name: Node syntax check | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 | |
| - name: Setup Node 24 | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 | |
| with: | |
| node-version: '24' | |
| - name: Check JS syntax for all src files | |
| run: | | |
| find src -name '*.js' -type f -print0 | xargs -0 -n1 node --check | |
| - name: Validate package.json | |
| run: node -e "JSON.parse(require('fs').readFileSync('package.json'))" | |
| - name: Validate JSON configs | |
| run: | | |
| for f in $(find . -maxdepth 2 -name '*.json' -not -path './node_modules/*' -not -path './.git/*'); do | |
| echo "Checking $f" | |
| node -e "JSON.parse(require('fs').readFileSync('$f'))" | |
| done | |
| # Mutation-spec consistency. Three baseline/anchor drifts reached master in one week | |
| # (#241's anchors, retry-rescue 82/87 -> 88, reasoning-continuity 289 -> 300) and the | |
| # test suite saw none of them: it does not run specs. The anchor/shape checks read files | |
| # only and cost ~0.3s, so they are mandatory rather than a local habit to remember. | |
| # | |
| # Baseline VALUE drift is measured here too, by EXECUTING the covered specs and reading | |
| # the runner's events (scripts/spec-baseline-check.mjs) — never by scraping display text | |
| # and never by rewriting a pin. A changed diff measures only the affected specs; a | |
| # synchronised checkout measures all of them, which costs ~100s for the whole set (82 | |
| # specs when that was measured; 85 today). The check used to run only in the local gate, | |
| # so a wrong pin reached CI green. | |
| specs: | |
| name: Mutation spec consistency | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 | |
| with: | |
| # Change-scoped selection diffs against origin/master, so the history has to be | |
| # here; a shallow clone would silently widen the selection to every spec. | |
| fetch-depth: 0 | |
| - name: Setup Node 24 | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 | |
| with: | |
| node-version: '24' | |
| - name: Anchors unique, specs well-formed | |
| run: node scripts/spec-static-check.mjs | |
| - name: Measured baselines match, from executed evidence | |
| run: node scripts/spec-baseline-check.mjs | |
| - name: Secret scan (includes test/ — half of a typical diff lives there) | |
| run: node scripts/secret-scan.mjs | |
| # The `specs` job scans the WORKING TREE. A credential that was committed and then | |
| # deleted is invisible there and still present in every clone, so this job scans every | |
| # blob reachable from any ref plus the unreachable objects an amend / rebase / | |
| # force-push leaves behind (the objects a rewrite was meant to hide). fetch-depth: 0 is | |
| # the whole point — a shallow clone would scan the tip and report it clean. | |
| # | |
| # A finding that is not in scripts/history-secret-scan.baseline fails this job. That | |
| # file is a reviewed list, one line per accepted historical finding with its reason; | |
| # it is not a mute button (the scan warns about stale entries, so a fixed leak leaves a | |
| # line someone has to delete). The scan is ~15s on a 1.9k-commit history and needs no | |
| # network, no binary download and no token — which is why it is the gate instead of | |
| # gitleaks; see HistorySecretScan.md for the gitleaks cross-check. | |
| history-secrets: | |
| name: History secret scan | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node 24 | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 | |
| with: | |
| node-version: '24' | |
| - name: Scan every blob in every ref | |
| run: node scripts/history-secret-scan.mjs | |
| wire-identity: | |
| name: Default-path request bytes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 | |
| with: | |
| # The comparison base is a tag, so the tags have to be here. | |
| fetch-depth: 0 | |
| - name: Setup Node 24 | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 | |
| with: | |
| node-version: '24' | |
| - name: Select the release baseline (strict ancestor, recorded) | |
| # `git describe --tags --abbrev=0` answers "nearest reachable tag", and at a | |
| # tagged HEAD that is HEAD's own tag: a rerun of a release commit compared | |
| # the tree with itself. scripts/wire-base.mjs picks the newest ANNOTATED | |
| # release tag that is a strict ancestor of the tested commit — no | |
| # lightweight fallback — so a rerun at a tag repeats the same comparison | |
| # against the PREVIOUS release, the recorded SHAs never change, and a | |
| # repository without such a tag fails closed (exit 3). | |
| run: | | |
| node scripts/wire-base.mjs | |
| BASE_TAG=$(node scripts/wire-base.mjs --field tag) | |
| BASE_SHA=$(node scripts/wire-base.mjs --field sha) | |
| HEAD_SHA=$(git rev-parse HEAD) | |
| if [ "$BASE_SHA" = "$HEAD_SHA" ]; then | |
| echo "::error::refusing to compare $HEAD_SHA against itself" | |
| exit 1 | |
| fi | |
| git worktree add --detach "$RUNNER_TEMP/wire-base" "$BASE_SHA" | |
| echo "comparing $HEAD_SHA against $BASE_SHA ($BASE_TAG)" | |
| echo "WIRE_BASE_TREE=$RUNNER_TEMP/wire-base" >> "$GITHUB_ENV" | |
| echo "WIRE_BASE_SHA=$BASE_SHA" >> "$GITHUB_ENV" | |
| echo "WIRE_BASE_TAG=$BASE_TAG" >> "$GITHUB_ENV" | |
| echo "WIRE_BASE_REQUIRED=1" >> "$GITHUB_ENV" | |
| - name: Same bytes as the last release, or this fails | |
| run: node --import ./test/setup-env.mjs --test test/wire-byte-identity.test.js | |
| test: | |
| name: Unit tests shard ${{ matrix.shard }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [0, 1, 2, 3] | |
| steps: | |
| - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 | |
| with: | |
| # test/docs-consistency-guard.test.js resolves the handoff's tag and commit | |
| # claims with git. The default depth-1 clone carries neither the history nor | |
| # the tags, so shards 1 and 3 failed on "ambiguous argument '<sha>^{commit}'" | |
| # and "no release tags reachable from HEAD" while the same suite passed | |
| # locally. The guard fails closed by design; give it the objects, not an | |
| # exemption. release.yml checks out full history for the same reason. | |
| fetch-depth: 0 | |
| - name: Setup Node 24 | |
| uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 | |
| with: | |
| node-version: '24' | |
| cache: 'npm' | |
| # Zero npm dependencies: package.json declares none, and the JPEG/PNG decoders | |
| # are vendored under src/vendor/. This step is a no-op today — kept so that a | |
| # future dependency cannot silently break the shards that import it. | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run tests | |
| run: npm run test:shard -- ${{ matrix.shard }} 4 |