Support transferring burned_project_routes and agent_organization_authorizations on org group transfer

Depends on !247673 (merged)

What does this MR do and why?

Adds organization transfer support for 2 infrastructure tables during group organization transfer:

  1. burned_project_routes — filtered by path prefix (LOWER(path) LIKE '<group full path>/%') using sanitize_sql_like to escape special characters. Conflicting source-org burns (where the target org already has a burn for the same path) are deleted before the remaining burns are moved — the target org's row already protects those paths, making the source row redundant.
  2. agent_organization_authorizations — correlated EXISTS on namespaces.traversal_ids to find agents belonging to projects in the hierarchy.

Both tables are on gitlab_main_org schema, so they are updated synchronously inside the GroupsService transaction using the existing update_organization_id_for helper from OrganizationUpdater.

The transfer_infrastructure step runs after transfer_topics and before schedule_ci_runners_transfer / event publishing.

Migration

AddPatternOpsIndexToBurnedProjectRoutes adds a text_pattern_ops index on (organization_id, lower(path)) to support the LIKE prefix query used by transfer_burned_project_routes.

Why pool_repositories is no_work_needed

pool_repositories is a shared resource: a single pool can serve multiple member projects (forks) across different groups and organizations. During org transfer, all transferred projects are disconnected from their pool via LeavePoolRepositoryWorker, so the pool record stays behind for its remaining members in the old organization. Updating its organization_id to the new org would break it for those other projects. The table is therefore marked organization_transfer_support: no_work_needed.

config/organizations/transfer_support.yml changes

  • burned_project_routes: todo → supported
  • agent_organization_authorizations: todo → supported
  • pool_repositories: todo → no_work_needed

References

How to set up and validate locally

  1. Create two organizations, a top-level group in the first org, with a subgroup and projects
  2. Create records for each table linked to the projects:
    burned = Authn::BurnedProjectRoute.create!(organization: org1, project_id: project.id, path: project.full_path, burned_at: Time.current)
    agent = Clusters::Agent.create!(project: project, name: 'test')
    auth = Clusters::Agents::Authorizations::CiAccess::OrganizationAuthorization.create!(agent: agent, organization: org1, config: {})
  3. Transfer the group:
    Organizations::Transfer::GroupsService.new(group: group, new_organization: org2, current_user: user).execute
  4. Verify all records now have organization_id matching org2

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by tim mccarthy

Merge request reports

Loading
Loading