diff --git a/changelog.d/144.md b/changelog.d/144.md new file mode 100644 index 0000000..3d1c212 --- /dev/null +++ b/changelog.d/144.md @@ -0,0 +1,10 @@ +### Fixed + +- The `docs/CONSUMERS.md` labels-caller stub lists the same `issues:` types + as ceremony's own caller — `edited` and `reopened` included — so a consumer + adopting the stub wakes when an issue body's `Blocked by #N` declaration is + edited, and when a closed issue re-enters the queue wearing labels derived + at close. The two lists drifted apart inside PR #32; a parity test now pins + them together, red if either file drops a type or the lists diverge. + Adopting the widened list is a stub edit riding the pin bump to the first + tag carrying this change (#144). diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index f22e635..9d8ac06 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -306,7 +306,10 @@ on: # 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] + # edited/reopened are unreleased — not in 0.2.0; add them with the pin + # bump to the first tag carrying ceremony#144, the same bump as the + # pull_request_target additions above. + types: [opened, edited, assigned, unassigned, labeled, unlabeled, closed, reopened] permissions: contents: read checks: read # mergeability/check-rollup read for PR state @@ -326,18 +329,23 @@ needs both explicit reads above; without them the failure appears as an empty 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. +refs to adopt it early. At `0.2.0` the type list stops at `closed`: `edited` +and `reopened` are newer, part of the pending stub edit below. `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 — 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. +a bare pin bump. The pending edits are `review_requested` and +`review_request_removed` on `pull_request_target:` (#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 — and `edited` and +`reopened` on `issues:` (#144): the wakes for a body edit rewriting the +`Blocked by #N` declaration the reconcile sweep parses, and for a closed +issue re-entering the queue wearing labels derived when it closed. Make both +edits together, with the pin bump to the first tag carrying ceremony#137 and +ceremony#144 — 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: diff --git a/test/labels.test.sh b/test/labels.test.sh index e571ca2..79f53fa 100755 --- a/test/labels.test.sh +++ b/test/labels.test.sh @@ -65,41 +65,61 @@ 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 +# The caller's trigger type lists and the CONSUMERS.md stub's must be the +# same lists. 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" +# listed (#137). edited/reopened are the wakes for the two events that +# falsify issue labels silently — an edited body rewrites the `Blocked by +# #N` declaration the reconcile sweep parses, and a reopened issue +# re-enters the queue wearing labels derived at close; PR #32 widened the +# caller by both and the stub never followed (#144). The stub is prose, so +# nothing but these rows keeps the lists from drifting: a type in one file +# only is a wake that fires at home and nowhere in the fleet, or the +# reverse. The NF guard keeps `issues: write` under permissions: from +# matching the issues: trigger key. +event_types() { # $1 = file, $2 = trigger key → that trigger's types line, unindented + awk -v key="$2:" '$1 == key && NF == 1 {f=1; next} f && /types: /{sub(/^ */,""); print; exit}' "$1" } -types_in_sync() { # $1 = caller, $2 = stub → 0 when both lists exist and match +types_in_sync() { # $1 = trigger key, $2 = caller, $3 = stub → 0 when both lists exist and match local a b - a="$(pr_target_types "$1")" b="$(pr_target_types "$2")" + a="$(event_types "$2" "$1")" b="$(event_types "$3" "$1")" [ -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" + types_in_sync pull_request_target "$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" +check "caller and stub issues lists are identical" 0 "" \ + types_in_sync issues "$CALLER" "$STUB" +check "the caller still lists all eight issue types" 0 \ + "types: [opened, edited, assigned, unassigned, labeled, unlabeled, closed, reopened]" \ + event_types "$CALLER" issues # 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 +# and the identity rows above go 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" + types_in_sync pull_request_target "$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" + types_in_sync pull_request_target "$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" + types_in_sync pull_request_target "$CALLER" "$mut_stub" +sed 's/, edited//' "$CALLER" >"$mut_caller" +check "an issue type dropped from the caller goes red" 1 "" \ + types_in_sync issues "$mut_caller" "$STUB" +sed 's/, edited//' "$STUB" >"$mut_stub" +check "an issue type dropped from the stub goes red" 1 "" \ + types_in_sync issues "$CALLER" "$mut_stub" +sed 's/closed, reopened/reopened, closed/' "$STUB" >"$mut_stub" +check "an issue-list reorder in one file only goes red" 1 "" \ + types_in_sync issues "$CALLER" "$mut_stub" summary