akw-factory-floorsfloors.akwlabs.com · ga

Protection on main

main cannot be committed to or pushed into — locally or on the web.

main moves only when a pull request merges. Two layers enforce that, and both are in version control.

The server

.github/rulesets/main.json is the protection itself. It is a file so that what guards the branch is reviewable, diffable and reappliable — a protection configured by clicking in a web form has no history, and nothing disagrees with it when it drifts.

npm run protect        # apply the ruleset, then verify
npm run protect:check  # verify only; change nothing
Rule Effect
pull_request a direct push is refused; main moves by merge only
non_fast_forward no force-push
deletion main cannot be deleted
required_linear_history no merge commits, so squash or rebase

No required status checks, on purpose

Actions are blocked org-wide, so a required check would never report and main would be permanently unmergeable. A gate that can never go green is not stricter; it is broken.

The gates run locally instead — npm run check — and the pull request template asks for their output rather than trusting a claim. Read the comment at the top of scripts/protect-main.sh before adding a required check.

The clone

The server refuses a push. It cannot refuse a commit, so without a local hook the work still lands on the local main and has to be moved afterwards — which is the moment people reach for a bypass.

Hook Refuses
pre-commit a commit made while main is checked out
pre-push a push whose destination ref is main or master
commit-msg a subject that is not one Conventional Commit type, or is over 72 characters

pre-push reads the destination ref from git's stdin rather than the current branch, because git push origin HEAD:main is the spelling that actually causes the accident and a current-branch check misses it entirely.

Verify, do not assume

npm run protect:check reads the effective rules for refs/heads/main from the API — what git will actually evaluate — rather than confirming that a ruleset object exists:

gh api repos/akwlabs-ai/akw-factory-floors/rules/branches/main --jq '[.[].type]|sort'

tests/hooks.test.ts asserts each hook in both directions: that it blocks, and that it stays quiet otherwise. A guard that blocks everything looks identical to a working guard from the passing side.

The one legitimate override

AKW_ALLOW_MAIN_COMMIT=1 lets pre-commit through, for replaying history during a repair you have already decided on. There is no override for pre-push, and none for the server.