ADR-0018: Branch protection is advisory, not enforced
ADR-0018: Branch protection is advisory, not enforced
- Status: Accepted
- Date: 2026-07-30
- Amends: ADR-0015
- Supersedes: —
- Superseded by: —
Context
ADR-0015 gives agents full autonomy on a branch and none to merge, and lists branch protection on main as the mechanism that enforces it. That consequence turned out to be unachievable as written.
On GitHub’s free tier, both protection mechanisms are unavailable for private repositories. Verified empirically on 30 July 2026 against psifive/tabletop-tracker:
| Mechanism | Result |
|---|---|
PUT /repos/{owner}/{repo}/branches/{branch}/protection | 403 — Upgrade to GitHub Pro or make this repository public |
POST /repos/{owner}/{repo}/rulesets | 403 — Upgrade to GitHub Pro or make this repository public |
Three ways out: make the repository public, pay $4/month for GitHub Pro, or accept that the rule is unenforced.
Decision
Keep the repository private and accept advisory enforcement.
- The rule in ADR-0015 stands unchanged: agents never push to
mainand never merge. - A committed
pre-pushhook at.githooks/pre-pushblocks pushes tomain, enabled withgit config core.hooksPath .githooks. - The hook is a mitigation, not a control. It is client-side, bypassable with
--no-verify, and inert untilcore.hooksPathis set.
Rationale
The repository is private because the roadmap and build plan are not something to publish pre-launch, and that preference is worth more than the enforcement gap it creates. $4/month is small in absolute terms, but the £0 constraint in docs/architecture.md §1 is a deliberate discipline and the Apple fee is meant to remain the only compulsory recurring spend.
The gap is narrower than it looks. Agents in this project act on AGENTS.md and docs/standards/the-loop.md, both of which state the rule prominently; the realistic failure is an accidental push, not a deliberate one, and the hook catches accidents. What is genuinely lost is the guarantee — and the honest thing is to record that rather than let the delivery system read as if main were protected when it is not.
Consequences
mainis not protected. A push to it will succeed if the hook is unconfigured or bypassed. Anyone readingdocs/standards/ci-and-gates.mdshould not assume otherwise.- WI-003 can no longer verify branch protection. Its DoD is amended to verify the hook instead, which is a weaker check for a weaker guarantee.
git config core.hooksPath .githooksbecomes a setup step indocs/standards/environment.mdand part of WI-001. A fresh clone without it has no protection at all — this is the mitigation’s main weakness.- CI still gates merges into
mainonce WI-003 lands. What is unenforced is the bypass — pushing straight past the PR — not the gate itself. - A push to
mainis a process failure and should be treated as one, reviewed like any other incident, regardless of whether the hook caught it.
Revisiting
This decision should be revisited on any of these triggers, and the answer may well change:
- A second contributor joins — convention across two people is materially weaker than across one.
- The repository goes public at launch, at which point protection is free and this ADR is superseded.
- An accidental push to
mainactually happens, which would be evidence the mitigation is insufficient.
Alternatives considered
Make the repository public. Free, and fully restores enforcement. Rejected: the roadmap and build plan should not be world-readable before launch. Reconsider at launch.
GitHub Pro at $4/month. Restores enforcement while staying private, and is under half the Apple fee already accepted. Rejected for now on the £0 principle rather than the amount — but this is the option to take if the repository gains a second contributor while still private.