Skip to content

Configuration

Store repository policy in .github/voucha.yml on the default branch or merge target branch. VOUCHA reads that merge-target file for every PR, so a PR cannot weaken its own gate by editing config in the feature branch.

All fields are optional. Invalid fields fall back to their defaults rather than breaking the whole policy file.

Copy templates/voucha.yml when a repository wants the built-in defaults committed explicitly. The default template uses draft_prs: ignore, so draft PRs stay quiet until they are marked ready for review. Copy templates/contributing-policy.md when the repository also wants maintainer language for AI-assisted or otherwise low-accountability PRs.

gates:
- type: multiple_choice
questions: 4
pass_threshold: 3
path_rules:
- paths: ["src/core/**", "migrations/**", ".github/workflows/**"]
gates:
- type: multiple_choice
questions: 6
pass_threshold: 5
require_approval: always
max_attempts: 2
cooldown_minutes: 30
min_changed_lines: 0
skip_paths: []
include_paths: ["src/core/**", "migrations/**"]
exemptions:
- type: author_login
logins: [octocat]
- type: author_association
associations: [CONTRIBUTOR]
- type: repository_permission
permissions: [write, maintain, admin]
- type: github_team
teams: [maintainers, octo-org/security]
- type: prior_merged_prs
min_count: 3
- type: linked_issue_match
require_same_repo: true
require_trusted_signal: true
min_match_score: 0.7
max_issues: 5
trusted_labels: [accepted, ready]
signals:
- type: honeypot
report_only: true
- type: code_honeypot
report_only: true
patterns: ["VOUCHA_DO_NOT_ADD_THIS"]
paths: ["src/**", "infra/**"]
require_approval: first_time
max_attempts: 3
cooldown_minutes: 0
draft_prs: ignore
trust:
default_author_associations: [OWNER, MEMBER, COLLABORATOR]
vouch:
enabled: false
file: .github/VOUCHED.td
accountability:
require_pr_acknowledgement: false
require_ai_disclosure: false
confirmation:
webauthn: true
bot_policy:
default: skip
trusted_logins: ["dependabot[bot]", "renovate[bot]"]
rechallenge:
on_push: included_paths
ignore_paths: ["docs/**", "*.md"]
questions: 2
min_changed_lines: 10
skip_paths: ["docs/**", "*.md"]
include_paths: []
context:
strategy: adaptive
investigator: auto
map_tokens: 8000
detail_tokens: 24000
max_files: 12
max_model_calls: 3
ignore_paths: ["dist/**", "*.lock"]
large_pr:
changed_files: 100
changed_lines: 5000
max_context_tokens: null
output:
comments: normal
labels:
passed: false
failed: true
flagged: true
contributor_message: null
enforcement:
auto_close:
enabled: false
outcomes: [failed_assisted, failed_final]
Area Fields What it controls
PR attestation gates The challenge type, question count, and passing threshold.
Scope skip_paths, include_paths, min_changed_lines, path_rules Which PRs should skip, enter, or receive stricter policy.
Trust trust, exemptions, bot_policy Which default author associations, Vouch statuses, authors, teams, repository roles, contributor history, bots, and planned issues can avoid a challenge or be rejected.
Approval and retry require_approval, accountability, max_attempts, cooldown_minutes, draft_prs, rechallenge Human approval, required PR-body accountability fields, drafts, retry limits, cooldown, and new-commit behavior.
Confirmation confirmation.webauthn Whether contributors may enroll passkeys and use an established passkey when an otherwise-correct result needs confirmation.
Passive evidence signals, output.labels Honeypot fields, code canaries, and flagged-pass labels.
Investigation context, max_context_tokens How PR evidence is condensed before quiz generation.
Reporting output.comments, output.contributor_message, output.labels PR comment volume, contributor-facing wording, and best-effort labels.
Enforcement enforcement.auto_close Optional PR auto-close behavior after terminal hard failures.

The current shipped gate is multiple_choice.

gates:
- type: multiple_choice
questions: 4
pass_threshold: 3

questions accepts 1 through 10. pass_threshold accepts 1 through 10 and is capped at the question count.

Legacy top-level pass_threshold still works when gates is omitted. New configs should use gates[0].pass_threshold.

skip_paths exempts a PR only when every changed file matches. include_paths turns VOUCHA into opt-in scope: when non-empty, a PR is exempt unless at least one changed file matches.

skip_paths: ["docs/**", "*.md"]
include_paths: ["src/core/**", "packages/runtime/**"]

min_changed_lines exempts tiny diffs based on additions plus deletions. Keep it low enough that a multi-file behavior change cannot hide behind it.

path_rules apply the first matching override to the effective policy. They can override gates, require_approval, max_attempts, cooldown_minutes, min_changed_lines, skip_paths, and include_paths.

path_rules:
- paths: ["src/auth/**", "migrations/**"]
require_approval: always
gates:
- type: multiple_choice
questions: 6
pass_threshold: 5

The glob implementation is intentionally small: ** matches path segments and * matches inside one segment. Other characters are literals.

