From 0d401ab24a762ee16e0ace385eb75b323036c7a0 Mon Sep 17 00:00:00 2001 From: rsnetworkinginc Date: Wed, 22 Jul 2026 17:52:12 +0300 Subject: [PATCH] fix(queue): terminalize active-review tracking in synchronize-amendment close guard (#8015) --- src/queue/review-evasion.ts | 2 ++ test/unit/queue-lifecycle-guards.test.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/queue/review-evasion.ts b/src/queue/review-evasion.ts index 20f16ef6c2..c65aa90568 100644 --- a/src/queue/review-evasion.ts +++ b/src/queue/review-evasion.ts @@ -1365,4 +1365,6 @@ async function closeSynchronizeAmendmentIfPolicyEnabled( /* v8 ignore next -- fail-safe: an audit write failure never blocks the handler. */ () => undefined, ); + /* v8 ignore next -- best-effort: the guarded CAS update never rejects against a healthy D1, and a cleanup failure here must never block the webhook. */ + await terminalizeActiveReviewTracking(env, repoFullName, pr.number, { onlyIfHeadSha: pr.headSha }).catch(() => undefined); } diff --git a/test/unit/queue-lifecycle-guards.test.ts b/test/unit/queue-lifecycle-guards.test.ts index 5804f13e5e..351f7872d9 100644 --- a/test/unit/queue-lifecycle-guards.test.ts +++ b/test/unit/queue-lifecycle-guards.test.ts @@ -3922,6 +3922,24 @@ describe("review-evasion protection (#review-evasion-protection)", () => { expect(audit?.detail).toContain("contributor"); }); + it("terminalizes the active-review tracking row on enforcement close -- leaves no dangling row for the PR's reviewed headSha, matching the four sibling guards", async () => { + const calls: Array<{ url: string; method: string }> = []; + stubEvasionFetch(calls); + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: generateRsaPrivateKeyPem(), GITHUB_APP_SLUG: "loopover-orb" }); + await setupEvasionRepo(env, { reviewEvasionProtection: "off", synchronizeClosePolicy: "close" }); + // A review was actively tracked on the PR's head before the author pushed the amending commit; the + // synchronize-amendment close must mark that row terminal, exactly as its four siblings do. + await repositoriesModule.startActiveReviewTracking(env, { repoFullName: "JSONbored/gittensory", pullNumber: 42, headSha: "def456", authorLogin: "contributor", deliveryId: "review-start-sync" }); + expect(await repositoriesModule.hasActiveReviewForHeadSha(env, "JSONbored/gittensory", 42, "def456")).toBe(true); + + await processJob(env, { type: "github-webhook", deliveryId: "sync-policy-terminalize", eventName: "pull_request", payload: synchronizePayload("contributor") }); + + expect(calls.some((c) => c.method === "PATCH" && c.url.endsWith("/pulls/42"))).toBe(true); + const audit = await env.DB.prepare("select outcome from audit_events where event_type = ?").bind("github_app.synchronize_amend_closed").first<{ outcome: string }>(); + expect(audit?.outcome).toBe("completed"); + expect(await repositoriesModule.hasActiveReviewForHeadSha(env, "JSONbored/gittensory", 42, "def456")).toBe(false); // terminalized + }); + it("does NOT record a moderation strike -- this is a blanket policy against an ordinary push, not a detected abuse pattern", async () => { const calls: Array<{ url: string; method: string }> = []; stubEvasionFetch(calls);