Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 29 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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/<branch>` 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/<n>/merge` for PR runs, `refs/heads/main` for the post-merge push, and
# `refs/heads/<branch>` 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
Expand Down
Loading