Skip to content

feat: add reverse span links, show span link detail #203

feat: add reverse span links, show span link detail

feat: add reverse span links, show span link detail #203

Workflow file for this run

name: Vouch
# Trust gate for outside contributions, built on https://github.com/mitchellh/vouch.
#
# Anyone with write access (org members, collaborators) and bots are always
# allowed. For everyone else this does two things:
#
# 1. Volume. Outside contributors get one open PR at a time, three once they
# are on the vouch list. Anything over that is closed, asking them to
# finish what they have open first. Drafts don't count.
# 2. Authenticity. A PR from someone not in .github/VOUCHED.td stays open but
# gets a `needs-vouch` label and a comment asking them to introduce
# themselves.
#
# Authors explicitly denounced in the list are always closed. Issues are NOT
# gated — opening one is how a new contributor introduces themselves.
#
# Maintainers manage the list from any issue or PR comment. The keyword has to
# be first on the first line:
# /vouch @user [reason] /denounce @user [reason] /unvouch @user
#
# The list edit arrives as a pull request because `main` is protected and
# GITHUB_TOKEN cannot bypass that. Note the bot's PR does not trigger `Main` —
# GitHub does not start workflow runs for events raised by GITHUB_TOKEN — so it
# needs a maintainer approval rather than a green CI run. This is the same path
# the changesets release PR already takes.
on:
pull_request_target:
types: [opened, reopened, ready_for_review]
issue_comment:
types: [created]
permissions: {}
jobs:
check-pr:
# Drafts are skipped and re-checked on ready_for_review, matching
# external-contributor-alerts.yml so the two agree on what counts.
if: >-
github.event_name == 'pull_request_target' &&
!github.event.pull_request.draft
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
pull-requests: write
steps:
# require-vouch: false means only denounced authors are blocked. Everyone
# unvouched comes back as `allowed`, which the next step picks up. No
# checkout needed — the action reads VOUCHED.td from the default branch
# over the API, and the denounce message is upstream's, not a file here.
- id: vouch
uses: mitchellh/vouch/action/check-pr@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
with:
pr-number: ${{ github.event.pull_request.number }}
require-vouch: false
auto-close: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# One step covers both checks so an author who is unvouched *and* over
# the PR limit gets a single comment rather than two.
- uses: actions/github-script@v9
env:
VOUCH_STATUS: ${{ steps.vouch.outputs.status }}
with:
script: |
const LABEL = 'needs-vouch';
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
const status = process.env.VOUCH_STATUS;
// skipped = bot. closed = denounced, already handled upstream.
if (status === 'skipped' || status === 'closed') return;
// `vouched` covers both collaborators and listed outsiders, so it
// cannot tell us who is internal. Same check as
// external-contributor-alerts.yml, including the slow path that
// catches org members with concealed membership.
let internal = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(pr.author_association);
if (!internal) {
try {
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
owner, repo, username: pr.user.login,
});
const p = data.user?.permissions ?? {};
internal = !!(p.admin || p.maintain || p.push || p.triage);
} catch (e) {
if (e.status !== 404) throw e; // 404 = not a collaborator => external
}
}
if (internal) { core.info(`#${pr.number} is internal; nothing to do.`); return; }
// Volume cap, per Jaeger's model: contributing more earns more
// concurrent PRs. Being on the vouch list is the reputation signal.
const allowance = status === 'vouched' ? 3 : 1;
const open = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', per_page: 100,
});
// Drafts do not count — they are not asking for review yet, which
// is the queue we are actually protecting.
const theirs = open.filter(p =>
p.user?.login === pr.user.login && p.number !== pr.number && !p.draft);
if (theirs.length >= allowance) {
const list = theirs.map(p => `#${p.number}`).join(', ');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number,
body: [
`Hi @${pr.user.login}, thanks for this.`,
``,
`We limit outside contributors to ${allowance} open pull request${allowance === 1 ? '' : 's'} at a time, and you already have ${list} open. Reviewing several changes from one author at once is more than we can give proper attention to, and it tends to mean none of them land.`,
``,
`I have closed this one to keep the queue honest — **your branch and your work are untouched**. Once ${theirs.length === 1 ? 'that PR' : 'one of those'} is merged or closed, reopen this and it will go through.`,
].join('\n'),
});
await github.rest.pulls.update({ owner, repo, pull_number: pr.number, state: 'closed' });
core.info(`Closed #${pr.number}: ${theirs.length} already open, allowance ${allowance}.`);
return;
}
if (status !== 'allowed') return; // vouched and within the limit
// The label doubles as the "already asked" marker so a reopen or a
// ready_for_review doesn't post the same comment a second time.
if (pr.labels?.some(l => l.name === LABEL)) {
core.info(`#${pr.number} already labelled; not commenting again.`);
return;
}
try {
await github.rest.issues.createLabel({
owner, repo, name: LABEL, color: 'FBCA04',
description: 'Author needs a maintainer to vouch for them',
});
} catch (e) {
if (e.status !== 422) throw e; // 422 = already exists
}
await github.rest.issues.addLabels({
owner, repo, issue_number: pr.number, labels: [LABEL],
});
const body = [
`Hi @${pr.user.login}, thanks for the pull request!`,
``,
`Before we review code from a first-time contributor we ask that a maintainer vouches for you, and you're not on our list yet. **This PR stays open** — it just isn't in the review queue until someone vouches.`,
``,
`To get vouched, open an issue saying hello and what you're working on:`,
``,
`https://github.com/${owner}/${repo}/issues/new?template=introduce-yourself.md`,
``,
`A maintainer will usually reply within a day or two, and then this PR gets picked up as normal. More detail in [our contributing guide](https://github.com/${owner}/${repo}/blob/${pr.base.repo.default_branch}/CONTRIBUTING.md#getting-vouched).`,
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body,
});
core.info(`Labelled and commented on #${pr.number}.`);
manage:
# Role-gated before the runner starts. The action re-checks the commenter's
# permission authoritatively; this only stops anyone on the internet from
# spinning up a write-token runner by typing /vouch. `contains` rather than
# `startsWith` so a leading space doesn't silently drop a real command.
if: >-
github.event_name == 'issue_comment' &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'),
github.event.comment.author_association) &&
(contains(github.event.comment.body, '/vouch') ||
contains(github.event.comment.body, '/unvouch') ||
contains(github.event.comment.body, '/denounce'))
runs-on: ubuntu-latest
timeout-minutes: 10
# Keyed per comment. A shared group would let a newly queued run cancel a
# pending one, silently dropping a vouch; each run cuts its own branch, so
# there is nothing to serialise.
concurrency:
group: vouch-manage-${{ github.event.comment.id }}
cancel-in-progress: false
permissions:
contents: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: mitchellh/vouch/action/manage-by-issue@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
with:
issue-id: ${{ github.event.issue.number }}
comment-id: ${{ github.event.comment.id }}
vouch-keyword: /vouch
denounce-keyword: /denounce
unvouch-keyword: /unvouch
pull-request: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# The action is silent on every failure path — no comment, no reaction —
# and issue_comment runs are not surfaced on the issue timeline, so a
# maintainer would otherwise assume a failed /vouch worked.
- if: failure()
uses: actions/github-script@v9
with:
script: |
await github.rest.issues.createComment({
...context.repo,
issue_number: context.payload.issue.number,
body: `The vouch command failed. [See the run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) — the list was not changed.`,
});