Delete orphaned group-level rows from scim_oauth_access_tokens

Series

group_id has been dead since group SCIM tokens moved to group_scim_auth_access_tokens in 17.9.

Prerequisites — target master, any order, all must land before step 2:

MR What
!261148 GroupScimAuthAccessToken gets its own serializer
!261149 Real group_id only on the group SCIM API entity
!261150 (merged) No SCIM token transfer on group org transfer

Steps — sequential, one milestone apart, since each needs the previous deployed, not just merged:

Step MR Milestone What State
1 !256720 (merged) 👈 19.5 Delete orphaned group-level rows merged
2 !256721 19.6 Remove code references, add ignore_column open
3 !256722 19.7 Drop group_id column open
4 !256725 19.8 Remove ignore_column rule open

What does this MR do and why?

When scim_oauth_access_tokens was split in 17.9 (!170565 (merged)), group-level tokens were copied to group_scim_auth_access_tokens. Rows with group_id set are orphaned — no code reads or writes them. This migration deletes them, unblocking the group_id column drop in !256722.

Data deletion

  • Records: ~4,550 on GitLab.com production (all rows in the table; GitLab.com has no instance-level tokens). Self-managed: at most one row per SCIM-enabled group.
  • Recovery: Every row has a counterpart in group_scim_auth_access_tokens (verified on production, see comment below: missing=0; 213 tokens differ only from rotations after the sync stopped, and the new table is newer in every case). Backups / PITR as fallback.
  • User experience impact: None. Group SCIM auth is served entirely from group_scim_auth_access_tokens; instance-level tokens are untouched.

Query plans

DELETE FROM "scim_oauth_access_tokens" WHERE "scim_oauth_access_tokens"."group_id" IS NOT NULL AND "scim_oauth_access_tokens"."id" >= 1 AND "scim_oauth_access_tokens"."id" < 1761

Plan: https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/58419/commands/163207

References

How to set up and validate locally

ActiveRecord::Base.logger = nil  # silence SQL logging

group = Group.first
org   = Organizations::Organization.first

# Seed a group-level token (the kind this migration deletes). Let the model
# generate the token; organization_id is NOT NULL by check constraint.
ScimOauthAccessToken.create!(group: group, organization: org)
puts "before:            #{ScimOauthAccessToken.where.not(group_id: nil).count} group row(s)"

# Run the migration directly (re-runnable; MigrationContext#run skips
# versions already recorded in schema_migrations)
require Rails.root.join('db/post_migrate/20260923094500_delete_group_scim_oauth_access_tokens.rb')
DeleteGroupScimOauthAccessTokens.new.migrate(:up)

puts "after:             #{ScimOauthAccessToken.where.not(group_id: nil).count} group row(s)"

ScimOauthAccessToken.create!(organization: org)
puts "instance token ok: #{ScimOauthAccessToken.find_for_instance.present?}"

Expected output:

before:            1 group row(s)
after:             0 group row(s)
instance token ok: true

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Edited by Lukas Wanko

Merge request reports

Loading
Loading