Add Code Owner approval removal to MR approval policies

What does this MR do and why?

Merge request approval policies could only remove all approvals on a new commit (remove_approvals_with_new_commit). This MR adds remove_code_owner_approvals_with_new_commit, removing only the approvals of Code Owners whose files changed — the granularity project-level approval settings already offer.

  • Routes into the existing ResetApprovalsService#delete_code_owner_approvals, inheriting project-level selective_code_owner_removals semantics.
  • The two are mutually exclusive; the schema rejects both, and remove_approvals_with_new_commit wins at runtime as a guard. Removal applies only to MRs with an active non-warn-mode violation.
  • Precedence follows the approval_settings rule from epic 9696: on conflict the most secure option wins. A policy raises "Keep approvals" to selective removal, but does not narrow "Remove all approvals".
  • Gated behind feature flag approval_policy_selective_code_owner_removals, disabled by default. Approval policies are already Ultimate-only, so no new license gate.

Scope: backend, YAML support, and the project settings page indicator. The setting is authored in YAML mode; the rule-mode radio group is a follow-up in !255413.

Policy schema changes

Both approval_policy_content.json and security_orchestration_policy.json gain the boolean under approval_settings, plus a not constraint rejecting both removal settings set to true.

"not": {
  "required": [
    "remove_approvals_with_new_commit",
    "remove_code_owner_approvals_with_new_commit"
  ],
  "properties": {
    "remove_approvals_with_new_commit": { "const": true },
    "remove_code_owner_approvals_with_new_commit": { "const": true }
  }
}
# accepted
approval_settings:
  remove_code_owner_approvals_with_new_commit: true

# accepted - only one is true, so the constraint does not fire
approval_settings:
  remove_approvals_with_new_commit: false
  remove_code_owner_approvals_with_new_commit: true

# rejected
approval_settings:
  remove_approvals_with_new_commit: true
  remove_code_owner_approvals_with_new_commit: true

Screenshots or screen recordings

Local GDK, flightjs/Flight (Ultimate), flag on. CODEOWNERS maps owned/* to @code-owner-cara, master protected with Code Owner approval required, project on Keep approvals. Policy: any_merge_request / commits: any, one approval from @reviewer-remy, the new setting true. MR changes owned/payments.js, approved by both.

Recording (3x, URL overlaid) — MR goes from "Ready to merge!" to blocked when a commit touching owned/payments.js lands:

Before: new commit After: new commit
e2e-1-before e2e-2-after
  • Code Owners rule owned/* goes 1 of 1 to 0 of 1, the policy rule stays 1 of 1, widget goes "Ready to merge!" to "Merge blocked".
  • System note names only the Code Owner: "reset approvals from @code-owner-cara".
  • A commit outside owned/ removed no approvals.
  • With Remove all approvals on the project (the default), all approvals go — the more secure option wins.

Project settings page — the Policy override popover appears when the policy changes behaviour, and is absent when the project is already at least as restrictive. Radios are not disabled: a policy applies per merge request, so the project setting still governs MRs outside its scope.

Project keeps approvals Project removes all approvals
settings-1-popover settings-2-no-icon

How to set up and validate locally

  1. Enable the approval_policy_selective_code_owner_removals feature flag.
  2. Set the project's When a commit is added to Keep approvals — Remove all approvals is the more secure option and wins.
  3. In an Ultimate project with CODEOWNERS, add remove_code_owner_approvals_with_new_commit: true to an approval policy in YAML mode, with a rule the MR violates.
  4. Approve as a Code Owner, then push a commit touching that owner's files — only that approval is removed. A commit touching unrelated files removes nothing.

References

Notes for reviewers

  • The flag resolves against the project, so enabling it for a group only reveals the editor options without enforcing in its projects.
  • #597609 (closed) left selective_code_owner_removals at enforced_by_policy: false because it had no policy counterpart. It has one now, so that table is stale — but the advisory-icon-not-lock decision in #478175 (closed) still holds.
  • Out of scope: a target-branch change still removes all approvals (UpdateService#delete_approvals_on_target_branch_change), matching existing project-level selective_code_owner_removals behaviour.
Edited by Alexander Turinske

Merge request reports

Loading
Loading