diff --git a/.github/bump-callers/README.md b/.github/bump-callers/README.md index 8eb9352..1c77c7c 100644 --- a/.github/bump-callers/README.md +++ b/.github/bump-callers/README.md @@ -30,7 +30,7 @@ forward automatically instead of silently drifting commits behind. | [`bump-cursor-review-callers.yml`](../workflows/bump-cursor-review-callers.yml) | `cursor-review.yml` or `cursor-review/**` | `CURSOR_REVIEW_CALLERS` | non-empty (hard-fails if empty) | | [`bump-agents-md-callers.yml`](../workflows/bump-agents-md-callers.yml) | `agents-md-integrity.yml` or `agents-md-integrity/**` | `AGENTS_MD_CALLERS` | empty `[]` (grows as callers land) | | [`bump-pr-size-callers.yml`](../workflows/bump-pr-size-callers.yml) | `pr-size.yml` or `scripts/check-pr-size/**` | `PR_SIZE_CALLERS` | empty `[]` (grows as callers land) | -| [`bump-pr-risk-callers.yml`](../workflows/bump-pr-risk-callers.yml) | `pr-risk.yml` or `scripts/pr-risk/**` (minus its `tests/` and `README.md`, which no caller executes) | `PR_RISK_CALLERS` | empty `[]` allowed (grows as callers land) | +| [`bump-pr-risk-callers.yml`](../workflows/bump-pr-risk-callers.yml) | `pr-risk.yml` or `scripts/pr-risk/**` (minus its `tests/` and `README.md`, which no caller executes) | `PR_RISK_CALLERS` | non-empty (hard-fails if empty) | | [`bump-assign-reviewers-callers.yml`](../workflows/bump-assign-reviewers-callers.yml) | `assign-reviewers.yml` | `ASSIGN_REVIEWERS_CALLERS` | empty `[]` (grows as callers land) | | [`bump-groom-callers.yml`](../workflows/bump-groom-callers.yml) | `groom.yml` or `groom/**` | `GROOM_CALLERS` | empty `[]` (grows as callers land) | | [`bump-auto-label-callers.yml`](../workflows/bump-auto-label-callers.yml) | `cursor-review-auto-label.yml` | `AUTO_LABEL_CALLERS` | non-empty (hard-fails if empty) | diff --git a/.github/workflows/bump-pr-risk-callers.yml b/.github/workflows/bump-pr-risk-callers.yml index 741ac1f..a8fe52a 100644 --- a/.github/workflows/bump-pr-risk-callers.yml +++ b/.github/workflows/bump-pr-risk-callers.yml @@ -22,24 +22,37 @@ name: Bump pr-risk callers # Actions run logs are both publicly viewable), and most callers are private, so # their names must never appear in this file or its logs. The list lives in the # repo-level Actions SECRET `PR_RISK_CALLERS` as a JSON array of -# {"repo","file","label"} objects, same shape as the other fleets' rosters. An -# empty list (`[]`, or an unset secret) is a clean no-op here (ALLOW_EMPTY -# below); the rollout's per-repo caller tickets add entries as callers land. +# {"repo","file","label"} objects, same shape as the other fleets' rosters. # # A SECRET, not a variable (BE-6472): the roster reaches the script through the # step's `env:` below, and Actions prints that env block BEFORE the step runs — # so a variable's raw value was published in this public repo's run log ahead of -# any masking the script could do (the KNOWN GAP this header used to carry). A -# secret is runner-masked everywhere, that dump included. The cost is read-back: -# there is no `gh secret get`, so the canonical callers.json in the private ops -# repo is the sole source of truth, and the run instead logs a count + sha256 -# fingerprint of the roster it used (reproduce with `jq -cS . callers.json | -# sha256sum`) so an edit can be audited against it. +# any masking the script could do (the KNOWN GAP this header used to carry). +# That was not hypothetical: run 31414569691 (2026-08-10) printed this fleet's +# whole roster in the clear. A secret is runner-masked everywhere, that dump +# included. The cost is read-back: there is no `gh secret get`, so the canonical +# callers.json in the private ops repo is the sole source of truth, and the run +# instead logs a count + sha256 fingerprint of the roster it used (reproduce +# with `jq -cS . callers.json | sha256sum`) so an edit can be audited against it. # `bump-callers.sh` still `::add-mask::`es every repo name before IT echoes # anything — Actions masks the roster as ONE exact string, not each name inside # it, so a name echoed on its own would otherwise print in the clear. -# NOTE: this fleet's roster is still UNSEEDED — the secret exists only once -# someone sets it (see the update flow below). +# +# The roster IS seeded (BE-6480), so `ALLOW_EMPTY: "false"` below makes an empty +# roster fatal: this fleet has live callers, so an empty roster is a bug, not a +# state to tolerate. Without the hard-fail, a roster that is emptied, renamed or +# never-read degrades into a silent clean exit — every caller stays pinned to a +# stale grader with nothing red anywhere to say so, which is exactly the +# pin-drift this fleet exists to prevent. Same posture as the cursor-review and +# auto-label fleets; the opposite of groom's, which is still rolling out. +# +# A secret cannot be read back, so that seeding is evidenced by the secret's +# creation timestamp (2026-08-05, alongside the variable it replaced, which +# carried two live callers) rather than by its contents. No run has exercised +# the secret path yet — BE-6472 landed after the last one — so the first run is +# what confirms it. If that run hard-fails on an empty roster, re-set the secret +# from the canonical callers.json rather than relaxing ALLOW_EMPTY: a loud +# failure here is the design working, not a regression. # # Update flow — adding/removing a caller needs NO public commit: # jq -c . callers.json | gh secret set PR_RISK_CALLERS --repo Comfy-Org/github-workflows @@ -142,9 +155,13 @@ jobs: VAR_NAME: PR_RISK_CALLERS TAG: pr-risk WORKFLOW_FILE: pr-risk.yml - # An empty roster — `[]`, or a secret nobody has set — is a clean no-op - # here, not an error. - ALLOW_EMPTY: "true" + # Hard-fail on an empty/missing roster — see the header. Already + # `bump-callers.sh`'s default, but stated explicitly so the property + # survives a future flip of that default: this fleet has live callers, + # so silently degrading back to a clean no-op would restore exactly the + # pin-drift the header says it prevents. Same as the auto-label and + # detect-unreviewed-merge entrypoints. + ALLOW_EMPTY: "false" # JSON array of {"repo","file","label"} — see the header comment for the # update flow. Kept in a repo-level SECRET (not this file, and not a # variable) so private caller names never land in this public repo. Actions diff --git a/README.md b/README.md index b91ac8c..914fd32 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,13 @@ Enrolling a repo is **two steps**: 1. Merge the caller workflow into your repo. 2. Ask a maintainer to add the repo to the matching roster secret here — - `GROOM_CALLERS`, `CURSOR_REVIEW_CALLERS`, `PR_SIZE_CALLERS`, - `AGENTS_MD_CALLERS`, or `ASSIGN_REVIEWERS_CALLERS`. They are **secrets**, not - variables, so private caller names stay out of this public repo's run logs — - which also means there is no read-back. + `GROOM_CALLERS`, `CURSOR_REVIEW_CALLERS`, `AUTO_LABEL_CALLERS`, + `PR_SIZE_CALLERS`, `PR_RISK_CALLERS`, `AGENTS_MD_CALLERS`, + `ASSIGN_REVIEWERS_CALLERS`, or `DETECT_UNREVIEWED_MERGE_CALLERS`. They are + **secrets**, not variables, so private caller names stay out of this public + repo's run logs — which also means there is no read-back. The + [`docs/callers/README.md` table](docs/callers/README.md#staying-current) maps + each workflow to its roster. The `bump-*-callers.yml` workflows read those rosters to open pin-bump PRs when a reusable moves. **A repo absent from the roster keeps its original SHA forever**, diff --git a/docs/callers/README.md b/docs/callers/README.md index 57150ed..8c74183 100644 --- a/docs/callers/README.md +++ b/docs/callers/README.md @@ -153,9 +153,9 @@ Enrolling a repo is **two steps**, and the second is the one people miss. | `cursor-review.yml` | `CURSOR_REVIEW_CALLERS` | | `cursor-review-auto-label.yml` | `AUTO_LABEL_CALLERS` | | `pr-size.yml` | `PR_SIZE_CALLERS` | + | `pr-risk.yml` | `PR_RISK_CALLERS` | | `agents-md-integrity.yml` | `AGENTS_MD_CALLERS` | | `assign-reviewers.yml` | `ASSIGN_REVIEWERS_CALLERS` | - | `pr-risk.yml` | `PR_RISK_CALLERS` | | `detect-unreviewed-merge.yml` | `DETECT_UNREVIEWED_MERGE_CALLERS` (not seeded yet — pins move by hand until it is) | Each entry is `{"repo": "...", "file": ".github/workflows/.yml", "label": ""}`. diff --git a/docs/callers/pr-risk.md b/docs/callers/pr-risk.md index 81643cc..bf95d1c 100644 --- a/docs/callers/pr-risk.md +++ b/docs/callers/pr-risk.md @@ -63,11 +63,29 @@ jobs: workflows_ref: ``` -This workflow **has** a fleet — `bump-pr-risk-callers.yml` opens the pin-bump PRs -— so enrolling is the usual two steps: merge the caller, then ask a maintainer to -add your repo to the `PR_RISK_CALLERS` roster secret (see -[Staying current](README.md#staying-current)). Skip the second and your pin never -moves. +Enrolling is **two steps** — merging the caller above is only the first. Ask a +maintainer to add this repo to the `PR_RISK_CALLERS` roster secret +(see [Staying current](README.md#staying-current)); until they do, the +[`bump-pr-risk-callers.yml`](../../.github/workflows/bump-pr-risk-callers.yml) +fleet does not know the caller exists, so both of its pins sit frozen and the +caller silently drifts behind the grader it runs. Skipping this half is the most +repeated mistake in this repo. + +One thing to know about that second step: + +- **Enrolment does not backfill your pin.** The fleet only runs on a push to + `main` touching `pr-risk.yml` or `scripts/pr-risk/**`, so a repo added to the + roster after the fact stays on whatever SHA it merged with until the grader + next changes. Ask the maintainer to `workflow_dispatch` + `bump-pr-risk-callers.yml` once after adding you — every bump entrypoint + carries `workflow_dispatch` for exactly this. + +Enrolling a private repo used to publish its name in this public repo's run log: +the roster was an Actions **variable** bound through `env:`, and Actions dumps +the step env before `bump-callers.sh` can mask it. BE-6472 moved every roster to +a **secret**, which the runner masks in that dump too, so that caveat no longer +applies to new enrolments — but a name already printed in an old public log +cannot be unpublished. ## Required permissions