Add CODEOWNERS reviewer assignment service and strategy pattern

Summary

Adds the core service and strategy for automatically assigning code owner reviewers to merge requests. This is Part 2 of the Native CODEOWNERS Reviewer Auto-Assignment epic.

What does this MR do?

Service: MergeRequests::ReviewerAssignment::AssignService

  • Orchestrates automatic reviewer assignment using a pluggable strategy
  • Inherits from BaseProjectService for consistent service conventions
  • Guard clauses: license check, feature flag, project setting, draft MR, existing reviewers
  • Strategy-agnostic: delegates all reviewer selection to the strategy (no knowledge of code owner rules)
  • Enforces the max reviewer limit (200)
  • Tracks assignment via auto_assign_reviewers internal event
  • Rescues ActiveRecord::RecordInvalid from UpdateReviewersService

Strategy: MergeRequests::ReviewerAssignment::CodeOwnersStrategy

  • Implements BaseStrategy interface (select_reviewers)
  • Delegates to wrapped_approval_rules for reviewer selection — reuses the existing approval system as the single source of truth
  • Handles inactive user filtering, author exclusion, and committer filtering via ApprovalWrappedRule#approvers

Strategy Factory: MergeRequests::ReviewerAssignment::StrategyFactory

  • Maps the reviewer_assignment_strategy project setting to the correct strategy class
  • Currently supports: code_owners

Design decisions

  • Strategy pattern: Designed to support future strategies (e.g., DAP intelligent selection per Epic &20711)
  • Strategy owns data sourcing: Each strategy is responsible for fetching its own reviewer candidates (e.g., code owner rules for CodeOwnersStrategy). This keeps AssignService reusable for strategies that don't use code owner rules.
  • Delegates to wrapped_approval_rules: Rather than reimplementing user aggregation from rules/groups/roles, the strategy delegates to ApprovalWrappedRule#approvers which already handles inactive user filtering, author exclusion based on project settings, and committer filtering. This avoids drift with the approval system over time.
  • Generic naming: Service lives under MergeRequests::ReviewerAssignment:: namespace to support reuse beyond CODEOWNERS
  • current_user is required: Callers must pass a user with :set_merge_request_metadata permission (typically the MR author). No automation_bot fallback.
  • Guard clauses return skipped: All early-return conditions use ServiceResponse.success(payload: { skipped: true }) since the caller is a background worker

Deferred to follow-up

  • Lifecycle trigger: No caller wires AssignService into the MR lifecycle yet (e.g., Sidekiq worker triggered when MR is marked as ready). This will be added in a subsequent part of the epic.
Edited by Marc Shaw

Merge request reports

Loading