Branch Protection Plan (SOC 2) β verified, apply-ready
Status: verified against live runtime identities + GitHub ruleset mechanics.
Apply main + staging Variant A now (zero automation risk). Variant B (real
review gate) needs the GitHub App-token migration first β see Β§5.
Targets: Formal Code Review Process, Only Authorized Employees Change Code,
Production Code Changes Restricted.
1. Verified identities (the constraint)
Section titled β1. Verified identities (the constraint)β| Path | Who actually does it | Healthy? |
|---|---|---|
staging release Version-PR merge |
github-actions[bot] (default GITHUB_TOKEN) β release.ymlβs ${RELEASE_PAT || github.token} is always github.token because RELEASE_PAT is never mapped from secrets. |
yes |
staging feature PRs |
humans (dboone31), PR UI |
yes |
main FF promotion |
github-actions[bot] β git push β¦:refs/heads/main on manual dispatch |
yes |
main promotion merges |
dboone31 (admin), UI |
yes |
Davidβs RELEASE_PAT |
dead / unwired β no workflow reads it, so post-org-move auth is irrelevant | n/a |
Hard constraint: the default GITHUB_TOKEN cannot be added to a ruleset
bypass list for a pull_request rule. No workflow mints a custom App token
today. So a require-review rule on either branch would break the bot with no
bypass β unless the automation moves onto a custom App token first (Β§5).
The existing ruleset
6222454(βPull-Requests Onlyβ) is not truly inert β it already carries the Drata-shaped rules (pull_requestcount 0 +required_status_checksβVerify pnpm supply-chain controlsβ + bypass admin) but with an emptyinclude[](targets nothing). Disable it to avoid auditor confusion:gh api -X PUT repos/ERP-Unlocked/ordermatic/rulesets/6222454 -f enforcement=disabled.
2. Design
Section titled β2. Designβmainβ restrict push, NO require-PR. Rulesdeletion+non_fast_forward; bypass = admin (RepositoryRole 5) + bot (Integration 15368). The FF push survives (a fast-forward isnβt blocked bynon_fast_forward, and thereβs no require-PR rule). Satisfies Production Code Changes Restricted.stagingVariant A (now, zero risk):required_status_checks(βVerify pnpm supply-chain controlsβ + βChangeset requiredβ) +non_fast_forward, review count 0. The botβs self-merge keeps working. Satisfies Only Authorized Employees via restricted history + required check.stagingVariant B (later):required_approving_review_count: 1β the real review gate that Formal Code Review Process most wants β but only after Β§5 (App token), or it hard-breaks the release auto-merge.
3. Apply now (both keep every automation working)
Section titled β3. Apply now (both keep every automation working)β# main β restrict pushgh api -X POST repos/ERP-Unlocked/ordermatic/rulesets --input - <<'JSON'{ "name":"main - production restricted","target":"branch","enforcement":"active", "conditions":{"ref_name":{"include":["refs/heads/main"],"exclude":[]}}, "rules":[{"type":"deletion"},{"type":"non_fast_forward"}], "bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}, {"actor_id":15368,"actor_type":"Integration","bypass_mode":"always"}] }JSON# staging β Variant Agh api -X POST repos/ERP-Unlocked/ordermatic/rulesets --input - <<'JSON'{ "name":"staging - reviewed integration","target":"branch","enforcement":"active", "conditions":{"ref_name":{"include":["refs/heads/staging"],"exclude":[]}}, "rules":[{"type":"deletion"},{"type":"non_fast_forward"}, {"type":"required_status_checks","parameters":{"strict_required_status_checks_policy":false, "do_not_enforce_on_create":false, "required_status_checks":[{"context":"Verify pnpm supply-chain controls"},{"context":"Changeset required"}]}}], "bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}, {"actor_id":15368,"actor_type":"Integration","bypass_mode":"always"}] }JSONIf a POST 422s on the 15368 entry, drop that one line and re-POST β the admin
entry + absence of a require-PR rule is what keeps the bot working.
4. Verify + rollback
Section titled β4. Verify + rollbackβ# static check: main has NO pull_request rule; both target correctlygh api repos/ERP-Unlocked/ordermatic/rulesets --jq '.[]|select(.enforcement=="active")|.id' \ | while read id; do gh api repos/ERP-Unlocked/ordermatic/rulesets/$id \ --jq '{name,target:.conditions.ref_name.include,rules:[.rules[].type]}'; done# live: confirm the latest release auto-merge still succeededgh run list --workflow=release.yml -L3 --json conclusion,displayTitle# rollback (reversible): disable a misbehaving rulesetgh api -X PUT repos/ERP-Unlocked/ordermatic/rulesets/<ID> -f enforcement=disabled5. Variant B unlock = the GitHub App-token migration (shared with the release plan)
Section titled β5. Variant B unlock = the GitHub App-token migration (shared with the release plan)βTo require a real review on staging (and, if Drata insists on review evidence
on the default branch, on main), the release/promotion automation must stop
using the default GITHUB_TOKEN:
- Create an org GitHub App (Contents R/W, Pull-requests R/W); install on the repo.
- Mint a token via
actions/create-github-app-tokeninrelease.ymlβsauto-merge-stagingjob (andauto-deploy-main.ymlif review-on-main is needed). - Add
{actor_id:<APP_ID>, actor_type:"Integration", bypass_mode:"always"}to the ruleset bypass and setrequired_approving_review_count: 1.
This is also the fix for the dead RELEASE_PAT config β see
../RELEASING.md. One App token, two problems solved.