This is an automated email from the ASF dual-hosted git repository.
mfordjody pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git
The following commit(s) were added to refs/heads/master by this push:
new d255365a Add maintainer approval status gate (#969)
d255365a is described below
commit d255365a826f692998dd4be1065a7f18dd0d74c3
Author: mfordjody <[email protected]>
AuthorDate: Mon Jul 20 02:43:37 2026 +0800
Add maintainer approval status gate (#969)
---
.asf.yaml | 29 ----------
.github/workflows/commands.yml | 37 +++++++++++-
.github/workflows/review-gate.yml | 117 ++++++++++++++++++++++++++++++++++++++
OWNERS | 4 +-
4 files changed, 153 insertions(+), 34 deletions(-)
diff --git a/.asf.yaml b/.asf.yaml
index 4833ba24..4b54410a 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -13,11 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-meta:
- # ASF currently gates repository ruleset reconciliation behind this rollout.
- environment: github_rulesets
- nextgen: true
-
notifications:
commits: [email protected]
issues: [email protected]
@@ -47,27 +42,3 @@ github:
contexts:
# This stable aggregate check fails unless every required CI job
passes.
- CI Required
- rulesets:
- # Stage the review policy on an isolated branch before targeting master.
- - name: "Solo Maintainer Review Gate"
- target: branch
- enforcement: active
- bypass_actors:
- - actor_id: 91593982
- actor_type: User
- bypass_mode: pull_request
- conditions:
- ref_name:
- include:
- - "refs/heads/policy/ruleset-sandbox"
- exclude: []
- rules:
- - type: deletion
- - type: non_fast_forward
- - type: pull_request
- parameters:
- dismiss_stale_reviews_on_push: true
- require_code_owner_review: true
- require_last_push_approval: false
- required_approving_review_count: 1
- required_review_thread_resolution: true
diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml
index 6e913d0f..a0cdfbe6 100644
--- a/.github/workflows/commands.yml
+++ b/.github/workflows/commands.yml
@@ -20,9 +20,9 @@
# /unassign [@user ...] remove assignees
# /lgtm (PR only, write access, not the PR author) label the
# PR with `lgtm`; if the commenter is an approver in
-# the root OWNERS file, request a squash merge (or
-# auto-merge while checks run). Repository rulesets
-# still enforce CI and required Code Owner review;
+# the root OWNERS file, record Maintainer Approval and
+# request a squash merge (or auto-merge while checks
+# run). Required status checks still enforce CI;
# non-approvers only apply the label
# /lgtm cancel remove the `lgtm` label and disable auto-merge
# /close close the issue/PR (author or write access)
@@ -49,6 +49,7 @@ permissions:
issues: write
pull-requests: write
actions: write
+ statuses: write
jobs:
command:
@@ -70,6 +71,36 @@ jobs:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
+ # Keep the required status tied to the built-in GitHub Actions App even
+ # when slash-command comments and merges use a custom bot identity.
+ - name: Record maintainer approval
+ if: github.event.issue.pull_request && github.actor == 'mfordjody'
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ github.token }}
+ script: |
+ const firstLine = (context.payload.comment.body ||
'').trim().split(/\r?\n/)[0].trim();
+ const match = firstLine.match(/^\/lgtm(?:\s+(cancel))?\s*$/i);
+ if (!match) return;
+
+ const {owner, repo} = context.repo;
+ const pullNumber = context.payload.issue.number;
+ const {data: pr} = await github.rest.pulls.get({owner, repo,
pull_number: pullNumber});
+ const authorIsMaintainer = pr.user.login.toLowerCase() ===
'mfordjody';
+ if (!match[1] && authorIsMaintainer) return;
+
+ await github.rest.repos.createCommitStatus({
+ owner,
+ repo,
+ sha: pr.head.sha,
+ state: match[1] && !authorIsMaintainer ? 'pending' : 'success',
+ context: 'Maintainer Approval',
+ description: match[1] && !authorIsMaintainer
+ ? 'Maintainer approval cancelled'
+ : 'Approved by @mfordjody via /lgtm',
+ target_url: pr.html_url,
+ });
+
- name: Dispatch command
uses: actions/github-script@v7
with:
diff --git a/.github/workflows/review-gate.yml
b/.github/workflows/review-gate.yml
new file mode 100644
index 00000000..196aa218
--- /dev/null
+++ b/.github/workflows/review-gate.yml
@@ -0,0 +1,117 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Maintainer Review Gate
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - reopened
+ - synchronize
+ - ready_for_review
+ pull_request_review:
+ types:
+ - submitted
+ - dismissed
+ push:
+ branches:
+ - master
+ workflow_dispatch:
+ inputs:
+ pr_number:
+ description: Optional pull request number to recompute
+ required: false
+ type: number
+
+permissions:
+ contents: read
+ pull-requests: read
+ statuses: write
+
+jobs:
+ approval:
+ name: Maintainer Approval
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ if: |
+ github.repository == 'apache/dubbo-kubernetes' &&
+ (github.event_name != 'pull_request_review' ||
+ github.event.review.user.login == 'mfordjody')
+ steps:
+ - name: Publish approval status
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const {owner, repo} = context.repo;
+ const maintainer = 'mfordjody';
+ const publish = async (sha, state, description, targetUrl) => {
+ await github.rest.repos.createCommitStatus({
+ owner,
+ repo,
+ sha,
+ state,
+ context: 'Maintainer Approval',
+ description,
+ target_url: targetUrl,
+ });
+ };
+
+ if (context.eventName === 'push') {
+ await publish(context.sha, 'success', 'Merged through the
protected branch', context.payload.compare);
+ return;
+ }
+
+ let pr = context.payload.pull_request;
+ if (context.eventName === 'workflow_dispatch') {
+ const prNumber = Number(context.payload.inputs?.pr_number || 0);
+ if (!prNumber) {
+ await publish(context.sha, 'success', 'Maintainer-authorized
workflow dispatch',
`${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`);
+ return;
+ }
+ ({data: pr} = await github.rest.pulls.get({owner, repo,
pull_number: prNumber}));
+ }
+
+ if (!pr) {
+ core.setFailed('Pull request payload is required');
+ return;
+ }
+
+ const authorIsMaintainer = pr.user.login.toLowerCase() ===
maintainer;
+ let approved = authorIsMaintainer;
+ if (!approved) {
+ const reviews = await
github.paginate(github.rest.pulls.listReviews, {
+ owner,
+ repo,
+ pull_number: pr.number,
+ per_page: 100,
+ });
+ approved = reviews.some((review) =>
+ review.user?.login.toLowerCase() === maintainer &&
+ review.state === 'APPROVED' &&
+ review.commit_id === pr.head.sha,
+ );
+ }
+
+ await publish(
+ pr.head.sha,
+ approved ? 'success' : 'pending',
+ authorIsMaintainer
+ ? 'Maintainer-authored pull request'
+ : approved
+ ? `Approved by @${maintainer}`
+ : `Waiting for @${maintainer} approval`,
+ pr.html_url,
+ );
diff --git a/OWNERS b/OWNERS
index 5d03d00d..9e7261b5 100644
--- a/OWNERS
+++ b/OWNERS
@@ -14,8 +14,8 @@
# limitations under the License.
# OWNERS drives the /lgtm merge command in .github/workflows/commands.yml.
-# GitHub CODEOWNERS and the repository rulesets independently enforce the
-# required maintainer review before another user's pull request can merge.
+# The required Maintainer Approval status independently enforces approval by
+# an approver before another user's pull request can merge.
approvers:
- mfordjody