Add inherited group trigger lookup for service account suggestions

What does this MR do and why?

Group inheritance for AI Catalog is still in progress. One part of it lets a group enable a flow item once and have it apply to all projects under that group - done through an "inheritance" kind of ItemConsumer. Creating that record from the UI isn't wired up yet (separate MR), so for now you need the Rails console to set it up (see validation steps).

The problem this fixes

When you @mention someone in a comment or assign them to an issue/MR, GitLab fetches a list of eligible users. For service accounts, if the SA has active flow triggers it shows an AI badge next to it. If the SA is assigned to an event it can handle it stays selectable; if not, it's greyed out.

Before this MR, that badge logic only looked at project-level (direct) triggers. So a SA that belongs to a group-level inheritance consumer - even if it's already a member of the project - would show up with no AI badge and be treated as unselectable for AI events.

What this MR change

Adds a shared trigger lookup layer that unions a project's own triggers with inheritance triggers from the root group, gated behind the ai_catalog_group_inherited_consumers feature flag.

Two surfaces benefit from this:

  • @mention autocomplete : powered by ParticipantsService via the REST endpoint. Inherited SAs now show up with the correct event bad

  • Assignee / reviewer picker : powered by DuoStatusType via the GraphQL autocompleteUsers query. Inheritance triggers are loaded once per root group per request (keyed in GraphQL context to avoid N+1).

Also adds a composite index on ai_catalog_item_consumers (group_id, kind, service_account_id) which replaces a post-filter that was scanning all consumers for a group before narrowing by SA.

References

How to set up and validate locally

Since the UI for creating inheritance consumers is not wired up yet, you need to create the records manually from the Rails console first.

1. Setup

Expand
group = Group.find_by_full_path('your-group')
project = group.projects.first
item = Ai::Catalog::Item.where(item_type: 2).first
root = User.find_by(username: 'root')

sa_result = Namespaces::ServiceAccounts::GroupCreateService.new(
  root,
  {
    namespace_id: group.id,
    name: item.name,
    username: "ai-#{item.name}-#{group.name}".parameterize,
    composite_identity_enforced: true,
    skip_ai_prefix_validation: true,
    skip_owner_check: true,
    organization_id: group.organization_id
  },
  uniquify_provided_username: true
).execute
sa = sa_result.payload[:user]

consumer = Ai::Catalog::ItemConsumer.new
consumer.group_id = group.id
consumer.ai_catalog_item_id = item.id
consumer.service_account_id = sa.id
consumer.kind = 1  # inheritance
consumer.save!(validate: false)

trigger = Ai::FlowTrigger.new
trigger.group_id = group.id
trigger.ai_catalog_item_consumer_id = consumer.id
trigger.event_types = [
  Ai::FlowTrigger::EVENT_TYPES[:mention],
  Ai::FlowTrigger::EVENT_TYPES[:assign]
]
trigger.description = "Test inheritance trigger"
trigger.active = true
trigger.save!(validate: false)

project.add_developer(sa)
Feature.enable(:ai_catalog_group_inherited_consumers, group)

2. Verify in console

Expand
result = Ai::FlowTrigger.applicable_to_project(project, triggered_on: :mention)
puts result.map { |t| "ID:#{t.id} group_id:#{t.group_id.inspect}" }
# should include the new inheritance trigger with group_id set

svc = Projects::ParticipantsService.new(project, root)
participants = svc.execute(project.issues.first)
sa_entry = participants.find { |p| p[:username] == sa.username }
puts sa_entry&.dig(:flow_trigger_events).inspect
# => [:MENTION, :ASSIGN]

NOTE

In my GDK I've created the Item consumer records below, with the Flow trigger mapped to assignee and mentioned event types. Service account user is @duo-release-note-flow-gitlab-duo, and it's a group-level inheritance consumer.

Expand
#<User id:1612 @duo-release-note-flow-gitlab-duo>

#<Ai::Catalog::ItemConsumer:0x000000014e8aadc8
id: 808,
ai_catalog_item_id: 5514,
group_id: 1000000,
project_id: nil,
created_at: "2026-09-24 09:46:27.938676000 +0000",
updated_at: "2026-09-24 09:46:27.938676000 +0000",
enabled: false,
locked: true,
pinned_version_prefix: nil,
service_account_id: 1612,
parent_item_consumer_id: nil,
kind: "inheritance">

#<Ai::FlowTrigger:0x0000000142264ee8
id: 183,
project_id: nil,
user_id: nil,
config_path: nil,
description: [FILTERED],
event_types: [0, 1],
created_at: "2026-09-24 09:46:43.666904000 +0000",
updated_at: "2026-09-24 09:46:43.666904000 +0000",
ai_catalog_item_consumer_id: 808,
filter: {},
active: true,
group_id: 1000000,
autonomous_service_account_id: nil>

3. Verify @mention autocomplete (REST path)

  1. Open any issue/MR in the project
  2. Click the comment box and type @ followed by the SA username prefix
  3. The SA should appear in the dropdown with an AI badge
Issue Comment image
MR Comment image
Epic Comment image

4. Verify assignee/reviewer picker (GraphQL path)

  1. On the same issue open the Assignees section in the right sidebar
  2. Search for the SA, it should appear with an AI badge
  3. The badge events should reflect mention and assign
Epic Assignee

image

MR Assignee

image

MR Reviewer : Disabled, since the reviewer event isn't mapped to this flow.

image

5. Verify FF off

Feature.disable(:ai_catalog_group_inherited_consumers)

Repeat steps 3 and 4, the SA still appears (it's a project member) but the AI badge should not show. Re-enable after:

Feature.enable(:ai_catalog_group_inherited_consumers, group)

Database Query Plan

Currently in production there are no item consumer records with kind inheritance. So to test the query, I've added 4k item_consumer records in the postgre.ai for the gitlab-org (9970) group.

Some queries will still return 0 records since the actual data isn't there, for example, when filtering by service account, none of the dummy records have a service account attached correctly, so those queries will show 0.

Click to expand

self.applicable_to_project with event_type

Query plan - https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/58494/commands/163406

applicable_to_namespace with by_service_accounts (@duo-fix-ci-cd-pipeline-gitlab-org user_id: 32742185)

Query Plan - https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/58494/commands/163417

applicable_to_project with triggered_on: :mention and with by_service_accounts (@duo-fix-ci-cd-pipeline-gitlab-org user_id: 32742185)

Query Plan - https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/58494/commands/163419

active_inherited_for_group

Query plan - https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/58494/commands/163421

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 #627518 (closed)

Edited by Jaydip Pansuriya

Merge request reports

Loading
Loading