Skip to content

Add branch protection rules to main #97

@PAMulligan

Description

@PAMulligan

Description

Configure GitHub branch protection on main so direct pushes, force-pushes, and accidental deletions are blocked, and so PRs cannot merge until CI is green.

Why

While shipping v0.6.0 a malformed release tag (v0.5.1) had to be rolled back with git push --force-with-lease +<sha>:main. That was only possible because main is currently unprotected — and would have been irrecoverable if anyone else had already pulled the bad state. As contributor count grows (or once external PRs start landing) the cost of an accidentally-broken main rises sharply. The stable-release contract advertised by the v1.0.0 milestone needs guarantees behind it, so this should land before v1.0 ships.

Acceptance Criteria

Configure the main branch (Settings → Branches, or via gh api -X PUT repos/PMDevSolutions/Nerva/branches/main/protection):

  • Block force-pushes (allow_force_pushes: false)
  • Block deletion (allow_deletions: false)
  • Require a pull request before merging
  • Require status checks to pass before merging, with these checks required (names from .github/workflows/ci.yml):
    • Validate Structure
    • Node.js 20 Compatibility
    • Node.js 22 Compatibility
    • Type-check Embedded Templates
    • Check Markdown Links
  • Require branches to be up to date before merging
  • Require linear history (matches the current squash-merge workflow used by gh pr merge --squash)
  • Allow admin/maintainer bypass so urgent rollbacks remain possible
  • Document the protection rules in CONTRIBUTING.md so contributors know what CI must pass before review

Release-workflow compatibility

The current .github/workflows/release.yml pushes directly to main (git push --follow-tags origin ${{ github.ref_name }}) after commit-and-tag-version creates a bump commit and tag. Once "require PR before merge" is enabled this push will fail unless we make a deliberate choice:

  • Option A — bypass: Add the github-actions[bot] actor (or a dedicated release PAT) to the branch-protection bypass list. Smallest change; keeps current release.yml working as-is.
  • Option B — release PR: Switch to a release-PR flow (e.g. release-please, or have release.yml open a PR from a release/vX.Y.Z branch and auto-merge once CI is green). Cleaner audit trail, more moving parts.
  • Option C — unprotected release branch: Run the release on a separate branch (e.g. release) that doesn't have PR-required protection, then merge to main. Mostly a worse hybrid.

Decide between these as part of this issue. A is the pragmatic default for a small team; B is the right end state if/when the project grows beyond solo maintenance.

Out of scope

  • CODEOWNERS configuration
  • Signed-commit enforcement (required_signatures) — probably premature pre-1.0
  • Required reviews from specific reviewers (single-maintainer project for now)
  • Protecting non-main branches (release/*, staging, etc. — only relevant if Option B/C is chosen)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions