diff --git a/migrations/0023_gate_quality_modes.sql b/migrations/0023_gate_quality_modes.sql index 98b2876caf..a7b8bb1ad1 100644 --- a/migrations/0023_gate_quality_modes.sql +++ b/migrations/0023_gate_quality_modes.sql @@ -1,4 +1,4 @@ -ALTER TABLE repository_settings ADD COLUMN linked_issue_gate_mode TEXT NOT NULL DEFAULT 'advisory'; -ALTER TABLE repository_settings ADD COLUMN duplicate_pr_gate_mode TEXT NOT NULL DEFAULT 'advisory'; +ALTER TABLE repository_settings ADD COLUMN linked_issue_gate_mode TEXT NOT NULL DEFAULT 'block'; +ALTER TABLE repository_settings ADD COLUMN duplicate_pr_gate_mode TEXT NOT NULL DEFAULT 'block'; ALTER TABLE repository_settings ADD COLUMN quality_gate_mode TEXT NOT NULL DEFAULT 'advisory'; ALTER TABLE repository_settings ADD COLUMN quality_gate_min_score INTEGER; diff --git a/migrations/0025_restore_gate_blocking_modes.sql b/migrations/0025_restore_gate_blocking_modes.sql new file mode 100644 index 0000000000..5c5ea0a073 --- /dev/null +++ b/migrations/0025_restore_gate_blocking_modes.sql @@ -0,0 +1,5 @@ +UPDATE repository_settings +SET + linked_issue_gate_mode = CASE WHEN linked_issue_gate_mode = 'advisory' THEN 'block' ELSE linked_issue_gate_mode END, + duplicate_pr_gate_mode = CASE WHEN duplicate_pr_gate_mode = 'advisory' THEN 'block' ELSE duplicate_pr_gate_mode END +WHERE gate_check_mode = 'enabled'; diff --git a/src/api/routes.ts b/src/api/routes.ts index 311b2aa6c1..9c7b2e0726 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -445,8 +445,8 @@ const repositorySettingsSchema = z.object({ checkRunMode: z.enum(["off", "enabled"]).default("off"), checkRunDetailLevel: z.enum(["minimal", "standard", "deep"]).default("standard"), gateCheckMode: z.enum(["off", "enabled"]).default("off"), - linkedIssueGateMode: z.enum(["off", "advisory", "block"]).default("advisory"), - duplicatePrGateMode: z.enum(["off", "advisory", "block"]).default("advisory"), + linkedIssueGateMode: z.enum(["off", "advisory", "block"]).default("block"), + duplicatePrGateMode: z.enum(["off", "advisory", "block"]).default("block"), qualityGateMode: z.enum(["off", "advisory", "block"]).default("advisory"), qualityGateMinScore: z.number().int().min(0).max(100).nullable().optional(), autoLabelEnabled: z.boolean().default(true), diff --git a/src/db/repositories.ts b/src/db/repositories.ts index 74c6bdba80..bfe3dcd735 100644 --- a/src/db/repositories.ts +++ b/src/db/repositories.ts @@ -386,8 +386,8 @@ export async function getRepositorySettings(env: Env, fullName: string): Promise checkRunMode: "off", checkRunDetailLevel: "minimal", gateCheckMode: "off", - linkedIssueGateMode: "advisory", - duplicatePrGateMode: "advisory", + linkedIssueGateMode: "block", + duplicatePrGateMode: "block", qualityGateMode: "advisory", qualityGateMinScore: null, autoLabelEnabled: true, @@ -436,8 +436,8 @@ export async function upsertRepositorySettings(env: Env, settings: Partial { output: { title: "Gittensory Gate is evaluating" }, }); expect(capturedBody).not.toHaveProperty("conclusion"); - expect(capturedBody.output?.text).toContain("advisory-first"); + expect(capturedBody.output?.text).toContain("preserves legacy linked-issue and duplicate-PR blockers"); }); it("finalizes a known pending Gate check by id without listing check runs first", async () => { diff --git a/test/unit/queue.test.ts b/test/unit/queue.test.ts index 28d51b71cb..50dbfe8fcf 100644 --- a/test/unit/queue.test.ts +++ b/test/unit/queue.test.ts @@ -690,7 +690,7 @@ describe("queue processors", () => { ); }); - it("publishes an opt-in gate check without requiring comment output", async () => { + it("publishes an opt-in gate check without requiring comment output while preserving linked-issue blockers", async () => { const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); await persistRegistrySnapshot( env, @@ -728,7 +728,7 @@ describe("queue processors", () => { } if (url.includes("/check-runs/900") && (init?.method ?? "GET") === "PATCH") { const body = JSON.parse(String(init?.body ?? "{}")) as { name?: string; status?: string; conclusion?: string; output?: { title?: string } }; - expect(body).toMatchObject({ name: "Gittensory Gate", status: "completed", conclusion: "success", output: { title: "Gittensory Gate passed" } }); + expect(body).toMatchObject({ name: "Gittensory Gate", status: "completed", conclusion: "failure", output: { title: "Gittensory Gate is blocking merge" } }); calls.gateChecks += 1; return Response.json({ id: 900 }); } diff --git a/test/unit/rules.test.ts b/test/unit/rules.test.ts index a1ec630333..b4b5cd3da7 100644 --- a/test/unit/rules.test.ts +++ b/test/unit/rules.test.ts @@ -181,7 +181,7 @@ describe("advisory rules", () => { expect(output.text).not.toMatch(/reward|wallet|trust score|score estimate/i); }); - it("keeps missing linked issue and duplicate PR findings advisory unless their gate modes block", () => { + it("keeps legacy Gate blockers by default while honoring explicit advisory or off modes", () => { const pr: PullRequestRecord = { repoFullName: repo.fullName, number: 21, @@ -195,7 +195,7 @@ describe("advisory rules", () => { }; const missingIssueAdvisory = buildPullRequestAdvisory(repo, pr, { requireLinkedIssue: true }); - expect(evaluateGateCheck(missingIssueAdvisory).conclusion).toBe("success"); + expect(evaluateGateCheck(missingIssueAdvisory).conclusion).toBe("failure"); expect(evaluateGateCheck(missingIssueAdvisory, { linkedIssueGateMode: "advisory" }).conclusion).toBe("success"); expect(evaluateGateCheck(missingIssueAdvisory, { linkedIssueGateMode: "off" }).conclusion).toBe("success"); expect(evaluateGateCheck(missingIssueAdvisory, { linkedIssueGateMode: "block" }).conclusion).toBe("failure"); @@ -206,6 +206,7 @@ describe("advisory rules", () => { }); expect(duplicateAdvisory.findings.map((finding) => finding.code)).toContain("duplicate_pr_risk"); + expect(evaluateGateCheck(duplicateAdvisory).conclusion).toBe("failure"); expect(evaluateGateCheck(duplicateAdvisory, { duplicatePrGateMode: "advisory" }).conclusion).toBe("success"); expect(evaluateGateCheck(duplicateAdvisory, { duplicatePrGateMode: "off" }).conclusion).toBe("success"); expect(evaluateGateCheck(duplicateAdvisory, { duplicatePrGateMode: "block" }).conclusion).toBe("failure");