Skip to content

docs: document main branch protection ruleset - #1408

Merged
Aviator-Coding merged 2 commits into
mainfrom
fm/homeops-branch-protection
Aug 23, 2026
Merged

docs: document main branch protection ruleset#1408
Aviator-Coding merged 2 commits into
mainfrom
fm/homeops-branch-protection

Conversation

@Aviator-Coding

Copy link
Copy Markdown
Owner

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

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
gh api repos/Aviator-Coding/home-ops/rulesets/21250320 -> id 21250320, enforcement "active", rules [deletion, non_fast_forward, pull_request(required_approving_review_count:0), required_status_checks([Labeler - Labeler, integration_id 15368])], bypass_actors [{RepositoryRole, actor_id 5, always}] -- matches docs/branch-protection.md's "Full applied payload" JSON block exactly.

gh pr view 1400 --json statusCheckRollup -q '.statusCheckRollup[].name' -> "Labeler - Labeler" (only)
gh pr view 1390 --json statusCheckRollup -q '.statusCheckRollup[].name' -> "Labeler - Labeler" (only)
gh pr view 1399 --json statusCheckRollup -q '.statusCheckRollup[].name' -> Flux Local (Filter/Test/Diff x2/Success), Image Pull (Filter/Extract x2/Diff/Pull/Success), Labeler - Labeler
All three match the doc's evidence table exactly.

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 only Labeler - Labeler, as claimed in the doc's evidence table
  • gh pr view 1390 --json statusCheckRollup -q '.statusCheckRollup[].name' — confirms PR #1390 (talos-only, pre-validate.yaml) posted only Labeler - Labeler
  • gh pr view 1399 --json statusCheckRollup -q '.statusCheckRollup[].name' — confirms PR #1399 (kubernetes/apps/** change) posted the full Flux Local + Image Pull + Labeler check set
  • Read .github/workflows/flux-local.yaml, image-pull.yaml, validate.yaml, labeler.yaml — confirmed trigger-level paths: 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 described
  • gh 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 claim
  • grep ignoreTests .renovate/autoMerge.json5 — confirms ignoreTests: false on all three automerge rules as cited in the doc
  • ls -la CLAUDE.md AGENTS.md — confirmed CLAUDE.md is a symlink to AGENTS.md as the doc's cross-references assume
  • Verified 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.

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.
@mortyops mortyops Bot added the area/github label Aug 23, 2026
@Aviator-Coding
Aviator-Coding merged commit 7d6b2b1 into main Aug 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant