feat(github): gate comment-shaped Ally review findings (BLO-21907) - #1049
feat(github): gate comment-shaped Ally review findings (BLO-21907)#1049allyblockcast[bot] wants to merge 3 commits into
Conversation
1 similar comment
Ally cannot file a formal pull_request_review on a self-authored PR, so it falls back to a plain PR comment. That comment opens no review thread and is invisible to reviewDecision/mergeStateStatus, so an unaddressed "before merge" finding on that surface had zero effect on merge eligibility (#1022: queued to merge ~65min after an unaddressed Important finding). Adds evaluateCommentReviewGate, keyed on "newest Ally consolidated-review comment since the PR's last push" so an addressed finding doesn't block forever, plus a live GitHub wiring (runPrCommentReviewGateCheck) that posts a required-check commit status. Fully opt-in via PAPERCLIP_PR_COMMENT_REVIEW_GATE_STATUS_CONTEXT (empty by default, same shape as the existing prReviewGateStatusContext/BLO-17456) — inert until an operator sets it and marks it required in branch protection, which agent tokens cannot do (403 reading protection). Extracted the shared Ally-detection regexes (hasAllyConsolidatedReviewHeading, hasActionablePrReviewFeedback) out of the webhook route into ally-review-detection.ts so the new gate service reuses the hardened patterns instead of duplicating them, and so the two modules don't import each other. Co-Authored-By: Paperclip <noreply@paperclip.ing>
dbc067d to
8f8dcda
Compare
kkroo
left a comment
There was a problem hiding this comment.
Reviewed #1049 after adding the missing author trust boundary: comment-shaped review gate now trusts only the configured Ally GitHub App identity, with spoof regression coverage. Verified focused gate/webhook/github-app-auth tests, server typecheck, Helm smoke tests, and diff check locally.
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Critical Issues (1)
Important Issues (2)
Strengths
Recommended Action
This PR is authored by |
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Prior Findings Dispositioned (3)
Critical Issues (1)
Important Issues (3)
Strengths
Recommended Action
This PR is authored by |
Thinking Path
Linked Issues or Issue Description
Fixes: BLO-21907
Refs: BLO-22029 (near-duplicate filed independently before BLO-21907 was found in search; being closed as duplicate)
What Changed
server/src/services/ally-review-detection.ts(new): extractedhasAllyConsolidatedReviewHeading/hasActionablePrReviewFeedbackout ofgithub-webhook.tsinto a standalone module with no dependents of its own, so the new gate service and the webhook route both reuse the same hardened detection regexes (each carries a one-off bug history — BLO-19067, BLO-15942, BLO-18865, feat(chart): stamp the approval plan marker on the API pod template (BLO-20733) #973, [codex] honor configured Ally concurrency 15 #937, fix(heartbeat): crash-time run marking + convergent recovery (BLO-20822) #952 — that a second copy would risk silently diverging from) without creating a route↔service import cycle.server/src/services/pr-comment-review-gate.ts(new):evaluateCommentReviewGate— pure predicate over a PR's comments + last-push timestamp. Fails only when the most recent Ally-consolidated-review comment since the last push carries an unresolved Critical/Important finding; a finding that predates the push, or is superseded by a later clean pass, does not block.runPrCommentReviewGateCheck— live wiring that fetches comments + head-commit timestamp from GitHub and posts the verdict as a commit status.server/src/services/github-app-auth.ts: three new fetch primitives (githubListIssueCommentsWithTimestamps,githubGetCommitCommittedAt,githubFetchPrHeadSha) alongside the existinggithubPostCommitStatusDetailed.server/src/routes/github-webhook.ts: two best-effort, fire-and-forget trigger points —issue_comment.created(any Ally-consolidated-review-shaped comment, blocking or clean) andpull_requestopened/synchronize/reopened (so a fresh head gets an eager status instead of reading "Expected — waiting for status" forever). Both read the raw payload directly, independent of the Paperclip-identifier-matching/actionable-feedback gating the existing wake logic applies for its own, unrelated purpose. Never affects the wake/response path — errors are caught and logged, not propagated.server/src/config.ts+ Helm chart (values.yaml,deployment-api.yaml,statefulset.yaml): newprCommentReviewGateStatusContext/PAPERCLIP_PR_COMMENT_REVIEW_GATE_STATUS_CONTEXT, empty by default — same opt-in shape as the existingprReviewGateStatusContext(BLO-17456). Fully inert until an operator sets it.Verification
pnpm exec vitest run server/src/__tests__/pr-comment-review-gate.test.ts— 9 tests, replaying the exact fix(dispatch): bound worst-case queue wait for every priority tier (BLO-21792) #1022 sequence (rejects it), an addressed-then-re-reviewed sequence (accepts it), a findings-predate-push case (does not block), and a formal-review-only PR (unaffected — the predicate never readspull_request_reviewobjects at all).pnpm exec vitest run server/src/__tests__/pr-comment-review-gate-check.test.ts— 7 tests covering the live wiring: inert with no context configured, correct status/description posted for blocking vs. clean, head-SHA resolution for theissue_commenttrigger (no head SHA in that webhook payload), and fetch/post failure reporting.pnpm exec vitest run server/src/__tests__/github-webhook.test.ts— full existing suite (109 tests) still green; the new trigger points don't change any existing wake behavior.pnpm exec tsc --noEmitinserver/— clean.node --test deploy/helm/paperclip/tests/*.test.mjs— 33/36 pass; the 3 failures are pre-existing (approval-plan-marker.test.mjs, requires a localrubybinary this sandbox doesn't have) and reproduce identically onmasterbefore this change.Manual checklist (cannot be completed by an agent — see below).
Risks
This touches the webhook route that drives every GitHub-originated Paperclip wake, so I scoped the new code to be provably inert by default:
void (async () => { try { ... } catch (err) { logger.warn(...) } })()— fire-and-forget, never awaited by the handler, so a failure or slowness in the new code cannot delay or fail the webhook response.runPrCommentReviewGateCheckreturns{ posted: false, reason: "not_configured" }and makes zero network calls whenPAPERCLIP_PR_COMMENT_REVIEW_GATE_STATUS_CONTEXTis unset — the default in every environment until an operator opts in.ally-review-detection.tsis a pure move (same regex source, same function bodies) — the full existinggithub-webhook.test.tssuite is the regression guard for that.Known scope boundary: the new triggers only fire when the PR carries a Paperclip identifier (branch/title/body), because they sit downstream of the same
context.identifiers.length === 0early-return the rest of the webhook route already uses. A PR with no Paperclip identifier reference won't get gated. In practice every agent-authored PR here carries one; a human/dependency PR without one would not. Flagging this rather than silently limiting coverage — happy to file a fast-follow if broader coverage is wanted.Not yet enforced: setting the config context alone does not block anything. Branch protection has to mark the new context required on
master, which is an org-admin action — agent tokens get403 Resource not accessible by integrationreading protection, so I cannot do this part myself.Model Used
Claude Sonnet 5 (
claude-sonnet-5[1m], 1M context), agentic coding session with repository inspection, live code tracing of the existing PR-review-gate-status infrastructure (BLO-17456) to reuse rather than reinvent, and test-driven verification.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue templateManual checklist for a human (per BLO-21907's acceptance criteria — agent tokens cannot do this)
githubApp.prCommentReviewGateStatusContext(e.g."review/ally-comment-gate") in the Blockcast Helm values for the environment(s) this should apply to, and roll the deployment.master's branch protection rules.Paperclip issue: https://paperclip.blockcast.net/BLO/issues/BLO-21907