diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 71a7155..28fdde1 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -31,10 +31,16 @@ jobs: scope: # Not on labeled/unlabeled: those events change no paths, so scope has # nothing new to derive — and label churn is precisely what they are. + # review_requested/review_request_removed likewise change no paths — they + # exist to wake reconcile (#137) — and running labeler on them widens + # exactly the window #130 documents, where a label written during a + # scope run is clobbered. if: >- github.event_name == 'pull_request_target' && github.event.action != 'labeled' && - github.event.action != 'unlabeled' + github.event.action != 'unlabeled' && + github.event.action != 'review_requested' && + github.event.action != 'review_request_removed' runs-on: ubuntu-latest concurrency: group: labels-scope-${{ github.event.pull_request.number }} diff --git a/.github/workflows/self-labels.yml b/.github/workflows/self-labels.yml index 53ba9ea..e9b8dac 100644 --- a/.github/workflows/self-labels.yml +++ b/.github/workflows/self-labels.yml @@ -10,7 +10,11 @@ on: issues: types: [opened, edited, assigned, unassigned, labeled, unlabeled, closed, reopened] pull_request_target: - types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled] + # review_requested/review_request_removed wake the sweep that clears (or + # restores) blocker:unrequested — without them the one event that makes + # the label false could not clear it, and a quiet repo wore the red flag + # until the advisory cron (#137). + types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled, review_requested, review_request_removed] permissions: contents: read checks: read # mergeability/check-rollup read for PR state diff --git a/changelog.d/137.md b/changelog.d/137.md new file mode 100644 index 0000000..f4980d4 --- /dev/null +++ b/changelog.d/137.md @@ -0,0 +1,10 @@ +### Fixed + +- `blocker:unrequested` now clears the moment the panel is asked: the labels + caller (and the `docs/CONSUMERS.md` stub) listens on `review_requested` and + `review_request_removed`, so the one event that falsifies the label — or + makes it true again — wakes the reconcile sweep instead of waiting for an + unrelated push or the advisory cron. The `scope` job skips both events: + they change no paths, and running the labeler on them widens the #130 + clobber window. Adopting the new triggers is a stub edit riding the pin + bump to the first tag carrying this change (#137). diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index f1017c1..f22e635 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -299,8 +299,12 @@ on: schedule: [{cron: "*/15 * * * *"}] # advisory; the handoff label is the real wake workflow_dispatch: # bootstraps missing labels on a fresh repo pull_request_target: - types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled] - # Unreleased — not in 0.1.0; add only with the first tag carrying ceremony#32. + # review_requested/review_request_removed are unreleased — not in 0.2.0; + # add them with the pin bump to the first tag carrying ceremony#137. They + # wake the sweep that clears blocker:unrequested when the panel is asked. + types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled, review_requested, review_request_removed] + # Available at 0.2.0 and later (the first tag carrying ceremony#32); a + # consumer pinned to 0.1.0 omits this block. issues: types: [opened, labeled, unlabeled, assigned, unassigned, closed] permissions: @@ -319,15 +323,21 @@ repositories allow check data to be read regardless, but a private consumer needs both explicit reads above; without them the failure appears as an empty `state:*` axis on the board rather than a red workflow run. -The `issues:` trigger is **unreleased** and is not in `0.1.0`. A consumer -pinned to `0.1.0` omits it. Add it only when bumping every ceremony reference -to the first tag carrying ceremony#32; never mix refs to adopt it early. +The `issues:` trigger is available at `0.2.0` and later — `0.2.0` is the +first tag carrying ceremony#32. A consumer pinned to `0.1.0` omits it. Adopt +it only by bumping every ceremony reference to `0.2.0` or later; never mix +refs to adopt it early. `pull_request_target` is intentional: fork PRs need the base repository's token to write labels. The reusable workflow executes no PR code. It checks out only the consumer's base branch and the pinned ceremony implementation. -The #52 ruling invariants ride exactly these triggers — the caller above is -unchanged since #18, so adopting them is a pin bump, not a stub edit. +The #52 ruling invariants ride exactly these triggers — but the caller above +is no longer the #18 shape, so adopting current triggers is a stub edit, not +a bare pin bump. The pending edit is `review_requested` and +`review_request_removed` (#137): the wake that clears `blocker:unrequested` +the moment the panel is asked, without which a quiet repo wears that flag +until the advisory cron. Make that edit with the pin bump to the first tag +carrying ceremony#137 — never before it and never through mixed refs. `.github/labels.conf` has one mandatory panel setting, one mandatory `triage-actors` setting, and then zero or more scope rows: @@ -339,11 +349,11 @@ scope:cli|C5DEF5|The command-line surface scope:docs|C5DEF5|Documentation ``` -The mandatory `triage-actors=` setting is also **unreleased** and is not -accepted by `0.1.0`. At that tag the file contains `panel=` plus scope rows -only; adding `triage-actors=` is a parse failure, not an ignored setting. Add -it at the same pin bump as the `issues:` trigger, to the first tag carrying -ceremony#32 — never before it and never through mixed refs. +The mandatory `triage-actors=` setting is likewise accepted at `0.2.0` and +later, and not by `0.1.0`. At that tag the file contains `panel=` plus scope +rows only; adding `triage-actors=` is a parse failure, not an ignored setting. +Add it at the same pin bump as the `issues:` trigger — `0.2.0` or later — +never before it and never through mixed refs. Both actor lists are whitespace-separated. `triage-actors` names the identities allowed to mint issues without the sweep applying `needs-triage`. Label rows use exactly diff --git a/test/labels.test.sh b/test/labels.test.sh index 6d35a3a..e571ca2 100755 --- a/test/labels.test.sh +++ b/test/labels.test.sh @@ -65,4 +65,41 @@ check "PR author is recused from the required panel" 0 "one three" printf '%s\n' check "LABELS.md enumerates no repo's scope labels" 1 "0" \ grep -c 'scope:[a-z0-9]' "$ROOT/LABELS.md" +# The caller's pull_request_target list and the CONSUMERS.md stub's must be +# the same list. review_requested/review_request_removed are the wake that +# clears blocker:unrequested — the label sat false for as long as a quiet +# repo stayed quiet because the one event that falsifies it was never +# listed (#137). The stub is prose, so nothing but this row keeps the two +# lists from drifting: a type in one file only is a wake that fires at home +# and nowhere in the fleet, or the reverse. +pr_target_types() { # $1 = file → its pull_request_target types line, unindented + awk '/pull_request_target:/{f=1; next} f && /types: /{sub(/^ */,""); print; exit}' "$1" +} +types_in_sync() { # $1 = caller, $2 = stub → 0 when both lists exist and match + local a b + a="$(pr_target_types "$1")" b="$(pr_target_types "$2")" + [ -n "$a" ] && [ "$a" = "$b" ] +} +CALLER="$ROOT/.github/workflows/self-labels.yml" +STUB="$ROOT/docs/CONSUMERS.md" +check "caller and stub pull_request_target lists are identical" 0 "" \ + types_in_sync "$CALLER" "$STUB" +# shellcheck disable=SC2016 # expansion belongs to the nested bash +check "the caller lists both review-request wakes" 0 "" bash -c \ + 'awk "/pull_request_target:/{f=1; next} f && /types: /{print; exit}" "$1" | + grep -F review_requested | grep -qF review_request_removed' _ "$CALLER" +# the failing cases: drop a type from either file, or reorder one list only, +# and the identity row above goes red — exercised here on mutated copies +mut_caller="$TMP/mut-caller.yml" mut_stub="$TMP/mut-stub.md" +sed 's/, review_request_removed//' "$CALLER" >"$mut_caller" +check "a type dropped from the caller goes red" 1 "" \ + types_in_sync "$mut_caller" "$STUB" +sed 's/, review_request_removed//' "$STUB" >"$mut_stub" +check "a type dropped from the stub goes red" 1 "" \ + types_in_sync "$CALLER" "$mut_stub" +sed 's/review_requested, review_request_removed/review_request_removed, review_requested/' \ + "$STUB" >"$mut_stub" +check "a reorder in one list only goes red" 1 "" \ + types_in_sync "$CALLER" "$mut_stub" + summary