From eb809401683fcd6df41d27578cf9fead2befb80f Mon Sep 17 00:00:00 2001 From: bitfathers94 <237535319+bitfathers94@users.noreply.github.com> Date: Fri, 31 Jul 2026 06:52:39 +0000 Subject: [PATCH] fix(review): preserve generic gate warnings on the surface-close hold path applySurfaceGate's fourth merge path returned the surface evaluation bare when the generic gate held with no blockers, dropping every warning-carried hold reason (size, guardrail, secret-scan) instead of unioning them like the three sibling paths already do. --- src/review/content-lane-wire.ts | 2 +- test/unit/content-lane-wire.test.ts | 39 +++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/review/content-lane-wire.ts b/src/review/content-lane-wire.ts index 15c62fbe04..8f0c3d99f3 100644 --- a/src/review/content-lane-wire.ts +++ b/src/review/content-lane-wire.ts @@ -139,7 +139,7 @@ export function applySurfaceGate( if (generic.blockers.length === 0 && generic.conclusion === "success") return surface; // generic was clean → surface stands if (generic.blockers.length === 0) { if (surface.conclusion === "success") return generic; - return surface; + return { ...surface, warnings: [...generic.warnings, ...surface.warnings] }; } // #3907: opt-in escape hatch from guard #3 below. Default (null/undefined/"advisory") preserves today's // behavior byte-identically. "gate" skips the override entirely, so an AI-judgment-only failure falls diff --git a/test/unit/content-lane-wire.test.ts b/test/unit/content-lane-wire.test.ts index c4b4faf775..b102aca93d 100644 --- a/test/unit/content-lane-wire.test.ts +++ b/test/unit/content-lane-wire.test.ts @@ -92,14 +92,39 @@ describe("applySurfaceGate", () => { expect(applySurfaceGate(genericHold, surfaceMerge)).toBe(genericHold); }); - it("lets a surface hard failure override a generic warning-only hold", () => { - const genericHold = gate({ - conclusion: "neutral", - blockers: [], - warnings: [{ code: "oversized_pr", title: "Large change", severity: "warning", detail: "large" }], - }); + it("lets a surface hard failure override a generic warning-only hold, preserving the generic gate's hold warnings", () => { + const oversizedPrFinding: AdvisoryFinding = { code: "oversized_pr", title: "Large change", severity: "warning", detail: "large" }; + const genericHold = gate({ conclusion: "neutral", blockers: [], warnings: [oversizedPrFinding] }); + + const out = applySurfaceGate(genericHold, surfaceClose); + expect(out?.conclusion).toBe("failure"); + expect(out?.blockers).toEqual(surfaceClose.blockers); + expect(out?.warnings).toEqual([oversizedPrFinding]); + }); + it("the unchanged sub-case: a success generic with warnings still returns the surface verbatim", () => { + const genericSuccess = gate({ conclusion: "success", blockers: [], warnings: [{ code: "quality_readiness_low", title: "Readiness is low", severity: "warning", detail: "" }] }); + expect(applySurfaceGate(genericSuccess, surfaceClose)).toBe(surfaceClose); + }); + it("the unchanged sub-case: a holding generic against a success surface still returns the generic verbatim", () => { + const oversizedPrFinding: AdvisoryFinding = { code: "oversized_pr", title: "Large change", severity: "warning", detail: "large" }; + const genericHold = gate({ conclusion: "neutral", blockers: [], warnings: [oversizedPrFinding] }); + const surfaceMerge = gate({ conclusion: "success", title: "Surface", summary: "valid entry" }); + expect(applySurfaceGate(genericHold, surfaceMerge)).toBe(genericHold); + }); + it("REGRESSION (#10011): preserves the generic gate's hold warnings when a non-success surface verdict overrides it", () => { + const oversizedPrFinding: AdvisoryFinding = { code: "oversized_pr", title: "Large change", severity: "warning", detail: "large" }; + const genericHold = gate({ conclusion: "neutral", blockers: [], warnings: [oversizedPrFinding] }); + + const manual = surfaceVerdictToGate({ verdict: "manual", summary: "auth declared" }).evaluation; + const outManual = applySurfaceGate(genericHold, manual); + expect(outManual?.conclusion).toBe("neutral"); + expect(outManual?.warnings.map((w) => w.code)).toEqual(["oversized_pr", "surface_lane_manual"]); - expect(applySurfaceGate(genericHold, surfaceClose)).toBe(surfaceClose); + const close = surfaceVerdictToGate({ verdict: "close", summary: "bad entry" }).evaluation; + const outClose = applySurfaceGate(genericHold, close); + expect(outClose?.conclusion).toBe("failure"); + expect(outClose?.blockers.map((b) => b.code)).toEqual(["surface_lane_reject"]); + expect(outClose?.warnings.map((w) => w.code)).toEqual(["oversized_pr"]); }); it("PRESERVES a generic hard blocker over a surface merge (a committed secret can never merge)", () => { const secret: AdvisoryFinding = { code: "secret_leak", title: "Secret", severity: "critical", detail: "leaked key" };