Skip to content

ci: gate main on one aggregate check instead of eleven job names - #391

Merged
cdeust merged 1 commit into
mainfrom
ci/aggregate-green-gate
Aug 8, 2026
Merged

ci: gate main on one aggregate check instead of eleven job names#391
cdeust merged 1 commit into
mainfrom
ci/aggregate-green-gate

Conversation

@cdeust

@cdeust cdeust commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Why

Branch protection on main names eleven ci.yml jobs as required status checks. That contract lives in GitHub settings — outside git, invisible in review, unversioned — and breaks on every rename.

It broke: #336 extracts the test steps into a reusable workflow, and a job delegating via uses: always reports as "<caller> / <callee>". The four matrix legs became Test (Python X.Y) / Test (Python X.Y), the four bare contexts main requires were never reported again, and #387 sits BLOCKED with every check green (run 31250898534). Renaming the four contexts unblocks that one PR and leaves the next rename to rediscover the same defect.

What

One required ci.yml context, CI Green (the ci-green job), needing every other job in the workflow. The covered list is in the file, in git, visible in a diff.

if: always() is load-bearing — without it the job is itself skipped when a need fails, the context is never reported, and the PR blocks with no explanation instead of a red X naming the culprit.

The two escape hatches, closed

An aggregate gate moves the failure mode rather than removing it, so scripts/check_ci_gate_complete.py (run by lint) refuses:

  • a job in ci.yml that is not in ci-green.needs — it would run outside the gate and could fail without blocking a merge;
  • a job carrying a job-level if: that is not in ALLOWED_SKIPS — it can report skipped and slip past;
  • a stale ALLOWED_SKIPS entry naming a job with no if: — an exemption guarding nothing hides a real skip.

Today ALLOWED_SKIPS holds exactly mcp-host-config (cannot run on fork PRs), with the reason inline.

Side effect worth stating

mcp-host-config and the two Docker Build jobs were required by nothing before this PR. They are gated now.

Not covered, deliberately

fuzz.yml and upstream-identity.yml are separate workflows, out of reach of a ci.yml aggregate. They are not required checks today and this PR does not make them so.

Merge order

  1. Merge this PR — it renames nothing, the eleven contexts still report, so it is not self-blocking.
  2. Set protection's required contexts to CI Green + CodeQL.
  3. ci: share the test suite between ci.yml and release.yml via a composite action #387 then merges with no further protection change — now, and for every future rename.

Verification

  • scripts/check_ci_gate_complete.py fails on main as it stands today (no ci-green job), passes here.
  • 8 unit tests: one per refusal, plus a live assertion against the repository's real ci.yml.
  • gate shell logic exercised on 5 result sets: all-success, allowed skip, unlisted skip, failure, cancelled.
  • ruff format --check + ruff check clean; actionlint 0 finding; check_doc_claims.py 0 finding.

Branch protection named eleven ci.yml jobs as required status checks —
a contract living in GitHub settings, outside git, that no diff shows and
every rename breaks. Extracting the test steps into a reusable workflow
(#336) renamed the four matrix legs to "Test (Python X.Y) / Test (Python
X.Y)", because a job delegating via `uses:` always reports as
"<caller> / <callee>". The four bare contexts main required were never
reported again and PR #387 sat BLOCKED with every check green
(run 31250898534). Renaming the contexts fixes that PR and leaves the
next rename to rediscover the same thing.

So protection names one ci.yml context, `CI Green`, and the covered list
lives in the `ci-green` job's `needs:` — in git, visible in review.
`if: always()` is load-bearing: without it the job is itself skipped when
a need fails, the context is never reported, and the PR blocks with no
explanation instead of a red X naming the culprit.

That moves the failure mode rather than removing it, so both escape
hatches are closed by scripts/check_ci_gate_complete.py, run by `lint`:

  - a job added to ci.yml but not to `needs` would run outside the gate
    and could fail without blocking a merge -> refused;
  - a job carrying a job-level `if:` legitimately reports `skipped`, so
    the gate accepts a skip only for jobs declared in ALLOWED_SKIPS (today:
    mcp-host-config, which cannot run on fork PRs) -> an undeclared
    conditional, or a stale exemption naming a job with no `if:`, refused.

Side effect worth stating: `mcp-host-config` and the two Docker Build
jobs were required by nothing before this commit. They are gated now.

Not covered, deliberately: fuzz.yml and upstream-identity.yml are separate
workflows, out of reach of a ci.yml aggregate; they are not required checks
today and this does not make them so.

8 tests, ruff clean, actionlint 0 finding, check_doc_claims 0 finding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01263uv1QqR8TVzw2jXYUrXn
@cdeust
cdeust merged commit de9ef0a into main Aug 8, 2026
22 checks passed
@cdeust
cdeust deleted the ci/aggregate-green-gate branch August 8, 2026 18:57
cdeust added a commit that referenced this pull request Aug 8, 2026
cdeust added a commit that referenced this pull request Aug 8, 2026
Re-applies the exact tree validated at 8b97994, which passed 22 checks
on the head of #394. Verified byte-identical: `git diff 8b97994` is empty.

What this contains, per reverted PR:
- #391 scripts/check_ci_gate_complete.py — the `CI Green` aggregate.
- #387 .github/actions/test-suite/action.yml — one test suite shared by
  ci.yml and release.yml instead of two drifting copies.
- #393 scripts/check_version_surfaces.py — one gate over all 14 version
  sites, pyproject.toml as the single canonical source.
- #394 the tag becomes an output of a green tree rather than a
  hand-pushed input.

No branch-protection change is required or requested. All eleven required
contexts keep their exact names — Lint, Type Check, Build Package,
Docker Smoke, Test (Python 3.10/3.11/3.12/3.13), Test (SQLite backend),
Test (Windows, SQLite backend) — this only adds jobs alongside them.
`CI Green` is not proposed as a required context; it gates the tag,
which needs no protection change to be enforced.
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.

1 participant