authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-03-08 20:36:37-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-08 20:36:37-07:00
logcd1c3df89cfa681622292f7f2a9777f81f8e2f10
tree5785ca1ea3274a00d00575b53c617ef74afa3fd0
parent3738373aa5ff0fff074ecb95686ef952c209957e
signature Signed by PGP key B5690EEEBB952194

Add PR vouch workflow with trust-status labels (#602)

* Add PR vouch workflow and trust-status labeling - Add `.github/workflows/pr-vouch.yml` to label PRs as trusted, unvouched, or denounced - Add `.github/VOUCHED.td` trust-list file for external contributors - Document automatic `vouch:*` labels and contributor expectations in `CONTRIBUTING.md` * Add vouch entries for trusted GitHub contributors - Append 16 GitHub accounts to `.github/VOUCHED.td` - Update trusted-voucher list used for PR labeling workflows

3 files changed, 206 insertions(+), 0 deletions(-)

.github/VOUCHED.td created+27
......@@ -0,0 +1,27 @@
1# Trust list for this repository.
2#
3# External contributors listed here are treated as trusted by the vouch
4# workflow. Collaborators with write access are automatically trusted and
5# do not need to be duplicated in this file.
6#
7# Syntax:
8# github:username
9# -github:username reason for denouncement
10#
11# Keep entries sorted alphabetically.
12github:adityavardhansharma
13github:binbandit
14github:chuks-qua
15github:cursoragent
16github:gbarros-dev
17github:github-actions[bot]
18github:hwanseoc
19github:jamesx0416
20github:jasonLaster
21github:JoeEverest
22github:nmggithub
23github:notkainoa
24github:PatrickBauer
25github:realAhmedRoach
26github:shiroyasha9
27github:Yash-Singh1
.github/workflows/pr-vouch.yml created+175
......@@ -0,0 +1,175 @@
1name: PR Vouch
2
3on:
4 pull_request_target:
5 types: [opened, reopened, synchronize, ready_for_review]
6 issue_comment:
7 types: [created]
8 push:
9 branches:
10 - main
11 paths:
12 - .github/VOUCHED.td
13 - .github/workflows/pr-vouch.yml
14
15permissions:
16 contents: read
17 issues: write
18 pull-requests: write
19
20jobs:
21 collect-targets:
22 name: Collect PR targets
23 runs-on: ubuntu-24.04
24 outputs:
25 targets: ${{ steps.collect.outputs.targets }}
26 steps:
27 - id: collect
28 uses: actions/github-script@v7
29 with:
30 script: |
31 if (context.eventName === "pull_request_target") {
32 const pr = context.payload.pull_request;
33 core.setOutput("targets", JSON.stringify([{ number: pr.number, user: pr.user.login }]));
34 return;
35 }
36
37 if (context.eventName === "issue_comment") {
38 const issue = context.payload.issue;
39 const body = context.payload.comment?.body ?? "";
40 if (!issue?.pull_request || !body.includes("/recheck-vouch")) {
41 core.setOutput("targets", "[]");
42 return;
43 }
44
45 core.setOutput(
46 "targets",
47 JSON.stringify([{ number: issue.number, user: issue.user.login }]),
48 );
49 return;
50 }
51
52 const pulls = await github.paginate(github.rest.pulls.list, {
53 owner: context.repo.owner,
54 repo: context.repo.repo,
55 state: "open",
56 per_page: 100,
57 });
58
59 const targets = pulls.map((pull) => ({
60 number: pull.number,
61 user: pull.user.login,
62 }));
63 core.setOutput("targets", JSON.stringify(targets));
64
65 label:
66 name: Label PR ${{ matrix.target.number }}
67 needs: collect-targets
68 if: ${{ needs.collect-targets.outputs.targets != '[]' }}
69 runs-on: ubuntu-24.04
70 concurrency:
71 group: pr-vouch-${{ matrix.target.number }}
72 cancel-in-progress: true
73 strategy:
74 fail-fast: false
75 matrix:
76 target: ${{ fromJson(needs.collect-targets.outputs.targets) }}
77 steps:
78 - id: vouch
79 name: Check PR author trust
80 uses: mitchellh/vouch/action/check-user@v1
81 with:
82 user: ${{ matrix.target.user }}
83 allow-fail: true
84 env:
85 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
86
87 - name: Sync PR labels
88 uses: actions/github-script@v7
89 env:
90 PR_NUMBER: ${{ matrix.target.number }}
91 VOUCH_STATUS: ${{ steps.vouch.outputs.status }}
92 with:
93 script: |
94 const issueNumber = Number(process.env.PR_NUMBER);
95 const status = process.env.VOUCH_STATUS;
96 const managedLabels = [
97 {
98 name: "vouch:trusted",
99 color: "1f883d",
100 description: "PR author is trusted by repo permissions or the VOUCHED list.",
101 },
102 {
103 name: "vouch:unvouched",
104 color: "fbca04",
105 description: "PR author is not yet trusted in the VOUCHED list.",
106 },
107 {
108 name: "vouch:denounced",
109 color: "d1242f",
110 description: "PR author is explicitly blocked by the VOUCHED list.",
111 },
112 ];
113
114 const managedLabelNames = managedLabels.map((label) => label.name);
115
116 for (const label of managedLabels) {
117 try {
118 const { data: existing } = await github.rest.issues.getLabel({
119 owner: context.repo.owner,
120 repo: context.repo.repo,
121 name: label.name,
122 });
123
124 if (
125 existing.color !== label.color ||
126 (existing.description ?? "") !== label.description
127 ) {
128 await github.rest.issues.updateLabel({
129 owner: context.repo.owner,
130 repo: context.repo.repo,
131 name: label.name,
132 color: label.color,
133 description: label.description,
134 });
135 }
136 } catch (error) {
137 if (error.status !== 404) {
138 throw error;
139 }
140
141 await github.rest.issues.createLabel({
142 owner: context.repo.owner,
143 repo: context.repo.repo,
144 name: label.name,
145 color: label.color,
146 description: label.description,
147 });
148 }
149 }
150
151 const nextLabelName =
152 status === "denounced"
153 ? "vouch:denounced"
154 : ["bot", "collaborator", "vouched"].includes(status)
155 ? "vouch:trusted"
156 : "vouch:unvouched";
157
158 const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
159 owner: context.repo.owner,
160 repo: context.repo.repo,
161 issue_number: issueNumber,
162 });
163
164 const preservedLabels = currentLabels
165 .map((label) => label.name)
166 .filter((name) => !managedLabelNames.includes(name));
167
168 await github.rest.issues.setLabels({
169 owner: context.repo.owner,
170 repo: context.repo.repo,
171 issue_number: issueNumber,
172 labels: [...preservedLabels, nextLabelName],
173 });
174
175 core.info(`PR #${issueNumber}: ${status} -> ${nextLabelName}`);
CONTRIBUTING.md+4
......@@ -8,6 +8,10 @@ You can still open an issue or PR, but please do so knowing there is a high chan
88
99If 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.
1010
11PRs are automatically labeled with a `vouch:*` trust status.
12
13If you are an external contributor, expect `vouch:unvouched` until we explicitly add you to [.github/VOUCHED.td](.github/VOUCHED.td).
14
1115## What We Are Most Likely To Accept
1216
1317Small, focused bug fixes.