require_approval accepts first_time, always, or never.

  • first_time: first-time or unknown GitHub authors need /voucha approve.
  • always: every challenged PR needs maintainer approval.
  • never: the challenge is served as soon as it is ready.

draft_prs accepts challenge, neutral, or ignore.

  • challenge: drafts follow normal policy.
  • neutral: drafts get a visible neutral check and no challenge.
  • ignore: drafts produce no VOUCHA check. This is the default.

max_attempts accepts 1 through 10. cooldown_minutes accepts 0 or greater and defaults to 0, so retries are immediate unless a repository configures a base wait. Repeated failed or abandoned attempts use 1x, 2x, 4x, then at most 8x that value. Every retry receives a fresh quiz, while reopening an unfinished attempt resumes the existing quiz.

Use exemptions for explicit trust decisions:

exemptions:
- type: author_login
logins: [octocat]
- type: author_association
associations: [CONTRIBUTOR]
- type: repository_permission
permissions: [write, maintain, admin]
- type: github_team
teams: [maintainers, octo-org/security]
- type: prior_merged_prs
min_count: 3

Owners, members, and collaborators are trusted by default through trust. Set the list to [] when they should take the challenge too:

trust:
default_author_associations: []

Vouch answers whether a project community already trusts a contributor to participate. VOUCHA answers whether an author understands a particular PR. Enable the integration to compose those decisions:

trust:
vouch:
enabled: true
file: .github/VOUCHED.td

VOUCHA reads the configured Trustdown file from the merge target. Vouched authors skip the challenge, unknown authors continue through normal VOUCHA policy, and denounced authors receive a failed check. A missing or unreadable file fails closed to the normal gate. Passing VOUCHA does not modify the Vouch list or turn a one-PR result into a durable contributor endorsement.

See Vouch integration for status precedence, Trustdown compatibility, security boundaries, and rollout checks.

author_login and author_association add repository-specific trust. repository_permission reuses GitHub’s collaborator permission API, matching both role_name values such as maintain, admin, and custom repository roles, and legacy permission values such as write or read. If GitHub cannot resolve access, VOUCHA falls back to the gate.

github_team trusts active members of named GitHub teams. Bare team slugs use the repository owner; org/team-slug can point at a specific organization. This requires the GitHub App to have Members read permission. roles is optional and defaults to both team members and team maintainers.

exemptions:
- type: github_team
teams: [maintainers, octo-org/security]
roles: [member, maintainer]

prior_merged_prs trusts authors after enough merged PRs in the same repository:

exemptions:
- type: prior_merged_prs
min_count: 3

Both exemptions fail closed: when GitHub cannot resolve membership or search history, the PR falls through to the normal gate.

Bots are controlled separately:

bot_policy:
default: skip
trusted_logins: ["dependabot[bot]", "renovate[bot]"]

default: challenge challenges bot authors except trusted named logins. Legacy skip_bots maps into this setting when bot_policy is omitted. Legacy skip_authors still works as an author allowlist, but new configs should prefer exemptions: [{ type: author_login, ... }].

accountability can require the PR body to include explicit responsibility fields before VOUCHA creates a quiz.

accountability:
require_pr_acknowledgement: true
require_ai_disclosure: true

With both options enabled, the PR body must include a checked acknowledgement and an AI assistance line:

- [x] I understand, tested, and can support this change.
AI assistance: yes

Use yes, no, n/a, or none for the disclosure value. Start from templates/pull_request_template.md so contributors see the required fields before they open the PR.

WebAuthn is enabled by default and remains optional for contributors:

confirmation:
webauthn: true

Set webauthn: false when the repository wants independent maintainer confirmation only. VOUCHA then hides passkey enrollment and confirmation, rejects all passkey registration and authentication endpoints for challenges created under that policy, and directs the author to a write-capable maintainer who is not the PR author. The maintainer comments /voucha confirm on the PR.

Existing credentials are not deleted. They remain usable in repositories that enable WebAuthn. Like the rest of the challenge policy, this value is loaded from the merge target and stored with the challenge, so a feature branch cannot enable its own passkey path. confirmation is repository-wide and cannot be overridden inside path_rules.

linked_issue_match exempts planned work only when the linked issue has maintainer approval evidence and the configured LLM says the PR semantically matches it.

exemptions:
- type: linked_issue_match
require_same_repo: true
require_trusted_signal: true
min_match_score: 0.7
max_issues: 5
trusted_labels: [accepted]

VOUCHA discovers normal closing references such as Fixes #123, Closes owner/repo#123, and GitHub issue URLs. With the defaults, the issue must be in the same repository. A maintainer or collaborator author is approval evidence. For any other issue, approval evidence can be either a current assignment of the PR author or a configured trusted_labels value that is currently present. GitHub’s issue-event history must show that a user with write, maintain, or admin access performed the assignment or applied the label. Assigning someone other than the PR author, self-assignment, and stale assignment events do not count.

min_match_score is the 0–1 semantic score returned by the configured LLM; 0.7 requires a clear match without demanding identical wording. max_issues caps how many closing references VOUCHA evaluates, in PR-body order (default 5, maximum 10).

