Support default AI access rules
TLG/Instance admins can create access rules that affect users in an instance or TLG that do not belong to any other group that has access rules setup, allowing better control of feature access
Recordings
- Self-managed/dedicated: https://youtu.be/i-mJFE6Cxfg
- SaaS: https://youtu.be/6fwNZfwwHtk
References
Related to Support member access rules with no group to ap... (#591502 - closed)
Database Review
Note that change_column_null takes an exclusive lock but tables are not in the LargeTables list.
Migrations
UP
❯ rails db:migrate
main: == [advisory_lock_connection] object_id: 136420, pg_backend_pid: 4119
main: == 20260303050932 AllowNullThroughNamespaceInInstanceAccessRules: migrating ===
main: -- transaction_open?(nil)
main: -> 0.0000s
main: -- change_column_null(:ai_instance_accessible_entity_rules, :through_namespace_id, true, nil)
main: -> 0.0014s
main: -- transaction_open?(nil)
main: -> 0.0000s
main: -- view_exists?(:postgres_partitions)
main: -> 0.0243s
main: -- index_exists?(:ai_instance_accessible_entity_rules, :accessible_entity, {:unique=>true, :where=>"through_namespace_id IS NULL", :name=>"idx_ai_iaer_default_rule_on_accessible_entity", :algorithm=>:concurrently})
main: -> 0.0013s
main: -- execute("SET statement_timeout TO 0")
main: -> 0.0003s
main: -- add_index(:ai_instance_accessible_entity_rules, :accessible_entity, {:unique=>true, :where=>"through_namespace_id IS NULL", :name=>"idx_ai_iaer_default_rule_on_accessible_entity", :algorithm=>:concurrently})
main: -> 0.0049s
main: -- execute("RESET statement_timeout")
main: -> 0.0003s
main: == 20260303050932 AllowNullThroughNamespaceInInstanceAccessRules: migrated (0.0667s)
main: == [advisory_lock_connection] object_id: 136420, pg_backend_pid: 4119
main: == [advisory_lock_connection] object_id: 136420, pg_backend_pid: 4120
main: == 20260303050933 AllowNullThroughNamespaceInNamespaceAccessRules: migrating ==
main: -- transaction_open?(nil)
main: -> 0.0000s
main: -- change_column_null(:ai_namespace_feature_access_rules, :through_namespace_id, true, nil)
main: -> 0.0094s
main: -- transaction_open?(nil)
main: -> 0.0000s
main: -- view_exists?(:postgres_partitions)
main: -> 0.0005s
main: -- index_exists?(:ai_namespace_feature_access_rules, [:root_namespace_id, :accessible_entity], {:unique=>true, :where=>"through_namespace_id IS NULL", :name=>"idx_ai_nfar_default_rule_on_root_ns_accessible_entity", :algorithm=>:concurrently})
main: -> 0.0019s
main: -- execute("SET statement_timeout TO 0")
main: -> 0.0007s
main: -- add_index(:ai_namespace_feature_access_rules, [:root_namespace_id, :accessible_entity], {:unique=>true, :where=>"through_namespace_id IS NULL", :name=>"idx_ai_nfar_default_rule_on_root_ns_accessible_entity", :algorithm=>:concurrently})
main: -> 0.0040s
main: -- execute("RESET statement_timeout")
main: -> 0.0003s
main: == 20260303050933 AllowNullThroughNamespaceInNamespaceAccessRules: migrated (0.0301s)
main: == [advisory_lock_connection] object_id: 136420, pg_backend_pid: 4120
DOWN
❯ bundle exec rails db:migrate:down:main RAILS_ENV=development VERSION=20260303050933
main: == [advisory_lock_connection] object_id: 135580, pg_backend_pid: 3496
main: == 20260303050933 AllowNullThroughNamespaceInNamespaceAccessRules: reverting ==
main: -- transaction_open?(nil)
main: -> 0.0000s
main: -- view_exists?(:postgres_partitions)
main: -> 0.0250s
main: -- index_name_exists?(:ai_namespace_feature_access_rules, "idx_ai_nfar_default_rule_on_root_ns_accessible_entity")
main: -> 0.0008s
main: -- execute("SET statement_timeout TO 0")
main: -> 0.0003s
main: -- remove_index(:ai_namespace_feature_access_rules, {:algorithm=>:concurrently, :name=>"idx_ai_nfar_default_rule_on_root_ns_accessible_entity"})
main: -> 0.0049s
main: -- execute("RESET statement_timeout")
main: -> 0.0003s
main: -- transaction_open?(nil)
main: -> 0.0000s
main: -- change_column_null(:ai_namespace_feature_access_rules, :through_namespace_id, false, nil)
main: -> 0.0016s
main: == 20260303050933 AllowNullThroughNamespaceInNamespaceAccessRules: reverted (0.0940s)
main: == [advisory_lock_connection] object_id: 135580, pg_backend_pid: 3496
❯ bundle exec rails db:migrate:down:main RAILS_ENV=development VERSION=20260303050932
main: == [advisory_lock_connection] object_id: 135600, pg_backend_pid: 3699
main: == 20260303050932 AllowNullThroughNamespaceInInstanceAccessRules: reverting ===
main: -- transaction_open?(nil)
main: -> 0.0000s
main: -- view_exists?(:postgres_partitions)
main: -> 0.0258s
main: -- index_name_exists?(:ai_instance_accessible_entity_rules, "idx_ai_iaer_default_rule_on_accessible_entity")
main: -> 0.0010s
main: -- execute("SET statement_timeout TO 0")
main: -> 0.0004s
main: -- remove_index(:ai_instance_accessible_entity_rules, {:algorithm=>:concurrently, :name=>"idx_ai_iaer_default_rule_on_accessible_entity"})
main: -> 0.0037s
main: -- execute("RESET statement_timeout")
main: -> 0.0004s
main: -- transaction_open?(nil)
main: -> 0.0000s
main: -- change_column_null(:ai_instance_accessible_entity_rules, :through_namespace_id, false, nil)
main: -> 0.0007s
main: == 20260303050932 AllowNullThroughNamespaceInInstanceAccessRules: reverted (0.1048s)
main: == [advisory_lock_connection] object_id: 135600, pg_backend_pid: 3699
Query plans
This is a small table; and the query plans won't matter much but the new indexes will benefit the queries
Currently we only see seq scans for the very small tables.
- Ai::NamespaceFeatureAccessRule.by_root_namespace_group_by_through_namespace(root_namespace)
Query plan: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/49384/commands/147291 After: https://console.postgres.ai/gitlab/gitlab-production-main/sessions/49384/commands/147295
Ai::FeatureAccessRule.accessible_for_user(user, entity)
Query and plan: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/49384/commands/147288
Ai::NamespaceFeatureAccessRule.accessible_for_user(user, entity, root_namespace)
Query plan: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/49384/commands/147289
Ai::FeatureAccessRule.duo_root_namespace_access_rules
Query plan: https://postgres.ai/console/gitlab/gitlab-production-main/sessions/49384/commands/147290
How to reproduce
-
In SM mode, navigate to http://gdk.test:8080/admin/gitlab_duo/configuration
-
Navigate to the 'Membership access section'
-
Click in 'Add group', and select a group to add rules. A new row for default should also be added

-
Toggle default and submit changes. When refreshing the changes should still be there
-
Removing the group specific rows should also remove the default rules, in case both are disabled.


