Reset existing dap_powered reviewer assignment strategy rows
Adds one post-deploy migration that resets project_settings.reviewer_assignment_strategy rows holding 2 (dap_powered) back to 0 (disabled). This merges after Phase D (!253170 (merged), merged 2026-09-07) - the enum-removal trap that used to force the opposite order is gone. The migration is now a single unbatched UPDATE, and the row count has been measured: 6 rows on gitlab.com.
By the time this runs the bespoke path is already deleted and the flag is already off, so this is tidy-up rather than the step that changes behaviour. Nothing blocks it - the affected rows are 6 internal projects on a Beta flag that never reached GA, and they get a heads-up in the internal feedback thread rather than a formal migration.
Detailed context for AI agents
Background
The dap_powered reviewer assignment strategy was a bespoke project setting, now replaced by the standard Duo Agent Platform flow-trigger setup. !248476 (merged) removed both write paths on 2026-08-20, but rows created before that still hold the old value.
Ordering flip
The old version of this description said the dap_powered enum removal "must not ship until this migration has run." That is no longer the case. The only thing that ever forced this migration (Phase C) before the code deletion (Phase D, !253170 (merged)) was a NoMethodError trap: Phase D was going to delete the dap_powered: 2 enum value, which would have deleted the generated reviewer_assignment_dap_powered? predicate out from under its caller in AutoAssignReviewersWorker. Phase D was rescoped on 2026-08-21 - as !248482 (closed), since closed in favour of !253170 (merged) - to keep the enum value, so that dependency is gone.
With it gone, the correct order is the revertible code change first and the irreversible data change last. Doing it the other way round meant taking an unrecoverable data change to fix a symptom that only existed because the code still read the column.
Full agreed sequence
- !242698 (merged) (Add merge_request created Duo Agent Platform trigger) merged 2026-08-28, and
merge_request_create_flow_triggerwas enabled globally on gprd 2026-09-01 (issue #618743 (closed)). It restores coverage for merge requests opened non-draft, which the bespoke path had and the marked-ready trigger does not. dap_powered_recommend_reviewerswas turned off on gprd and gstg 2026-09-02. Rollout issue #602418 (closed). This is the step where the feature actually stops running, and it reverts in seconds.- !253170 (merged) (Phase D) merged 2026-09-07, deleting what was by then already-dead code.
- This MR merges and the reset runs - the only remaining step.
What this migration is actually for
Worth being precise, because the original issue framing overstated it. Stale rows never break anything. StrategyFactory::STRATEGIES maps only code_owners, so a dap_powered row misses the lookup, build returns nil, and AssignService returns skipped('No strategy available') as a success. That is true before the enum is dropped and after it, since an unmapped raw value deserializes to nil and misses the same lookup.
And the feature loss does not happen here either - it happens at step 2, the flag flip. By the time this migration runs those projects have already stopped getting DAP assignment.
So what is left is genuine but small:
- wasted work -
reviewer_auto_assignment_enabled?stays true (!= 'disabled'), soAutoAssignReviewersWorkerkeeps getting enqueued on every create and ready transition just to no-op - dishonest state - the row holds a live-looking value that nothing acts on, and the settings checkbox shows unchecked
That is the whole case for it. If a reviewer decides that is not worth a production data migration, closing this MR is a defensible outcome - the rows are permanently inert after Phase D either way.
Correction to carry over
An earlier version of this description claimed there is "no longer any way to move a project off the dap_powered value." That is wrong and must not be repeated. The settings checkbox has checked_value: 'code_owners' and unchecked_value: 'disabled', so any save on the project's Merge requests settings page clears a stale row. The rows are self-healing on .com. The migration matters mainly because self-managed instances cannot be fixed by hand.
Why disabled and 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 from settings.
Migration shape
One post-deploy migration, 20260825144211. A single unbatched UPDATE - no each_batch, no BATCH_SIZE, no disable_ddl_transaction!:
def up
execute(<<~SQL)
UPDATE project_settings
SET reviewer_assignment_strategy = 0
WHERE reviewer_assignment_strategy = 2
SQL
endThere is no index on reviewer_assignment_strategy, so the cost is one sequential scan of project_settings however this is written. each_batch paid for three scans and bought nothing, because batching only helps when the match set is large enough to want the lock broken up, and six rows is not that. Only the six matched rows are locked, for the duration of the scan. Expected runtime is roughly 107 s, well inside the 10 minute post-deploy guideline.
project_settings is table_size: medium and not on the high-traffic list (rubocop/rubocop-migrations.yml). Its primary key is project_id, not id.
Measured on gitlab.com
db:gitlabcom-database-testing ran against the production main clone at commit c7f0967e, on the previous each_batch revision:
| matching rows | 6 |
| total runtime | 309.6 s, 3 timing-guideline warnings |
| first boundary probe | 124.6 s (SELECT project_id ... ORDER BY project_id LIMIT) |
the UPDATE |
107.3 s, 6 rows |
| second boundary probe | 74.9 s, 0 rows |
| ci database | 3.1 s, skipped (wrong gitlab_schema) |
That is what motivated dropping the batching: two of those three scans existed only to discover there was no second batch.
It also settles the temporary partial index question, which an earlier revision had open for the database reviewer. In the same pipeline, AddIndexProjectSettingsOnAiAuditEventsStorage - an index on this same table - measured 77.1 s. That is most of the 107 s an index would save, and it would be dropped again immediately, so the three-migration shape from the service desk settings backfill (db/post_migrate/20260717205311..13) is not worth it here.
There is no schema change at all, so db/structure.sql is untouched and only one db/schema_migrations/ version file is added.
down is a no-op. The column does not retain the previous value, so this is not reversible without a record of the affected projects captured beforehand - which is what the audit is for.
No blockers, and no audit
Earlier revisions of this MR and of #607678 (closed) blocked on auditing which affected rows ever actually functioned, and on notifying or migrating those projects first. Both are dropped. Nothing blocks this MR.
There are 6 affected rows, they are internal projects, and the feature was a Beta capability behind a wip flag with default_enabled: false that never reached GA. The consent argument for notify-before-change was calibrated for customer projects, where creating a flow trigger inside someone's project is an action in their space; it does not apply to internal projects on an opt-in beta. Affected teams get a note in the internal feedback thread (#603172), and anyone who wants the behaviour back creates a Merge request > Marked ready trigger on the Recommend Reviewers flow, per !248483 (merged).
Consequence to be aware of rather than to act on: down stays a no-op with no rollback artifact, so after this runs there is no record of which projects held the value. For reference, the gate for "ever functioned" was Project#dap_powered_recommend_reviewers_available? (feature flag with project actor, plus duo_foundational_flows_enabled, plus recommend_reviewers/v1 in enabled_flow_catalog_item_ids, plus StageCheck) - deleted by !253170 (merged), and reading a flag that is off by step 2, so it is not reconstructable after the fact.
Enum removal is a separate later cleanup
The dap_powered: 2 enum value is NOT removed here, and it is no longer part of Phase D either. When it does go it has to carry three things:
- a correction to
doc/api/projects.md, which !248476 (merged) amended in four places to promise thatGET /projects/:idcan still returndap_poweredfor projects configured before 19.4 - an update to
ee/spec/views/projects/settings/merge_requests/_reviewer_auto_assignment_settings.html.haml_spec.rb:62, which callsbuild(:project_setting, reviewer_assignment_strategy: :dap_powered)and will raiseArgumentErrorthe moment the enum value goes - an update to
ee/spec/workers/merge_requests/auto_assign_reviewers_worker_spec.rb:94, added by !253170 (merged), which has a context ("when reviewer_assignment_strategy is still dap_powered") that deliberately exercises the stale value and will need updating when the enum value is dropped
Notes for review
- The migration uses the literal
2and0rather than the enum, so it does not depend on application code. - Residual write path worth flagging, deliberately not fixed here: the HTML settings controller permits the attribute with no value allowlist (
ee/app/controllers/ee/projects/settings/merge_requests_controller.rb:38) and the enum has novalidate:(app/models/project_setting.rb:92), so a hand-crafted form POST could still writedap_poweredback after this runs. Judged not worth blocking on: the value is inert, no UI affords it, and it closes when the enum is eventually dropped. A model validation would be worse - it would make existingdap_poweredrows fail validation on unrelatedproject_settingsaves. - The previous pipeline failed
static-analysis 2/2ongitlab:js:routes:updated_check- path-helper drift from master unrelated to this MR. The branch was rebased onto master7bd1e20d6730on 2026-09-07, which resolved it. - Milestone
19.4is correct - master is19.4.0-pre.
Testing
bundle exec rspec spec/migrations/20260825144211_reset_dap_powered_reviewer_assignment_strategy_spec.rb4 examples, 0 failures. RuboCop clean. scripts/validate_migration_checksum and scripts/validate_migration_timestamps pass. Migration run up locally against main/ci/sec.
Re-verified after the 2026-09-07 rebase onto master 7bd1e20d6730: same 4 examples, 0 failures, RuboCop clean on both files. The diff is still exactly 3 files (migration, db/schema_migrations/20260825144211, migration spec), no schema change.
Coverage: the dap_powered row resets to disabled; code_owners and disabled rows are untouched; and the old batching example was replaced by one that asserts exactly one UPDATE project_settings statement is issued with four matching rows present, so it fails the moment batching is reintroduced. down is asserted to leave the reset in place.
The db/schema_migrations/ checksum file did not need regenerating - the checksum is a SHA256 of the version string, not of the migration body.
References
- Related to #607678 (closed)
- Epic: gitlab-org#23019
- Phase A, merged: !248388 (merged)
- Phase B, setting removal, merged: !248476 (merged)
- Phase D, merged 2026-09-07: !253170 (merged)
- Restores non-draft coverage, merged 2026-08-28: !242698 (merged)
- Feature flag rollout: #602418 (closed)
MR acceptance checklist
- Migration spec added
- Follows the code review guidelines