Friction (empirical, 2026-07-03)
manage_pr_review has a body-STRUCTURE validator - it mechanically rejects a review whose body doesn't match the pr-review template (verified live this session). It has NO review-STATE validator. So an agent can post state: APPROVED (or an "Approve"-verdict COMMENT) on a PR whose live reviewDecision === 'CHANGES_REQUESTED' - overriding a peer's blocking review by authority - and the tool surfaces nothing.
Live instance: I (Grace) posted an "Approve" verdict on #14527 at 05:09 while @neo-gpt had an outstanding CHANGES_REQUESTED from 01:34 (and @neo-opus-ada added one at 05:15). I had checked CI-green and the template - never the reviewDecision. The operator caught it as "the final rubber-stamp." That is the most trust-destroying review-failure class: approving over a peer's block.
Root cause
The pr-review guide §2.1 (verify state) + §10.1 (reviewRequests/reviewDecision) are DISCIPLINE - skippable under pressure. The manage_pr_review pre-flight validator (which already mechanically enforces the template) is the natural home for a STATE gate. Structure > discipline: the lint stops recurrence, the prose doesn't.
Fix (concrete - mirrors the existing template validator)
Extend the manage_pr_review pre-flight: before accepting state: 'APPROVED', fetch the PR's live reviewDecision + outstanding CHANGES_REQUESTED reviews at/after current head. If any are unaddressed:
- Block with an actionable error naming the RC reviewer(s) + SHA + the §9.1 Reviewer-Yield path - the same "read the actual state before retrying" shape the template validator already uses.
- Allow override ONLY via an explicit
acknowledgedRequestChanges argument naming each outstanding RC reviewer + its disposition (addressed-by-<sha> / superior-evidence: <what the author's isolation test missed>, per §9.1). Forces awareness + explicit disposition, never a blind approve.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
manage_pr_review request schema (ai/mcp/server/github-workflow/openapi.yaml) |
This ticket; current OpenAPI manage_pr_review surface; pr-review guide §9.1 |
Add optional acknowledgedRequestChanges for action: create + state: APPROVED. Shape: object mapping each outstanding RC reviewer login to a disposition string. Valid dispositions are addressed-by-<sha> or superior-evidence: <specific evidence>. |
Ignored for REQUEST_CHANGES, COMMENT, and update; malformed or incomplete acknowledgment rejects before GraphQL mutation. |
OpenAPI description + schema |
Unit tests assert schema-independent service contract and no behavior change for non-APPROVED states. |
PullRequestService.managePrReview() approval pre-flight |
This ticket; closed #13587 as merge-gate complement; current source has template validation but no live RC state gate |
Before submitting APPROVE, fetch live PR head + review state and reject when outstanding CHANGES_REQUESTED reviews apply to the current head unless every RC reviewer is acknowledged. |
If GitHub state lookup fails, fail closed with structured error; do not submit the review mutation. |
JSDoc @param / method summary |
Regression tests: APPROVED-over-live-RC rejected; APPROVED-with-complete-ack accepted; APPROVED-with-no-RC unchanged. |
| Rejection response |
Existing managePrReview() structured error pattern; template validator precedent |
Return actionable PR_REVIEW_STATE_VALIDATION_FAILED response naming RC reviewer(s), current head SHA, and the §9.1 Reviewer-Yield override path. |
Partial ack lists missing reviewer(s); stale addressed-by-<sha> that does not match current head is rejected. |
No external docs beyond OpenAPI/method JSDoc |
Unit tests assert error code/message and that GraphQL review mutation is not called. |
| COMMENT-state "Approve" prose warning |
Stretch AC in this ticket |
If implemented, state: COMMENT with an approval-status body over live RC returns a warning but does not block. |
If not implemented in this PR, leave documented as stretch/out of scope; do not weaken the blocking APPROVED gate. |
PR body notes if deferred |
Optional test only if stretch is implemented. |
Acceptance Criteria
Substrate Accretion Defense
Net-adds one pre-flight branch to the existing validator (no always-loaded prose -> zero per-turn token cost). Decay-mitigation: forecloses the highest-trust-cost review failure class; retire only if the manage_pr_review review model changes.
Related
- Closed #13587 (merge-gate-level "block strict merge-ready on outstanding review requests") - this is the SUBMISSION-gate complement: stop the approve being POSTED, not just from being merge-counted.
- The pr-review template validator (the structure-gate precedent this extends).
Origin: this session's #14527 rubber-stamp; operator-flagged friction->gold.
Friction (empirical, 2026-07-03)
manage_pr_reviewhas a body-STRUCTURE validator - it mechanically rejects a review whose body doesn't match the pr-review template (verified live this session). It has NO review-STATE validator. So an agent can poststate: APPROVED(or an "Approve"-verdictCOMMENT) on a PR whose livereviewDecision === 'CHANGES_REQUESTED'- overriding a peer's blocking review by authority - and the tool surfaces nothing.Live instance: I (Grace) posted an "Approve" verdict on #14527 at 05:09 while @neo-gpt had an outstanding
CHANGES_REQUESTEDfrom 01:34 (and @neo-opus-ada added one at 05:15). I had checked CI-green and the template - never thereviewDecision. The operator caught it as "the final rubber-stamp." That is the most trust-destroying review-failure class: approving over a peer's block.Root cause
The pr-review guide §2.1 (verify state) + §10.1 (reviewRequests/reviewDecision) are DISCIPLINE - skippable under pressure. The
manage_pr_reviewpre-flight validator (which already mechanically enforces the template) is the natural home for a STATE gate. Structure > discipline: the lint stops recurrence, the prose doesn't.Fix (concrete - mirrors the existing template validator)
Extend the
manage_pr_reviewpre-flight: before acceptingstate: 'APPROVED', fetch the PR's livereviewDecision+ outstandingCHANGES_REQUESTEDreviews at/after current head. If any are unaddressed:acknowledgedRequestChangesargument naming each outstanding RC reviewer + its disposition (addressed-by-<sha>/superior-evidence: <what the author's isolation test missed>, per §9.1). Forces awareness + explicit disposition, never a blind approve.Contract Ledger Matrix
manage_pr_reviewrequest schema (ai/mcp/server/github-workflow/openapi.yaml)manage_pr_reviewsurface; pr-review guide §9.1acknowledgedRequestChangesforaction: create+state: APPROVED. Shape: object mapping each outstanding RC reviewer login to a disposition string. Valid dispositions areaddressed-by-<sha>orsuperior-evidence: <specific evidence>.REQUEST_CHANGES,COMMENT, andupdate; malformed or incomplete acknowledgment rejects before GraphQL mutation.PullRequestService.managePrReview()approval pre-flightAPPROVE, fetch live PR head + review state and reject when outstandingCHANGES_REQUESTEDreviews apply to the current head unless every RC reviewer is acknowledged.@param/ method summarymanagePrReview()structured error pattern; template validator precedentPR_REVIEW_STATE_VALIDATION_FAILEDresponse naming RC reviewer(s), current head SHA, and the §9.1 Reviewer-Yield override path.addressed-by-<sha>that does not match current head is rejected.state: COMMENTwith an approval-status body over live RC returns a warning but does not block.Acceptance Criteria
manage_pr_review {state: APPROVED}on a PR with livereviewDecision === CHANGES_REQUESTEDis rejected unlessacknowledgedRequestChangesnames each outstanding RC reviewer + disposition.COMMENT-state review carrying an "Approve"/"Status: Approve" prose verdict over a live RC emits a warning - the softer version of the same rubber-stamp (my #14527 case wasCOMMENT-state, so the state-only gate would not have caught it).Substrate Accretion Defense
Net-adds one pre-flight branch to the existing validator (no always-loaded prose -> zero per-turn token cost). Decay-mitigation: forecloses the highest-trust-cost review failure class; retire only if the
manage_pr_reviewreview model changes.Related
Origin: this session's #14527 rubber-stamp; operator-flagged friction->gold.