From 61bb443dc047de4f4b0b77df3663a4552d029993 Mon Sep 17 00:00:00 2001 From: John Weiler Date: Wed, 11 Feb 2026 12:24:33 -0500 Subject: [PATCH 1/2] Add PR review enforcement GitHub Action Adds the review-enforcement workflow from the API repo to enforce that PRs have at least one approval from a non-bot reviewer, and to auto-request reviews from the product team when Dependabot CI fails. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/review-enforcement.yml | 128 +++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 .github/workflows/review-enforcement.yml diff --git a/.github/workflows/review-enforcement.yml b/.github/workflows/review-enforcement.yml new file mode 100644 index 00000000..c1f9731b --- /dev/null +++ b/.github/workflows/review-enforcement.yml @@ -0,0 +1,128 @@ +name: Enforce Review Policy + +on: + pull_request: + types: [opened, synchronize, reopened] + pull_request_review: + types: [submitted, dismissed] + workflow_run: + workflows: ["Test"] + types: [completed] + +jobs: + enforce-review: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Enforce review policy + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + const event = context.eventName; + const isDependabot = pr.user?.login === 'dependabot[bot]'; + + // Skip for Dependabot on all events + if (isDependabot) { + core.info('Dependabot PR detected - skipping review enforcement.'); + return; + } + + // Only enforce review requirement on pull_request_review events + if (event === 'pull_request') { + core.info('PR opened/synchronized - waiting for review, not enforcing yet.'); + return; + } + + // From here on, we know it's a pull_request_review event (submitted or dismissed): enforce rule + core.info('Review event detected - checking current approval status...'); + + const reviews = await github.rest.pulls.listReviews({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number + }); + + const approvals = reviews.data.filter(r => r.state === 'APPROVED'); + const hasNonBazApproval = approvals.some( + r => r.user?.login && + r.user.login !== 'baz-reviewer' && + r.user.type === 'User' + ); + + if (!hasNonBazApproval) { + core.setFailed('At least one approval from a non-baz-reviewer is required.'); + } + + dependabot-ci-failure: + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'failure' + permissions: + pull-requests: write + steps: + - name: Request review from random product team member on CI failure + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GALILEO_AUTOMATION_GITHUB_TOKEN }} + script: | + // Get the PR associated with this workflow run + const workflowRun = context.payload.workflow_run; + + if (!workflowRun.pull_requests || workflowRun.pull_requests.length === 0) { + core.info('No pull request associated with this workflow run.'); + return; + } + + const prNumber = workflowRun.pull_requests[0].number; + + // Get PR details + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + const isDependabot = pr.user?.login === 'dependabot[bot]'; + + if (!isDependabot) { + core.info('PR is not from Dependabot - skipping CI failure notification.'); + return; + } + + core.info(`Dependabot PR #${prNumber} CI failed - requesting review from random product team member.`); + + // Get members of the rungalileo/product team + let teamMembers; + try { + const { data: members } = await github.rest.teams.listMembersInOrg({ + org: 'rungalileo', + team_slug: 'product', + }); + teamMembers = members.map(m => m.login); + } catch (error) { + core.setFailed(`Failed to fetch team members: ${error.message}`); + return; + } + + if (teamMembers.length === 0) { + core.setFailed('No members found in rungalileo/product team.'); + return; + } + + // Select a random team member + const randomMember = teamMembers[Math.floor(Math.random() * teamMembers.length)]; + core.info(`Selected random reviewer: ${randomMember}`); + + // Request review from the selected member + try { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + reviewers: [randomMember] + }); + core.info(`Successfully requested review from ${randomMember} for PR #${prNumber}`); + } catch (error) { + core.setFailed(`Failed to request review: ${error.message}`); + } From 81a634ee122a4c66f1a76a6be0072d4c9a5c60df Mon Sep 17 00:00:00 2001 From: John Weiler Date: Wed, 11 Feb 2026 12:28:43 -0500 Subject: [PATCH 2/2] Remove CODEOWNERS Co-Authored-By: Claude Opus 4.6 --- .github/CODEOWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6c098888..e69de29b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +0,0 @@ -* @rungalileo/product