refactor(ci): thin PR gate to lint/typecheck/build/gate; demote full suite to non-blocking push-to-main workflow

- pr-checks.yml: gate job = boot smoke + pnpm test:gate; shards/slow/inventory removed
- full-suite.yml (new): 4-way shards, engine slow tier, inventory guard on push to main
- ci.yml deleted (trigger-disabled since FN-1541; was dead config)
- ci-workflow.test.ts rewritten to pin the new gate shape; release/signing blocks preserved
- docs/contributing.md: pnpm test:gate is the merge gate; verify:workspace repositioned as deep opt-in

BRANCH-PROTECTION CUTOVER: set required checks to exactly [Lint, Typecheck, Build, Gate] after merging; open PRs must rebase.
This commit is contained in:
gsxdsm
2026-06-05 09:02:36 -07:00
parent 9d00f533ea
commit fd7fed22f4
6 changed files with 355 additions and 319 deletions

View File

@@ -1,69 +0,0 @@
name: CI
# CI auto-trigger disabled per FN-1541 — workflow preserved for manual use via workflow_dispatch
on:
workflow_dispatch:
# 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 and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Lint
run: pnpm lint
test-shards:
name: Test shard ${{ matrix.shard }}/3
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Test (deterministic shard)
run: pnpm test:ci:shard --shard ${{ matrix.shard }} --total 3
build:
name: Build
runs-on: ubuntu-latest
needs: [lint, test-shards]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Install Bun
uses: oven-sh/setup-bun@v2
- name: Build workspace
run: pnpm build
- name: Run CLI slow lane (opt-in suites)
run: pnpm test:slow-cli
- name: Build standalone binary
run: pnpm --filter @runfusion/fusion build:exe
- name: Verify binary exists
run: test -f packages/cli/dist/fn

167
.github/workflows/full-suite.yml vendored Normal file
View File

