From 776e340d62dc4cefc7035ccb2f351082767b144d Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:55:09 +0100 Subject: [PATCH] fix(ci): give the required status check a version-stable name Closes #38. The `test` job was named `Julia ${{ matrix.julia-version }} / ${{ matrix.os }}`, and that string is what branch ruleset 23793298 requires as a status check. A ruleset matches a required check by display name. Bumping either pin renamed the check, and a renamed check does not report at all -- it does not fail, it is simply absent, and an absent required check can never be satisfied. Every open pull request would have deadlocked until an admin bypassed the rule. The trigger was an ordinary version bump: the single most routine edit this repository gets. Both halves of the name interpolated, so the OS pin carried the same fault as the Julia pin. Measured, by parsing three hypothetical bumps through both versions of the file: matrix before after 1.12.5 / ubuntu-24.04 "Julia 1.12.5 / ubuntu-24.04" "Julia tests" 1.12.6 / ubuntu-24.04 "Julia 1.12.6 / ubuntu-24.04" "Julia tests" 1.13.0 / ubuntu-26.04 "Julia 1.13.0 / ubuntu-26.04" "Julia tests" The versions are not hidden, only moved off the identifier: they remain in `strategy.matrix` and in the `Set up Julia` step's log. `test/unit/test_install_pins.jl` gains a reflexive testset asserting that NO job name in ci.yml interpolates anything. It is written over every job rather than over the one job we require today, because a rule enforced at each door in turn is a rule the next door escapes. Verified by killing the mutant: against the unedited ci.yml the assertion fails on exactly one job of three (97 pass / 1 fail), and passes 98/98 after the rename. The job ID `test` is unchanged, so `needs: [test]` and the existing `ci["jobs"]["test"]` assertions are untouched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X3hgXxWm6umMgZkjYyHnnm --- .github/workflows/ci.yml | 15 ++++++++++++++- test/unit/test_install_pins.jl | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2d595e..02701a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,20 @@ jobs: echo "commit subject ok: $subject" test: - name: Julia ${{ matrix.julia-version }} / ${{ matrix.os }} + # The name is a fixed string, and deliberately carries no version. + # + # A branch ruleset matches a required status check by the DISPLAY NAME of the job + # that posts it. When that name was `Julia ${{ matrix.julia-version }} / ${{ matrix.os }}` + # a pin bump renamed the check, and a renamed check does not report at all -- it + # does not fail, it is simply absent, and an absent required check can never be + # satisfied. Every pull request would have deadlocked until an admin bypassed the + # rule, triggered by nothing more alarming than editing a version number. + # + # The versions have not been hidden, only moved somewhere that does not double as + # an identifier: `strategy.matrix` below, and the `Set up Julia` step's own log. + # `test/unit/test_install_pins.jl` asserts that no job name in this file + # interpolates anything, so this cannot regress silently. + name: Julia tests runs-on: ${{ matrix.os }} # The repository's root .Rprofile sources renv/activate.R, so R started from # the checkout auto-activates renv and rewrites the library paths. renv is a diff --git a/test/unit/test_install_pins.jl b/test/unit/test_install_pins.jl index 762e4f8..c25c79c 100644 --- a/test/unit/test_install_pins.jl +++ b/test/unit/test_install_pins.jl @@ -181,4 +181,26 @@ is_sha256(s) = s isa AbstractString && occursin(r"^[0-9a-f]{64}$", s) @test !occursin(pins["tools"][tool]["version"], runs) end end + + @testset "no job name interpolates a pin" begin + ci = YAML.load_file(CI_PATH) + + # A required status check is matched by the DISPLAY NAME of the job that posts + # it. `name: Julia ${{ matrix.julia-version }} / ${{ matrix.os }}` therefore + # renames the check on every pin bump, and a renamed check does not report at + # all -- it does not fail, it is simply absent, and a required check that never + # reports can never be satisfied. Every pull request then deadlocks until an + # admin bypasses the rule, which is the one outcome branch protection exists to + # prevent. The bump that triggers it is a one-line edit to this very file's + # subject matter, so the deadlock arrives by way of an ordinary maintenance + # change that looks safe. + # + # Asserting this over EVERY job rather than over the one job we happen to + # require today is deliberate: the rule is "a job name is a stable identifier", + # and a rule enforced at each door in turn is a rule that a new door escapes. + for (id, job) in ci["jobs"] + name = get(job, "name", id) + @test !occursin("\${{", name) + end + end end