Docs

Gates

Ring 1 pull request checks

The seven checks that need an open pull request: task link, Surface scope, test plan, evidence freshness, review verdicts, the Principal's tick and bare digits. The forge runs them; the local hooks never do.

7 checks
Ring 1
Pull request
CI

About pull request checks

Seven of Vinaya's 37 core checks judge a pull request and nothing earlier. Each one needs the open pull request's body, head or comments, so the local hooks skip it and the forge runs it.

Five implement generic gates (see Gates). Two are Vinaya-specific.

CheckImplements
closes-nVinaya-specific
surface-scopeScope
test-planEvidence completeness
evidence-freshEvidence freshness
review-gateReview validity
principal-test-plan-waitAuthorization and Decision validity
body-bare-digitsVinaya-specific

All seven report the same way as every check: exit 0 is a pass, and exit 1 with one or more error lines is a failure.

How they run

All seven belong to Ring 1. Two properties decide where each one actually runs:

  • Needs an open pull request (requiresOpenPr: true). vinaya check --all --local, the form the pre-commit and pre-push hooks use, skips the check.
  • Own workflow (ownWorkflow: true). vinaya check --all never selects the check. A dedicated workflow runs it by name instead.
CheckNeeds open PROwn workflowChecks PR writes (validates)Where it runs in CI
closes-nyesnobodyvinaya-checks.yml · vinaya check --all --diff-only
surface-scopeyesno—vinaya-checks.yml · vinaya check --all --diff-only
test-planyesnobodyvinaya-checks.yml · vinaya check --all --diff-only
evidence-freshyesnobodyvinaya-checks.yml · vinaya check --all --diff-only
review-gatenoyes—vinaya-review.yml · vinaya review gate; also vinaya-review-verdict.yml · vinaya review gate (verdict check)
principal-test-plan-waityesyesbodyvinaya-body-checks.yml · vinaya check principal-test-plan-wait
body-bare-digitsyesyesbodyvinaya-body-checks.yml · vinaya check body-bare-digits

The four workflows:

WorkflowTriggerCode it runs
vinaya-checks.ymlpull_request: opened, reopened, edited (no synchronize)The pull request's checkout, with the CLI build downloaded from the ci.yml run for the same head
vinaya-body-checks.ymlpull_request_target: opened, reopened, editedThe default branch, built in the job; never the pull request's code
vinaya-review.ymlpull_request_target: opened, synchronize, reopened, labeled, unlabeledThe default branch, built in the job
vinaya-review-verdict.ymlissue_comment: created, when the comment contains VERDICTThe default branch, built in the job

On Vinaya's pull-request writes

A check with validates: 'body' also runs when Vinaya writes a pull-request body, before the body reaches the forge.

  • vinaya pr create: no pull request number exists yet, so every check that needs an open pull request is skipped.
  • vinaya pr edit and vinaya pr report --push: the number is passed, and the full validates: 'body' set runs.

Checks

closes-n

A pull request must close the task its branch belongs to, so the branch and its Closes #N reference have to agree.

RefusesA pull request whose branch and Closes #N reference do not name the same task.
RunsCI: vinaya-checks.yml, inside check --all --diff-only. Write path: pr edit and pr report --push. Skipped on pr create, where the closesN brief section covers the same rule. Never in the hooks.
ReadsPR_BODY; BRANCH (falls back to git rev-parse --abbrev-ref HEAD); AEG_REPO (falls back to the origin remote); GITHUB_TOKEN or GH_TOKEN. From the forge: the branch's own tranche, and the task Issues that the body's Closes #N references name. Scope diff.
OutcomesPass. Fail with the gate's message. An unreachable forge leaves no tranche data, and the check then fails with "no topology file found".
ImplementsVinaya-specific
Settingschecks (replace or no-op). rings.ring1_forgeWriteInterception: false removes it from the write path only.

surface-scope

A task may only change the files its Issue's ## Surface allows, unless the pull request records the extra paths openly.

RefusesA changed path beyond the task Issue's ## Surface (inside an out: glob, or matching no in: glob) that the body's AEG:BEYOND-SURFACE block does not record. Also refuses, by name, when the scope cannot be determined.
RunsCI: vinaya-checks.yml, inside check --all --diff-only. Not on the write path. Never in the hooks.
ReadsBRANCH; BASE_SHA (default origin/main, then main); AEG_REPO; PR_BODY or PR_BODY_FILE; GITHUB_TOKEN or GH_TOKEN. The changed files from git diff --name-only <base>...HEAD. The tranche topology from the forge, to find the task's Issue (a backlog branch names its Issue directly), then that Issue's body via gh issue view.
OutcomesPass. Skip (exit 0) when the branch is not a task branch. Fail naming the first unrecorded beyond-Surface path and the glob it crosses. Fail when the repository cannot be resolved, the forge is unreachable, no Issue number is recorded for the task, the Issue body cannot be fetched, the Issue has no well-formed ## Surface, or its out: line has zero globs.
ImplementsScope
Settingschecks

test-plan

Every pull request must say how it was tested, and the Principal's and live items must actually be done.

RefusesA body with no ## Test Plan section (or the Test Plan: unit-tests-only sentinel). Unticked [principal] items. On a task branch, a [live] line of the task's frozen brief with no passed AEG:LIVE entry at the pull request's head.
RunsCI: vinaya-checks.yml, inside check --all --diff-only. Write path: pr edit and pr report --push. Never in the hooks.
ReadsPR_BODY; BRANCH; PULL_REQUEST_HEAD_SHA (the pull request's own head, never the merge ref); GITHUB_REPOSITORY (to read principals, which picks the brief's author); GITHUB_TOKEN or GH_TOKEN. On a task branch, the newest frozen aeg:brief:v<k> comment on the task Issue. Scope diff.
OutcomesPass. Fail (structural) for a missing Test Plan. Fail as pending for unticked [principal] items. Because the check is marked as owed by the Principal, a run whose only red is this pending failure reads green to check --all's exit code and to the loop's CI reader. Fail closed (never pending) when the frozen brief cannot be read, including a not-found Issue, or when the head has no body.
ImplementsEvidence completeness
Settingschecks; principals (read from the default branch)

evidence-fresh

The evidence in a pull request body must describe the code actually at its head, not an earlier version.

RefusesA body whose AEG:EVIDENCE block does not match the pull request's real head. Group A, the git diff --numstat, is recomputed and compared exactly. Group B, the attested check --all --diff-only, must name the current head on its Head: line, compared by patch id against the real base branch. Group C, the [agent] command headings, must equal the body's own Test Plan command list, in order. Commands are never re-run.
RunsCI: vinaya-checks.yml, inside check --all --diff-only. Write path: pr edit and pr report --push. Never in the hooks.
ReadsPR_BODY; PR_NUMBER; BASE_SHA (else origin/main, then main); GITHUB_TOKEN or GH_TOKEN. Head and base via gh pr view --json headRefOid,baseRefName. Pull request comments when the block is the placeholder. principals from the default branch. Scope diff.
OutcomesPass. Skip (exit 0) with no body, with no AEG:EVIDENCE anchor (the anchor is opt-in), or with no PR_NUMBER. Fail when the block sits inside <details>, when head and base cannot be resolved, when the merge base cannot be resolved, or when any group mismatches. A placeholder block fails once a Principal-authored aeg:developer:round-<n> marker comment exists; if that read fails, it counts as "a round may have happened".
ImplementsEvidence freshness
Settingschecks; principals (read from the default branch)

review-gate

Nothing merges without a valid Code review and Security review of the current code, or a Principal's waiver.

RefusesMissing, stale, superseded or unauthorized Code and Security verdicts. The gate wants a code-reviewer APPROVE and a security-review PASS bound to the current inputs, or the vinaya/waiver:review label applied by a principal. It judges reviews only: it reads no Test Plan state and no other check's result.
RunsCI: vinaya-review.yml, job vinaya review gate, from default-branch code. A first step reads comments and labels through the API, and fails without building when there is neither a line-anchored VERDICT: comment nor the waiver label. Re-evaluated by vinaya-review-verdict.yml when a verdict comment lands (see Gate mechanisms). Not in the hooks, not in check --all.
ReadsPR_NUMBER; GITHUB_REPOSITORY; GITHUB_TOKEN or GH_TOKEN. Comments, labels, the waiver label's actor, the head (by git ls-remote), base, policy, objectives version and frozen brief hash. Policy and principals come from the default branch. No BASE_SHA or other ref override, by design. Scope full.
OutcomesPass. Skip (exit 0) with no PR_NUMBER (locally, before a pull request exists). Fail closed when any input cannot be resolved. Fail when the review gate's verdict is fail.
ImplementsReview validity
Settingschecks; principals and reviewPolicy (both read from the default branch)

principal-test-plan-wait

Merge waits for the Principal: the pull request stays red until every [principal] item is ticked.

RefusesMerge while a [principal] Test Plan item is unticked, or, on a task branch, while a frozen-brief [live] line has no passed AEG:LIVE entry at the pull request's head. It grades only this half; a missing Test Plan is test-plan's job.
RunsCI: vinaya-body-checks.yml, job vinaya check principal-test-plan-wait, from default-branch code. A body edit (ticking a box) re-runs it. Also on the pr edit write path, where its pending-only failure never refuses the write. Not in the hooks, not in check --all.
ReadsPR_BODY; BRANCH; PULL_REQUEST_HEAD_SHA; GITHUB_REPOSITORY; GITHUB_TOKEN or GH_TOKEN; the task Issue's frozen brief. Uses the same Test Plan logic as test-plan. Scope diff.
OutcomesPass. Fail as pending, naming the unticked lines. Fail (blocking, not pending) when the frozen brief cannot be read or the head has no body. Deliberately not marked as owed by the Principal: its red stays red until the box is ticked.
ImplementsAuthorization and Decision validity
Settingschecks; principals (read from the default branch)

body-bare-digits

Numbers in pull request prose belong in code spans, so a bare digit in the narrative text is refused.

RefusesA bare digit in pull request narrative prose outside a fenced, indented or inline code span, after the check's built-in exemptions.
RunsCI: vinaya-body-checks.yml, job vinaya check body-bare-digits, from default-branch code. It needs pull_request_target because its Changesets exemption keys on PR_NUMBER, which a pull_request run's own YAML could redirect. vinaya pr create and vinaya pr edit also run the same logic directly on the body, without the exemption. Not in the hooks, not in check --all.
ReadsPR_BODY; PR_NUMBER; GITHUB_REPOSITORY; GITHUB_TOKEN or GH_TOKEN. For the exemption: the pull request's branch and author via gh pr view, and releaseActor from the default branch. Scope diff.
OutcomesPass. Skip (exit 0) for a Changesets release pull request (branch changeset-release/main authored by the release actor) or with no body. Fail once per violation, with its line.
ImplementsVinaya-specific
Settingschecks; releaseActor (read from the default branch)

Configuration

KeyEffect on these checks
checksAn entry whose key is exactly a check's name replaces it, for example with a no-op from vinaya new noop-check <id>.
rings.ring1_forgeWriteInterceptionfalse skips the validates: 'body' pass on the pull-request write path. The CI runs are unaffected.
principalsRead from the default branch by test-plan, evidence-fresh, review-gate and principal-test-plan-wait.
reviewPolicyRead from the default branch by review-gate.
releaseActorRead from the default branch by body-bare-digits for its exemption.

What gets logged

Every run through the check runner records one gate · summary event, and one gate · checked event per check that did not pass. A pass or skip records nothing of its own.

gate · checked carries check, check_version, policy_version (planned; written as null), input_fingerprint (a hash of the check's environment and changed files, never the values), outcome, a short reason code and duration_ms.

In vinaya-checks.yml the job sets VINAYA_UNATTENDED and VINAYA_LOG_TOKEN, so events go to the logs.url destination that the default branch's vinaya.config.json declares. With no token (for example on a fork's pull request) nothing is recorded for that run. The other three workflows set neither variable. Nothing is posted to GitHub as a log. See Events for every field.

What's next