Make the feature flag the authority on flow availability
What does this MR do and why?
A flow that is gated behind a feature flag can still be enabled for a group. Turning the flag off afterwards left the flow running.
That happens because enabling a flow and gating it are two separate things. The record says the flow is on. The flag says it is off. Nothing reconciled the two, so triggers kept firing for a flow that is disabled in every sense that matters.
The flag is now the authority in two places: when a trigger starts a run, and when a session is created. The second half is the one that matters, because it stops the work before a session record exists.
Session creation needs its own check. Most entry points authorize against the AI catalog item consumer, and that check already denies when the flag is off. Two do not. One checks only the enablement record and the group-level switch, and it serves the workplan and readiness score flows. The other authorizes against the project or group instead of the consumer, and it serves the messaging adapters and the OpenTelemetry session endpoint. Session creation is the one point every entry point passes through, so the check belongs there.
The refusal does not name the flow. It says the flow is not available and stops. A specific message would let anyone who guesses a flow reference confirm that an unreleased flow exists. The flow execution authorizer is vague for the same reason.
Enabling a flow while its flag is off stays allowed, which is deliberate: a group can be prepared ahead of a rollout without the flow running in the meantime. Flows with no feature flag are unaffected.
References
- Found while wiring triggers for #609293 (closed)
- Phase 1 epic: &23131
Screenshots or screen recordings
Not applicable. There is no user-facing surface in this change.
How to set up and validate locally
Open a Rails console. risk_classification/v1 is a flagged flow, so it stands in for any of the four that carry a feature_flag:
user = User.find_by_username('root')
project = Project.find_by_full_path('gitlab-duo/test')
def attempt(project, user)
Ai::DuoWorkflows::CreateWorkflowService.new(
container: project,
current_user: user,
params: { environment: 'web', goal: 'check the gate', workflow_definition: 'risk_classification/v1' }
).execute
end-
With the flag off, creating a session is refused and no record is written:
Feature.disable(:duo_mr_risk_classification) before = Ai::DuoWorkflows::Workflow.count r = attempt(project, user) r.status # => :error r.message # => "This flow is not available" r.http_status # => :forbidden Ai::DuoWorkflows::Workflow.count - before # => 0On
masterthe same call returns:successand creates a session. -
Turn the flag on and the same call goes through:
Feature.enable(:duo_mr_risk_classification) before = Ai::DuoWorkflows::Workflow.count attempt(project, user).status # => :success Ai::DuoWorkflows::Workflow.count - before # => 1Clean up the session you just made with
Ai::DuoWorkflows::Workflow.where(goal: 'check the gate').last.destroy!. -
Confirm a flow with no flag is untouched, so nothing changes for every other flow:
Ai::Catalog::FoundationalFlow['code_review/v1'].feature_flag # => nil Ai::Catalog::FoundationalFlow['code_review/v1'].blocked_by_feature_flag?(project.root_ancestor) # => false -
For the trigger half, fire a trigger for a flagged flow with the flag off. The run comes back as an error reading
flow is disabled by a feature flag, where before it would have started a session.
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.