## Summary
Every PR's blocking checks were dominated by redundant full rebuilds,
not by tests. Measured on recent runs: the Gate job spent ~6 of its ~7.5
min on a cold `pnpm build` (the exact-key dist cache missed on virtually
every PR) for ~45s of actual boot smoke + gate tests; Build ran ~8 min
and Typecheck ~4 min, both fully cold every time. Expected end state
once the warm job has run on main: all four blocking checks in roughly
2–4 min wall-clock.
### Gate job
- New `gate-dist-*` cache namespace with `restore-keys`, additionally
caching `.fusion/cache/plugin-build-cache.json` (build-workspace's
per-package content-hash skip cache) and `packages/cli/dist`. The
always-run `pnpm build` reconciles a near-match restore by content hash
and rebuilds only the packages the PR touched. This is safe *because*
the gate builds after restoring — the shard jobs' "no restore-keys" rule
(FN-4232/FN-4605 stale-dist incidents) still stands there, since they
consume dist without building.
- `FUSION_CLI_FULL_PACKAGE=0` on the gate build: skips the multi-minute
CLI desktop/plugins/DTS packaging tail nothing in the gate consumes
(same shape `pnpm verify:fast` proves locally). Full CLI packaging
coverage stays blocking in the Build job.
### Build job
- Restore-only tap (`actions/cache/restore`) of the same warmed cache.
Restore-only because this job runs FULL CLI packaging (`CI=true`) and
saving that shape would swap the cache's canonical fast-CLI contents out
from under the Gate job. Its distinctive coverage is preserved:
`ensureFullPackageCliPlanned` force-plans the CLI in full mode
regardless of cache state.
### Typecheck job
- Caches per-package tsc incremental buildinfo — self-validating (tsc
hashes every input against it and re-checks whatever changed), so
`restore-keys` is correctness-neutral by construction.
- **Fixes a real incrementality bug:** `tsconfig.json` and
`tsconfig.app.json` in the dashboard both inherited
`${configDir}/dist/.tsbuildinfo` from `tsconfig.base.json`, so the two
typecheck programs clobbered each other's buildinfo and re-checked the
full program every run — incremental typechecking never worked for the
dashboard, in CI or locally. `tsconfig.app.json` now writes
`dist/.tsbuildinfo-app`. Measured: dashboard typecheck 44s cold → 5.6s
warm.
### Warm job (full-suite.yml, push to main)
- New `warm-gate-build-cache` job saves both caches from main on every
push. Caches saved on a PR merge ref are invisible to other PRs, so
without this every PR's *first* run would still build/check cold.
## Guardrails
`ci-workflow.test.ts` pins the coupled invariants so they can't drift
apart silently:
- restore-keys requires the reconciling `pnpm build` after restore,
before boot smoke
- the mtime-defeating seed step stays exact-hit-only
- byte-identical cache path lists between the Gate/Build/warm blocks
(actions/cache versions caches by path list — a drifted list makes
caches mutually invisible)
- Build stays restore-only and must NOT opt out of full CLI packaging
- Typecheck cache shape + the distinct dashboard app buildinfo path
## Notes
- First PR runs after this lands still build cold until the warm job has
run once on main.
- No changeset: CI config + test-only per AGENTS.md.
## Verification
- `ci-workflow.test.ts` + `package-config.test.ts`: 106 tests pass
- Dashboard typecheck run twice locally: 44s cold → 5.6s warm, both
`.tsbuildinfo` and `.tsbuildinfo-app` written, exit 0
- Cache block path/key parity verified programmatically across both
workflow files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Performance**
* Improved CI build and type-check performance through incremental
caching.
* Added cache warming from the main branch to speed up pull request
checks.
* Enabled faster CLI packaging during gate validation while retaining
full packaging coverage elsewhere.
* **Bug Fixes**
* Prevented dashboard TypeScript build information from being
overwritten, preserving incremental type-checking reliability.
* **Tests**
* Added coverage to verify CI cache behavior, build ordering, cache
paths, and packaging modes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
248 lines
10 KiB
YAML
248 lines
10 KiB
YAML
name: PR Checks
|
|
|
|
# The thin trusted merge gate (docs/plans/2026-06-04-001-refactor-fast-trusted-test-gate-plan.md).
|
|
# Blocking checks are exactly: Lint, Typecheck, Build, Gate.
|
|
#
|
|
# BRANCH-PROTECTION CUTOVER: required status checks are matched by job name.
|
|
# When this file changes job names, update the repo's branch-protection
|
|
# required checks to exactly [Lint, Typecheck, Build, Gate] — a stale required
|
|
# name (e.g. "Test shard 1/4") that no longer reports will block every PR
|
|
# with "Expected — waiting for status". Open PRs must rebase onto main after
|
|
# the cutover so they run this workflow shape.
|
|
#
|
|
# Everything that used to run here as shards / slow tier / inventory guard is
|
|
# non-blocking and lives in full-suite.yml (push to main).
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
concurrency:
|
|
group: pr-checks-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
# Least-privilege token: every job here only reads the repo (checkout + cache).
|
|
permissions:
|
|
contents: read
|
|
|
|
# FN-4863: Opt JavaScript actions into Node 24 ahead of GitHub's forced cutover on 2026-06-02.
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
|
|
jobs:
|
|
lint:
|
|
name: Lint
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js and pnpm
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
- name: Lint
|
|
run: pnpm lint
|
|
|
|
- name: Changeset format
|
|
run: pnpm check:changesets
|
|
|
|
- name: Dashboard route modularity
|
|
run: pnpm check:routes-modular
|
|
|
|
typecheck:
|
|
name: Typecheck
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js and pnpm
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
# FNXC:CIGateSpeed 2026-07-23-00:05:
|
|
# tsc incremental buildinfo cache. tsconfig.base.json enables
|
|
# `incremental` with per-package dist/.tsbuildinfo; a restored buildinfo
|
|
# is SELF-VALIDATING (tsc hashes every input against it and re-checks
|
|
# whatever changed), so restore-keys can never let a stale check through
|
|
# — it only shrinks the re-checked set. Cold typecheck was ~4 min of the
|
|
# PR critical path. Keyed by SHA so every commit saves a fresh snapshot;
|
|
# restore-keys picks the nearest prior one (same PR, or main via the
|
|
# warm-gate-build-cache job in full-suite.yml, which must keep an
|
|
# identical path list — actions/cache versions caches by path list).
|
|
- name: Cache TypeScript incremental buildinfo
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
packages/*/dist/.tsbuildinfo
|
|
packages/dashboard/dist/.tsbuildinfo-app
|
|
plugins/*/dist/.tsbuildinfo
|
|
key: typecheck-tsbuildinfo-${{ runner.os }}-${{ github.sha }}
|
|
restore-keys: |
|
|
typecheck-tsbuildinfo-${{ runner.os }}-
|
|
|
|
- name: Typecheck
|
|
run: pnpm typecheck
|
|
|
|
build:
|
|
name: Build
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js and pnpm
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
# FNXC:CIGateSpeed 2026-07-23-00:05:
|
|
# RESTORE-ONLY tap of the gate's incremental dist cache (see the gate
|
|
# job's cache block for the full safety rationale: `pnpm build` always
|
|
# runs and reconciles a near-match restore per package by content hash).
|
|
# Restore-only (actions/cache/restore, never save) because this job runs
|
|
# FULL CLI packaging (CI=true → desktop + bundled plugins + DTS), and
|
|
# saving that shape would swap the cache's canonical fast-CLI contents
|
|
# out from under the Gate job. Full CLI packaging itself is never skipped
|
|
# regardless of cache state (ensureFullPackageCliPlanned force-plans the
|
|
# CLI in full mode), so this job's distinctive coverage is preserved.
|
|
# Path list must stay byte-identical to the gate job's block.
|
|
- name: Compute dist source hash
|
|
id: dist-hash
|
|
run: echo "hash=$(node scripts/ensure-test-artifacts.mjs --print-source-hash)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Restore built dist artifacts (incremental, restore-only)
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
path: |
|
|
packages/core/dist
|
|
packages/dashboard/dist
|
|
packages/engine/dist
|
|
packages/plugin-sdk/dist
|
|
packages/cli/dist
|
|
plugins/fusion-plugin-dependency-graph/dist
|
|
plugins/fusion-plugin-hermes-runtime/dist
|
|
plugins/fusion-plugin-openclaw-runtime/dist
|
|
plugins/fusion-plugin-paperclip-runtime/dist
|
|
.fusion/cache/plugin-build-cache.json
|
|
key: gate-dist-${{ runner.os }}-${{ steps.dist-hash.outputs.hash }}
|
|
restore-keys: |
|
|
gate-dist-${{ runner.os }}-
|
|
|
|
- name: Build
|
|
run: pnpm build
|
|
|
|
# The only merge-blocking TEST signal (R3). Runs the boot smoke (the app
|
|
# starts and serves) plus the curated engine-core suite and the CI-shape
|
|
# test — see `test:gate` in the root package.json. Gate membership is the
|
|
# explicit allow-list in packages/engine/vitest.config.ts (engine-core
|
|
# project); a flaky gate test is evicted by removing it from that list.
|
|
gate:
|
|
name: Gate
|
|
runs-on: ubuntu-latest
|
|
# FNXC:FixPgTestsAndCi 2026-06-26-09:10:
|
|
# Provision a PostgreSQL service container so the postgres/*.pg.test.ts
|
|
# suites (pgDescribe) run in the merge gate. The pg-test-harness probe
|
|
# detects reachability via a TCP probe on localhost:5432 and skips when
|
|
# unavailable, so this service is what makes the 57 PG twin tests actually
|
|
# execute instead of being silently skipped.
|
|
services:
|
|
postgres:
|
|
image: postgres:15
|
|
env:
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_PASSWORD: postgres
|
|
POSTGRES_DB: postgres
|
|
ports:
|
|
- 5432:5432
|
|
# Mark the service healthy only when pg_isready succeeds on the mapped
|
|
# port, so job steps don't start before Postgres accepts connections.
|
|
options: >-
|
|
--health-cmd "pg_isready -h localhost -p 5432 -U postgres"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 10
|
|
env:
|
|
# Point the PG test harness at the service container. psql admin DDL
|
|
# (CREATE/DROP DATABASE) runs against this URL's maintenance database.
|
|
FUSION_PG_TEST_URL_BASE: "postgresql://postgres:postgres@localhost:5432"
|
|
PGPASSWORD: "postgres"
|
|
# The gate's value is speed; without a job timeout a hung build or
|
|
# deadlocked vitest worker blocks every PR for GitHub's default 6 hours.
|
|
# Expected runtime is ~3-5 min.
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js and pnpm
|
|
uses: ./.github/actions/setup-node-pnpm
|
|
|
|
# FNXC:CIGateSpeed 2026-07-22-23:30:
|
|
# Gate-scoped INCREMENTAL dist cache (`gate-dist-*` namespace, distinct from
|
|
# the shard jobs' exact-match `dist-*` contract). Unlike the shard jobs —
|
|
# where restore-keys is forbidden because tests consume restored dist
|
|
# WITHOUT a build (stale dist was the FN-4232/FN-4605 failure mode) — the
|
|
# gate always runs `pnpm build` after restore. build-workspace.mjs verifies
|
|
# every package's git content hash against .fusion/cache/plugin-build-cache.json
|
|
# (cached below alongside dist) and rebuilds anything changed, missing, or
|
|
# unhashed, so a near-match restore can only speed the build up, never let
|
|
# stale dist through. Measured before this change: every PR missed the
|
|
# exact key and paid a full ~6-8 min build for ~45s of actual gate tests.
|
|
# NEVER add node_modules here (breaks Windows pnpm junctions elsewhere).
|
|
# The warm-gate-build-cache job in full-suite.yml saves this same cache
|
|
# (identical path list — actions/cache versions caches by path list, so
|
|
# the two blocks must stay in sync) on every push to main so a PR's FIRST
|
|
# gate run restores main's build instead of building cold.
|
|
- name: Compute dist source hash
|
|
id: dist-hash
|
|
run: echo "hash=$(node scripts/ensure-test-artifacts.mjs --print-source-hash)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Cache built dist artifacts (incremental)
|
|
id: dist-cache
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
packages/core/dist
|
|
packages/dashboard/dist
|
|
packages/engine/dist
|
|
packages/plugin-sdk/dist
|
|
packages/cli/dist
|
|
plugins/fusion-plugin-dependency-graph/dist
|
|
plugins/fusion-plugin-hermes-runtime/dist
|
|
plugins/fusion-plugin-openclaw-runtime/dist
|
|
plugins/fusion-plugin-paperclip-runtime/dist
|
|
.fusion/cache/plugin-build-cache.json
|
|
key: gate-dist-${{ runner.os }}-${{ steps.dist-hash.outputs.hash }}
|
|
restore-keys: |
|
|
gate-dist-${{ runner.os }}-
|
|
|
|
# Only seed the mtime-defeating artifact hash-cache on an EXACT hit; on a
|
|
# restore-keys near-hit the restored dist may be stale for changed
|
|
# packages, and `pnpm build` below is what reconciles it.
|
|
- name: Seed artifact hash-cache on cache hit
|
|
if: steps.dist-cache.outputs.cache-hit == 'true'
|
|
run: node scripts/ensure-test-artifacts.mjs --seed-artifact-cache
|
|
|
|
# FNXC:CIGateSpeed 2026-07-22-23:30:
|
|
# Boot smoke needs the built workspace including the CLI. Fast CLI
|
|
# packaging (bin.js + extension.js, no desktop/bundled-plugin/DTS staging)
|
|
# is sufficient for boot smoke + test:gate and is the same shape
|
|
# `pnpm verify:fast` proves locally; CI=true would otherwise force the
|
|
# multi-minute full packaging tail on every gate run. Full CLI packaging
|
|
# coverage stays blocking in the separate Build job.
|
|
- name: Build
|
|
run: pnpm build
|
|
env:
|
|
FUSION_CLI_FULL_PACKAGE: "0"
|
|
|
|
- name: Boot smoke (app starts and serves)
|
|
run: node scripts/boot-smoke.mjs
|
|
|
|
- name: Gate tests (curated engine-core + CI-shape)
|
|
run: pnpm test:gate
|
|
|
|
# Advisory desktop-packaging validation lives in its OWN workflow (desktop-packaging.yml) so this
|
|
# thin gate stays exactly [Lint, Typecheck, Build, Gate] — the job set here maps 1:1 to the
|
|
# branch-protection required checks (CI-shape test enforces the invariant).
|