Reset existing dap_powered reviewer assignment strategy rows

Phase C. Blocked by #607677 (closed), and must not start until the Phase A trigger path is verified in production. This is the point rollback stops being a code revert.

Problem — risk corrected after code verification

Once the setting is removed there is no way to change reviewer_assignment_strategy off dap_powered, but rows still hold the value.

The original claim that stale rows would make "auto-assignment run with no strategy" after Phase D turns out to be wrong — the verified behaviour is a graceful no-op: Rails reads an unmapped enum value as nil (ActiveRecord::Enum::EnumType#deserialize → mapping.key(2) → nil; no raise on read), reviewer_auto_assignment_enabled? is then true (nil != 'disabled'), but StrategyFactory.build returns nil for unmapped names (strategy_factory.rb — STRATEGIES only maps code_owners) and AssignService returns skipped('No strategy available') as a success (assign_service.rb:18). Already covered by strategy_factory_spec.rb:32-40.

So the reset is not crash-prevention. It is still required because stale rows:

  1. silently lose the feature — the project owner chose DAP assignment and gets nothing, with no signal;
  2. waste work — reviewer_auto_assignment_enabled? being true keeps enqueueing AutoAssignReviewersWorker (ee/.../update_service.rb:128, reload_merge_head_diff_service.rb:30) and marking the Redis flag (create_service.rb:29) just to no-op;
  3. render dishonest UI — the settings checkbox shows unchecked (reviewer_assignment_code_owners? false) while the row holds a live-looking value.

(The related hard-failure trap — NoMethodError from reviewer_assignment_dap_powered? once the enum value is gone — belongs to Phase D sequencing and is called out on #607679 (closed).)

Implementation

1. Audit first, split by whether the row ever worked

Count and list projects with project_settings.reviewer_assignment_strategy = 2. Because the REST API was never flag-gated (see #607677 (closed)), this set may be wider than the dap_powered_recommend_reviewers rollout. The worker's DAP branch only ever ran when Project#dap_powered_recommend_reviewers_available? held (feature flag with project actor + duo_foundational_flows_enabled + recommend_reviewers/v1 in enabled_flow_catalog_item_ids + StageCheck, ee/app/models/ee/project.rb:736-744) — rows that never satisfied that gate never executed DAP assignment and only need the reset, not migration. Console sketch:

rows = ProjectSetting.where(reviewer_assignment_strategy: 2).includes(:project)
functioning, inert = rows.partition { |ps| ps.project.dap_powered_recommend_reviewers_available? }

2. Migrate the functioning projects to triggers

For each functioning project, enable the Recommend Reviewers flow and create a Merge request > Marked ready trigger, or notify the owners with instructions. Do this before resetting the value, so nobody silently loses the feature. Decide the consent posture explicitly: creating triggers inside customer projects is an action in their space — notify-with-instructions is the default unless product signs off on auto-creation. (These projects already have the flow enabled and a service account provisioned — that's part of the availability gate — so the missing piece is only the Ai::FlowTrigger row.)

3. Batched post-deploy migration

Validated approach — project_settings is table_size: medium, not on the high-traffic list (rubocop/rubocop-migrations.yml), so a regular post-deploy migration with each_batch is within guidelines (≤ 10 min); a batched background migration is not needed for a small-cardinality update (the BBM precedent on this table, ResetProjectSettingsDuoFoundationalFlowsEnabled, touched every row). Two facts shape the migration: the PK is project_id (not id), and there is no index on reviewer_assignment_strategy — so use the three-migration shape from db/post_migrate/20260717205311..13 (service desk settings): temporary partial index → batched update → drop index.

# 1) add_concurrent_index :project_settings, :project_id,
#      name: 'tmp_idx_project_settings_reviewer_strategy_dap',
#      where: 'reviewer_assignment_strategy = 2'

# 2) the reset
class ResetDapPoweredReviewerAssignmentStrategy < Gitlab::Database::Migration[2.3]
  restrict_gitlab_migration gitlab_schema: :gitlab_main_org
  disable_ddl_transaction!
  milestone '19.5'

  BATCH_SIZE = 500

  def up
    scope = ->(model) { model.where(reviewer_assignment_strategy: 2) }

    each_batch(:project_settings, scope: scope, of: BATCH_SIZE) do |batch, _model|
      batch.update_all(reviewer_assignment_strategy: 0)
    end
  end

  def down
    # no-op: the previous value is not recoverable; restore from the audit list if needed
  end
end

# 3) remove_concurrent_index by name

The audit list from step 1 is the de-facto rollback artifact — attach it to this issue before the migration ships.

Reset to disabled, not code_owners

Resetting to code_owners would silently start assigning every code owner on projects that deliberately chose the narrower DAP strategy. disabled is the honest default; owners who want code owners can opt back in.

Acceptance criteria

  • Affected project count and list recorded on this issue, split into functioning vs never-functioning rows
  • Every functioning project either has a trigger configured or has been notified
  • Post-deploy migration (tmp index → batched reset to 0 → drop index) shipped
  • No rows with value 2 remain before the Phase D enum removal ships
Edited by Marc Shaw