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
BaseProjectServicefor 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_reviewersinternal event - Rescues
ActiveRecord::RecordInvalidfromUpdateReviewersService
Strategy: MergeRequests::ReviewerAssignment::CodeOwnersStrategy
- Implements
BaseStrategyinterface (select_reviewers) - Delegates to
wrapped_approval_rulesfor 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_strategyproject 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 keepsAssignServicereusable 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 toApprovalWrappedRule#approverswhich 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_useris required: Callers must pass a user with:set_merge_request_metadatapermission (typically the MR author). No automation_bot fallback. -
Guard clauses return
skipped: All early-return conditions useServiceResponse.success(payload: { skipped: true })since the caller is a background worker
Deferred to follow-up
-
Lifecycle trigger: No caller wires
AssignServiceinto 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.
Related
- Epic: &20708
- DAP Epic: &20711
- Part 1 (DB migration): !224175 (merged)
- Column rename: !227050 (closed)
- Issue: #589700
Edited by Marc Shaw