Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/VOUCHED.td
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Trust list for this repository.
#
# External contributors listed here are treated as trusted by the vouch
# workflow. Collaborators with write access are automatically trusted and
# do not need to be duplicated in this file.
#
# Syntax:
# github:username
# -github:username reason for denouncement
#
# Keep entries sorted alphabetically.
github:adityavardhansharma
github:binbandit
github:chuks-qua
github:cursoragent
github:gbarros-dev
github:github-actions[bot]
github:hwanseoc
github:jamesx0416
github:jasonLaster
github:JoeEverest
github:nmggithub
github:notkainoa
github:PatrickBauer
github:realAhmedRoach
github:shiroyasha9
github:Yash-Singh1
175 changes: 175 additions & 0 deletions .github/workflows/pr-vouch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
name: PR Vouch

on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
issue_comment:
types: [created]
push:
branches:
- main
paths:
- .github/VOUCHED.td
- .github/workflows/pr-vouch.yml

permissions:
contents: read
issues: write
pull-requests: write

jobs:
collect-targets:
name: Collect PR targets
runs-on: ubuntu-24.04
outputs:
targets: ${{ steps.collect.outputs.targets }}
steps:
- id: collect
uses: actions/github-script@v7
with:
script: |
if (context.eventName === "pull_request_target") {
const pr = context.payload.pull_request;
core.setOutput("targets", JSON.stringify([{ number: pr.number, user: pr.user.login }]));
return;
}
if (context.eventName === "issue_comment") {
const issue = context.payload.issue;
const body = context.payload.comment?.body ?? "";
if (!issue?.pull_request || !body.includes("/recheck-vouch")) {
core.setOutput("targets", "[]");
return;
}
core.setOutput(
"targets",
JSON.stringify([{ number: issue.number, user: issue.user.login }]),
);
return;
}
const pulls = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
per_page: 100,
});
const targets = pulls.map((pull) => ({
number: pull.number,
user: pull.user.login,
}));
core.setOutput("targets", JSON.stringify(targets));
label:
name: Label PR ${{ matrix.target.number }}
needs: collect-targets
if: ${{ needs.collect-targets.outputs.targets != '[]' }}
runs-on: ubuntu-24.04
concurrency:
group: pr-vouch-${{ matrix.target.number }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.collect-targets.outputs.targets) }}
steps:
- id: vouch
name: Check PR author trust
uses: mitchellh/vouch/action/check-user@v1
with:
user: ${{ matrix.target.user }}
allow-fail: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Sync PR labels
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ matrix.target.number }}
VOUCH_STATUS: ${{ steps.vouch.outputs.status }}
with:
script: |
const issueNumber = Number(process.env.PR_NUMBER);
const status = process.env.VOUCH_STATUS;
const managedLabels = [
{
name: "vouch:trusted",
color: "1f883d",
description: "PR author is trusted by repo permissions or the VOUCHED list.",
},
{
name: "vouch:unvouched",
color: "fbca04",
description: "PR author is not yet trusted in the VOUCHED list.",
},
{
name: "vouch:denounced",
color: "d1242f",
description: "PR author is explicitly blocked by the VOUCHED list.",
},
];
const managedLabelNames = managedLabels.map((label) => label.name);
for (const label of managedLabels) {
try {
const { data: existing } = await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
});
if (
existing.color !== label.color ||
(existing.description ?? "") !== label.description
) {
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description,
});
}
} catch (error) {
if (error.status !== 404) {
throw error;
}
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description,
});
}
}
const nextLabelName =
status === "denounced"
? "vouch:denounced"
: ["bot", "collaborator", "vouched"].includes(status)
? "vouch:trusted"
: "vouch:unvouched";
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
});
const preservedLabels = currentLabels
.map((label) => label.name)
.filter((name) => !managedLabelNames.includes(name));
await github.rest.issues.setLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: [...preservedLabels, nextLabelName],
});
core.info(`PR #${issueNumber}: ${status} -> ${nextLabelName}`);
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ You can still open an issue or PR, but please do so knowing there is a high chan

If that sounds annoying, that is because it is. This project is still early and we are trying to keep scope, quality, and direction under control.

PRs are automatically labeled with a `vouch:*` trust status.

If you are an external contributor, expect `vouch:unvouched` until we explicitly add you to [.github/VOUCHED.td](.github/VOUCHED.td).

## What We Are Most Likely To Accept

Small, focused bug fixes.
Expand Down