omnigraph/docs/dev/branch-protection.md
Andrew Altshuler 81b66f9427
ci: run Test Workspace only on main, not on pull requests (#212)
The full workspace + failpoints suite was the slowest PR gate (~15min
warm, up to the 75min cold ceiling) and dominated PR turnaround. Gate the
`test` job with `if: github.event_name != 'pull_request'` so it runs only
on push to `main` (post-merge), on `v*` tags, and on manual
`workflow_dispatch`. `RustFS S3 Integration` needs `test`, so it becomes
push-/dispatch-only by the same cascade.

Drop `Test Workspace` from the required-check list in
branch-protection.json: a required context that never reports on PRs (the
job no longer runs there) would leave every PR permanently pending — the
job-never-reports trap the policy already documents.

Trade-off accepted deliberately (chosen by the maintainer): a regression
the suite would catch now lands on `main` and reddens the post-merge run
instead of being blocked pre-merge, so `main` can briefly break. Mitigations
documented in ci.md: run `cargo test --workspace --locked` locally before
merging non-trivial changes (or trigger the workflow on your branch via
workflow_dispatch), and regenerate openapi.json locally for server/API
changes (the auto-regen step lived in the now-PR-skipped test job).

The fast PR gates remain: Classify Changes, Check AGENTS.md Links, the
AWS-feature build/test, and the two CODEOWNERS checks.

NOTE: an admin must run ./scripts/apply-branch-protection.sh after this
merges, or GitHub keeps requiring the now-unreported Test Workspace context.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 19:23:41 +03:00

5.5 KiB

Branch protection on main

main is gated by a declarative branch-protection policy. The source of truth is .github/branch-protection.json; the apply mechanism is scripts/apply-branch-protection.sh. Re-running the script with a changed JSON is idempotent.

This page explains what the policy says and how to change it.

Current policy

Setting Value Why
Required status checks (strict) Classify Changes, Check AGENTS.md Links, Test omnigraph-server --features aws, CODEOWNERS matches source, CODEOWNERS not hand-edited Every PR must pass the AWS-feature build/test, AGENTS.md link integrity, and the CODEOWNERS hygiene checks. Test Workspace is deliberately NOT required — it runs only on push to main (post-merge), tags, and manual workflow_dispatch, to keep PR turnaround fast (it was the ~15min+ slow gate). It is therefore not listed here: a required check that never reports on PRs (the test job is if: github.event_name != 'pull_request') would leave every PR permanently pending — the same job-never-reports trap the CODEOWNERS contexts call out below. The trade-off (a regression lands on main and is caught by the post-merge run, so main can briefly go red) and its mitigations are documented in ci.md. The two CODEOWNERS contexts must equal the job name: values in .github/workflows/codeowners.yml verbatim — a context naming a job that never reports (the old CODEOWNERS / drift used the job id, and the job was path-filtered) leaves every PR permanently pending and forces admin overrides. strict: true requires the branch to be up-to-date with main before merge.
Required approving reviews 1 At least one reviewer. With a 2-person team, going higher would block all merges when one person is unavailable.
Require code-owner reviews true The reviewer must be a code owner per .github/CODEOWNERS. This is what makes the codeowners chassis enforced.
Dismiss stale reviews on new commits true A push after approval invalidates the prior review. Prevents the "approve, then sneak in unreviewed changes" pattern.
Require linear history true No merge commits — squash or rebase only. Matches recent practice.
Disallow force pushes true No history rewrites on main.
Disallow branch deletions true main cannot be deleted.
Required conversation resolution true All review comment threads must be resolved before merge.
Enforce on admins false Admins can override the gates (enforce_admins: false in the JSON). This is the intended escape hatch for the 2-person team; tightening to true is tracked under hardening below.
Required signed commits not yet Not enabled. Would lock out maintainers until everyone enrolls GPG/SSH commit signing. Tracked as a follow-up.

How to apply

Run from the repository root:

./scripts/apply-branch-protection.sh

The script reads .github/branch-protection.json, strips the human-readable _comment field (the GitHub API rejects unknown keys), and PUTs to repos/ModernRelay/omnigraph/branches/main/protection.

Requires gh authenticated with a token that has admin permissions on the repository.

To preview without applying:

DRY_RUN=1 ./scripts/apply-branch-protection.sh

How to change the policy

  1. Edit .github/branch-protection.json.
  2. Open a PR. The JSON change goes through normal review.
  3. After the PR merges, an admin runs ./scripts/apply-branch-protection.sh to push the new policy to GitHub.

The script is not run automatically by CI. Branch-protection changes are admin actions that should be applied deliberately — a CI-driven automatic apply would mean any merged PR could rewrite protection rules, which defeats the purpose. The script's existence makes the apply reproducible; the admin's manual invocation is the audit point.

How to read the current GitHub state

gh api repos/ModernRelay/omnigraph/branches/main/protection

Outputs the live policy. Compare against .github/branch-protection.json to detect drift.

Why declared as code

  • Audit trail: git log .github/branch-protection.json shows every change with a reviewable diff and a merge commit.
  • Disaster recovery: if branch protection is accidentally removed or weakened via the UI, the JSON is the canonical recovery point.
  • Consistency: pairs with .github/codeowners-roles.yml (the CODEOWNERS source of truth). Repository policy lives in the repository.

What this gates

After branch protection is applied, every PR targeting main must:

  1. Pass all listed status checks.
  2. Be up-to-date with main (rebase or merge-from-main).
  3. Have at least one approving review from a code owner for the touched paths.
  4. Have all review conversations resolved.
  5. Be squash- or rebase-merged (no merge commits).

Even repository admins are subject to these rules.

Subsequent hardening (not in this PR)

The branch-protection policy is the foundation. Future hardening adds:

  • Required signed commits (required_signatures: true) — once maintainers enroll GPG/SSH signing.
  • Tag protection for v* tags via repos/.../tags/protection.
  • Required reviewers from specific teams for high-leverage paths (e.g., docs/dev/invariants.md) via CODEOWNERS tier expansion + the N-unique-approvers CI workaround.
  • More required CI checks: cargo deny, cargo audit, cargo fmt --check, cargo clippy -D warnings, CodeQL, secret scanning, schema-lint (MR-946).

See the hardening playbook for the full plan.