Add a goals column to AI flow triggers
What does this MR do and why?
First of four MRs for #627896. It adds storage only — nothing reads the column yet.
A trigger derives its goal from the event, and for most event types that goal is not an instruction: an assign event sends the bare issue id, a pipeline event sends the raw webhook payload, a scheduled run sends the cron description.
This adds a goals jsonb column on ai_flow_triggers, keyed by event type like filter already is:
{ "assign": "Triage this issue.", "merge_request": "Review this MR." }The goal is per event, not per trigger, because one trigger holds several event conditions and each needs different behaviour. The issue author confirmed this after an earlier version of this MR used a single goal text column.
A JSON schema caps each goal at 4096 characters. That is well below Workflow::GOAL_MAX_LENGTH on purpose: a trigger goal is one input to the rendered workflow goal, which RunService budgets the discussion thread into.
It also adds the ai_flow_trigger_goals feature flag and a model validation that refuses a newly set goal while the flag is off. A trigger that already carries a goal still saves, so a rollback strands no rows. The gate sits on the model rather than on the mutation service, so it covers every writer. Defining the flag here also lets MRs 2, 3 and 4 each branch from this one instead of from each other.
Two things a reviewer may ask about:
- Foundational flows refuse a goal.
accepts_trigger_goalis false for every flow, because they still read resource identity out of the goal string. Each is freed by #608242. This means Duo Developer, the issue author's main use case, stays blocked until then. - Whether
mentionshould accept a goal is still open. A mention already carries the user's instruction in the comment text.
References
How to set up and validate locally
-
Run
bin/rails db:migrate, then in a console load a trigger on a custom AI Catalog flow.trigger = Ai::FlowTrigger.find(<id>) trigger.event_types -
A goal for an event it listens to saves; one for an event it does not is refused.
trigger.update(goals: { 'assign' => 'Triage this issue.' }) # => true trigger.update(goals: { 'pipeline_hooks' => 'Investigate.' }) # => false trigger.errors[:goals] -
A blank goal is dropped rather than stored.
trigger.goals = { 'assign' => ' ' } trigger.validate trigger.goals # => {} -
A goal on a
code_review/v1trigger is refused.Ai::FlowTrigger.find(<id>).update(goals: { 'assign' => 'Focus on SQL.' }) # => false -
bundle exec rspec ee/spec/models/ai/flow_trigger_spec.rb
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.