This is an automated email from the ASF dual-hosted git repository.

400Ping pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/mahout.git


The following commit(s) were added to refs/heads/main by this push:
     new e23c955cf [CI] Require two committer approvals before a PR can be 
merged (#1530)
e23c955cf is described below

commit e23c955cfca32a5c69bfd394cedf2eb1ddff7dc5
Author: Jie-Kai Chang <[email protected]>
AuthorDate: Wed Oct 14 14:18:15 2026 +0800

    [CI] Require two committer approvals before a PR can be merged (#1530)
    
    Signed-off-by: 400Ping <[email protected]>
---
 .asf.yaml                                         |  2 +-
 .github/scripts/check_pr_approval.py              | 44 +++++++++++++++++------
 .github/workflows/pr-approval-check.yml           | 13 ++++---
 docs/community/pr-policy-and-review-guidelines.md |  9 +++--
 4 files changed, 46 insertions(+), 22 deletions(-)

diff --git a/.asf.yaml b/.asf.yaml
index c1ca308e7..3212511b5 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -61,7 +61,7 @@ github:
   # protected_branches entry for main.
   #
   # PR policy (docs/community/pr-policy-and-review-guidelines.md): a PR needs
-  # at least one approving review from a committer before it can be merged,
+  # at least two approving reviews from committers before it can be merged,
   # unless it was opened by a PMC member (.github/pmc-members.txt).
   # GitHub's own required_approving_review_count (as used by Airflow) cannot
   # exempt PMC members, so the rule is enforced through the required status
diff --git a/.github/scripts/check_pr_approval.py 
b/.github/scripts/check_pr_approval.py
index d3daa1fcb..53cb7e447 100644
--- a/.github/scripts/check_pr_approval.py
+++ b/.github/scripts/check_pr_approval.py
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-"""Check that a pull request has at least one approving review from a 
committer.
+"""Check that a pull request has enough approving reviews from committers.
 
 This backs the ``Committer approval`` required status check on ``main``
 (see ``.github/workflows/pr-approval-check.yml`` and ``.asf.yaml``).
@@ -22,16 +22,17 @@ This backs the ``Committer approval`` required status check 
on ``main``
 Rules:
 
 * A pull request opened by a PMC member (``.github/pmc-members.txt``) is 
exempt.
-* Otherwise, at least one reviewer whose *latest* review is ``APPROVED`` must 
have
-  write access to the repository. Per ASF policy every committer has write 
access,
-  so "has write access" is used as the definition of "is a committer".
+* Otherwise, at least ``REQUIRED_APPROVALS`` (default 2) distinct reviewers 
whose
+  *latest* review is ``APPROVED`` must have write access to the repository. Per
+  ASF policy every committer has write access, so "has write access" is used as
+  the definition of "is a committer".
 * Following GitHub's own semantics, a ``COMMENTED`` review does not revoke an
   earlier approval, while ``CHANGES_REQUESTED`` or a dismissal does.
 
 Only the Python standard library is used so the script runs in CI and locally:
 
     GITHUB_TOKEN=$(gh auth token) python .github/scripts/check_pr_approval.py 
\\
-        --repo apache/mahout --pr 1234
+        --repo apache/mahout --pr 1234 [--required-approvals 2]
 
 Exit status is 0 when the PR may be merged and 1 otherwise.
 """
@@ -50,6 +51,8 @@ from typing import Any
 
 API_URL = os.environ.get("GITHUB_API_URL", "https://api.github.com";)
 DEFAULT_PMC_FILE = Path(__file__).resolve().parents[1] / "pmc-members.txt"
+# Committer approvals a non-PMC PR needs (REQUIRED_APPROVALS in the workflow).
+DEFAULT_REQUIRED_APPROVALS = 2
 # Review states that change a reviewer's standing. COMMENTED is deliberately
 # excluded: on GitHub a comment-only review leaves a previous approval in 
place.
 DECISIVE_STATES = {"APPROVED", "CHANGES_REQUESTED", "DISMISSED"}
@@ -131,7 +134,11 @@ def has_write_access(token: str, repo: str, login: str) -> 
bool:
 
 
 def check(
-    token: str, repo: str, pr_number: int, pmc_members: set[str]
+    token: str,
+    repo: str,
+    pr_number: int,
+    pmc_members: set[str],
+    required_approvals: int = DEFAULT_REQUIRED_APPROVALS,
 ) -> tuple[bool, list[str]]:
     """Evaluate the rule. Returns ``(ok, log_lines)``."""
     lines: list[str] = []
@@ -168,9 +175,12 @@ def check(
                 f"Approved by @{login} (not a committer; does not satisfy the 
rule)."
             )
 
-    if committer_approvers:
+    if len(committer_approvers) >= required_approvals:
         return True, lines
-    lines.append("No approving review from a committer.")
+    lines.append(
+        f"{len(committer_approvers)} of {required_approvals} required 
committer "
+        "approvals."
+    )
     return False, lines
 
 
@@ -195,6 +205,13 @@ def main(argv: list[str] | None = None) -> int:
         default=DEFAULT_PMC_FILE,
         help=f"PMC member list (default: {DEFAULT_PMC_FILE})",
     )
+    parser.add_argument(
+        "--required-approvals",
+        type=int,
+        default=int(os.environ.get("REQUIRED_APPROVALS", 
DEFAULT_REQUIRED_APPROVALS)),
+        help="committer approvals a non-PMC PR needs "
+        f"(default: $REQUIRED_APPROVALS or {DEFAULT_REQUIRED_APPROVALS})",
+    )
     args = parser.parse_args(argv)
 
     token = os.environ.get("GITHUB_TOKEN")
@@ -202,6 +219,8 @@ def main(argv: list[str] | None = None) -> int:
         parser.error(
             "GITHUB_TOKEN, --repo/$GITHUB_REPOSITORY and --pr/$PR_NUMBER are 
required"
         )
+    if args.required_approvals < 1:
+        parser.error("--required-approvals must be at least 1")
 
     pmc_members = load_pmc_members(args.pmc_file)
     if not pmc_members:
@@ -211,15 +230,18 @@ def main(argv: list[str] | None = None) -> int:
         )
 
     try:
-        ok, lines = check(token, args.repo, int(args.pr), pmc_members)
+        ok, lines = check(
+            token, args.repo, int(args.pr), pmc_members, 
args.required_approvals
+        )
     except GitHubError as exc:
         print(f"::error::{exc}")
         return 1
 
+    n = args.required_approvals
     if ok:
-        verdict = "PASS: at least one committer approval (or PMC author)."
+        verdict = f"PASS: at least {n} committer approval(s) (or PMC author)."
     else:
-        verdict = "FAIL: needs at least one approving review from a committer."
+        verdict = f"FAIL: needs at least {n} approving review(s) from 
committers."
     lines.append(verdict)
     print("\n".join(lines))
 
diff --git a/.github/workflows/pr-approval-check.yml 
b/.github/workflows/pr-approval-check.yml
index 819ab5452..011758aa6 100644
--- a/.github/workflows/pr-approval-check.yml
+++ b/.github/workflows/pr-approval-check.yml
@@ -15,11 +15,12 @@
 # limitations under the License.
 #
 # Enforces the PR policy (docs/community/pr-policy-and-review-guidelines.md):
-# a pull request needs at least one approving review from a committer before
-# it can be merged, unless it was opened by a PMC member
+# a pull request needs at least REQUIRED_APPROVALS (2) approving reviews from
+# committers before it can be merged, unless it was opened by a PMC member
 # (.github/pmc-members.txt). The "Committer approval" job is a required status
-# check on main via .asf.yaml, mirroring Airflow's
-# required_approving_review_count: 1 but with the PMC exemption.
+# check on main via .asf.yaml, mirroring GitHub's
+# required_approving_review_count: 2 but with the PMC exemption.
+# PMC members can bypass it in an emergency (main ruleset in .asf.yaml).
 #
 name: PR Approval Check
 
@@ -59,8 +60,10 @@ jobs:
             .github/scripts/check_pr_approval.py
           sparse-checkout-cone-mode: false
 
-      - name: Check for a committer approval
+      - name: Check for committer approvals
         env:
           GITHUB_TOKEN: ${{ github.token }}
           PR_NUMBER: ${{ github.event.pull_request.number }}
+          # Committer approvals a non-PMC PR needs. Keep the docs in sync.
+          REQUIRED_APPROVALS: "2"
         run: python3 .github/scripts/check_pr_approval.py
diff --git a/docs/community/pr-policy-and-review-guidelines.md 
b/docs/community/pr-policy-and-review-guidelines.md
index fc8ab65f4..f0db6a90f 100644
--- a/docs/community/pr-policy-and-review-guidelines.md
+++ b/docs/community/pr-policy-and-review-guidelines.md
@@ -74,7 +74,7 @@ For non-trivial code contributions, contributors are expected 
to have an ASF ICL
 When deciding whether to merge, committers should verify all of the following:
 
 - Required CI checks are green (enforced automatically, see below).
-- At least one committer approves (enforced automatically, see below).
+- At least two committers approve (enforced automatically, see below).
 - All blocking comments are resolved.
 
 #### Auto-merge and CI
@@ -115,13 +115,12 @@ decision.
 
 The `Committer approval` status check 
([`pr-approval-check.yml`](https://github.com/apache/mahout/blob/main/.github/workflows/pr-approval-check.yml))
 is required on `main` and re-runs whenever a review is submitted, edited, or 
dismissed:
 
-- The PR must have at least one reviewer whose latest review is an approval 
and who has write access to the repository (i.e. a committer). Approvals from 
non-committers are welcome but do not satisfy the check.
+- The PR must have at least **two** distinct reviewers whose latest review is 
an approval and who have write access to the repository (i.e. committers). 
Approvals from non-committers are welcome but do not count. The number is set 
by `REQUIRED_APPROVALS` in the workflow.
 - Following GitHub semantics, a comment-only review keeps an earlier approval 
in place; "Request changes" or dismissing the review removes it.
 - PRs opened by PMC members are exempt. The list of PMC GitHub accounts lives 
in 
[`.github/pmc-members.txt`](https://github.com/apache/mahout/blob/main/.github/pmc-members.txt)
 and must be updated when the PMC roster changes.
+- This applies to auto-merge and manual merges alike; PMC members can bypass 
it in an emergency (see above).
 
-The two-approval expectation for high-impact changes below is not enforced by 
tooling and remains a committer responsibility.
-
-The following changes should receive at least two committer approvals and 
prior public discussion (issue or `dev@` thread):
+The following changes should additionally have prior public discussion (issue 
or `dev@` thread):
 
 - API or behavior-breaking changes.
 - Architecture or major dependency changes.

Reply via email to