Compare commits

...
5 Commits
Author SHA1 Message Date
adminandClaude Opus 5 ca8fd01819 CI-9: correct the guard comment's grading claim, and name the third blind spot
checks / unit (pull_request) Successful in 7m54s
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-19 14:44:16 +02:00
adminandClaude Opus 5 d2ad080e45 CI-9: guard the trigger mechanically, and re-derive two figures I made up
checks / unit (pull_request) Successful in 7m55s
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-19 14:25:22 +02:00
adminandClaude Opus 5 074157c4c2 CI-9: drop the pull_request branches filter, because a skipped check clears the gate
checks / unit (pull_request) Successful in 7m57s
`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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-19 13:58:58 +02:00
adminandClaude Opus 5 186a044a3a Merge pull request 'SAFETY-48: the slew step ceiling reaches the integrator, and what it bounds is the LEAD' (#93) from SAFETY-48 into release/1.0
Reviewed on the pull request and merged there. Retargeted from TEL-10 to release/1.0 after #92 merged, and NOT merged on the inherited green: retargeting alone left the old `skipped` conclusion attached to a6af332 and the combined status still read success, so the required context was satisfied by a workflow that had never run. Run 799 is a genuine pull_request execution on the merge into release/1.0 - all five steps, the add-on's own tests occupying 08:56:58 to 09:04:47 UTC.

THE CAP MOVING A SATURATION-FROZEN INTEGRATOR IS ACCEPTABLE, NOT MERELY DISCLOSED. The disclosed figures reproduce exactly against the committed capture - 79 160 frozen ticks, 1095 moved, 679 further out on the side they were already on, worst 2424 W - and the worked tick in the comment is present with exactly the numbers written down and is the worst of that subset. What decides it is where the integrator lands rather than which way it moves: on the shipped configuration integrator_max_w is unset, so target == i_w after the bounds and the cap holds |i_w - slewed| to one slew step on every slew-limited cycle, frozen ones included. Measured over the whole 256 251-tick recording the carried integrator never stands more than two slew steps from the command on the wire - 800 W against release/1.0's 9200 W. On the frozen path this replaces an integrator bounded only by integrator_max_w with one bounded at two steps, so the loop is less wound up on those ticks and not more. The same-cycle command cannot be affected at all, the cap moving i_w after slewed is decided.

THE ALTERNATIVE IS REFUSED FOR A MEASURED REASON. Gating the cap with `if frozen: _lead_cap = float("inf")` reproduces all four of section 5's corrected figures - export 34.71 kWh, wasted 3.49 kWh, worst quarter 3434 W, worst observed lead 4961 W. The gate does leave the bound with a hole large enough to make the card pointless.

NOTHING FALSE FOUND. Section 3a's lag table reproduces cell for cell, including the rows that say the peak improvement is absent at every lag at or below 5.0 s; the card does not claim a capacity-tariff benefit and says so in four places. Form A is committed nowhere, so it was rebuilt from section 3's prose alone and reproduces all seven figures - 34.43 / 110.62 / 2656 / 2.99 / 3934.59 / +2.73 / +991.53 - which makes section 3 a specification rather than a summary. The one-sided cap was swept against the two-sided band over 136 080 states on an independent grid: 0 differences, corroborating habit 22's retraction.

MUTATION EVIDENCE RE-DRIVEN, EVERY COUNT EXACT, on purged cp -a trees one file at a time: L2 zero bytes 4+3, L4 one red in test_telemetry with test_control GREEN as claimed, L5a zero bytes 5+5, L6 zero bytes 6+3.

SUITE RE-RUN BY THE REVIEWER on the merge rather than the branch tip: release/1.0 29/29, this merge 30/30, exit 0.

One non-blocking note left on the pull request: "worst case 2424 W" is scoped to the 679 further-out ticks, while the largest move over all 1095 is 4220 W. That case is benign - the cap pulling a stale positive integrator down to follow a charging command, magnitude falling - which is why it is a note and not a finding.

Merging is not deploying. The live install still runs release/1.0's control law and slew_w 400 applied by hand; this merge authorises no deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-19 11:05:25 +02:00
adminandClaude Opus 5 660d079bf3 Merge pull request 'TEL-10: the loop's integrator stops being invisible' (#92) from TEL-10 into release/1.0
Reviewed on the pull request and merged there. `checks / unit (pull_request)` run 798 is a real execution, not a skip: all five steps ran and "the add-on's own tests" occupied 08:04:51 to 08:12:38 UTC.

CONTROL BEHAVIOUR IS UNCHANGED, VERIFIED MECHANICALLY RATHER THAN READ. goodwe_controller/app/control.py is blob 180f58a on release/1.0 and on TEL-10 alike, same sha256 of contents, and release/1.0 is a direct ancestor of TEL-10 so there is no merge-base divergence for a change to hide behind. The only executable edit inside cycle() is prev_w=self.target becoming a named local, loop_prev, passed to the same call. Three write sites for self.i_w exist and only two have a telemetry counterpart; the third is the arbiter re-seed at main.py:3049, inside the same branch and immediately followed by compute(), so it cannot leave loop_i_w stale. app/mqtt.py appends to SENSORS and discovery_payloads() walks it in order, so every existing entity keeps its index.

SUITE RE-RUN BY THE REVIEWER, on the MERGE rather than the branch tip, in the CI image on a purged tree, :ro, one file at a time: release/1.0 29/29, this merge 30/30, and the stacked SAFETY-48 30/30 against both bases. All exit 0.

MUTATION EVIDENCE RE-DRIVEN RATHER THAN READ, because the previous round's counts were irreproducible. M1 is 5 reds on TEL-10's tree and 4 on SAFETY-48's, exactly as the header says and for the reason it gives; M2 is confirmed GREEN and is reported as a survivor; M7 is -96 B and 10 reds in test_mqtt.py plus 1 in test_telemetry.py; M9 is a zero-byte reordering and 7 reds. Every claimed count exact.

R6's correction is true and not merely made: part 1's headline now says EIGHT and the slice under it asserts eight entity configs, part 3's NINE headline is backed by a full missing/changed diff that does cover all nine, and the unasked-for fix two lines down reads eight against the eight-prefix slice.

Merging is not deploying. Nothing here reached the inverter and no deploy is authorised by this merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-19 10:54:02 +02:00
+238 -5
View File
@@ -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/<head sha>/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/<head sha>/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,85 @@
# 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.
#
# 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 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]`,
# `[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
@@ -224,6 +323,140 @@ 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 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/<n>/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
# 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.
#
# ⚠️ 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 (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.
- 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