name: Vouch Check on: pull_request_target: types: [opened, reopened] permissions: contents: read pull-requests: write jobs: vouch-gate: if: github.repository_owner == 'NVIDIA' runs-on: ubuntu-latest env: ORG_READ_TOKEN: ${{ secrets.ORG_READ_TOKEN }} steps: - name: Check org membership id: org-check if: env.ORG_READ_TOKEN != '' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: github-token: ${{ secrets.ORG_READ_TOKEN }} result-encoding: string script: | const author = context.payload.pull_request.user.login; try { const { status } = await github.rest.orgs.checkMembershipForUser({ org: context.repo.owner, username: author, }); if (status === 204 || status === 302) { console.log(`${author} is an org member. Skipping vouch check.`); return 'skip'; } } catch (e) { if (e.status !== 404) { console.log(`Org membership check error (status=${e.status}): ${e.message}`); } } return ''; - name: Check if contributor is vouched if: steps.org-check.outputs.result != 'skip' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | const author = context.payload.pull_request.user.login; const authorType = context.payload.pull_request.user.type; // Skip bots (dependabot, renovate, github-actions, etc.). if (authorType === 'Bot') { console.log(`${author} is a bot. Skipping vouch check.`); return; } // Check the VOUCHED.td file on the dedicated "vouched" branch. // NOT the PR branch — the PR author could add themselves in their fork. let vouched = false; try { const { data } = await github.rest.repos.getContent({ owner: context.repo.owner, repo: context.repo.repo, path: '.github/VOUCHED.td', ref: 'vouched', }); const content = Buffer.from(data.content, 'base64').toString('utf-8'); const usernames = content .split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#') && !line.startsWith('-')); vouched = usernames.some( name => name.toLowerCase() === author.toLowerCase() ); } catch (e) { console.log(`Could not read VOUCHED.td: ${e.message}`); } if (vouched) { console.log(`${author} is in VOUCHED.td. Approved.`); return; } // Not vouched — close the PR with an explanation. console.log(`${author} is not vouched. Closing PR.`); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, body: [ `Thank you for your interest in contributing to OpenShell, @${author}.`, '', 'This project uses a **vouch system** for first-time contributors. Before submitting a pull request, you need to be vouched by a maintainer.', '', '**To get vouched:**', '1. Open a [Vouch Request](https://github.com/NVIDIA/OpenShell/discussions/new?category=vouch-request) discussion.', '2. Describe what you want to change and why.', '3. Write in your own words — do not have an AI generate the request.', '4. A maintainer will comment `/vouch` if approved.', '5. Once vouched, open a new PR (preferred) or reopen this one after a few minutes.', '', 'See [CONTRIBUTING.md](https://github.com/NVIDIA/OpenShell/blob/main/CONTRIBUTING.md#first-time-contributors) for details.', ].join('\n'), }); await github.rest.pulls.update({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.payload.pull_request.number, state: 'closed', });