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.