Skip to content

fix(test): planning-lane E2E is RED on main (2/7, incl. its own contr… #1640

fix(test): planning-lane E2E is RED on main (2/7, incl. its own contr…

fix(test): planning-lane E2E is RED on main (2/7, incl. its own contr… #1640

Workflow file for this run

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]
# Key the concurrency group by SHA, not ref: on push to main the ref is
# always refs/heads/main, so a ref-keyed group with cancel-in-progress would
# let consecutive merges cancel each other's runs — silently skipping the
# only coverage for everything the gate dropped. Per-SHA groups never collide.
concurrency:
group: full-suite-${{ github.sha }}
cancel-in-progress: false
# Least-privilege token: jobs only read the repo (checkout + cache) and upload
# workflow artifacts (timings), which needs no extra permission scope.
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:
test-shards:
name: Test shard ${{ matrix.shard }}/4
runs-on: ubuntu-latest
# FNXC:FixPgTestsAndCi 2026-06-26-09:10:
# Provision a PostgreSQL service container so the postgres/*.pg.test.ts
# suites run in the non-blocking full suite too (parity with the gate).
# The pg-test-harness probe skips gracefully if unreachable.
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -h localhost -p 5432 -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
FUSION_PG_TEST_URL_BASE: "postgresql://postgres:postgres@localhost:5432"
PGPASSWORD: "postgres"
# Backstop for a wedged shard. The per-invocation watchdog (L2,
# scripts/lib/run-vitest-watchdog.mjs) kills any single hung invocation at
# its budget ceiling (<=30min), so this job budget only fires if L2 itself
# wedges — and it must sit strictly above that ceiling so L2 fires first.
# Without this, a hang ran to GitHub's 6h default (the silent-black-hole bug
# this plan closes).
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Checkout
uses: actions/checkout@v7
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@v6
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@v7
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
# FNXC:TestInfrastructure 2026-07-24-01:05:
# .timings/ is a dot-directory and upload-artifact excludes hidden
# files by default, so this step silently uploaded NOTHING since it was
# added ("No files were found") and the timing snapshot could never be
# refreshed from CI. Hidden files must be included for the glob to match.
# FNXC:CI 2026-07-28-01:35: pin is actions/upload-artifact@v7 (Dependabot #2444).
include-hidden-files: true
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
# Runs only `vitest list` (no tests execute), so this is generous headroom,
# not a tight bound — but no CI job should be able to hang to the 6h ceiling.
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- 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@v6
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
line-count-audit:
name: Line-count audit
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
# FNXC:TestInfrastructure 2026-06-21-10:26:
# Keep line-count drift visible in automated post-merge signal without restoring it to the blocking PR gate.
# The guard is intentionally absent from pretest/pr-checks so task verification and merge progress are not blocked by file-size cleanup work.
- name: Run line-count audit
continue-on-error: true
run: pnpm check:line-count
# 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
# FNXC:FixPgTestsAndCi 2026-07-14-00:00:
# Provision a PostgreSQL service container so reliability-interaction test
# helpers (which now require a PG-backed TaskStore after SQLite removal
# VAL-REMOVAL-005) can run in the engine-slow tier too.
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -h localhost -p 5432 -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
FUSION_PG_TEST_URL_BASE: "postgresql://postgres:postgres@localhost:5432"
PGPASSWORD: "postgres"
# Real-git slow suites; same backstop rationale as test-shards. Sits above
# the L2 per-invocation ceiling so the watchdog fires first on a single hang.
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v7
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
# FNXC:CIGateSpeed 2026-07-22-23:30:
# Warm the PR gate's incremental build cache from main. The Gate job in
# pr-checks.yml restores `gate-dist-*` with restore-keys; caches saved on a
# PR merge ref are invisible to other PRs, so without this job every PR's
# FIRST gate run builds cold (~6-8 min). Saving main's built dist plus
# .fusion/cache/plugin-build-cache.json here lets that first run rebuild only
# the packages the PR actually changed. The path list MUST stay byte-identical
# to the Gate job's cache block (actions/cache versions caches by path list —
# a drifted list makes the caches mutually invisible). Fast CLI packaging
# matches the Gate's consumer shape. This is a cache warmer, not a test or
# verification step; the blocking Build job on PRs keeps full-build coverage.
warm-gate-build-cache:
name: Warm gate build cache
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
- 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@v6
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 (incremental against restored cache)
run: pnpm build
env:
FUSION_CLI_FULL_PACKAGE: "0"
# FNXC:CIGateSpeed 2026-07-23-00:05:
# Also warm the Typecheck job's tsc incremental buildinfo cache from main
# (same PR-invisibility rationale as the dist cache above). The path list
# must stay byte-identical to the Typecheck job's cache block in
# pr-checks.yml. Restored buildinfo is self-validating — tsc re-checks
# anything that changed — so restore-keys is correctness-neutral.
- name: Cache TypeScript incremental buildinfo
uses: actions/cache@v6
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 (warms incremental buildinfo)
run: pnpm typecheck