ci: gate merges on one aggregate check instead of four named jobs - #193
Conversation
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>
Deploying with
|
| 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
left a comment
There was a problem hiding this comment.
Auto-approved: @Aswinmcw is a member of @Aswincloud/admins.
There was a problem hiding this comment.
🟢 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
cijob (name: ✅ CI) that always runs (if: always()) and fails if any upstream job result isfailure,cancelled, orskipped. - Add
src/__tests__/ciGate.test.jsto assert the gate exists, depends on every other job, usesalways(), fails on all non-success results, and includes themerge_grouptrigger. - Add
yamlas a devDependency (and lockfile updates) to support parsing.github/workflows/ci.ymlin 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.
Why
main's ruleset names 🔍 Lint / 🧪 Test / 🏗️ Build / 🔒 Security individually, so every new job inci.ymlhas 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
✅ CIjob that depends on every other job inci.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 partWithout it the gate inherits the default
needsbehaviour 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:So it always runs and inspects
needs.*.resultitself, treating'skipped'as a failure alongside'failure'and'cancelled'— withneeds: setup, one early failure skips everything downstream, and a gate watching only for'failure'would wave through a run where nothing butsetupexecuted.The condition is a step-level
if:rather than a shellifwrapped around${{ }}: inside arun: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 test —src/__tests__/ciGate.test.js, five assertions covering drift,always(), the non-success branches, the display name the ruleset matches on, and themerge_grouptrigger. Each was mutation-tested and fails only its own mutation.Scope: five required checks → two, not one
pr-checks.ymlhas nomerge_grouptrigger, so they never report in the queueneeds:only reaches jobs in the same file, which is the ceiling here.Two consequences worth knowing
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.✅ CIis observed reporting here, the ruleset can be pointed at it.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
format:check, copyright strict, buildnpm audit→ 0 vulnerabilitiesyamldeclared 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