From 2ecbd4c9bbbdb0130591cd63228c73bc3e37b398 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 20:42:58 +0900 Subject: [PATCH] fix(governance): restack production rule ambiguity --- .../lib/production-environment-governance.mjs | 31 +++++++++++++------ .../production-environment-governance.test.ts | 20 ++++++++++++ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/scripts/lib/production-environment-governance.mjs b/scripts/lib/production-environment-governance.mjs index 985d4568e..e532dc1e6 100644 --- a/scripts/lib/production-environment-governance.mjs +++ b/scripts/lib/production-environment-governance.mjs @@ -54,8 +54,9 @@ export function evaluateProductionEnvironment(environment) { const rules = Array.isArray(environment.protection_rules) ? environment.protection_rules.filter((rule) => rule && typeof rule === "object" && !Array.isArray(rule)) : []; - const reviewerRule = rules.find((rule) => rule.type === "required_reviewers"); - const branchRule = rules.find((rule) => rule.type === "branch_policy"); + const reviewerRules = rules.filter((rule) => rule.type === "required_reviewers"); + const branchRules = rules.filter((rule) => rule.type === "branch_policy"); + const reviewerRule = reviewerRules[0]; const reviewers = Array.isArray(reviewerRule?.reviewers) ? reviewerRule.reviewers.map(normalizeReviewer).filter(Boolean) : []; @@ -68,9 +69,9 @@ export function evaluateProductionEnvironment(environment) { `observed=${bounded(environment.name) || "missing"}`, ), check( - "required reviewers rule exists", - Boolean(reviewerRule), - `rule_count=${rules.filter((rule) => rule.type === "required_reviewers").length}`, + "required reviewers rule exists exactly once", + reviewerRules.length === 1, + `rule_count=${reviewerRules.length}`, ), check( "required reviewers are concrete identities", @@ -83,9 +84,9 @@ export function evaluateProductionEnvironment(environment) { `prevent_self_review=${String(reviewerRule?.prevent_self_review ?? "missing")}`, ), check( - "branch policy rule exists", - Boolean(branchRule), - `rule_count=${rules.filter((rule) => rule.type === "branch_policy").length}`, + "branch policy rule exists exactly once", + branchRules.length === 1, + `rule_count=${branchRules.length}`, ), check( "only protected branches may deploy", @@ -106,11 +107,16 @@ export function evaluateProductionEnvironment(environment) { `GitHub environment must be production, observed ${bounded(environment.name) || "missing"}.`, )); } - if (!reviewerRule) { + if (reviewerRules.length === 0) { failures.push(failure( "required_reviewers_rule_missing", "Production must define a required_reviewers protection rule.", )); + } else if (reviewerRules.length > 1) { + failures.push(failure( + "required_reviewers_rule_ambiguous", + `Production must expose exactly one required_reviewers protection rule, observed ${reviewerRules.length}.`, + )); } if (reviewers.length === 0) { failures.push(failure( @@ -124,11 +130,16 @@ export function evaluateProductionEnvironment(environment) { "Production deployment initiators must be prevented from approving their own deployment.", )); } - if (!branchRule) { + if (branchRules.length === 0) { failures.push(failure( "branch_policy_rule_missing", "Production must define a branch_policy protection rule.", )); + } else if (branchRules.length > 1) { + failures.push(failure( + "branch_policy_rule_ambiguous", + `Production must expose exactly one branch_policy protection rule, observed ${branchRules.length}.`, + )); } if (branchPolicy?.protected_branches !== true) { failures.push(failure( diff --git a/test/production-environment-governance.test.ts b/test/production-environment-governance.test.ts index 60f608e84..c1239b5f2 100644 --- a/test/production-environment-governance.test.ts +++ b/test/production-environment-governance.test.ts @@ -79,6 +79,26 @@ describe("production environment governance", () => { expect(failureCodes(result)).toContain(expectedCode); }); + it("fails closed when GitHub returns more than one required-reviewers rule", () => { + const value = protectedEnvironment(); + value.protection_rules.push({ ...value.protection_rules[0], id: 102 }); + + const result = evaluateProductionEnvironment(value); + + expect(result.status).toBe("FAIL"); + expect(failureCodes(result)).toContain("required_reviewers_rule_ambiguous"); + }); + + it("fails closed when GitHub returns more than one branch-policy rule", () => { + const value = protectedEnvironment(); + value.protection_rules.push({ ...value.protection_rules[1], id: 103 }); + + const result = evaluateProductionEnvironment(value); + + expect(result.status).toBe("FAIL"); + expect(failureCodes(result)).toContain("branch_policy_rule_ambiguous"); + }); + it("fails closed for malformed API data", () => { const result = evaluateProductionEnvironment(null);