Writing a policy¶
The policy is mergeproof.yaml at the repository root. It is read from the pull request's base
branch, so a pull request cannot change the rules that apply to it.
version: 1
project: my-service # optional label
evidence_block: evidence # info string of the fenced block in the PR description
rules:
- id: source-needs-tests # unique, appears in every report
description: Source changes ship with tests for the touched module.
severity: block # block (default) or warn
when: # all listed conditions must hold; lists are "any of"
paths: ["src/**/*.py"]
exclude_paths: ["src/**/__init__.py"]
instructions: > # free text shown next to the requirements
A regression test that fails before the change and passes after it.
require:
- check: tests.changed
name: unit tests touched # label in reports; defaults to the check id
severity: block # overrides the rule's severity for this requirement
with: # parameters, validated against the check's schema
map: { "src/{pkg}/{name}.py": "tests/**/test_{name}*.py" }
instructions: ... # overrides the rule's instructions for this requirement
mergeproof validate checks the file: unknown checks and bad parameters are reported with the
rule and requirement they belong to. A JSON schema gives editors the
same knowledge as you type; mergeproof init puts its $schema line at the top of the file.
when: which changes a rule applies to¶
| key | meaning |
|---|---|
paths |
at least one changed file matches one of these globs |
exclude_paths |
files matching these are ignored when evaluating paths |
labels |
the PR carries at least one of these labels |
title |
the PR title matches this regex (case-insensitive) |
base_branches |
the target branch matches one of these globs |
authors |
the PR author is one of these logins, e.g. copilot[bot] |
A rule without when applies to every pull request. Globs support **, *, ? and {name}
captures; a capture in a paths glob is available to tests.changed templates.
Severity and verdict¶
Each requirement ends in a status: pass, fail, warn, pending (evidence not there yet:
CI still running, reviewer has not verified), skip (not applicable), error (misconfigured or
crashed). The report's verdict is:
| verdict | when |
|---|---|
fail |
a block requirement failed or errored |
pending |
no failure, but a block requirement is pending |
warn |
only warn-severity requirements are unmet |
pass |
everything is satisfied |
fail and pending block the merge when the mergeproof status is required.
The evidence block¶
Evidence lives in the PR description as a fenced YAML block whose info string is the policy's
evidence_block (default evidence). Several blocks merge; later keys win. Invalid YAML is reported
rather than ignored.
```evidence
environment: staging
image: registry.example.com/app:pr-77-4444444
links:
- what: search with an empty query
before: https://logs.example.com/run/1
after: https://logs.example.com/run/2
```
mergeproof template prints the block a change still needs, with placeholders.
Inheritance: extends¶
One organisation policy, many repositories. A policy may extend one or more bases and adjust them:
extends: github:acme/policies/python-service.yaml@v1 # or a list; later bases win
project: billing-api
rules:
- id: source-needs-tests # same id as a base rule: replaces it entirely
when: { paths: ["src/**/*.py"] }
require:
- check: tests.changed
with: { map: { "src/billing/{name}.py": "src/billing/tests/test_{name}*.py" } }
- id: changelog # switch a base rule off
enabled: false
- id: payments-need-owner-label # add one of your own
when: { paths: ["src/billing/payments/**"] }
require:
- check: pr.labels
with: { any_of: ["payments-approved"] }
Two forms of base:
| form | meaning |
|---|---|
github:OWNER/REPO/path/file.yaml@REF |
fetched from GitHub; REF must be a commit sha or a version tag (v1, 2.3.0). A branch is refused, so a pull request cannot change its own gate by pushing to the policies repository. Add #sha256=<digest> to pin the content as well; then any ref is accepted and the digest is checked |
path:relative/file.yaml |
a file in the same repository, relative to the extending file; for monorepos |
Resolution is deterministic: bases in order, then the local file; project and evidence_block
from the last file that sets them; rules keyed by id. Bases may extend other bases, five
levels deep at most, and cycles are errors. Bases pinned by sha or digest are cached under
~/.cache/mergeproof (or MERGEPROOF_CACHE_DIR), so CI does not refetch them. Private policy
repositories are read with GITHUB_TOKEN.
mergeproof validate lists which file each rule came from; mergeproof explain says it next
to the rule heading. The worked example is
examples/shared-policy.
Checks¶
Every check and its parameters is listed in the checks reference.
Verifiers¶
A verifier turns a link into yes or no. http ships in core. Others are plugins registered under
the mergeproof.verifiers entry-point group; see Plugins and the Langfuse example
in examples/plugins/.