From 074157c4c2be18aed0e045fdb8fa197f691ca83c Mon Sep 17 00:00:00 2001 From: "Claude (CI-9)" Date: Sat, 19 Sep 2026 13:49:03 +0200 Subject: [PATCH 1/3] CI-9: drop the pull_request branches filter, because a skipped check clears the gate `branches: [release/1.0, master]` meant a stacked pull request - one based on another feature branch - never matched. Gitea does not stay silent about a workflow it sits out: it posts the required context itself with state `skipped`, the combined-status rollup reports that as `success`, and the merge gate accepts it. Retargeting does not repair it, because the skip is a commit status on the head sha and so survives the base change. Measured on Gitea 1.27.3 with throwaway PR #94 against a branch carrying a copy of release/1.0's protection rule: only status is `skipped` -> POST .../merge 409 head out of date (CLEARED) no status at all -> POST .../merge 405 not all required (refused) status `pending` -> POST .../merge 405 not all required (refused) status `success` -> POST .../merge 409 head out of date (CLEARED) The 409s come from a deliberately wrong head_commit_id in the probe, which Gitea checks only after branch protection - so a 409 means the request had already cleared the gate, and nothing was ever merged. `skipped` is the only state that leaks. Fixing this at the gate is not possible. Gitea branch protection can require that a context is PRESENT, never that it is SUCCESS: its whole status-check vocabulary is `enable_status_check` and `status_check_contexts`, per this server's own /swagger.v1.json. So the fix has to be upstream - never emit a skip - which means no event filter. Cost: every pull request now runs the full ~8 minute suite, including the intermediate steps of a stack. 49 of the 50 pull requests in this repository's history were opened straight against release/1.0 and would have run anyway. Also corrects two things CI-01's header stated as fact. A filter miss is fail-OPEN, not the fail-closed pending-forever it described; and it is a skip-ci keyword, not a filter miss, that actually withholds the context and leaves it pending. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU --- .gitea/workflows/checks.yml | 93 +++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/checks.yml b/.gitea/workflows/checks.yml index bbaeea7..f8e2376 100644 --- a/.gitea/workflows/checks.yml +++ b/.gitea/workflows/checks.yml @@ -3,12 +3,35 @@ # of real coverage including a wall-clock cadence test, and not one machine ever # ran them. Every merge here was a human reading a diff. # -# ⚠️ NO `paths:` FILTER, AND ITS ABSENCE IS LOAD-BEARING. Branch protection on -# release/1.0 requires the context `checks / unit (pull_request)`. Gitea leaves a -# required context it never receives PENDING forever, so the moment this workflow -# learns to sit a PR out, that PR becomes unmergeable. Condition a STEP if you +# ⚠️ NO EVENT FILTER OF ANY KIND - NO `paths:`, AND SINCE CI-9 NO `branches:` +# EITHER - AND THAT ABSENCE IS LOAD-BEARING. Branch protection on release/1.0 +# requires the context `checks / unit (pull_request)`. Condition a STEP if you # must; never the workflow. # +# ⚠️ CI-01 WROTE THE REASON DOWN WRONG, AND THE WRONG REASON WAS THE SAFE- +# SOUNDING ONE. It said an unreceived required context sits PENDING forever, so +# a sat-out pull request becomes unmergeable - annoying, but fail-safe. That is +# not what this Gitea does. MEASURED on Gitea 1.27.3, 2026-09-19, throwaway +# PR #94: when a workflow exists but its event filter does not match, Gitea does +# not stay silent. It POSTS the required context itself, with state `skipped`: +# +# GET /api/v1/repos/admin/goodwe-addon/commits//statuses +# [{"status":"skipped","context":"checks / unit (pull_request)", +# "description":"Skipped","target_url":""}] <- no run, no job, no url +# +# GET /api/v1/repos/admin/goodwe-addon/commits//status +# {"state":"success","total_count":1} <- the rollup LAUNDERS it +# +# and Gitea's merge gate accepts it. Same PR, same head sha, one status on it +# and that status a skip, POST .../pulls/94/merge answered 409 `head out of +# date` - the 409 being a deliberately wrong `head_commit_id` in the probe, i.e. +# the request had ALREADY CLEARED BRANCH PROTECTION. The control, a required +# context with no status at all, answered 405 `Not all required status checks +# successful`. So the true failure mode is the exact inverse of the one written +# here before: sitting a pull request out does not make it unmergeable, it makes +# it MERGEABLE WITHOUT EVER BEING TESTED, and the badge says success while it +# happens. Fail-open, not fail-closed. Do not reintroduce a filter here. +# # ⚠️ THE RUNNER IS SHARED WITH THE OTHER REPO'S RIG. `goodwe-runner` on # 192.168.2.147 carries the labels `self-hosted` and `goodwe-rig` and is # registered `:host`, and it is the only runner there is - so this job wears the @@ -23,9 +46,69 @@ # that step; a green from this workflow is a claim about content that can land. name: checks +# ⚠️ CI-9: `pull_request:` TAKES NO `branches:` LIST, ON PURPOSE. It used to +# read `branches: [release/1.0, master]`, which meant a STACKED pull request - +# one whose base is another feature branch rather than a release branch - did +# not match, and so was skipped, laundered to `success`, and mergeable untested +# by the mechanism described at the top of this file. +# +# ⚠️ AND RETARGETING DID NOT REPAIR IT, WHICH IS WHAT MADE THIS P1. The skip is +# a COMMIT STATUS ON THE HEAD SHA, not a property of the pull request, so moving +# the base afterwards leaves it exactly where it was. Observed live on PR #93 +# (2026-09-19): retargeted TEL-10 -> release/1.0, and the 10:05:00 `skipped` +# stayed attached to head a6af332 with the rollup still reading success. It was +# caught by a human who forced a real execution by reopening the pull request +# (run 799, 10:56). Reproduced deliberately on throwaway PR #94: retargeted onto +# a branch carrying a byte-identical copy of release/1.0's protection rule, and +# the single `skipped` status survived the retarget unchanged - no new run, no +# new status - and still cleared the gate. +# +# ⚠️ WHY NOT FIX THIS AT THE GATE INSTEAD, which would be the better place. It +# cannot be done. Gitea's branch protection cannot express "this context must be +# SUCCESS" as opposed to "this context must be PRESENT AND NOT FAILING". The +# whole of its status-check vocabulary is two fields - `enable_status_check` +# (bool) and `status_check_contexts` (a list of globs MATCHING CONTEXT NAMES) - +# confirmed against this server's own schema at /swagger.v1.json, definitions +# EditBranchProtectionOption and CreateBranchProtectionOption. There is no field +# for the required STATE anywhere in either. The success/skip comparison lives +# in Gitea's compiled code, so the only lever left is upstream of it: make sure +# the context is never a skip in the first place. Hence this block. +# +# ⚠️ WHAT THIS COSTS, stated honestly, because it is a real trade. Every pull +# request now runs the full suite, including the intermediate steps of a stack +# that nobody intends to merge on its own. A run is ~8 minutes wall clock +# (run 799: 7m55s) on a runner that is `capacity: 1` and shared with the other +# repo's rig, so each extra run is ~8 minutes of slot that a rig campaign then +# queues behind. Measured scale of the new work: of the 50 pull requests in this +# repository's history, 49 were opened directly against release/1.0 and would +# have run anyway; stacking is rare here but not hypothetical - PR #93 was +# stacked on TEL-10 five days ago and is the reason this file is being edited. +# The bill is therefore small today and grows with however much stacking the +# team adopts. It is worth paying: the alternative is that the cheap case stays +# untested and indistinguishable from the tested one. +# +# ⚠️ THIS DOES NOT CLOSE THE `skipped` HOLE, IT REMOVES THIS REPOSITORY'S ONLY +# CAUSE OF ONE. Gitea's half - that a `skipped` required context is accepted by +# the merge gate - is untouched and uncloseable from here. What CI-9 can say is +# that after this edit there is no longer anything in this repository that +# produces one: there is exactly one workflow file, it now has no event filter +# of any kind, and the other candidate cause was MEASURED and is not one. +# +# THE MEASUREMENT, because the obvious guess is wrong and was nearly written +# here as fact. A skip-ci keyword in a commit message (`[skip ci]`, `[ci skip]`, +# `[no ci]`, `[skip actions]`, `[actions skip]`) does NOT behave like a filter +# miss. On throwaway PR #94, head b36effb, commit subject ending `[skip ci]`: +# +# .../commits/b36effb/statuses -> [] <- NO status, not a skip +# .../commits/b36effb/status -> {"state":"pending"} <- so the rollup is pending +# POST .../pulls/94/merge -> 405 Not all required status checks successful +# +# i.e. skip-ci is FAIL-CLOSED - it withholds the context entirely, which is the +# pending-forever behaviour CI-01's header wrongly attributed to a filter miss. +# It blocks the merge rather than laundering it, so it needs no fix and no +# app.ini change. Only the filter miss ever posted a `skipped`. on: pull_request: - branches: [release/1.0, master] workflow_dispatch: # ⚠️ NO `concurrency:` BLOCK, AND ITS ABSENCE IS AS LOAD-BEARING AS THE MISSING -- 2.54.0 From d2ad080e45680cfe2dd3cb2fa494ca85c4a5c0f5 Mon Sep 17 00:00:00 2001 From: "Claude (CI-9)" Date: Sat, 19 Sep 2026 14:25:22 +0200 Subject: [PATCH 2/3] CI-9: guard the trigger mechanically, and re-derive two figures I made up Review findings on PR #95. 1. Two figures in the header were written under the word "measured" and were not measured. "of the 50 pull requests in this repository's history, 49..." came from a /pulls call I did not notice was truncated at Gitea's 50-item page cap; and "#93 was stacked on TEL-10 five days ago" was invented - #93 was created 2026-09-19T10:05:00 and merged 11:05:26, the same morning, under three hours before CI-9 was opened. Both re-derived from a paged API query and now stated with their denominator: 93 pull requests predate this ticket (#1..#93, excluding CI-9's own #94..#98), 92 against release/1.0, exactly one stacked (#2, TEL-01 -> SAFETY-04). The 92 is flagged as an upper bound rather than a census, because the API reports a pull request's CURRENT base and so hides any that was opened stacked and later retargeted - which is #93 itself. The ratio, and so the cost argument, is unchanged. 2. The fix rested on an invariant - checks.yml has no event filter - that was defended only by a comment. Prose has already failed in this exact file: 9c1a23328a shipped "NO `paths:` FILTER, AND ITS ABSENCE IS LOAD-BEARING" and `branches: [release/1.0, master]` in the same commit, eighteen lines apart, and that survived 20 days and 84 merges. Adds a step that reads the `on:` block of the MERGED checks.yml and fails if it has regained a `branches:`, `paths:`, `tags:`, `types:` or any `-ignore` variant. This is not the card's option 3 - it does not try to detect its own skip, which is impossible; it bites on the pull request that reintroduces a filter, which the unfiltered workflow still grades. Two blind spots are named in the step's comment, both needing a protection-rule change that is the owner's call. Also softens "uncloseable from here", which the review judged overstated: Gitea's acceptance of a `skipped` context is unreachable from this repository, but the consequence is substantially closeable, and the guard is what closes it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU --- .gitea/workflows/checks.yml | 132 ++++++++++++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 12 deletions(-) diff --git a/.gitea/workflows/checks.yml b/.gitea/workflows/checks.yml index f8e2376..289965c 100644 --- a/.gitea/workflows/checks.yml +++ b/.gitea/workflows/checks.yml @@ -79,20 +79,36 @@ name: checks # that nobody intends to merge on its own. A run is ~8 minutes wall clock # (run 799: 7m55s) on a runner that is `capacity: 1` and shared with the other # repo's rig, so each extra run is ~8 minutes of slot that a rig campaign then -# queues behind. Measured scale of the new work: of the 50 pull requests in this -# repository's history, 49 were opened directly against release/1.0 and would -# have run anyway; stacking is rare here but not hypothetical - PR #93 was -# stacked on TEL-10 five days ago and is the reason this file is being edited. -# The bill is therefore small today and grows with however much stacking the -# team adopts. It is worth paying: the alternative is that the cheap case stays +# queues behind. +# +# SCALE OF THE NEW WORK, re-derived from the API and stated with its denominator, +# because the first version of this paragraph had a made-up one. Counting the 93 +# pull requests that predate this ticket (#1..#93; CI-9's own #94..#98 excluded, +# and note /pulls caps a page at 50, so this needs paging to see at all): 92 were +# opened against release/1.0 and would have run anyway, and exactly ONE was +# stacked - #2, TEL-01 -> SAFETY-04, closed unmerged, 2026-08-24. No pull request +# has ever targeted `master`, so half the old filter was dead from the start. +# +# ⚠️ THAT 92 IS AN UPPER BOUND, NOT A CENSUS, and the reason is this very bug. +# The API reports a pull request's CURRENT base, so one that was opened stacked +# and later retargeted is recorded against its final base and is invisible here. +# #93 is precisely that case - opened 2026-09-19T10:05:00 on TEL-10, retargeted, +# merged into release/1.0 at 11:05:26 the same morning, under three hours before +# CI-9 was opened - and it counts in the 92. So the true historical stacking rate +# is somewhere above 1/93, and unmeasurable from here. +# +# The bill is therefore small today and grows with however much stacking the team +# adopts. It is worth paying: the alternative is that the cheap case stays # untested and indistinguishable from the tested one. # -# ⚠️ THIS DOES NOT CLOSE THE `skipped` HOLE, IT REMOVES THIS REPOSITORY'S ONLY -# CAUSE OF ONE. Gitea's half - that a `skipped` required context is accepted by -# the merge gate - is untouched and uncloseable from here. What CI-9 can say is -# that after this edit there is no longer anything in this repository that -# produces one: there is exactly one workflow file, it now has no event filter -# of any kind, and the other candidate cause was MEASURED and is not one. +# ⚠️ THIS DOES NOT CHANGE GITEA'S ACCEPTANCE OF A `skipped` REQUIRED CONTEXT - +# that half is in its compiled code and is not reachable from this repository. +# The CONSEQUENCE, though, is very largely closeable from here, and is closed: +# after this edit there is nothing in this repository that produces a skip - +# there is exactly one workflow file, it now has no event filter of any kind, +# and the other candidate cause was MEASURED and is not one. The `guard the +# trigger` step below then keeps it that way mechanically rather than by asking +# the next editor to read this comment. # # THE MEASUREMENT, because the obvious guess is wrong and was nearly written # here as fact. A skip-ci keyword in a commit message (`[skip ci]`, `[ci skip]`, @@ -307,6 +323,98 @@ jobs: exit 1 fi + # ⚠️ THE GUARD ON THE TRIGGER. THIS STEP EXISTS BECAUSE PROSE ALREADY + # FAILED IN THIS FILE, ONCE, FOR TWENTY DAYS. + # + # `git show 9c1a23328a` is CI-01's first commit. It shipped the header + # "⚠️ NO `paths:` FILTER, AND ITS ABSENCE IS LOAD-BEARING" and the line + # `branches: [release/1.0, master]` IN THE SAME COMMIT, EIGHTEEN LINES + # APART. The comment forbade one kind of event filter while the code + # directly above the job carried another, and nobody caught it through 84 + # merges until the gate let a stacked pull request through. So the whole of + # CI-9's fix rests on an invariant - "checks.yml has no event filter" - + # that has already been silently violated in this exact file by the person + # who wrote the warning against violating it. A louder comment is not a + # remedy for that. A red step is. + # + # WHY THIS CAN WORK AT ALL, given CI-9 rejected the card's option 3 (a step + # that detects its own workflow having been skipped) as impossible. That + # option is impossible because a skipped workflow runs no steps, so there + # is nothing alive to notice. This is the different, tractable case: it + # fires on the pull request that REINTRODUCES a filter, and that pull + # request is still graded by the unfiltered workflow which is on the base + # today. The edit has to get past this step before it can start causing + # skips. It is a ratchet, not a skip detector. + # + # WHAT IT INSPECTS is the MERGED tree in $WS - the content that would + # actually land on the base - not the branch tip and not the copy of this + # file that Gitea used to decide whether to run. Those are three different + # trees here (see the long note on the checkout step) and only the merged + # one is the thing being authorised. + # + # ⚠️ TWO ACKNOWLEDGED BLIND SPOTS, both needing a branch-protection change + # to close, which is the repository owner's call and not this file's: + # + # 1. A reintroduced `paths:` filter crafted to exclude the reintroducing + # pull request's OWN diff would skip that pull request, so this step + # would not run to object to it. + # 2. An edit that lands on a feature branch through a stacked pull + # request is graded by the HEAD's workflow, so a filter already + # present in the head hides the edit from this step - until that + # branch is proposed for release/1.0, where the base matches the + # reintroduced filter and this step runs and bites. Caught at the + # release boundary, which is the boundary that matters. + # + # It is deliberately a FAILURE and not a warning: a warning would leave + # the required context green, which is the precise defect CI-9 is about. + - name: guard the trigger - refuse a checks.yml that has regained an event filter + run: | + set -eu + f="$WS/goodwe-addon/.gitea/workflows/checks.yml" + test -f "$f" || { echo "checks.yml is missing from the merged tree."; exit 1; } + # Walk the top-level `on:` block of the MERGED file. Full-line comments + # at column 0 are skipped outright - this file's own prose quotes + # `paths:` and `branches:` many times and must not trip its own guard - + # and an inline `#` is trimmed from any line that is kept. + bad=$(awk ' + function scan(t) { + sub(/#.*/, "", t) + if (t ~ /(^|[^A-Za-z0-9_-])(branches|branches-ignore|paths|paths-ignore|tags|tags-ignore|types)[[:space:]]*:/) + return 1 + return 0 + } + /^[[:space:]]*#/ { next } + /^("on"|'"'"'on'"'"'|on)[[:space:]]*:/ { + inon = 1 + rest = $0; sub(/^[^:]*:/, "", rest) + if (scan(rest)) print NR ": " $0 + next + } + inon == 1 { + if ($0 ~ /^[[:space:]]*$/) next + if ($0 ~ /^[[:space:]]/) { if (scan($0)) print NR ": " $0; next } + inon = 0 + } + ' "$f") + if [ -n "$bad" ]; then + echo "REFUSED: checks.yml has regained an event filter in its \`on:\` block." + echo "$bad" + echo + echo " A filter means some pull request stops matching this workflow. Gitea does" + echo " not stay silent about a workflow it sits out - it posts the required context" + echo " \`checks / unit (pull_request)\` itself with state \`skipped\`, the combined" + echo " status rolls that up as \`success\`, and the merge gate ACCEPTS it. That is" + echo " CI-9: a pull request merged on a workflow that never ran a step. It was" + echo " reproduced by merging PR #96 for real through a protected branch." + echo + echo " Branch protection cannot save you here - it can require that a context is" + echo " PRESENT, never that it is SUCCESS - so the filter is the whole defence." + echo " If you genuinely need to narrow what runs, condition a STEP, never the" + echo " workflow. See the \`on:\` block's comments for the measurements." + exit 1 + fi + echo "OK: the \`on:\` block of the merged checks.yml carries no event filter." + - name: build the test image (shipped deps, from apk - see .gitea/ci/Dockerfile) run: | set -eu -- 2.54.0 From ca8fd01819b4ad0ac40a4f6b18bbea119d0410d5 Mon Sep 17 00:00:00 2001 From: "Claude (CI-9)" Date: Sat, 19 Sep 2026 14:44:16 +0200 Subject: [PATCH 3/3] CI-9: correct the guard comment's grading claim, and name the third blind spot Documentation only - no change to the guard, the trigger, or any step. The guard's comment block contradicted itself. It said the pull request that reintroduces a filter "is still graded by the unfiltered workflow which is on the base today", while the checkout step's note a few lines below says grading uses the HEAD's workflow. The head's is correct, and the review measured it on PR #101: head carrying a filter that excludes the base, base carrying the unfiltered workflow -> no run, context posted `skipped`, rollup `success`, gate cleared (409). The wrong version made the step sound like an unconditional ratchet. What is actually true is narrower: it bites whenever the reintroduced filter still names the pull request's own base, which is the ordinary case. That correction exposes a third blind spot, worse than the two already listed. A reintroduced `branches:` filter that simply does not name the base escapes the guard entirely, because the pull request carrying it is itself skipped. It needs no craft - `branches: [main]` from a template, or a tidy-up to `[master]`, which nothing in this repository's history has ever targeted - and it lands as a skipped, laundered, untested merge into release/1.0. After that every subsequent pull request is skipped as well: the gate off completely, not merely the stacked case leaking. It is documented, not fixed, and deliberately so. No in-repo guard can close it, because any such guard lives inside the workflow that gets skipped. Closing it needs a second permanently unfiltered workflow whose context is added to release/1.0's `status_check_contexts` - a forge change, the owner's decision, being put to them separately, and explicitly not a blocker on CI-9. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU --- .gitea/workflows/checks.yml | 60 +++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/checks.yml b/.gitea/workflows/checks.yml index 289965c..425738e 100644 --- a/.gitea/workflows/checks.yml +++ b/.gitea/workflows/checks.yml @@ -341,10 +341,26 @@ jobs: # that detects its own workflow having been skipped) as impossible. That # option is impossible because a skipped workflow runs no steps, so there # is nothing alive to notice. This is the different, tractable case: it - # fires on the pull request that REINTRODUCES a filter, and that pull - # request is still graded by the unfiltered workflow which is on the base - # today. The edit has to get past this step before it can start causing - # skips. It is a ratchet, not a skip detector. + # fires on the pull request that REINTRODUCES a filter, and it does so + # whenever the filter being reintroduced STILL NAMES THAT PULL REQUEST'S + # OWN BASE - the ordinary case, because a filter put back by hand names + # the release branch people are actually targeting. The edit then has to + # get past this step before it can start causing skips. It is a ratchet, + # not a skip detector. + # + # ⚠️ AND IT IS THE HEAD'S WORKFLOW THAT DECIDES WHETHER ANYTHING RUNS, NOT + # THE BASE'S. An earlier draft of this comment claimed the reintroducing + # pull request "is still graded by the unfiltered workflow which is on the + # base today". That was wrong, it contradicted the checkout step's own note + # a few lines below, and the error was not cosmetic: it made this step + # sound like an unconditional ratchet, which is exactly the false comfort + # CI-9 exists to stamp out. MEASURED on PR #101 - head carrying a filter + # that excludes the base, base carrying the unfiltered workflow - and the + # result was no run at all, the context posted `skipped`, the rollup + # reading `success`, and the gate CLEARED (409). Gitea takes the workflow + # file from refs/pull//head, so a head that filters itself out is simply + # never graded, and nothing on the base can reach out and stop it. That is + # what blind spot 3 below is made of. # # WHAT IT INSPECTS is the MERGED tree in $WS - the content that would # actually land on the base - not the branch tip and not the copy of this @@ -352,18 +368,44 @@ jobs: # trees here (see the long note on the checkout step) and only the merged # one is the thing being authorised. # - # ⚠️ TWO ACKNOWLEDGED BLIND SPOTS, both needing a branch-protection change - # to close, which is the repository owner's call and not this file's: + # ⚠️ THREE ACKNOWLEDGED BLIND SPOTS. NOT ONE OF THEM IS CLOSEABLE FROM + # INSIDE THIS FILE, and the reason is the same in all three: each is a case + # where THIS WORKFLOW IS ITSELF THE THING THAT GETS SKIPPED, and a guard + # that lives inside a skipped workflow does not run. Closing them needs a + # SECOND, permanently unfiltered workflow whose own context is added to + # release/1.0's `status_check_contexts`, so that something survives to + # report when this file is sat out. That is a forge change and the + # repository owner's decision; it is being put to them separately and is + # deliberately NOT held as a blocker on CI-9. # # 1. A reintroduced `paths:` filter crafted to exclude the reintroducing # pull request's OWN diff would skip that pull request, so this step # would not run to object to it. + # # 2. An edit that lands on a feature branch through a stacked pull # request is graded by the HEAD's workflow, so a filter already # present in the head hides the edit from this step - until that - # branch is proposed for release/1.0, where the base matches the - # reintroduced filter and this step runs and bites. Caught at the - # release boundary, which is the boundary that matters. + # branch is proposed for release/1.0, where (unless it is case 3) the + # base matches the reintroduced filter and this step runs and bites. + # Caught at the release boundary, which is the boundary that matters. + # + # 3. ⚠️ THE WORST OF THE THREE, AND WORSE THAN BOTH OF THE OTHERS PUT + # TOGETHER: a reintroduced `branches:` filter that simply DOES NOT + # NAME THE BASE escapes this step entirely, because the pull request + # carrying it is itself skipped. It takes no craft and no malice to + # write - `branches: [main]` copied out of a GitHub template, or a + # tidy-up to `[master]`, a branch that NO pull request in this + # repository's history has ever targeted - and it arrives as a + # skipped, laundered, untested merge into release/1.0. + # + # What makes it categorically worse is what happens the moment after. + # Once that file is ON release/1.0, every subsequent pull request is + # graded against it and every one of them is skipped too. This is not + # the stacked case leaking through a crack; it is the gate off + # completely, for everything, silently, with every badge green. The + # only thing standing between the repository and that state today is + # a human reading this file - which is precisely the kind of defence + # this step exists because it has already failed once (9c1a23328a). # # It is deliberately a FAILURE and not a warning: a warning would leave # the required context green, which is the precise defect CI-9 is about. -- 2.54.0