Draft: [skip ci] Add services to turn GitLab Duo on for a Slack workspace
What does this MR do and why?
Today a GitLab Duo session started from Slack runs and is billed in whatever group the person who mentioned the bot has as their default Duo namespace, so people in the same Slack workspace end up in different groups with different settings and credits. We are changing that to one Slack workspace, one GitLab Duo group, chosen once by an Owner of a top-level group (or by the administrator for a workspace installed for the whole instance). This MR adds the rules and the two actions behind that choice, turning GitLab Duo on or off for a workspace; nothing calls them from the UI or from Slack yet.
Nothing changes for anyone yet. Everything sits behind the new slack_duo_group feature flag (off by default). It builds on the table in !261830 (closed), so this MR targets that branch.
What's in it
EnableService: makes a top-level group the Duo group of a Slack workspace and turns on the Slack flow in it, as one switch instead of two. It is idempotent, so a "Fix" button can call it again. If the flow cannot be provisioned it undoes the claim and answers with an error, instead of reporting a working state that fails every mention.DisableService: removes the choice and turns the Slack flow off in the group, unless another workspace still runs there.- Who can do it: an Owner of a top-level group (the same check as installing the app on a group). A group can only claim a workspace it is connected to itself, through an install of its own or one of its projects. A workspace installed for the whole instance is the administrator's.
- A second group cannot take a workspace over. A group that is not connected to the workspace is not told which group has it. The exception is a claim whose group lost every connection to the workspace (for example, the app was removed from it): its Owners cannot even see the toggle any more, so a connected group may then take the workspace over.
DuoGroup.available?checksslack_duo_groupand theslack_duo_api_flowflag of the Slack flow for the same group, so the rollout can go one top-level group at a time.- Feature flag
slack_duo_group, typewipwhile the stack is incomplete, default off.
Where this fits
This is one of a stack of merge requests. Each one targets the branch of the one before it, so review and merge them in this order. Audit events and the documentation follow as separate merge requests.
- !261830 (closed): Table and model
- !261845 (closed): Services to turn GitLab Duo on or off (this MR)
- !262156 (closed): Endpoint and page data
- !262157 (closed): Mention path
- !262158 (closed): GitLab Duo section on the integration page
- !262159 (closed): Claim on the first group install
- !262160 (closed): Admin area picker (self-managed)
- !262161 (closed): Slack Marketplace install page
- !262165 (closed): Audit events
- !262171 (closed): Documentation
Plan and decisions: #633566. Rollout of the feature flag: #633582. Design discussion: gitlab-org#23704.
Context for review agents
- Goal: every GitLab Duo session started from a Slack workspace runs and bills in one top-level group chosen by an Owner (the administrator on self-managed for an instance-wide install), instead of each user's default Duo namespace. Plan: #633566.
- Everything is behind the default-off
slack_duo_groupfeature flag (typewip, becomesbetabefore rollout, rolled out per top-level group). The Slack flow has its own flagslack_duo_api_flow;DuoGroup.available?requires both for the same group. - Deliberate decisions, do not flag them unless you find a real defect:
- One Duo group per workspace (
team_idunique), one group can run several workspaces. Noorganization_idcolumn:namespace_idis the single sharding key. The unique index onteam_idholds per cell only, tracked in #633611. - A group install claims the workspace, a project install does not. Copies of group or instance installs (
inherit_from_id) never let a group claim a workspace. A workspace installed for the whole instance (self-managed only) is the administrator's to assign. - An unclaimed workspace has no fallback to the user's default namespace once the flag is on: the user gets a reply that says who can set it up.
- A Duo group always runs the Slack API flow (
slack_assistant/v1), the Developer flow is out. Members of the Duo group only, also on self-managed. Guests pass the membership check and are blocked later, documented as not supported for now. - Audit events for enable and disable come in a separate small MR before the flag leaves
wip.
- One Duo group per workspace (
- Test world for manual validation: #633566 (comment 4012898135).
- Reviewers who want to see the failure handling:
EnableServicereturnsreason: :flow_not_provisionedand leaves neither a row nor an enabled flow whenAi::Catalog::Flows::SyncFoundationalFlowsServicereports failures. This is covered byenable_service_spec.rb. - Please look hard at: the claim rules in
EnableService#claimable?and the ordering of the checks (a group that is not connected must not learn who has the workspace).
References
- Implementation issue: #633566
- Rollout issue for the flag: #633582
- Design discussion and proof of concept video: gitlab-org#23704
- Proof of concept, not for merge: !261784 (closed)
Screenshots or screen recordings
None: this MR has no UI. The next MRs in the stack add the endpoint and the settings page, which is where you can click through it.
How to set up and validate locally
-
Seed the test world (users, groups and fake Slack installs in every state, no Slack needed): #633566 (comment 4012898135). Run it from your
gitlabdirectory:bin/rails runner -e development seed_duo_slack.rbIt also enables the
slack_duo_group,slack_duo_agentandslack_duo_api_flowfeature flags. Run it again at any time to reset all GitLab Duo state. -
Open
bin/rails consoleand run this. It calls the services as different people on the seeded groups (sd-ahas the app installed,sd-otherhas the same Slack workspace,sd-multiis not connected to it):duo = Integrations::SlackWorkspace::DuoGroup call = ->(service, path, user) do Integrations::SlackWorkspace::DuoGroups.const_get(service).new( group: Group.find_by_full_path(path), current_user: User.find_by_username(user), params: { team_id: 'T0WWA' } ).execute end call.(:EnableService, 'sd-a', 'sd-owner').success? # => true duo.for_team('T0WWA').namespace.full_path # => "sd-a" call.(:EnableService, 'sd-a', 'sd-owner').success? # => true, still one row call.(:EnableService, 'sd-other', 'sd-owner2').reason # => :claimed_by_another_group call.(:EnableService, 'sd-multi', 'sd-owner').reason # => :workspace_not_connected call.(:DisableService, 'sd-a', 'sd-maint').reason # => :forbidden call.(:DisableService, 'sd-a', 'sd-owner').success? # => true duo.for_team('T0WWA') # => nil -
Check that the flag gates it:
Feature.disable(:slack_duo_group), then repeat the first call. It answersreason: :not_available. Enable the flag again afterwards.
| Step | What it shows |
|---|---|
| Owner enables | One row, and the Slack flow is turned on in the group |
| Enable again | Safe to repeat, no second row |
Owner of sd-other |
A second group cannot take the workspace over |
Owner of sd-multi |
A group that is not connected cannot claim it, and is not told who has it |
| Maintainer | Cannot change it |
| Claim of a group that lost its connection | A connected group can take the workspace over (covered by enable_service_spec.rb) |
| Disable | The row and the Slack flow go away together |
Both ways of running GitLab behave the same here. To check, restart the web server with GITLAB_SIMULATE_SAAS=1 and run the console steps again.
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.