Skip to content

Rolling it out

A gate that blocks people on day one gets disabled on day two. The pieces are designed to be turned on in order.

1. Observe

Merge the workflow and the policy with the mergeproof status not required. Every PR gets the comment and the status; nothing is blocked. Watch for a week: which rules fire, which globs are wrong, which requirement nobody understands. Fix the policy, not the contributors.

Rule severity says what the comment reports; a merge is blocked only once the status is required. So the core rules can be block from the start and the report stays truthful.

2. Require

Add mergeproof to the required checks on the default branch. From now on the rules marked block gate merges. Keep the heuristics, such as "the changelog changed", at severity: warn; they nudge without stopping a hotfix.

3. Grow the policy from evidence

Add a rule when a class of regression slips through, and say in its description what it caught. Remove or soften a rule that only ever produces noise.

Replay before you tighten

"Will this rule annoy people" has a factual answer. replay rebuilds the context of pull requests that already merged, evaluates a policy against each, and says what would have been blocked:

GITHUB_TOKEN=$(gh auth token) mergeproof replay --policy mergeproof.yaml --last 50 --repo OWNER/NAME
 #128  2026-09-14  pass     feat(search): paginate results
 #127  2026-09-13  fail     fix(tools): empty query no longer raises
       needs  fix-or-feature-has-live-evidence · before/after traces
 #126  2026-09-13  pass     docs: rollout guide
 ...
50 merged pull requests in OWNER/NAME; the policy applied to 31 and would have blocked 9
most common blockers:
    7  fix-or-feature-has-live-evidence · before/after traces
    2  tool-change-has-integration-tests · integration tests touched

--against current evaluates the policy on the default branch as well and marks the verdicts that change, so a proposed tightening shows only what it adds. --since 2026-08-01 bounds by date, -f json is for scripts, and --fail-on-block makes it usable as a CI check on policy PRs.

Replay is read-only. It needs a token that can read the repository; nothing is posted. Checks that call out, such as verified links, run for real, so an old trace that has since expired shows as unverified, which is itself worth knowing.

--save scenarios/ writes one file per pull request in the shape the examples use, so the cases that mattered become fixtures the test suite runs forever.

What to require in the first policy

Two rules carry most of the value in a codebase that agents change:

- id: module-change-has-integration-tests
  when: { paths: ["src/tools/**/*.py"] }
  require:
    - check: tests.changed
      with:
        map: { "src/tools/{name}.py": "tests/integration/tools/test_{name}*.py" }
        existing_only: true          # only where the module already has a test file

- id: fix-or-feature-has-live-evidence
  when: { paths: ["src/**/*.py"], title: "^(fix|feat)(\\(.+\\))?!?: " }
  require:
    - check: evidence.links
      with: { key: traces, min_pairs: 1, verify: braintrust }
    - check: review.human_verified

Everything else can start as a warning. For repositories where prompts or models change, the third rule worth having early is an evaluation bar on prompts/** (braintrust.eval or langfuse.eval), so a prompt tweak comes with a golden-dataset run that is still above it.