test(cliffs): the cascade's whole gain is CROSS-CHUNK, and +8 was the wrong baseline (#84) #229
Workflow file for this run
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
| # The whole quality gate, run automatically. Until this file existed, `pnpm run | |
| # verify` only ran when a human remembered to (or at deploy time, which can be | |
| # days after the breaking merge) - see issue #54. | |
| # | |
| # `verify` no longer runs as one job, because the test phase is ~95% of it and | |
| # a runner is ~3x slower than a dev machine: the single job measured 9m03s on | |
| # PR #116. The phases are now split across a `static` job and a sharded `tests` | |
| # matrix that run in parallel, with a `verify` job aggregating them. | |
| # | |
| # The anti-drift rule that motivated the old VERBATIM comment still holds, and | |
| # is now enforced differently: this file names only package.json SCRIPTS | |
| # (`verify:static`, `verify:shard`), never the underlying commands. `verify`, | |
| # `verify:static` and `verify:shard` are all composed from the same | |
| # `verify:lint` script, so there is still exactly one definition of each phase | |
| # and CI cannot drift from local. Do not inline `vp check` etc. here. | |
| # Note it is never `check` - that is `vp check --fix` and CI must never rewrite | |
| # files. | |
| # | |
| # WHY THE JOB NAMED `verify` STILL EXISTS: ruleset `EJ` requires status checks | |
| # named `verify` and `build`. A required check that never appears blocks every | |
| # PR forever, so the aggregating job below MUST keep the id `verify`. Renaming | |
| # it needs a ruleset PUT in the same change - see CLAUDE.md on the two-step. | |
| # | |
| # WHY 3 SHARDS, MEASURED NOT GUESSED: vitest splits by FILE, so a single file | |
| # is an unsplittable floor. `test/previewAgreement.spec.ts` is 67s of the 68s | |
| # local suite (~200s on a runner). Ideal-split time is CI_test/N, so N=3 is | |
| # where that first drops below the floor - and N=4+ buys literally nothing | |
| # until that file is split into its three independent tests. Raise N only | |
| # after splitting it, and re-measure rather than assuming. | |
| # | |
| # The production build runs too, in the separate `build` job below - see its | |
| # own comment for why it is not a phase inside `verify`. | |
| # | |
| # What is deliberately NOT here: | |
| # - `pnpm refs:sync`. It needs a Factorio binary and ~/GitHub/factorio-data, | |
| # neither of which exists on a runner. `verify` is designed to pass with no | |
| # Factorio installed and that property is what makes this workflow possible. | |
| # - Any deploy step or credential. Cloudflare Pages does not build this repo | |
| # (`deploy:app` uploads an already-built `dist`), so CI here is a check | |
| # only and the job needs no secrets at all. | |
| name: verify | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| # So a run can be forced without an empty commit. | |
| workflow_dispatch: | |
| # Superseded pushes to the same PR are pointless at ~4 minutes a run, so those | |
| # get cancelled. Pushes to `main` do NOT - `cancel-in-progress` there means a | |
| # second merge kills the first one's run, and `main` is the branch the deploy | |
| # ships from, so every commit on it should carry its own verdict. | |
| # | |
| # This is not hypothetical: on 2026-07-30 six PRs merged inside half an hour and | |
| # the runs for `605a4cc` and `be5f592` were both cancelled by the merges that | |
| # followed them. Neither commit is unverified in practice - each had a green | |
| # `verify` on its own PR before the ruleset would let it merge - but "green on | |
| # the PR" and "green as it sits on main" are different claims, and only the | |
| # second one survives a rebase-free merge into a `main` that has since moved. | |
| # Ruleset `EJ` has since set `strict_required_status_checks_policy: true`, which | |
| # closes most of that gap by forcing a PR up to date before it can merge; a | |
| # per-commit verdict on `main` is still the thing that proves it, so these runs | |
| # stay uncancelled. | |
| concurrency: | |
| group: verify-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| # Minimum scope: the job only reads the tree. It writes no statuses, comments, | |
| # packages or releases, so nothing beyond `contents: read` is granted. | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Everything in `verify` that is NOT the app test suite: `vp check`, | |
| # `check:vue`, and `preview:test`. ~24s on a runner, so it finishes long | |
| # before any shard and costs nothing to run as its own job. | |
| static: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| env: | |
| # `preview:test` shells out to wrangler (`wrangler types --check`). Keep it | |
| # from phoning home for telemetry on a runner where nobody can answer the | |
| # opt-in prompt. | |
| WRANGLER_SEND_METRICS: "false" | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version-file: .node-version | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm run verify:static | |
| # The app test suite, split across runners. Each shard is a separate machine, | |
| # which is the ONLY way to raise the parallelism ceiling - `maxWorkers` was | |
| # already measured as a dead end on one box (see CLAUDE.md). | |
| tests: | |
| runs-on: ubuntu-latest | |
| # A shard is ~1/3 of a ~9-minute suite. 15 minutes stays a hang detector. | |
| timeout-minutes: 15 | |
| strategy: | |
| # One failing shard must not cancel the others: a red `verify` should say | |
| # everything that is broken, not just whichever shard tripped first. | |
| fail-fast: false | |
| matrix: | |
| shardIndex: [1, 2, 3] | |
| shardTotal: [3] | |
| steps: | |
| # Third-party actions are pinned to a full commit SHA, never a moving tag. | |
| # The trailing comment names the release each SHA is, and Renovate updates | |
| # the SHA and that comment together - see .github/renovate.json5. | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # No `version:` input on purpose. pnpm/action-setup >= 6 reads | |
| # `devEngines.packageManager` from package.json, so the pnpm pin stays in | |
| # exactly one place. Hard-coding it here would be a second pin to drift. | |
| # This step must precede setup-node: `cache: pnpm` below resolves the | |
| # store path by running pnpm, so pnpm has to be on PATH already. | |
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| # `.node-version` (26.5.0) is the single source of truth for the Node | |
| # version, and this is its first real consumer - the file went from | |
| # documentation to machinery when this workflow landed. `engines.node` | |
| # stays a permissive floor and is deliberately NOT what CI runs. | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version-file: .node-version | |
| cache: pnpm | |
| # A full workspace install. Anything narrower can leave a sibling | |
| # workspace's symlink dangling and produce fake | |
| # `TS2307: Cannot find module 'vitest'` errors; a real full install is the | |
| # one that reports `Scope: all 3 workspace projects`. | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm run verify:shard -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} | |
| # The required status check. Ruleset `EJ` requires a check named `verify`, so | |
| # this job must keep that id even though the work now happens above. | |
| # | |
| # `!cancelled()` rather than `always()`: a cancelled run (superseded push, per | |
| # the concurrency group) should stay cancelled, not be converted into a | |
| # failure. But a FAILED dependency must fail this job - a `needs:` job whose | |
| # dependency failed is otherwise skipped, and a skipped required check does | |
| # not block a merge. Hence the explicit result assertions. | |
| verify: | |
| needs: [static, tests] | |
| if: ${{ !cancelled() }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Assert every phase passed | |
| env: | |
| STATIC: ${{ needs.static.result }} | |
| TESTS: ${{ needs.tests.result }} | |
| run: | | |
| echo "static=$STATIC tests=$TESTS" | |
| # `tests` is the matrix aggregate: 'success' only if every shard was. | |
| [ "$STATIC" = "success" ] || { echo "::error::static phase: $STATIC"; exit 1; } | |
| [ "$TESTS" = "success" ] || { echo "::error::test shards: $TESTS"; exit 1; } | |
| # `verify` is check + type-check + tests. None of them build, so a change that | |
| # passes all three and breaks the production build reached `main` unnoticed | |
| # until somebody deployed - possibly days later, and landing on whoever was | |
| # deploying rather than whoever broke it (issue #61). This job closes that. | |
| # | |
| # Deliberately a SEPARATE job rather than an extra phase inside `verify`: | |
| # `deploy` already runs `pnpm build` immediately after `pnpm run verify`, so | |
| # folding the build into `verify` would build twice on every deploy and slow | |
| # the local gate people actually run by hand. Separate also means it runs in | |
| # parallel with the test job instead of after it. | |
| build: | |
| runs-on: ubuntu-latest | |
| # The build is ~1s of Rolldown after ~30s of setup. 15 minutes is a hang | |
| # detector, matching the job above. | |
| timeout-minutes: 15 | |
| steps: | |
| # Default `fetch-depth: 1` is correct here, and that was MEASURED rather | |
| # than assumed - #61 was filed believing the build needed deeper history. | |
| # `scripts/buildStamp.ts` runs exactly three git commands: | |
| # `rev-parse HEAD`, `rev-parse --short HEAD`, and `status --porcelain`. | |
| # None of them read history, so a shallow checkout is enough and the | |
| # earlier "the build stamp reads git history" was imprecise - it reads git | |
| # *state*. | |
| # | |
| # Worth knowing rather than fixing: on a `pull_request` event checkout | |
| # lands on the merge commit, so the stamp this job produces is a synthetic | |
| # SHA that exists nowhere in the repo. Harmless, because CI never deploys | |
| # its artifact - `deploy:app` builds locally and uploads that. If a CI | |
| # build ever becomes the thing that ships, this needs revisiting. | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version-file: .node-version | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| # This catches a build that FAILS. It does not enforce "zero warnings", | |
| # which CLAUDE.md leans on ("anything that does appear is new and worth | |
| # reading"). Both ways of enforcing that were considered and rejected: | |
| # | |
| # - Grepping the build output for /warn/i. `vp build` has no | |
| # `--fail-on-warn` (checked against vp 0.2.6 `--help`), and this repo | |
| # has already been burned once by grepping build output - a version | |
| # grep returned zero because the minifier had rewritten the string. | |
| # A false pass here is worse than no check. | |
| # - A `build.rollupOptions.onLog` hook that throws on `warn`. Robust, but | |
| # it would hard-fail every local build too the moment a dependency | |
| # emits one benign warning, and dependency-sourced warnings are exactly | |
| # what this repo has seen (zlib-asm needed two suppressions before #46 | |
| # removed it). The value is in reading a new warning, not in blocking on | |
| # it. | |
| # | |
| # So warnings stay visible in this job's log and unenforced. If that ever | |
| # needs to change, the `onLog` route is the sturdy one - not a grep. | |
| - run: pnpm vp build |