Skip to content

docs(profile): accuracy pass, every claim checked against the repos #14

docs(profile): accuracy pass, every claim checked against the repos

docs(profile): accuracy pass, every claim checked against the repos #14

# Requires a named-maintainer review before merge for external contributors.
# AI-only approvals and bot approvals do not count.
# Branch protection rules enforce the code-owner review requirement.
name: "Policy: Awaiting maintainer review"
on:
pull_request_target:
types: [opened, synchronize, reopened]
branches: [main]
# Re-evaluate the gate when a review is submitted or dismissed so an
# approval clears the failing status without waiting for a new push.
# Note: pull_request_review does not support a branches filter, so the
# base-branch check lives in the job-level `if` below.
pull_request_review:
types: [submitted, dismissed]
permissions:
contents: read
jobs:
check-approval:
name: "Policy: Awaiting maintainer review"
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
if: github.event.pull_request.base.ref == 'main'
steps:
- name: Check for named-maintainer review
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const MAINTAINERS = ['imran-siddique'];
// Maintainer-authored PRs skip the gate. author_association is
// not reliable for this: it only reports MEMBER when the
// author's org membership is public, and a PR author cannot
// approve their own PR, so without this skip a maintainer with
// private membership could never clear the gate.
const author = context.payload.pull_request.user.login;
if (MAINTAINERS.includes(author)) {
core.info(`Author ${author} is a maintainer, skipping gate`);
return;
}
const association = context.payload.pull_request.author_association;
if (association === 'MEMBER' || association === 'OWNER') {
core.info(`Author is ${association}, skipping gate`);
return;
}
// Both pull_request_target and pull_request_review payloads
// carry the PR at context.payload.pull_request.
const prNumber = context.payload.pull_request.number;
// Re-fetch the PR so we compare against the head SHA at
// evaluation time, not a possibly stale SHA from the payload.
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const headSha = pr.head.sha;
// Paginate: listReviews otherwise returns only the first 30.
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
// Only a maintainer's most recent non-comment review counts,
// and it must approve the current head commit. An approval of
// an older commit does not clear the gate after a later push
// (prevents approve-then-swap).
const latestByMaintainer = new Map();
for (const review of reviews) {
if (
review.user &&
review.user.type === 'User' &&
MAINTAINERS.includes(review.user.login) &&
review.state !== 'COMMENTED'
) {
latestByMaintainer.set(review.user.login, review);
}
}
const maintainerApproval = [...latestByMaintainer.values()].find(
(r) => r.state === 'APPROVED' && r.commit_id === headSha
);
if (!maintainerApproval) {
core.setFailed(
'This PR is awaiting a named-maintainer review of the current head commit.\n' +
'This is a policy gate, not a CI failure: the PR is mergeable once a named maintainer approves.\n' +
'Approvals of earlier commits do not count after new pushes.\n' +
'See CODEOWNERS or MAINTAINERS.md for the list of maintainers.'
);
} else {
core.info(
`Maintainer @${maintainerApproval.user.login} approved this PR at head ${headSha}.`
);
}