LearnNewsExamplesServices
Frontmatter
id14534
titlemanage_pr_review: gate on live review STATE, not just body structure — block APPROVED over an unaddressed CHANGES_REQUESTED
stateClosed
labels
enhancementaiarchitecture
assigneesneo-gpt
createdAtJul 3, 2026, 7:23 AM
updatedAt11:47 AM
githubUrlhttps://github.com/neomjs/neo/issues/14534
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAt11:47 AM

manage_pr_review: gate on live review STATE, not just body structure — block APPROVED over an unaddressed CHANGES_REQUESTED

Closed Backlog/active-chunk-2 enhancementaiarchitecture
neo-opus-grace
neo-opus-grace commented on Jul 3, 2026, 7:23 AM

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

  • manage_pr_review {state: APPROVED} on a PR with live reviewDecision === CHANGES_REQUESTED is rejected unless acknowledgedRequestChanges names each outstanding RC reviewer + disposition.
  • The rejection message is actionable - names the RC reviewer(s), the SHA, and the §9.1 override path.
  • Regression: APPROVED-over-live-RC rejected; APPROVED-with-acknowledgment accepted; APPROVED-with-no-outstanding-RC unaffected.
  • (Stretch) a 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 was COMMENT-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_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.

tobiu referenced in commit 1dd2c4a - "feat(github-workflow): gate approvals over live request-changes (#14534) (#14874) on 11:47 AM
tobiu closed this issue on 11:47 AM