Use loaded approval rules in the security policy merge check

The security policy mergeability check ran a fresh approval_rules query for every merge request, even on the list endpoints that already preload the association (63 queries / 145ms on a GET /groups/:id/merge_requests page, 51 queries / 448ms on GET /projects/:id/merge_requests, worst single query 204ms, GitLab.com 2026-09-22). When the rules are already loaded it now filters them in Ruby. Otherwise it keeps the existing query.

Split out of !257173.

Detailed context for AI agents

Change (ee/app/services/merge_requests/mergeability/check_security_policy_violations_service.rb)

merge_request_has_approval_policy_rules? called merge_request.approval_rules.with_approval_policy_rule.any?. with_approval_policy_rule is a scope (where.not(approval_policy_rule_id: nil) in ee/app/models/concerns/approval_rule_like.rb), so it always built a new query, even when MergeRequest.with_api_entity_associations had already preloaded approval_rules. That produced one SELECT 1 AS one FROM approval_merge_request_rules WHERE merge_request_id = ? AND approval_policy_rule_id IS NOT NULL LIMIT 1 per merge request on the list endpoints.

Fix: the new private approval_policy_rules_present? checks approval_rules.loaded?:

  • Loaded: any? { |rule| rule.approval_policy_rule_id.present? }, no query.
  • Not loaded: the original with_approval_policy_rule.any?, so single-MR paths (merge widget, mergeability worker) run the same query as before, and don't load every rule row just to check one column.

The other half of the condition, scan_result_policy_reads_through_approval_rules.any?, is unchanged. It is also preloaded on the list endpoints.

Measured on GitLab.com 2026-09-22 (per_page=100):

  • Group request (gitlab-org): 63 executions, 145ms total.
  • Project request (gitlab-org/gitlab): 51 executions, 448ms total, slowest single execution 204ms.

Why split out: this was the first commit of !257173, alongside an unrelated feature-flagged change that caches the X-Total count on the group MR API. It needs a different reviewer (security policies, EE) and can ship on its own, with no feature flag.

Spec

ee/spec/services/merge_requests/mergeability/check_security_policy_violations_service_spec.rb: with a merge request loaded through with_api_entity_associations, executing the service issues no SELECT 1 AS one FROM "approval_merge_request_rules" query. The existing examples build merge_request without preloading, so they cover the unloaded path.

Verification

  • The full service spec passes locally (26 examples).
  • Rubocop is clean.

Merge request reports

Loading
Loading