Expose duo_namespace_access_rules to top-level namespace settings

What does this MR do and why?

Expose ai_feature_rules to top-level namespace settings

Add support for managing top-level namespace access rules

This setting is feature flagged

Depends on !216915 (merged)

References

Screenshots or screen recordings

Sample response

{
  "duo_namespace_access_rules": [
    {
      "through_namespace": {
        "id": 963,
        "name": "kraken",
        "full_path": "gitlab-duo/kraken"
      },
      "features": [
        "duo_classic",
        "duo_agent_platform"
      ]
    },
    {
      "through_namespace": {
        "id": 964,
        "name": "nebula",
        "full_path": "gitlab-duo/nebula"
      },
      "features": [
        "duo_agent_platform"
      ]
    }
  ]
}

How to set up and validate locally

  1. Run GDK in SaaS mode
  2. With the feature flag disabled: Feature.disable(:duo_access_through_namespaces, root_ancestor)
    1. Get group settings, inspect the response to verify no rules exist: duo_namespace_access_rules: []

      curl 'http://gdk.test:3000/api/v4/groups/1000000' \
        -H "Authorization: Bearer $GITLAB_TOKEN" \
        -H 'Content-Type: application/json'
  3. With the feature flag enabled, Feature.enable(:duo_access_through_namespaces, root_ancestor)
    1. Create feature access rules for the namespace, inspect the response to verify rules are added: duo_namespace_access_rules is updated accordingly

      curl -w 'http://gdk.test:3000/api/v4/groups/1000000' \
        -X 'PUT' \
        -H "Authorization: Bearer $GITLAB_TOKEN" \
        -H 'Content-Type: application/json' \
        --data-raw '{
          "duo_namespace_access_rules": [
            { "through_namespace": { "id": 963 }, "features": ["duo_classic", "duo_agent_platform"] },
            { "through_namespace": { "id": 964 }, "features": ["duo_agent_platform"] }
          ]
        }'
    2. Remove feature access rules, inspect the response to verify rules have been removed: duo_namespace_access_rules: []

         curl -w 'http://gdk.test:3000/api/v4/groups/1000000' \
        -X 'PUT' \
        -H "Authorization: Bearer $GITLAB_TOKEN" \
        -H 'Content-Type: application/json' \
        --data-raw '{
          "duo_namespace_access_rules": [
            { "through_namespace": { "id": 963 }, "features": [] },
            { "through_namespace": { "id": 964 }, "features": [] }
          ]
        }'

Query plan

DELETE FROM "ai_namespace_feature_access_rules"
WHERE "ai_namespace_feature_access_rules"."root_namespace_id" = 1000000

--- 

 Delete on ai_namespace_feature_access_rules  (cost=0.15..5.22 rows=0 width=0)
   ->  Index Scan using index_ai_nfar_on_root_namespace_on_accessible_entity on ai_namespace_feature_access_rules  (cost=0.15..5.22 rows=4 width=6)
         Index Cond: (root_namespace_id = 1000000)
(3 rows)
INSERT INTO "ai_namespace_feature_access_rules" ("created_at", "updated_at", "root_namespace_id", "through_namespace_id", "accessible_entity")
VALUES
    ('2026-01-07 12:44:12.719604', '2026-01-07 12:44:12.719604', 1000000, 103, 'duo_classic'),
    ('2026-01-07 12:44:12.719604', '2026-01-07 12:44:12.719604', 1000000, 103, 'duo_agent_platform')


--- 

gitlabhq_development=# explain INSERT INTO "ai_namespace_feature_access_rules" ("created_at","updated_at","root_namespace_id","through_namespace_id","accessible_entity") VALUES ('2026-01-07 12:44:12.719604', '2026-01-07 12:44:12.719604', 1000000, 103, 'duo_classic'), ('2026-01-07 12:44:12.719604', '2026-01-07 12:44:12.719604', 1000000, 103, 'duo_agent_platform');
                                  QUERY PLAN
-------------------------------------------------------------------------------
 Insert on ai_namespace_feature_access_rules  (cost=0.00..0.03 rows=0 width=0)
   ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=72)

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.

Related to #583898

Edited by Eduardo Bonet

Merge request reports

Loading