docs: document main branch protection ruleset - #1408
Merged
Conversation
Applied a GitHub ruleset on main via the API (PR-required, no force-push, no deletion, admin bypass for the repo owner). Only "Labeler - Labeler" is a required status check: flux-local/image-pull/validate.yaml all path-filter at the workflow trigger level, so they never post a check outside their paths and would permanently block any PR that doesn't touch a matching path if required as-is. Documents the live-measured proof and the follow-up needed to safely add them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Turn on branch protection for main in Aviator-Coding/home-ops: required green checks before merge, so a red PR can never land again (one already did). Today main has zero protection and zero rulesets. Apply it as a GitHub ruleset via gh api (already done live, ruleset id 21250320, verified by API read-back), and document the applied configuration in a small docs page in the repo (docs/branch-protection.md) - that documentation is this task's PR; the ruleset itself was applied via API as part of this task, not through this PR's diff.
Key constraint: required status checks interact badly with path-conditional workflows - a required check that never starts on a PR blocks that PR forever. I enumerated which of this repo's PR-triggered workflows run on EVERY pull request versus only on path-filtered subsets: flux-local.yaml and image-pull.yaml trigger only on kubernetes/** paths, validate.yaml triggers only on talos/, bootstrap/, .renovate/, .renovaterc.json5, kubernetes/apps/system-upgrade/, scripts/ci/, and .mise.toml (all filtered at the on:pull_request:paths: trigger level, so the workflow never even starts, and posts no check at all, for a PR outside those paths). labeler.yaml is the only PR-triggered workflow with no path filter on its trigger, so it is the only check proven to always post a resolvable status (success, or skipped-counts-as-passing via its same-repo fork guard) on every PR. I verified this empirically against real merged PRs via the GitHub API (not just by reading the YAML): PR #1400 (docs-only, 2026-08-23) posted only the Labeler check; PR #1390 (talos-only, before validate.yaml existed) posted only the Labeler check - this is the historical 'one check' case referenced in the task; PR #1399 (kubernetes/apps/ change) posted Flux Local, Image Pull, and Labeler checks; validate.yaml's own recent runs (its own workflow-runs history) confirm it correctly triggers for PRs that do touch its scoped paths (e.g. a currently open renovate/kubectl-1.x PR touching .mise.toml).
Given that evidence, I deliberately required only 'Labeler - Labeler' (GitHub Actions app, integration_id 15368) as the required status check in the ruleset, and did NOT require flux-local/image-pull/validate's checks, because doing so today would permanently block every PR that doesn't touch their specific paths (proven by the #1400 and #1390 examples above) - exactly the trap the task warned about. I documented this decision, the concrete evidence, and the follow-up needed to close the gap (moving path filtering from the workflow's on:pull_request:paths: trigger down into a job-level check, the same way labeler.yaml's job-level fork-guard already works, so the workflow's check always posts and can then be safely required) in docs/branch-protection.md.
Other ruleset rules applied: pull_request (require a PR before merging main, required_approving_review_count: 0 since this is a solo-maintainer repo where the owner merges their own PRs), non_fast_forward (block force-push), deletion (block branch deletion). Bypass: actor_type RepositoryRole, actor_id 5 (Admin), bypass_mode always - equivalent to 'the repo owner is never hard-locked' since Aviator-Coding is the sole admin collaborator on this personal (non-org) repo, verified via the collaborators API. The merge bot app/mortyops (Renovate) was deliberately given no bypass entry, so required_status_checks now formally gates it too (in addition to its own existing ignoreTests:false automerge setting) - this is intentional per the task and is documented as such.
No test PRs were created to probe this - verification was done by reading the ruleset back via the API (gh api repos/Aviator-Coding/home-ops/rulesets/21250320, confirmed enforcement: active) and by reasoning from/measuring real workflow trigger history against already-merged PRs, per the task's explicit instruction not to probe with test PRs.
Also added a one-line pointer to the new doc in docs/reference.md's index table, and a row in AGENTS.md's WHERE TO LOOK table (this repo's CLAUDE.md is a symlink to AGENTS.md) summarizing where branch protection lives and why only Labeler is required today, since this is durable knowledge future sessions will want (AGENTS.md already documents other GitHub Actions path-filter gaps in its NOTES section, e.g. the postBuild.substitute collision and the flux-local blind spot, so this fits the file's existing pattern).
What Changed
docs/branch-protection.md, documenting the GitHub ruleset now applied tomain(id21250320): rules (deletion,non_fast_forward,pull_requestwith 0 required approvals,required_status_checks), the admin bypass actor, and the full applied JSON payload.Labeler - Labeleris required today -flux-local.yaml,image-pull.yaml, andvalidate.yamlall path-filter at theon: pull_request:trigger level and never post a check outside their paths, backed by a table of real merged-PR check results (docs(rook-ceph): update CephX rotation changelog with PR #1398 and Rook v1.20.6 status #1400, fix(talos): sync machineconfig installer pin to v1.13.9 #1390, fix(monitoring): genericize Talos1MemoryPressure alert off talos-1/48GB pin #1399) - and describe the follow-up (move path filtering to job level) needed to safely require them..github/workflows/README.md's "Why" note and Branch Protection Issues troubleshooting list to point at the new doc, and add pointer rows inAGENTS.md's WHERE TO LOOK table anddocs/reference.md's index.Risk Assessment
✅ Low: Docs-only change (branch-protection.md + two index pointers) with no code or config touched; I independently verified every factual claim against the live GitHub ruleset, collaborators API, and actual check-run history for the three cited PRs, and all matched exactly.
Testing
This is a documentation-only change with no application code or automated test suite to run, so validation consisted of fact-checking every empirical claim in docs/branch-protection.md against live state: the GitHub ruleset API read-back matches the doc's payload exactly, the three cited PRs' actual posted check names match the doc's evidence table exactly, and the workflow trigger path-filters/fork-guard behavior described matches the current YAML. All claims verified true; no discrepancies found.
Evidence: Live GitHub API verification of ruleset and PR check-history claims
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
gh api repos/Aviator-Coding/home-ops/rulesets/21250320 — live ruleset matches the doc's "Full applied payload" JSON block byte-for-byte (rules, bypass_actors, required_status_checks all identical)gh pr view 1400 --json statusCheckRollup -q '.statusCheckRollup[].name' — confirms PR #1400 (docs-only) posted onlyLabeler - Labeler, as claimed in the doc's evidence tablegh pr view 1390 --json statusCheckRollup -q '.statusCheckRollup[].name' — confirms PR #1390 (talos-only, pre-validate.yaml) posted onlyLabeler - Labelergh pr view 1399 --json statusCheckRollup -q '.statusCheckRollup[].name' — confirms PR #1399 (kubernetes/apps/** change) posted the full Flux Local + Image Pull + Labeler check setRead .github/workflows/flux-local.yaml, image-pull.yaml, validate.yaml, labeler.yaml — confirmed trigger-levelpaths:filters (kubernetes/**, and validate.yaml's 7-item list) match the doc exactly, and labeler.yaml has no trigger path filter with a job-level fork guard as describedgh api repos/Aviator-Coding/home-ops/collaborators -q '.[] | {login, permissions: .permissions.admin, role: .role_name}' — confirms Aviator-Coding is the sole admin collaborator, supporting the bypass_actors claimgrep ignoreTests .renovate/autoMerge.json5 — confirmsignoreTests: falseon all three automerge rules as cited in the docls -la CLAUDE.md AGENTS.md — confirmed CLAUDE.md is a symlink to AGENTS.md as the doc's cross-references assumeVerified all relative markdown links in docs/branch-protection.md resolve to existing files (.github/workflows/*.yaml)git status --short — clean working tree, no stray files from this change or from verification✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.