@@ -0,0 +1,167 @@
name: Full Suite (non-blocking)
# The demoted test tier (docs/plans/2026-06-04-001-refactor-fast-trusted-test-gate-plan.md).
# Runs the full sharded suite, the engine slow tier, and the dashboard
# inventory guard on every push to main — post-merge signal only. These jobs
# are NON-BLOCKING by design: they never run on PRs and must never be added
# to branch-protection required checks. A red run here is information, not a
# merge stopper; see docs/testing.md for the quarantine ratchet that keeps
# this tier honest.
on:
push:
branches: [main]
concurrency:
group: full-suite-${{ github.ref }}
cancel-in-progress: true
# 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:
test-shards:
name: Test shard ${{ matrix.shard }}/4
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Engine tests run real git operations (merge-base against main,
# case-variant ref checks) that require full history. Shallow
# clones silently break tests like worktree-acquisition's resume
# misbinding path.
fetch-depth: 0
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
# Dist-artifact cache (L1): ensureTestArtifacts otherwise rebuilds dist/
# for 8 packages (~71s) on every shard because CI starts with no dist.
# Key on a stable, pre-build, git-based hash of ALL build packages' source
# inputs. Exact-match only — NO restore-keys: a partial/stale dist hit is
# the exact failure mode this repo has been bitten by (FN-4232/FN-4605),
# and ensureTestArtifacts still validates/rebuilds anything missing-or-stale
# after restore, so a miss is safe but a wrong-content hit would not be.
# NEVER add node_modules here (breaks Windows pnpm junctions elsewhere).
- 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
id: dist-cache
uses: actions/cache@v4
with:
path: |
packages/core/dist
packages/dashboard/dist
packages/engine/dist
packages/plugin-sdk/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
key: dist-${{ runner.os }}-${{ steps.dist-hash.outputs.hash }}
# On a cache HIT, restored dist files carry their save-time mtimes while
# checkout rewrites src mtimes to "now" (src newer than dist), which would
# make ensureTestArtifacts' mtime fallback rebuild everything and defeat
# the cache. Seed the per-package content-hash cache so its content-hash
# short-circuit fires instead. ensureTestArtifacts still runs (inside
# test:ci:shard) and rebuilds anything genuinely missing/changed.
- 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
- name: Test (deterministic shard)
run: pnpm test:ci:shard --shard ${{ matrix.shard }} --total 4
# Each shard emits per-file vitest JSON timing reporter output under
# .timings/. Upload as an artifact so the timing snapshot can be
# refreshed locally/from the default branch via
# `node scripts/ci-test-shard.mjs --write-timings`. We do NOT commit the
# snapshot automatically — refresh is manual/scheduled only.
- name: Upload per-shard test timings
if: always()
uses: actions/upload-artifact@v4
with:
name: test-timings-shard-${{ matrix.shard }}
# Relative outputFile paths mean each package writes its own
# <pkgDir>/.timings/ file — glob the whole tree, not just the root.
path: |
.timings/timings-*.json
packages/*/.timings/timings-*.json
plugins/*/.timings/timings-*.json
plugins/examples/*/.timings/timings-*.json
if-no-files-found: ignore
retention-days: 14
# The dashboard quality gate used to enumerate its test files by hand, so any
# unenumerated app/ or src/ test file ran in NO project. This guard fails when
# a dashboard test file is neither executed by a quality project (curated +
# backfill lanes) nor on the reviewed skip-list. Cheap: it only runs
# `vitest list`, not the tests.
test-inventory-guard:
name: Dashboard curated-gate guard
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
# Same dist-artifact cache as test-shards (L1): the curated-gate guard runs
# `vitest list`, whose config resolution can touch built dist, so it also
# pays the cold-dist rebuild. Exact-match key on the pre-build source hash;
# NO restore-keys (stale dist is the failure mode), NO node_modules.
- 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
id: dist-cache
uses: actions/cache@v4
with:
path: |
packages/core/dist
packages/dashboard/dist
packages/engine/dist
packages/plugin-sdk/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
key: dist-${{ runner.os }}-${{ steps.dist-hash.outputs.hash }}
- 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
- name: Assert every dashboard test file is gated or skip-listed
run: node scripts/check-test-inventory.mjs --dashboard-curated
# The engine-slow tier (src/**/*.slow.test.ts) runs here with a non-empty
# execution assertion, so a glob/config drift that silently empties the tier
# fails this workflow instead of passing vacuously. Engine slow tests do real
# git operations, so a full clone (fetch-depth: 0) is required.
test-slow:
name: Engine slow tier
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run engine-slow with non-empty-execution assertion
run: node scripts/assert-engine-slow-nonempty.mjs

View File