If the issue, approval evidence, or model result is unavailable, cross-repo, or weakly related, the PR falls through to the normal gate instead of failing.

signals defaults to the form honeypot:

signals:
- type: honeypot
report_only: true

Set signals: [] to disable passive honeypot collection. Supported passive signals are forced report-only even if the config says otherwise.

code_honeypot scans added diff lines for maintainer-authored literal canaries:

signals:
- type: code_honeypot
report_only: true
patterns: ["VOUCHA_DO_NOT_ADD_THIS"]
paths: ["src/**", "infra/**"]

patterns supports up to 20 non-empty strings. paths defaults to ["**"] and can contain up to 50 glob patterns.

rechallenge controls whether new commits invalidate a previous pass:

rechallenge:
on_push: included_paths
ignore_paths: ["docs/**", "*.md"]
questions: 2

VOUCHA compares the latest passed head with the incoming head and evaluates only that commit delta. on_push accepts never, always, or included_paths:

  • never carries the prior pass to every later head.
  • always resets the gate for any non-ignored delta.
  • included_paths resets only when the delta touches the effective include_paths; if that list is empty, any non-ignored delta resets it.

ignore_paths lets low-risk deltas carry the pass forward and excludes those files from mixed follow-up quiz evidence. A reset stores the passed head as its baseline and generates an up-to-questions-long follow-up quiz from only the commits after that head. First-time approval carries forward within the PR; require_approval: always still requires approval for the follow-up. If a comparison cannot safely produce a normal ahead-only delta, VOUCHA falls back to a full-PR challenge instead of silently preserving the pass. Legacy rechallenge_on_push: true maps to on_push: always when rechallenge is omitted.

output controls PR noise and labels:

output:
comments: normal
labels:
passed: false
failed: true
flagged: true
contributor_message: >
Thanks for contributing, {{author}}. You have {{max_attempts}} attempts;
use {{challenge_url}} when you're ready.

comments accepts quiet, normal, or detailed. The nested labels switches independently control VOUCHA:passed, VOUCHA:failed, and VOUCHA:flagged. VOUCHA removes stale outcome labels as the check moves between passing and failing states. The flagged label remains defense in depth for passed legacy/imported records with strong automation evidence; inconclusive signals never add it.

Use the nested object form shown above; scalar boolean values are invalid and fall back to the default outcome-specific settings.

contributor_message optionally replaces the opening text in an active challenge comment (including after maintainer approval or a retry). It accepts Markdown and the {{author}}, {{max_attempts}}, and {{challenge_url}} placeholders. VOUCHA retains the challenge link and outcome wording so the PR always has an unambiguous next step and status.

enforcement.auto_close is off by default. When enabled, VOUCHA closes the PR after configured terminal hard-failure outcomes; it never closes retryable failures, neutral service failures, drafts, or superseded challenges.

enforcement:
auto_close: true

The shorthand above closes PRs for both supported auto-close outcomes: failed_assisted and failed_final. Use the object form to narrow it:

enforcement:
auto_close:
enabled: true
outcomes: [failed_final]

Auto-close is best-effort. If GitHub rejects the close request, the VOUCHA check still stays failed and the PR comment asks maintainers to review manually.

context.strategy: adaptive is the normal mode. VOUCHA first builds an investigation artifact from PR metadata, file map, and selected patch evidence, then generates the quiz from that artifact.

context:
strategy: adaptive
investigator: auto
map_tokens: 8000
detail_tokens: 24000
max_files: 12
max_model_calls: 3
ignore_paths: ["dist/**", "*.lock"]
large_pr:
changed_files: 100
changed_lines: 5000

investigator accepts auto, worker, or flue. auto uses the main Worker for normal PRs and the Flue investigator for large PRs when configured. flue requires a configured Flue investigator; if it is missing or fails, quiz generation reports neutral rather than falling back to raw large-diff generation.

context.ignore_paths removes low-signal files from quiz evidence without changing whether the PR is challenged.

max_context_tokens is a legacy/direct-generation cap used by context.strategy: truncate. Keep it null unless you intentionally want that older truncation path.

Field Default
gates [{ type: "multiple_choice", questions: 4, pass_threshold: 3 }]
path_rules []
signals [{ type: "honeypot", report_only: true }]
exemptions []
require_approval first_time
trust { default_author_associations: ["OWNER", "MEMBER", "COLLABORATOR"] }
max_attempts 3
cooldown_minutes 0
draft_prs ignore
accountability { require_pr_acknowledgement: false, require_ai_disclosure: false }
confirmation { webauthn: true }
bot_policy { default: "skip", trusted_logins: [] }
rechallenge { on_push: "included_paths", ignore_paths: ["docs/**", "*.md"], questions: 2 }
min_changed_lines 10
skip_paths ["docs/**", "*.md"]
include_paths []
context adaptive Worker/Flue auto selection with 8000 map tokens, 24000 detail tokens, 12 files, and large PR threshold of 100 files or 5000 changed lines
output { comments: "normal", labels: { passed: false, failed: true, flagged: true }, contributor_message: null }
enforcement { auto_close: { enabled: false, outcomes: ["failed_assisted", "failed_final"] } }