Expose flow trigger goals through GraphQL
What does this MR do and why?
Third of four MRs for #627896 (closed). MR 1 added the goals jsonb column, keyed by event type. This exposes it through GraphQL, so the trigger form in MR 4 can read and write it.
It adds a goals field to AiFlowTriggerType, a goals argument to the aiFlowTriggerCreate mutation, and a goals argument to the aiFlowTriggerUpdate mutation. It also adds an acceptsTriggerGoal field to AiCatalogFlow and to AiCatalogThirdPartyFlow. The three goals items use GraphQL::Types::JSON, the type the neighbouring filter field and argument already use. The two acceptsTriggerGoal items are Booleans, and the form needs them to decide whether to show a goal input at all. All five items are marked as an experiment for 19.5.
Three things worth a reviewer's attention:
- One rule, one place. Whether a flow accepts a configured goal now lives in a single method,
Ai::Catalog::Item#accepts_trigger_goal?.Ai::FlowTrigger#goal_supported?delegates to it and memoizes the result withstrong_memoize_attr, instead of keeping its own copy. The capability field and the write validation have to agree. If they drifted apart, the form would offer a goal input for a flow that then rejects the goal on save. - No flag strip in the service. MR 1's model validation already refuses a goal that is added or changed while
ai_flow_trigger_goalsis off, so a write with the flag off returns a readable error. Accepting the write and dropping the goal in silence would be worse: the dropped goal would come alive later when the flag is enabled. acceptsTriggerGoalsits on two flow types, not on one shared field. Only a flow can carry a trigger, so onAiCatalogAgentthe field would answer a question that cannot be asked. Worse, afalsevalue there could not be told apart from a flow that refuses goals. Putting the field on the two flow types removes that ambiguity: a caller must check the type before it can ask. One tradeoff remains: onAiCatalogThirdPartyFlowthe field can only ever betrue, becausefoundational_flowrequiresflow?. It stays there anyway, so MR 4's frontend does not have to re-derive the rule in JavaScript.
A foundational flow still refuses a goal. Those flows read resource identity out of the goal string, so an added instruction is rejected or dropped. accepts_trigger_goal stays false until a flow declares otherwise. #608242 (closed) frees them one at a time.
Update semantics come free from GraphQL. An explicit null clears the goals. An omitted argument leaves the stored goals alone.
All five GraphQL changes are in one MR on purpose. Each one regenerates doc/api/graphql/reference/_index.md and public/-/graphql/introspection_result.json, so split apart they would conflict.
This targets master, because MR 1 merged and its branch no longer exists.
References
- Work item: #627896 (closed)
- MR 1, merged: !256658 (merged)
- MR 2, the dispatch change: !257218 (merged)
- Frees foundational flows: #608242 (closed)
- Feature flag rollout issue: #630482
How to set up and validate locally
-
Enable the flag in a Rails console, then note the global ID of a trigger that listens to the
mentionevent.Feature.enable(:ai_flow_trigger_goals) Ai::FlowTrigger.last.to_global_id.to_s -
Open
/-/graphql-explorerand run this mutation. Put the goals object in the variables pane, so the event-type key reaches the server unchanged.mutation($input: AiFlowTriggerUpdateInput!) { aiFlowTriggerUpdate(input: $input) { aiFlowTrigger { id goals } errors } }{ "input": { "id": "gid://gitlab/Ai::FlowTrigger/1", "goals": { "mention": "Summarise the discussion so far." } } } -
Confirm
goalscomes back on the response anderrorsis empty.
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.