@@ -1,12 +1,21 @@
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]
# Also run on every push to main so post-merge regressions surface
# immediately instead of being discovered on the next PR.
push:
branches: [main]
concurrency:
group: pr-checks-${{ github.ref }}
@@ -62,94 +71,13 @@ jobs:
- name: Build
run: pnpm build
test-shards:
name: Test shard ${{ matrix.shard }}/4
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
# Engine tests run real git operations (merge-base against main,
# case-variant ref checks) that require full history. Shallow
# clones silently break tests like worktree-acquisition's resume
# misbinding path.
fetch-depth: 0
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
# Dist-artifact cache (L1): ensureTestArtifacts otherwise rebuilds dist/
# for 8 packages (~71s) on every shard because CI starts with no dist.
# Key on a stable, pre-build, git-based hash of ALL build packages' source
# inputs. Exact-match only — NO restore-keys: a partial/stale dist hit is
# the exact failure mode this repo has been bitten by (FN-4232/FN-4605),
# and ensureTestArtifacts still validates/rebuilds anything missing-or-stale
# after restore, so a miss is safe but a wrong-content hit would not be.
# NEVER add node_modules here (breaks Windows pnpm junctions elsewhere).
- 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
id: dist-cache
uses: actions/cache@v4
with:
path: |
packages/core/dist
packages/dashboard/dist
packages/engine/dist
packages/plugin-sdk/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
key: dist-${{ runner.os }}-${{ steps.dist-hash.outputs.hash }}
# On a cache HIT, restored dist files carry their save-time mtimes while
# checkout rewrites src mtimes to "now" (src newer than dist), which would
# make ensureTestArtifacts' mtime fallback rebuild everything and defeat
# the cache. Seed the per-package content-hash cache so its content-hash
# short-circuit fires instead. ensureTestArtifacts still runs (inside
# test:ci:shard) and rebuilds anything genuinely missing/changed.
- 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
- name: Test (deterministic shard)
run: pnpm test:ci:shard --shard ${{ matrix.shard }} --total 4
# U1 (R4): each shard emits per-file vitest JSON timing reporter output
# under .timings/. Upload as an artifact so the timing snapshot can be
# refreshed locally/from the default branch via
# `node scripts/ci-test-shard.mjs --write-timings`. We do NOT commit the
# snapshot from PR branches — refresh is manual/scheduled only.
- name: Upload per-shard test timings
if: always()
uses: actions/upload-artifact@v4
with:
name: test-timings-shard-${{ matrix.shard }}
# Relative outputFile paths mean each package writes its own
# <pkgDir>/.timings/ file — glob the whole tree, not just the root.
path: |
.timings/timings-*.json
packages/*/.timings/timings-*.json
plugins/*/.timings/timings-*.json
plugins/examples/*/.timings/timings-*.json
if-no-files-found: ignore
retention-days: 14
# Plan U2 / R7: the dashboard quality gate used to enumerate its test files by
# hand, so any unenumerated app/ or src/ test file ran in NO project. This
# guard fails when a dashboard test file is neither executed by a quality
# project (curated + backfill lanes) nor on the reviewed skip-list. Cheap:
# it only runs `vitest list`, not the tests.
test-inventory-guard:
name: Dashboard curated-gate guard
# 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
steps:
- name: Checkout
@@ -158,10 +86,12 @@ jobs:
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
# Same dist-artifact cache as test-shards (L1): the curated-gate guard runs
# `vitest list`, whose config resolution can touch built dist, so it also
# pays the cold-dist rebuild. Exact-match key on the pre-build source hash;
# NO restore-keys (stale dist is the failure mode), NO node_modules.
- name: Install Bun
uses: oven-sh/setup-bun@v2
# Dist-artifact cache (same contract as full-suite.yml): exact-match
# key only, NO restore-keys (stale dist is the known failure mode,
# FN-4232/FN-4605), NEVER node_modules (breaks Windows pnpm junctions).
- name: Compute dist source hash
id: dist-hash
run: echo "hash=$(node scripts/ensure-test-artifacts.mjs --print-source-hash)" >> "$GITHUB_OUTPUT"
@@ -185,25 +115,13 @@ jobs:
if: steps.dist-cache.outputs.cache-hit == 'true'
run: node scripts/ensure-test-artifacts.mjs --seed-artifact-cache
- name: Assert every dashboard test file is gated or skip-listed
run: node scripts/check-test-inventory.mjs --dashboard-curated
# Boot smoke needs the full built workspace (CLI dist is not in the
# cache list above); cached packages make this incremental-fast.
- name: Build
run: pnpm build
# Plan U2 / R8: the engine-slow tier (src/**/*.slow.test.ts) previously ran in
# NO automated gate — only via the local `test:full`. This job runs it and
# asserts a non-empty execution, so a glob/config drift that silently empties
# the tier fails CI instead of passing vacuously. Engine slow tests do real
# git operations, so a full clone (fetch-depth: 0) is required.
test-slow:
name: Engine slow tier
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Boot smoke (app starts and serves)
run: node scripts/boot-smoke.mjs
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- name: Run engine-slow with non-empty-execution assertion
run: node scripts/assert-engine-slow-nonempty.mjs
- name: Gate tests (curated engine-core + CI-shape)
run: pnpm test:gate