diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4924dca1..5c12fffc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,37 +1,40 @@ name: CI -# MANUAL ONLY. This suite runs when a human dispatches it and at no other time. The `push` to -# `main` trigger went first (it re-ran the same content the PR run had already passed, for no new -# information); `pull_request` has now gone too, so the automatic half of this workflow is empty. +# ON EVERY MERGE, and on demand. A push to `main` is what a merge looks like from Actions' side, +# so every merged PR now re-runs lint, the suite, and the build-identity check against the tree +# that actually resulted from the merge. # -# Be clear about what that costs: NOTHING VERIFIES A PULL REQUEST BEFORE IT MERGES ANY MORE. There -# is no lint, no suite, and no build-identity check standing between a branch and `main`. Nor does -# branch protection compensate. There IS a repository ruleset named `main`, and it does list -# required status checks, but it currently matches no refs at all -- its `ref_name.include` is -# empty, and `GET /repos/:owner/:repo/rules/branches/main` returns `[]`, meaning zero rules are in -# force on the branch. Its two required contexts ("Lint, Unit tests, Build" and "Integration -# Tests") also match no job this repo defines. So there is no required status check a merge could -# fail on: a PR merges because a human decided to merge it, and that decision is the only gate at -# merge time. +# That last phrase is the point, and it is why this is NOT the redundant run it was once called. +# A `pull_request` run tests the PR head; a `push` run tests `main` after the merge landed. Those +# differ whenever two independently-green branches conflict semantically -- each passes alone, the +# merge of them does not. Only the post-merge run can see that. # -# What survives is the gate inside `release.yml`. That workflow is itself manual, but every run of -# it re-runs `ruff` + `pytest` automatically before it will build an artifact -- so a red `main` -# still cannot ship. That is the safety net that matters most for a tool that moves money: broken -# code can reach `main`, but it cannot reach a wheel, a tag, or a release asset without the suite -# passing first. +# `pull_request` is here for a specific, load-bearing reason: it is what makes this job report a +# status named `test` on a PR, and the `main` ruleset REQUIRES that context before a merge is +# allowed (issue #236). The two are a matched pair and must be changed together -- removing this +# trigger while the ruleset still requires `test` would leave every PR waiting forever on a check +# that can never report, which is precisely the broken state #236 was filed to fix. That is not a +# hypothetical: the ruleset previously required "Lint, Unit tests, Build" and "Integration Tests", +# names no job here has ever produced. # -# The accepted consequence, stated plainly: a breakage introduced by a merge is discovered at -# release time, or whenever somebody next dispatches this workflow -- not at merge time. The -# window between "merged" and "found" is now however long passes before one of those two happens. -# Dispatching this suite against `main` after a batch of merges, and before cutting a release, is -# what closes that window. Restoring `pull_request:` below is a one-line change if the trade stops -# being worth it. +# The context string is `test` because the job below is `test:` and declares no `name:`. Renaming +# that job, or giving it a `name:`, silently changes the context and breaks the ruleset the same +# way. If you rename it, update the ruleset in the same change. +# +# So the two triggers do different jobs: `pull_request` GATES the merge, `push` VERIFIES the result +# of it. `release.yml` remains the gate that matters for money -- it re-runs `ruff` + `pytest` +# itself before it will build an artifact, so a red `main` cannot become a wheel, a tag, or a +# release asset even if both of these were removed. on: + pull_request: + push: + branches: [main] workflow_dispatch: -# `github.ref` is `refs/heads/` for a `workflow_dispatch` run -- the only kind of run this -# workflow now has -- so the group keys on the branch that was dispatched. Two dispatches of the -# same branch cancel each other; dispatches of different branches run side by side. +# `github.ref` is `refs/pull//merge` for PR runs, `refs/heads/main` for the post-merge push, and +# `refs/heads/` for a dispatch -- distinct keys, so a PR run never cancels the post-merge +# run. Pushes to the same PR do cancel each other, and back-to-back merges cancel the older `main` +# run -- correct in both cases: only the newest result matters. concurrency: group: ci-${{ github.ref }} cancel-in-progress: true