git / moonbite-network / deploy/BRANCH-PROTECTION.md · raw

# Branch protection for `main` (checklist)Why this matters here: `deploy.yml` auto-deploys `main` to the production hostthat serves the wallet and `/downloads`. The `workflow_run` gate makes deployrun only *after* CI succeeds — but that gate protects **deploy**, not **merge**.Without a branch-protection rule, a red-CI or force-pushed commit can still landon `main`; branch protection is what stops an unvalidated commit from becoming`main` in the first place. The two together mean: **`main` only ever advancesthrough a CI-passing commit, and prod only ever runs a CI-validated commit.**Apply this in the GitHub web UI (no local change; it's a repo setting). Repo:`moonbitecoin/moonbite-network`. You need admin on the repo, and a working`gh`/web session — note the invalid `GITHUB_TOKEN` env var breaks `gh`, so usethe browser or `unset GITHUB_TOKEN` first.---## Do this first: run CI once so the check names existRequired-status-check pickers only list checks GitHub has **seen recently**. PRs#2/#3 should have produced them. The CI job is `test` on a 3.11/3.12 matrix, sothe checks are named:- `test (3.11)`- `test (3.12)`If the dropdown doesn't show them yet, let one CI run finish on a PR, then comeback.---## Option A — Branch protection rule (classic)**Settings → Branches → Add branch protection rule.**- [ ] **Branch name pattern:** `main`- [ ] **Require a pull request before merging** — blocks direct pushes to `main`.      - [ ] Required approvals: **0** (solo maintainer — GitHub won't let you            approve your own PR; set 0 so you aren't locked out. Raise to 1 only            once a second trusted maintainer exists.)      - [ ] *(optional)* Require conversation resolution before merging- [ ] **Require status checks to pass before merging**      - [ ] Require branches to be **up to date before merging** (forces a            re-run against the latest `main`, so a check can't pass stale)      - [ ] Select the required checks: **`test (3.11)`** and **`test (3.12)`**            (these include the pytest suite *and* the release-checksum guard,            both steps of the `test` job)- [ ] **Do not allow bypassing the above settings** (a.k.a. "Include      administrators") — **on**. This is what actually guarantees the deploy      safety for a solo admin; otherwise you can push past your own rule and the      gate means nothing. (You can toggle it off for a genuine emergency, then      back on.)- [ ] **Allow force pushes:** **off** (never force-push `main` — matches the      project rule; a force-push rewrites what prod will deploy)- [ ] **Allow deletions:** **off**- [ ] *(optional, recommended given release signing)* **Require signed commits**- [ ] *(optional)* **Require linear history** (if you prefer rebase/squash merges      and no merge commits on `main`)Leave everything else default. Save.---## Option B — Repository ruleset (newer equivalent)GitHub's **Settings → Rules → Rulesets → New branch ruleset** is the modernform and can target `main` by pattern with the same effect:- [ ] **Enforcement status:** Active- [ ] **Target branches:** Default branch (`main`)- [ ] **Bypass list:** empty (add nobody — the equivalent of "include      administrators")- [ ] Rules:      - [ ] Restrict deletions      - [ ] Block force pushes      - [ ] Require a pull request before merging (required approvals: 0 for solo)      - [ ] Require status checks to pass → add `test (3.11)` and `test (3.12)`,            and tick **Require branches to be up to date**      - [ ] *(optional)* Require signed commits / linear historyRulesets and a classic rule can coexist; pick one to avoid confusing overlap.---## Fastest path: one `gh` command block (do this, then verify)- [ ] Run exactly this (requires `gh auth status` = `moonbitecoin`, admin on the repo;      `unset GITHUB_TOKEN GH_TOKEN` first if a stale token is exported):```bashunset GITHUB_TOKEN GH_TOKENgh auth status# require CI (both matrix legs, up to date), require a PR, enforce for admins,# no force-push, no deletegh api -X PUT repos/moonbitecoin/moonbite-network/branches/main/protection \  -H "Accept: application/vnd.github+json" --input - <<'JSON'{"required_status_checks":{"strict":true,"contexts":["test (3.11)","test (3.12)"]}, "enforce_admins":true, "required_pull_request_reviews":{"required_approving_review_count":0,"dismiss_stale_reviews":true}, "restrictions":null,"allow_force_pushes":false,"allow_deletions":false,"required_linear_history":false}JSONgh api repos/moonbitecoin/moonbite-network/branches/main/protection \  --jq '{checks:.required_status_checks.contexts, strict:.required_status_checks.strict, admins:.enforce_admins.enabled, pr:.required_pull_request_reviews.required_approving_review_count, force:.allow_force_pushes.enabled, delete:.allow_deletions.enabled}'```- [ ] Expected: `checks` lists both legs, `strict:true`, `admins:true`, `pr:0`,      `force:false`, `delete:false`.## Optional: apply via API instead of the UI (same call, longer form)After clearing the bad token (`unset GITHUB_TOKEN GH_TOKEN`) and confirming`gh auth status` shows `moonbitecoin` active:```bashgh api -X PUT repos/moonbitecoin/moonbite-network/branches/main/protection \  --input - <<'JSON'{  "required_status_checks": {    "strict": true,    "contexts": ["test (3.11)", "test (3.12)"]  },  "enforce_admins": true,  "required_pull_request_reviews": { "required_approving_review_count": 0 },  "restrictions": null,  "allow_force_pushes": false,  "allow_deletions": false}JSON```Verify:```bashgh api repos/moonbitecoin/moonbite-network/branches/main/protection \  --jq '{checks: .required_status_checks.contexts, admins: .enforce_admins.enabled, force: .allow_force_pushes.enabled}'```---## After enabling — sanity check- [ ] Open a throwaway PR that deliberately fails CI (e.g. a broken test) and      confirm the **Merge** button is blocked until checks pass.- [ ] Confirm a direct `git push origin main` is rejected (PR required).- [ ] Confirm `deploy.yml` still fires only after a green CI run on `main`      (it deploys `github.event.workflow_run.head_sha`).## Not covered here (separate hardening, see RELEASE-SIGNING.md and deploy.yml)- Non-root deploy user with a narrow sudoers entry.- SHA-pinned GitHub Actions and signed-tag-only deploys.- The minisign release-signing key ceremony — **OPEN, owner-only task**: no  signing key exists yet and none should be generated by automation or an  agent. Until the ceremony is done, `publish-node-binaries.sh` emits  checksums without a signature and `deploy/moonbite-release.pub` is a  placeholder. Track it as a launch blocker in LAUNCH_CHECKLIST.md.