24b3516c1d
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 38s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m8s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 3m15s
Universal: PR Check / Secret Scan (pull_request) Successful in 3m5s
Generic: Project CI / Tests (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Org-level branch protection was already consulted at the single enforcement choke point `GetFirstMatchProtectedBranchRule`, but only as a FALLBACK: if any repo-level rule matched the branch, the org rule was ignored entirely. That let a repo define a looser rule for a pattern and effectively opt out of the org's protection. Make the choke point LAYER the two rules instead: when both an org rule and a repo rule match a branch, return their most-restrictive (fail-closed) combination, so the org rule is a mandatory floor a repo can only tighten. - models/git/protected_branch_merge.go: mergeMostRestrictive + helpers. Allow flags AND'd; gate/require/block flags OR'd; RequiredApprovals max'd; required sets (status contexts, protected files) unioned; allow sets (whitelists, unprotected files) intersected. A disabled allowlist means "everyone", so it only constrains when enabled. - models/git/protected_branch_list.go: GetFirstMatchProtectedBranchRule now fetches both the repo rule and the org rule and merges when both match; returns whichever exists when only one matches. Org lookup factored into getFirstMatchOrgProtectedBranchRule. Supersedes the materialization approach previously proposed for this issue — the org fallback already existed, so only this one function needed to change. Fail-closed by design: any merge edge errs toward MORE protection (over-restrict) rather than less, so it cannot open a hole. Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here — relying on CI to validate build, formatting, and tests. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT