fix: add contribution gate dry run mode
This commit is contained in:
@@ -2,9 +2,10 @@
|
||||
#
|
||||
# Maintainers and collaborators bypass the gate automatically. Use this file
|
||||
# for external contributors who are allowed through the automated front door.
|
||||
# Seed active contributors here before switching the gate workflows to enforce mode.
|
||||
#
|
||||
# Supported entries:
|
||||
# pr:username
|
||||
# issue:username
|
||||
# all:username
|
||||
all:Hmbown
|
||||
all:hmbown
|
||||
|
||||
@@ -9,6 +9,10 @@ permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: contribution-gate-approval
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
approve:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -56,12 +60,14 @@ jobs:
|
||||
const targetLogin = issue.user.login;
|
||||
const normalizedLogin = targetLogin.toLowerCase();
|
||||
const entry = `${scope}:${normalizedLogin}`;
|
||||
const branchSlug = normalizedLogin.replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'contributor';
|
||||
|
||||
const defaultContent = [
|
||||
'# Scoped contribution-gate allowlist.',
|
||||
'#',
|
||||
'# Maintainers and collaborators bypass the gate automatically. Use this file',
|
||||
'# for external contributors who are allowed through the automated front door.',
|
||||
'# Seed active contributors here before switching the gate workflows to enforce mode.',
|
||||
'#',
|
||||
'# Supported entries:',
|
||||
'# pr:username',
|
||||
@@ -119,6 +125,29 @@ jobs:
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: openPrs } = await github.rest.pulls.list({
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
const repoFullName = `${owner}/${repo}`.toLowerCase();
|
||||
const pendingPr = openPrs.find(openPr => {
|
||||
const sameRepo = (openPr.head?.repo?.full_name || '').toLowerCase() === repoFullName;
|
||||
const body = openPr.body || '';
|
||||
return sameRepo && body.includes(`Adds \`${entry}\` to \`${path}\`.`);
|
||||
});
|
||||
|
||||
if (pendingPr) {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: issue.number,
|
||||
body: `@${targetLogin} already has a pending allowlist update PR for ${scope} contributions: ${pendingPr.html_url}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const nextContent = `${content.trimEnd()}\n${entry}\n`;
|
||||
const { data: blob } = await github.rest.git.createBlob({
|
||||
owner,
|
||||
@@ -140,7 +169,6 @@ jobs:
|
||||
],
|
||||
});
|
||||
|
||||
const branchSlug = normalizedLogin.replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'contributor';
|
||||
const branchName = `contribution-gate/${scope}-${branchSlug}-${Date.now()}`;
|
||||
await github.rest.git.createRef({
|
||||
owner,
|
||||
|
||||
@@ -8,11 +8,16 @@ permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
env:
|
||||
# Keep new gates observable first. Switch to "enforce" only after maintainers
|
||||
# have seeded active contributors and reviewed the dry-run signal.
|
||||
CONTRIBUTION_GATE_MODE: dry-run
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close unapproved external issues
|
||||
- name: Gate unapproved external issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
@@ -20,6 +25,12 @@ jobs:
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const privileged = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
|
||||
const gateMode = (process.env.CONTRIBUTION_GATE_MODE || 'dry-run').trim().toLowerCase();
|
||||
const enforceGate = gateMode === 'enforce';
|
||||
|
||||
if (!['dry-run', 'enforce'].includes(gateMode)) {
|
||||
core.warning(`Unknown CONTRIBUTION_GATE_MODE "${gateMode}"; defaulting to dry-run.`);
|
||||
}
|
||||
|
||||
if (privileged.has(issue.author_association)) return;
|
||||
if (issue.user.login === 'github-actions[bot]') return;
|
||||
@@ -60,6 +71,10 @@ jobs:
|
||||
return;
|
||||
}
|
||||
|
||||
const gateMessage = enforceGate
|
||||
? 'This repository currently uses a maintainer-managed contribution gate, so issues from contributors who are not listed in `.github/APPROVED_CONTRIBUTORS` are closed automatically.'
|
||||
: 'This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this issue is staying open. When enforcement is enabled, issues from contributors who are not listed in `.github/APPROVED_CONTRIBUTORS` will be closed automatically.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
@@ -67,12 +82,14 @@ jobs:
|
||||
body: [
|
||||
`Thanks @${issue.user.login} for the report.`,
|
||||
'',
|
||||
'This repository currently uses a maintainer-managed contribution gate, so issues from contributors who are not listed in `.github/APPROVED_CONTRIBUTORS` are closed automatically.',
|
||||
gateMessage,
|
||||
'',
|
||||
'Please read `CONTRIBUTING.md` for the expected issue shape. A maintainer can grant issue access by commenting `/lgtmi` on an issue.',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
if (!enforceGate) return;
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
|
||||
@@ -9,11 +9,16 @@ permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
# Keep new gates observable first. Switch to "enforce" only after maintainers
|
||||
# have seeded active contributors and reviewed the dry-run signal.
|
||||
CONTRIBUTION_GATE_MODE: dry-run
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close unapproved external pull requests
|
||||
- name: Gate unapproved external pull requests
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
@@ -21,6 +26,12 @@ jobs:
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const privileged = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
|
||||
const gateMode = (process.env.CONTRIBUTION_GATE_MODE || 'dry-run').trim().toLowerCase();
|
||||
const enforceGate = gateMode === 'enforce';
|
||||
|
||||
if (!['dry-run', 'enforce'].includes(gateMode)) {
|
||||
core.warning(`Unknown CONTRIBUTION_GATE_MODE "${gateMode}"; defaulting to dry-run.`);
|
||||
}
|
||||
|
||||
if (privileged.has(pr.author_association)) return;
|
||||
if (pr.user.login === 'github-actions[bot]') return;
|
||||
@@ -61,6 +72,10 @@ jobs:
|
||||
return;
|
||||
}
|
||||
|
||||
const gateMessage = enforceGate
|
||||
? 'This repository currently uses a maintainer-managed contribution gate, so pull requests from contributors who are not listed in `.github/APPROVED_CONTRIBUTORS` are closed automatically.'
|
||||
: 'This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this pull request is staying open. When enforcement is enabled, pull requests from contributors who are not listed in `.github/APPROVED_CONTRIBUTORS` will be closed automatically.';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
@@ -68,12 +83,14 @@ jobs:
|
||||
body: [
|
||||
`Thanks @${pr.user.login} for taking the time to contribute.`,
|
||||
'',
|
||||
'This repository currently uses a maintainer-managed contribution gate, so pull requests from contributors who are not listed in `.github/APPROVED_CONTRIBUTORS` are closed automatically.',
|
||||
gateMessage,
|
||||
'',
|
||||
'Please read `CONTRIBUTING.md` for the expected contribution shape. A maintainer can grant PR access by commenting `/lgtm` on a pull request.',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
if (!enforceGate) return;
|
||||
|
||||
await github.rest.pulls.update({
|
||||
owner,
|
||||
repo,
|
||||
|
||||
+23
-10
@@ -170,9 +170,18 @@ Validation:
|
||||
## Contribution Gate
|
||||
|
||||
CodeWhale uses a maintainer-managed contribution gate for the community front
|
||||
door. Maintainers and collaborators bypass this gate automatically. External
|
||||
contributors must be listed in `.github/APPROVED_CONTRIBUTORS` before their
|
||||
issues or pull requests remain open.
|
||||
door. Maintainers and collaborators bypass this gate automatically. The gate
|
||||
workflows default to dry-run / comment-only mode so maintainers can observe the
|
||||
signal before closing contributor work. In dry-run mode, unapproved external
|
||||
issues and pull requests receive a short thank-you / CONTRIBUTING pointer and
|
||||
remain open.
|
||||
|
||||
When maintainers are ready to enforce the gate, set
|
||||
`CONTRIBUTION_GATE_MODE: enforce` in the PR and issue gate workflows. In enforce
|
||||
mode, external contributors must be listed in
|
||||
`.github/APPROVED_CONTRIBUTORS` before their issues or pull requests remain
|
||||
open. Before enabling enforcement, seed the allowlist broadly enough for active
|
||||
external contributors who should not be interrupted by the rollout.
|
||||
|
||||
The allowlist is scoped:
|
||||
|
||||
@@ -180,17 +189,21 @@ The allowlist is scoped:
|
||||
- `issue:username` allows issues.
|
||||
- `all:username` allows both.
|
||||
|
||||
When an unapproved external contributor opens an issue or pull request, the
|
||||
matching gate workflow leaves a short thank-you / CONTRIBUTING pointer and
|
||||
closes it. A maintainer can approve someone by commenting `/lgtm` on a pull
|
||||
request for PR access, or `/lgtmi` on an issue for issue access. The exact bare
|
||||
commands `lgtm` and `lgtmi` are also accepted for compatibility, but the
|
||||
prefixed forms are preferred because they are harder to trigger accidentally in
|
||||
ordinary review discussion.
|
||||
A maintainer can approve someone by commenting `/lgtm` on a pull request for PR
|
||||
access, or `/lgtmi` on an issue for issue access. The exact bare commands
|
||||
`lgtm` and `lgtmi` are also accepted for compatibility, but the prefixed forms
|
||||
are preferred because they are harder to trigger accidentally in ordinary review
|
||||
discussion.
|
||||
|
||||
Approvals do not edit `main` directly. The approval workflow opens a small
|
||||
allowlist update PR so the new entry is reviewable before it takes effect.
|
||||
|
||||
If the gate fires on a good contributor incorrectly, use the same approval flow
|
||||
to restore them: comment `/lgtm` or `/lgtmi`, merge the generated allowlist PR,
|
||||
then reopen the affected issue or pull request. If GitHub will not allow the
|
||||
closed item to be reopened, ask the contributor to resubmit after the allowlist
|
||||
PR is merged.
|
||||
|
||||
## Agent-Assisted Improvements
|
||||
|
||||
CodeWhale is allowed to help improve CodeWhale, but the contribution still has
|
||||
|
||||
Reference in New Issue
Block a user