fix(gate): the tooling-partition gate states its own remedy (BACKLOG #1434) #839
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: review gate | |
| # A pull request cannot merge until a reviewer has marked it read. | |
| # | |
| # THE GAP THIS CLOSES. GitHub cannot start a session on this machine, and the live session channel | |
| # reads one config directory with no glob, so it stops at an account boundary. Builders run as | |
| # background sessions bound to different accounts; they open pull requests and cannot be reached. | |
| # Autofix covers the CI half by waking a LIVE session on a failed check, but it is a per-session | |
| # checkbox in a desktop window, so a background session can never have it. Nothing at all reported | |
| # "this is green and nobody has looked at it". | |
| # | |
| # WHY A LABEL AND NOT AN APPROVAL. Every session on this machine pushes as ONE GitHub identity: `gh` | |
| # auth lives at AppData\Roaming\GitHub CLI\hosts.yml, machine-wide, not per config root, and reports | |
| # `wshallwshall` from every account root. CODEOWNERS assigns every path, including `*`, to that same | |
| # account, and it authored 28 of the last 30 merged pull requests. GitHub does not let an author | |
| # approve their own pull request, so `required_approving_review_count: 1` would wedge every pull | |
| # request permanently. REVIEWER.md records the same conclusion at its section 5. | |
| # | |
| # So this is a PROCESS gate, not an IDENTITY gate. It enforces that a step happened. It does not | |
| # establish that an independent party looked, and it must not be described as if it does. The | |
| # identity version needs a second GitHub account whose token the reviewer session carries in | |
| # GH_TOKEN; this gate is compatible with that and does not have to be redone when it arrives. | |
| # | |
| # FAIL-CLOSED, WHICH IS WHY THE LABEL IS POSITIVE. An earlier draft used a `review-requested` label | |
| # written automatically and removed by a human, with the gate failing while it was present. That | |
| # fails OPEN: if the marking workflow never ran, the label is absent and the gate passes unreviewed | |
| # work. A gate whose safe state depends on another workflow having succeeded is not a gate. This one | |
| # requires the label to be PRESENT, so the default state of a brand-new pull request is blocked. | |
| # | |
| # HOW A REVIEWER SIGNALS. `gh pr edit <N> --add-label reviewed`. That is the whole protocol. Nothing | |
| # automated ever adds it. | |
| # | |
| # RE-REVIEW IS AUTOMATIC. On `synchronize` this workflow REMOVES the label: commits nobody has read | |
| # are unread again, even if the pull request was marked an hour ago. That is the one thing the | |
| # workflow writes, and it only ever writes in the blocking direction. | |
| # | |
| # NO CONCURRENCY BLOCK, DELIBERATELY. This job's name is intended to become a required status | |
| # context, and a cancelled required check can never go green. backlog-hygiene.yml carried | |
| # `cancel-in-progress` on a key that collapsed to one group on `merge_group`, and entries cancelled | |
| # each other: measured 49 of 151 runs cancelled, 32.5 percent, zero failures, evicting them from the | |
| # queue. The job below runs in seconds, so there is nothing to supersede and no reason to risk it. | |
| # | |
| # ARMING IT IS A SEPARATE, OWNER-ONLY STEP. Merging this workflow changes nothing on its own. It | |
| # blocks a merge only once "a reviewer has read this" is added to the required status checks in | |
| # branch protection, and `.github/required-contexts.txt` is updated to match. | |
| on: | |
| pull_request: | |
| # THE LIST IS LOAD-BEARING AND FAILS SILENTLY. `labeled` is what lets a reviewer clear the check: | |
| # without it the label emits no run, the red check-run stands, and the only trigger left that | |
| # re-runs this job is `synchronize` -- whose first step REMOVES the label. The pull request then | |
| # cannot be merged by any action at all, and `strict = true` means it cannot even be brought up to | |
| # date past the block. `unlabeled` is the opposite half: without it, withdrawing the label leaves a | |
| # GREEN context behind. Both are pinned by | |
| # tests/test_merge_gate_controls.py::test_the_review_gate_reruns_when_a_reviewer_adds_the_label, | |
| # because dropping `labeled` reddened nothing before that test existed. | |
| types: [opened, reopened, ready_for_review, synchronize, labeled, unlabeled] | |
| merge_group: | |
| permissions: | |
| pull-requests: write | |
| jobs: | |
| reviewed: | |
| # The name IS the required-context string. Keep it stable; changing it silently detaches the gate. | |
| name: a reviewer has read this | |
| runs-on: ubuntu-latest | |
| steps: | |
| # PASSES UNCONDITIONALLY IN THE QUEUE, and that is correct rather than lax. A merge_group event | |
| # carries no pull request, so there is no label to read. By the time an entry is in the queue | |
| # the gate has already been satisfied on the pull request itself; failing here would mean | |
| # nothing could ever merge. | |
| - name: Queue entries carry no pull request | |
| if: github.event_name == 'merge_group' | |
| # QUOTED. An unquoted YAML scalar cannot contain ": " -- it reads as a mapping. The sibling | |
| # lesson is in backlog-hygiene.yml, where an unquoted job name was silently truncated at " #". | |
| run: 'echo "merge_group carries no pull request. The gate was satisfied on the PR itself."' | |
| - name: A new commit makes it unread again | |
| if: github.event_name == 'pull_request' && github.event.action == 'synchronize' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| NUMBER: ${{ github.event.pull_request.number }} | |
| # The only write this workflow performs, and it only ever moves toward blocked. | |
| # | |
| # ITS EXIT CODE IS NOT DISCARDED, and that became load-bearing on 2026-08-31 when this job's | |
| # context entered branch protection. The line read `... 2>/dev/null || true` until then: a | |
| # REQUIRED job swallowing a failure, which is the shape tests/test_security_posture.py refuses | |
| # and caught here on the first run after the context was recorded. | |
| # | |
| # THERE WAS NOTHING TO SWALLOW. This step runs on `synchronize` only, and `synchronize` means | |
| # commits nobody has read, so the run ends RED regardless -- the next step returns that verdict | |
| # from `$ACTION` without consulting the API at all. Letting the removal's own failure through | |
| # changes no outcome; it only stops hiding the reason. | |
| # | |
| # A SILENTLY FAILED REMOVAL IS ITS OWN TRAP, which is why hiding it cost something. If the | |
| # label survives, re-adding it emits no `labeled` event, so nothing re-runs this job and the | |
| # pull request sits red with nothing a reviewer can do. Printed, it names itself. | |
| run: gh pr edit "$NUMBER" --remove-label reviewed | |
| - name: Require the reviewed label | |
| if: github.event_name == 'pull_request' | |
| env: | |
| LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} | |
| ACTION: ${{ github.event.action }} | |
| run: | | |
| # On `synchronize` the label was just removed above, so the event payload is stale by one | |
| # step. Treat that action as unreviewed by definition rather than reading a value that is | |
| # already wrong -- reading the payload here would pass a pull request that was just | |
| # invalidated, which is the exact failure this step exists to prevent. | |
| if [ "$ACTION" = "synchronize" ]; then | |
| echo "::error::New commits have not been read. Re-review, then: gh pr edit <N> --add-label reviewed" | |
| exit 1 | |
| fi | |
| case ",$LABELS," in | |
| *,reviewed,*) | |
| echo "reviewed label present. Gate satisfied." ;; | |
| *) | |
| echo "::error::Not yet read by a reviewer. When you have read it: gh pr edit <N> --add-label reviewed" | |
| exit 1 ;; | |
| esac |