Skip to content

ci: gate merges on one aggregate check instead of four named jobs - #193

Merged
Aswinmcw merged 1 commit into
mainfrom
ci/aggregate-required-check
Aug 3, 2026
Merged

ci: gate merges on one aggregate check instead of four named jobs#193
Aswinmcw merged 1 commit into
mainfrom
ci/aggregate-required-check

Conversation

@Aswinmcw

@Aswinmcw Aswinmcw commented Aug 3, 2026

Copy link
Copy Markdown
Member

Why

main's ruleset names 🔍 Lint / 🧪 Test / 🏗️ Build / 🔒 Security individually, so every new job in ci.yml has to be added to the ruleset by hand. A job nobody remembers to add still runs on every PR and blocks nothing — a coverage gap whose only symptom is a green check.

What

A ✅ CI job that depends on every other job in ci.yml, so the ruleset can name one context and wiring a new job into the gate is enough to make it gate.

if: always() is the load-bearing part

Without it the gate inherits the default needs behaviour and is skipped when a dependency fails — and GitHub counts a skipped required check as passing. The naive version of this job therefore inverts the protection it appears to add:

lint goes red → gate is skipped → ruleset sees success → PR merges

So it always runs and inspects needs.*.result itself, treating 'skipped' as a failure alongside 'failure' and 'cancelled' — with needs: setup, one early failure skips everything downstream, and a gate watching only for 'failure' would wave through a run where nothing but setup executed.

The condition is a step-level if: rather than a shell if wrapped around ${{ }}: inside a run: block the expression is text bash parses after substitution, so a line-wrapped one would depend on the expansion collapsing to valid shell.

This relocates the manual step rather than removing it

A new job still needs adding to needs:. The gain is that the list now lives in the repo, so it can be enforced by a testsrc/__tests__/ciGate.test.js, five assertions covering drift, always(), the non-success branches, the display name the ruleset matches on, and the merge_group trigger. Each was mutation-tested and fails only its own mutation.

Scope: five required checks → two, not one

Check Can it join?
Lint, Test, Build, Security ✅ same workflow file
Workers Builds ❌ external check, no job to depend on — stays separately required
Lighthouse, Bundle Size, Dependency Review pr-checks.yml has no merge_group trigger, so they never report in the queue

needs: only reaches jobs in the same file, which is the ceiling here.

Two consequences worth knowing

  1. 🎭 E2E and 📦 Setup become required, being in ci.yml. E2E is 15/15 green historically and caught a real regression in fix(ui): make the menu dismissable and the form enforce what it prints #192, but it adds ~1m to the critical path and a flake now blocks the queue.
  2. The ruleset swap is a separate, ordered step. Requiring a context that has never reported deadlocks the merge queue for 60 minutes and then fails. This PR only adds the job; once ✅ CI is observed reporting here, the ruleset can be pointed at it.

⚠️ Merging this PR alone changes no enforcement

The ruleset still names the four jobs. After merge, ruleset 6505932's required checks should become ✅ CI + Workers Builds: aswin-portfolio. I have not touched the ruleset.

Verification

  • 338 unit tests / 20 files (was 333/19), 31 e2e, lint, format:check, copyright strict, build
  • npm audit → 0 vulnerabilities
  • All 5 guard assertions mutation-tested — each kills its own mutation and no other
  • Shell-verified the guard both ways: all-success → exit 0, any failure → exit 1
  • yaml declared as a devDependency (^2.9.0, matching the repo's caret convention); the guard parses the workflow and it was previously only transitive via vite/lint-staged

🤖 Generated with Claude Code

main's ruleset named 🔍 Lint / 🧪 Test / 🏗️ Build / 🔒 Security individually, so
every new job in ci.yml had to be added to the ruleset by hand. A job nobody
remembered to add still ran on every PR and blocked nothing — a coverage gap
whose symptom is a green check.

Add a `✅ CI` job that depends on every other job in the file, so the ruleset can
name one context and wiring a new job into the gate is enough to make it gate.

`if: always()` is the load-bearing part. Without it the gate inherits the default
`needs` behaviour and is *skipped* when a dependency fails — and GitHub counts a
skipped required check as passing. The naive version of this job inverts the
protection it appears to add: lint goes red, gate is skipped, ruleset sees
success, PR merges. So it always runs and inspects `needs.*.result` itself,
treating 'skipped' as a failure alongside 'failure' and 'cancelled' — with
`needs: setup`, one early failure skips everything downstream.

The condition is a step-level `if:` rather than a shell `if` wrapped around
${{ }}: inside a `run:` block the expression is text bash parses after
substitution, so a line-wrapped one would depend on the expansion collapsing to
valid shell.

This relocates the manual step rather than removing it — a new job still needs
adding to `needs:`. The gain is that the list now lives in the repo, so
src/__tests__/ciGate.test.js can enforce it: five assertions covering drift,
`always()`, the non-success branches, the display name the ruleset matches on,
and the merge_group trigger. Each was mutation-tested and fails only its own
mutation.

Scope: Workers Builds is an external check with no job to depend on and stays
separately required, so this takes main from five required checks to two.
pr-checks.yml cannot join — no merge_group trigger, so its jobs never report in
the queue.

Declares `yaml` as a devDependency; the guard test parses the workflow and it was
only present transitively via vite/lint-staged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Aswinmcw
Aswinmcw requested a review from Aswin-coder as a code owner August 3, 2026 09:59
Copilot AI review requested due to automatic review settings August 3, 2026 09:59
@Aswinmcw
Aswinmcw requested review from a team as code owners August 3, 2026 09:59
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
aswin-portfolio 9272b2c Commit Preview URL

Branch Preview URL
Aug 03 2026, 09:59 AM

@Aswincloud-Bot Aswincloud-Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: @Aswinmcw is a member of @Aswincloud/admins.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Ready to approve

The aggregate gate job and its accompanying drift-prevention test are consistent with the stated goal and appear correctly wired to all existing jobs and the merge_group trigger.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR adds an aggregate GitHub Actions job (✅ CI) to serve as the single required check for merge gating, reducing ruleset maintenance by having one job depend on (and fail based on) the results of all other jobs in ci.yml. It also adds a Vitest guard test to prevent drift between the workflow’s job list and the gate’s needs, and introduces yaml as a dev dependency to parse the workflow reliably.

Changes:

  • Add a new ci job (name: ✅ CI) that always runs (if: always()) and fails if any upstream job result is failure, cancelled, or skipped.
  • Add src/__tests__/ciGate.test.js to assert the gate exists, depends on every other job, uses always(), fails on all non-success results, and includes the merge_group trigger.
  • Add yaml as a devDependency (and lockfile updates) to support parsing .github/workflows/ci.yml in tests.
File summaries
File Description
.github/workflows/ci.yml Introduces the aggregate ✅ CI gate job with always() and explicit upstream-result failure conditions.
src/__tests__/ciGate.test.js Adds a guard test that enforces correct wiring and behavior of the aggregate gate and required triggers.
package.json Adds yaml as a devDependency for workflow parsing in tests.
package-lock.json Lockfile update reflecting the new yaml devDependency.
Review details
  • Files reviewed: 3/4 changed files
  • Comments generated: 0
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@Aswinmcw
Aswinmcw added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 19562b1 Aug 3, 2026
16 checks passed
@Aswinmcw
Aswinmcw deleted the ci/aggregate-required-check branch August 3, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants