Org rules

Org rules are your team's merge rules for agent-written code, written once in a file Verity checks on every pull request. Each rule says which files it covers, what must be true about a change to them, and whether a miss fails the check or only warns.

Verity answers each rule from the diff and the agent's session. It never reads the PR description, because the agent wrote it: a "flag exemption" line or a ticked checkbox is not evidence.

The rules file

Rules live in .verity/rules.yml in the repository. verity capture init writes a starter file from your repository's files, every rule a warning. Verity reads the file at the tip of the pull request's base branch, so a pull request can't weaken the rules it is checked against, and rule changes go through normal review.

version: 1
rules:
  - id: ci-config-changed          # files only: a change is the finding
    files: [".github/workflows/**"]
    severity: warn
    why: "Build and CI changes affect every PR."

  - id: tests-after-edit           # a built-in fact, decided by rules
    files: ["src/**/*.ts"]
    exclude: ["src/**/*.test.ts"]
    check: tests_passed_after_last_edit
    expect: yes
    severity: fail
    why: "Code changes need a passing test run."

  - id: auth-flagged               # a question, answered by a model
    files: ["src/auth/**"]
    ask: "Is the new or changed behaviour behind a feature flag?"
    about: diff
    expect: yes
    severity: fail
    why: "Auth changes must be switchable off in production."

Fields

FieldRequiredMeaning
versionyesAlways 1.
idyesLowercase kebab-case, at most 64 characters, unique in the file. It names the override label.
filesyesGlobs matched against the pull request's changed paths. A rename uses its new path.
excludenoGlobs to leave out. Use this instead of ! patterns.
checknoA built-in fact, from the list below.
pathswith check: session_readGlobs for the files whose reading is the finding. See What the agent did.
commandswith check: session_ranCommand patterns, such as git reset --hard, at most 200 characters each. See What the agent did.
asknoA yes/no question, at most 500 characters. Not with check.
aboutwith askWhat the question is answered from: diff (the default) or session.
expectwith check or askThe answer that passes: yes or no.
severityyesfail fails the check run; warn only reports.
no_sessionnoFor rules that need a session (a session fact, or about: session), what happens when the pull request has no session: fire (the default) or pass.
unsurewith askWhat an unsure answer does on a fail rule: warn (the default) or fail. See Unsure answers.
whyyesAt most 300 characters. Shown on the pull request.
fixnoAt most 500 characters: how a coding agent fixes the rule when it fires. Given to agents through Verity's MCP tools, never shown on the pull request. See Fixes for agents.

A file holds at most 50 rules.

How a rule is answered

A rule has one of three kinds of condition, from most to least exact:

  • Files only (no check or ask): changing a matching file is the finding.
  • A built-in fact (check): decided by rules from the session or the diff, with no model, so it is never unsure. See Built-in facts.
  • A question (ask): answered by a small classification model, from the file's diff or from the session. Use it only where judgement is needed. A question about the session is sent what the session did after the file's last edit (commands, browser calls and the files it read), and what the human wrote before it. To ask whether the agent ever did something, at any point, use session_read or session_ran instead: see What the agent did.

Verity answers each matched file separately, so a finding names the file: "Views/Edit.cshtml: no page load after the last edit". At most 30 files are judged per rule; any more fire.

Rules fail closed. Any answer other than the one you expect fires the rule: "no", an unsure answer, no answer at all (the model couldn't be reached), or no session when the rule needs one. When a fail rule fires only because the answers were unsure, it shows as a warning and doesn't fail the check. To have it fail the check instead, set unsure: fail: see Unsure answers.

Rules apply to every pull request in the repository, including ones written by people.

Unsure answers

By default an unsure answer to a fail rule's question is a warning. The model couldn't tell, so a reviewer looks. For a rule where doubt should block, such as one about secrets, set unsure: fail. Then an unsure answer fails the check like "no" does, and the usual override label lets it through. A comment in the code that argues with the rule ("this is not a secret") can make the model unsure, but it can't turn such a rule into a warning.

version: 1
rules:
  - id: no-secret-leak
    files: ["src/**"]
    ask: Does this change put a secret (a key, token or password) in the code, or log data that can carry one?
    expect: "no"
    severity: fail
    unsure: fail
    why: Secrets in code or logs leak.

When Verity couldn't read the session at all (your organization's bucket was unreachable, or the sessions were too large), a session question's answer stays a warning even with unsure: fail. A problem on Verity's side never blocks a pull request.

Built-in facts

A check names one of these. A file fact is answered for each matched file. A PR fact is answered once for the whole pull request: the rule's files only decide when it applies, and every matched file gets the same answer. A fact that needs a session answers "no session linked" without one, which fires unless the rule says no_session: pass.

checkScopeNeeds a sessionYes when
tests_passed_after_last_editfileyesA test passed after the last edit to every changed part of the file. Docs, lockfiles, comment-only changes and the like need no run.
ran_after_last_editfileyesAs above, but a build, typecheck or lint counts too.
page_loaded_after_last_editfileyesAfter the file's last edit, the session made a browser tool call, or a playwright test or cypress run passed.
edited_by_sessionfileyesThe session wrote part of the file, or a command it ran possibly did.
pr_changes_testsPRnoThe pull request changes at least one test file (by the test globs in the configuration).
session_readPRyesAt any point in the pull request's part of the session, the agent read a file matching the rule's paths, with a tool or a command such as cat.
session_ranPRyesAt any point in the pull request's part of the session, the agent ran a command matching one of the rule's commands.

Two examples:

version: 1
rules:
  # Migrations are written by people.
  - id: migrations-by-people
    files: ["db/migrations/**"]
    check: edited_by_session
    expect: no
    no_session: pass
    severity: fail
    why: "Schema changes are written and reviewed by a person."
  # A billing change comes with a test change.
  - id: billing-tests
    files: ["src/billing/**"]
    check: pr_changes_tests
    expect: yes
    severity: warn
    why: "Billing changes need a test that shows them."

What the agent did

session_read and session_ran check the agent's own actions: whether it read a secrets file, or ran a destructive command or one that skips the git hooks. They look at everything the session did for this pull request, before and after its edits, and no model is involved. Use them with expect: no.

version: 1
rules:
  - id: no-secret-files-read
    files: ["**"]
    check: session_read
    paths: ["**/.env*", "**/*.pem", "**/credentials*"]
    expect: "no"
    severity: fail
    no_session: pass
    why: "Anything an agent reads can end up in a prompt, a log or the code."
  - id: no-destructive-commands
    files: ["**"]
    check: session_ran
    commands: ["git reset --hard", "git clean -f", "rm -rf", "git push --force", "git push -f"]
    expect: "no"
    severity: warn
    why: "Destructive commands can lose work; a reviewer should know."
  - id: no-hook-bypass
    files: ["**"]
    check: session_ran
    commands: ["git commit --no-verify", "git commit -n", "git push --no-verify"]
    expect: "no"
    severity: fail
    no_session: pass
    why: "Hooks exist to stop bad commits."

paths are globs like files, matched against paths in the repository: write **/.env* to match .env files in every folder. Files outside the repository aren't recorded, so they can't be matched.

A command pattern is the program's name, then words its arguments must contain, in any order:

  • A short flag matches whether its letters are bundled or apart: rm -rf matches rm -fr dist and rm -r -f dist, but not rm -r dist.
  • A long flag matches itself or itself with a value: --force matches --force and --force=true, but not --force-with-lease. List that separately if you mean it.
  • Any other word must be an argument exactly: git reset --hard matches git reset --hard origin/main, but not git reset --soft.

A command's quoted text is read as a value, so echo "git reset --hard" doesn't match, but a commit message that is exactly --no-verify does. sudo is looked through (sudo rm -rf x matches rm -rf). Environment variables (HUSKY=0) aren't matched. A command Verity can't parse is matched on its words, so it can't hide a match.

These facts can't be fixed after the fact, so a fired rule has no default fix: a reviewer decides, and overrides it if the action was fine.

On the pull request

The comment and the check run both show:

  • a tally in the heading, such as "❌ 1 failing rule · ⚠️ 2 rule warnings · ✅ 3 passed rules";
  • each failing rule with its override label;
  • a table of every rule that applied (❌ failing, ⚠️ warning, ☑️ overridden, ✅ passed), with the question and expected answer of each rule that fired, and the files it fired on;
  • the rules that matched no file, folded.

The check run fails when a fail rule fires and no override covers it. Otherwise it is neutral, which GitHub treats as passing.

A pull request that changes more than 3,000 files also fails in a repository with rules, titled "Too many files to check rules": GitHub doesn't list that many files, so Verity can't check them. A reviewer overrides it with the label verity-override:too-many-files.

Overrides

When a rule fires but the change is fine, a reviewer adds the label verity-override:<rule id> to the pull request, for example verity-override:auth-flagged. The check run updates.

  • The label counts only when a GitHub user who isn't the pull request's author adds it. A bot, or the author, can't override.
  • A pull request approval never counts as an override.
  • An agent using a person's GitHub token looks like that person.
  • An override lasts until the label is removed, even across later pushes.

Fixes for agents

An agent reading a failing rule through Verity's MCP tools gets a fix: what to do about it. It is the rule's own fix when the rules file has one:

version: 1
rules:
  - id: razor-page-loaded
    files: ["**/*.cshtml"]
    check: page_loaded_after_last_edit
    expect: yes
    severity: fail
    why: "The build doesn't compile views."
    fix: >
      Start the app (dotnet run --project src/Web), load the route that renders this
      view with Playwright, and check it returns 200 and shows the edited markup.

Without one:

  • A built-in fact has a default: run the covering tests, run a build or test, load the page, or add a test. edited_by_session, session_read and session_ran have none, since what the session did can't change afterwards.
  • Any other rule tells the agent that a person has to review it. The agent must not add the override label or change code just to make the rule pass, and arguing in the pull request doesn't help, because Verity doesn't read it.
  • A warn rule tells the agent to mention it to the developer.

Write a fix only where an agent can really do it. A rule that needs a person's judgement, such as an API contract change, is better left without one.

Make rules block merging

Mark the Verity check as required in the branch's protection rules on GitHub. A fired fail rule then blocks the merge until it is fixed or overridden. warn rules never block.

Start with every rule set to warn, watch what fires for a week or two, then switch the ones you trust to fail.

An invalid rules file

  • A pull request that makes the file invalid fails the check, with the problems listed. Nothing overrides that: fix the file.
  • An invalid file already on the base branch (possible only by bypassing the check): no rules are checked, the check run says so, and it stays neutral, so the pull request that fixes the file can merge.