diff --git a/.github/workflows/pr-risk.yml b/.github/workflows/pr-risk.yml index d62bb4d..7e1fda2 100644 --- a/.github/workflows/pr-risk.yml +++ b/.github/workflows/pr-risk.yml @@ -114,8 +114,9 @@ name: PR Risk Grade (reusable) # last-writer-wins with still EXACTLY ONE `risk:*` label — possibly the staler tier, which gates # nothing meanwhile and which the next grade re-syncs (on the LAST push there is no next grade, so # re-dispatch on `pr_number` if a final grade looks wrong). It can no longer leave two contradictory -# labels on a PR under one `label_map`; remapping `label_map` orphans the old names, which is a -# one-time repo-side cleanup. What the shape does cost is a narrower residual: the PUT is built from +# labels on a PR under one `label_map`; the first remapped grade retires the default +# `risk:R0`..`risk:R3` and `risk:ungraded` labels. Custom names from an older map still need cleanup. +# What the shape does cost is a narrower residual: the PUT is built from # a snapshot read, so a NON-owned label added by someone else in the read→PUT window is dropped # (`risk-dispute` included) and one removed in it is resurrected. That window opens only on a run # that actually changes the grade and is roughly one API round-trip — about three on the first diff --git a/docs/callers/pr-risk.md b/docs/callers/pr-risk.md index 4c3c5fd..24fb855 100644 --- a/docs/callers/pr-risk.md +++ b/docs/callers/pr-risk.md @@ -122,6 +122,12 @@ fail the caller's next run at startup. | `repo_map_path` | `.github/risk.json` | Consumer risk-map override, read from the PR **base ref**. | | `repo_runbooks_path` | `.github/risk-runbooks.json` | Consumer runbook-registry override, read from the PR **base ref**. | +To show `risk:low` through `risk:xhigh`, pass this `label_map` in the caller: +`R0=risk:low,R1=risk:medium,R2=risk:high,R3=risk:xhigh,unknown=risk:unknown`. +The next grade removes a default `risk:R0`..`risk:R3` or `risk:ungraded` label +and leaves only the mapped label. Custom names from an older map still need +one-time cleanup. + ## Gotchas **Fork PRs need `pull_request_target`, not `pull_request`.** A fork PR under a diff --git a/scripts/pr-risk/README.md b/scripts/pr-risk/README.md index 2a4cbd7..8334a99 100644 --- a/scripts/pr-risk/README.md +++ b/scripts/pr-risk/README.md @@ -198,11 +198,9 @@ Operational caveats for a backfill: invisible: GitHub records it on the PR timeline as an `unlabeled` event by the grader token. Dispatch when the queue is quiet, and use `pr_number` when you want the per-PR group to serialize a re-grade against event runs. -- **Remapping `label_map` orphans the old names.** Ownership is defined by the - *current* map, so labels applied under a previous one are no longer owned: - they ride through every future PUT beside the new target and no re-grade will - clear them. Delete the retired label names repo-side once, as part of the - remap. +- **Remapping retires the default `risk:R0`..`risk:R3` and `risk:ungraded` + labels.** The first re-grade removes the old default and leaves one mapped + label. Custom names from an older map still need one-time repo-side cleanup. - **The pre-grader reads retry.** Rate limits are global, not per-PR, so the base-ref and override reads — the first hop for every target — retry a transient failure with backoff, as the grader already does. Without it one diff --git a/scripts/pr-risk/apply-risk-label.sh b/scripts/pr-risk/apply-risk-label.sh index b413a6d..56f0e45 100755 --- a/scripts/pr-risk/apply-risk-label.sh +++ b/scripts/pr-risk/apply-risk-label.sh @@ -36,11 +36,9 @@ # before-snapshot and the after-read, so the diff that would have to catch it is empty by # construction. # -# ONE MORE LIMIT, on a different axis: ownership is defined by the CURRENT LABEL_MAP. Change a -# caller's `label_map` and labels applied under the old map are, by definition, no longer owned — -# they are carried through every future PUT beside the new target, and nothing here will clean them -# up. Remapping is a one-time repo-side cleanup (delete the retired label names), not something a -# re-grade heals. +# ONE MORE LIMIT, on a different axis: ownership is defined by the CURRENT LABEL_MAP plus the five +# default labels. A remap retires those defaults on the first re-grade. Custom names from an +# older map are unknowable and still need one-time repo-side cleanup. # # The label is applied with the plain GITHUB_TOKEN on purpose: GITHUB_TOKEN-applied labels do # not fire `labeled` workflow triggers, which makes the shadow check incapable of starting a @@ -107,6 +105,7 @@ for t in R0 R1 R2 R3 unknown; do [ -n "$l" ] || die "LABEL_MAP maps tier '$t' to an empty label" OWNED+=("$l") done +OWNED+=("risk:R0" "risk:R1" "risk:R2" "risk:R3" "risk:ungraded") TARGET="$(label_for "$TIER")" # Colors keyed by TIER (not label text, which callers may remap): green .. red, gray unknown. diff --git a/scripts/pr-risk/tests/test_apply_risk_label.sh b/scripts/pr-risk/tests/test_apply_risk_label.sh index 78ab30f..d46010f 100755 --- a/scripts/pr-risk/tests/test_apply_risk_label.sh +++ b/scripts/pr-risk/tests/test_apply_risk_label.sh @@ -33,6 +33,8 @@ echo "— caller remap (a 1-indexed R1..R4 scheme is one input) —" MAP='R0=risk:R1,R1=risk:R2,R2=risk:R3,R3=risk:R4,unknown=risk:ungraded' eq "R0 remaps to risk:R1" "risk:R1" "$(run R0 "$MAP")" eq "R3 remaps to risk:R4" "risk:R4" "$(run R3 "$MAP")" +NAMED_MAP='R0=risk:low,R1=risk:medium,R2=risk:high,R3=risk:xhigh,unknown=risk:unknown' +eq "unknown remaps to risk:unknown" "risk:unknown" "$(run unknown "$NAMED_MAP")" echo "— validation refuses bad input before any write —" run R7 >/dev/null 2>&1; eq "bad tier exits 2" 2 "$?" @@ -203,6 +205,15 @@ eq "the extra owned label is squashed down to the one target" \ "api -X PUT repos/test/repo/issues/7/labels -f labels[]=keep-me -f labels[]=risk:R2" \ "$putheal" +echo "— a remap retires the default grade label instead of keeping both —" +: > "$GH_LOG"; printf 'risk:R2\nrisk:ungraded\nkeep-me\n' > "$CURRENT_LABELS" +PATH="$SANDBOX/bin:$PATH" REPO=test/repo PR_NUMBER=7 TIER=R2 LABEL_MAP="$NAMED_MAP" \ + bash "$SCRIPT" >/dev/null 2>&1 +putremap="$(grep -- '-X PUT repos/test/repo/issues/7/labels ' "$GH_LOG")" +eq "the old defaults are replaced by the mapped label" \ + "api -X PUT repos/test/repo/issues/7/labels -f labels[]=keep-me -f labels[]=risk:high" \ + "$putremap" + # Already-correct label: no write at all beyond the read. : > "$GH_LOG"; printf 'risk:R2\nkeep-me\n' > "$CURRENT_LABELS" outsync="$(PATH="$SANDBOX/bin:$PATH" REPO=test/repo PR_NUMBER=7 TIER=R2 bash "$SCRIPT" 2>/dev/null)"