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