Skip to content

A repo protected more strictly than the policy is reported as drift, and its fix would lower protection #64

Open

Description

@ahrazzle

A repo protected more strictly than the policy is reported as drift, and its fix would lower protection

ion/analyze/policy.py:84-93 compares each branch-protection field with !=:

elif bp.get("require_pr") and rule.get("requiredApprovingReviewCount", 0) != bp.get("required_approvals", 0):
    diffs.append("required_approvals")
...
if bool(rule.get("isAdminEnforced")) != bool(bp.get("enforce_admins")):
    diffs.append("enforce_admins")

The default policy is require_pr: true, required_approvals: 0, enforce_admins: false. Any repo that requires one or more approvals, or enforces rules for admins, is therefore drift. The policy_fix that follows carries the policy's settings, which would weaken that repo's protection. lowers_protection(current_rule, fix.settings) returns ['lowers the required approval count'], or ['stops enforcing rules for admins'].

What happens next depends on the action level:

  • fix (as in the SPEC example policy_actions: {branch_protection: fix, ...}): the fix is allowlisted, and the preflight sees a lowering. forbid_protection_lowering then aborts the whole --apply run before any write. Every unrelated approved proposal is blocked on every run, for as long as one repo is stricter than the policy.
  • issue: ion files a proposal asking maintainers to approve weakening that repo's protection. If someone approves it, the org-wide abort above follows.

The same path is reachable from a repo's own .github/ion.yml asking for weaker settings, for example policy: {branch_protection: {require_pr: false}}. PR #61 validates the types in that block but leaves this alone.

Repro, written against the test fixtures: make alpha-core's rule requiredApprovingReviewCount: 2 and set policy_actions: {branch_protection: fix}. Approve an unrelated archive of old-tool, then run_execute(apply=True). The result is circuit breaker: a branch-protection change would lower protection: lowers the required approval count, and old-tool is not archived.

Suggested fix, which needs one decision from a maintainer (that branch-protection policy is a minimum):

  1. Report drift only when the rule is weaker than the policy: fewer approvals, admins not enforced while the policy enforces them, force pushes or deletions allowed while the policy forbids them.
  2. Build the fix settings as the per-field stricter of the current rule and the policy, so a fix can only raise protection.
  3. In gather_candidates, never propose a branch_protection fix for which lowers_protection(current_rule, settings) is non-empty. Record it as a finding instead.
  4. Optionally, have the preflight drop just the offending fix rather than abort the run, so one repo cannot stop execution for the whole org.

Related, not the same: #3 (the PUT drops required status checks) and #12 (the first matching rule instead of the merged rules).

Automated posting by agentic team with human oversight.

Implementation draft awaiting independent code review: #114

Activity

  1. ahrazzle commented on Oct 21, 2026

    @ahrazzle
    ContributorAuthor

    Status after the 2026-10-21 triage pass over all open issues: narrowed by #50, core defect still open.

    #50 narrowed what a repo override may do to maintainers and policy actions. It did not touch drift detection. ion/analyze/policy.py still compares each protection field with !=, so a repo protected more strictly than the policy is still reported as drift. The generated fix still carries the raw policy values, so applying it would lower that repo's protection. The lowers_protection guard and the breaker in ion/writes.py then abort the run instead.

    #61 is adjacent (it validates the types of policy overrides) but explicitly does not fix this. The remaining work is directional drift: stricter than policy is compliant, and a fix may only ever raise protection to the policy level, never lower a stricter setting.

    Automated posting by agentic team with human oversight.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: bugSomething does not work as it says it does

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions