Investigate and address stale `vulnerability_reads` ES docs during group transfer + `task: :all` consolidation
## Background [!229223](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/229223) adds `Search::Elastic::Delete::VulnerabilityReadsService` and wires it into the group transfer flow via `delete_vulnerability_reads_with_old_routing`. During review, [@bala.kumar](https://gitlab.com/bala.kumar) asked whether we could simplify the group transfer cleanup by calling `task: :all` (which fans out to all `PROJECT_TASKS`) instead of scheduling `delete_project_vulnerabilities` and `delete_project_vulnerability_reads` separately. [@terrichu](https://gitlab.com/terrichu) identified two blockers: 1. **Migration guard**: `delete_vulnerability_reads_with_old_routing` is gated on `Elastic::DataMigrationService.migration_has_finished?(:create_vulnerability_reads_index)`. `task: :all` in `DeleteWorker` fans out to all `PROJECT_TASKS` without any per-task migration guards, so we can't consolidate until `DeleteWorker` supports conditional task execution. 2. **Potential data bug**: `ElasticGroupAssociationDeletionWorker` (called via `process_group_associations`) handles group-level work items using `group_<root_ancestor_id>` routing. However, it only targets `namespace_id` — meaning **project-level work items** (which also use `group_<root_ancestor_id>` routing) may **not** get stale document cleanup during a group transfer. This needs manual verification. ## Potential Bug: Project-level Work Items Not Cleaned Up on Group Transfer During a group transfer, `process_group_associations` calls `ElasticGroupAssociationDeletionWorker` which runs a `delete_by_query` on the `WorkItem` index filtered by `namespace_id` (group IDs) with the old root ancestor's routing key. This correctly handles group-scoped work items (epics, group issues). However, **project-level work items** are indexed with `group_<root_ancestor_id>` routing and have a `project_id` field. The `ElasticGroupAssociationDeletionWorker` filters only by `namespace_id` (group IDs), not `project_id`. After a group transfer, project-level work items under the transferred group may still exist in the old routing shard under the old `root_ancestor_id`, and the worker would not delete them because it only queries by `namespace_id` (which for project work items is the project namespace, not the group namespace). `DeleteWorker` with `task: :delete_project_work_items` (`ProjectWorkItemsService`) does handle project-level work items correctly (filters by `project_id` + excludes new `traversal_id`), but it is **not called** during group transfer — only during project transfer (`EE::Projects::TransferService`). ### What needs verification - [ ] Manually verify: after a group transfer, do project-level work items under the transferred group get stale docs cleaned up in the `WorkItem` ES index? - [ ] Check whether `EE::Projects::TransferService` also calls `delete_project_work_items` and whether that covers the group transfer case transitively. ## Proposal ### Part 1: Verify and fix the potential bug (project-level work items on group transfer) If confirmed, the fix is to also schedule `DeleteWorker` with `task: :delete_project_work_items` for each project during group transfer (similar to how `delete_vulnerability_reads_with_old_routing` is now called per-project). This should be done in `post_update_hooks` in `EE::Groups::TransferService`. ### Part 2: Support migration guards in `task: :all` (refactor) To allow consolidation of the per-task scheduling calls, `DeleteWorker#run_all_project_tasks` should support skipping tasks that have a migration guard. Options: - Add an optional `migration_guard` key to the `PROJECT_TASKS` hash that is checked before scheduling. - Or gate the entire `task: :all` path on all required migrations having finished. Once this is in place, the group transfer code can be simplified to a single `DeleteWorker.perform_async(task: :all, ...)` call per project. ## References - [!229223](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/229223) — Add deletion service for vulnerability_reads ES index (parent MR) - Original discussion: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/229223#note_3253898923
issue