Bail out of a flow trigger run when the flow does not support the resource type

Ai::FlowTriggers::RunService will happily start a flow for a resource type the flow cannot handle. recommend_reviewers/v1 declares no supported_events, so an @duo mention on an issue starts a workflow whose goal is the flow's description sentence bound to merge_request_iid.

!248388 (merged) guarded the goal template and the additional_context_resolver on MergeRequest, which stops the NoMethodError and stops an issue iid being read as a merge request iid - but the run still starts and no-ops. It should not start at all.

Proposal: declare the resource types a foundational flow accepts, and bail in RunService#validation_error when the trigger resource is not one of them.

Detailed context for AI agents

Why it happens

Ai::Catalog::FoundationalFlow has no notion of an accepted resource type. Two existing attributes look adjacent and neither works:

  • supported_events is event-shaped. It is nil for recommend_reviewers/v1 (ee/app/models/ai/catalog/foundational_flow/items.rb), and nil means any event is allowed - see Ai::FlowTrigger#supported_events_match_foundational_flow. Narrowing it to merge-request-ish events would be the wrong axis anyway: it is the resource that matters, not the event.
  • precondition is a Gitlab::FilterEvaluator filter run against the webhook payload and merged per hook scope (ee/app/services/ai/flow_triggers/filter_evaluator.rb). Expressing "the resource must be a MergeRequest" there means writing one rule per payload shape the flow can receive, and keeping them in sync.

So the check needs a new declaration, e.g. supported_resource_types: [::MergeRequest], defaulting to nil for "no restriction" so existing flows are unaffected.

Where the guard goes

Ai::FlowTriggers::RunService#validation_error - it already returns ServiceResponse.error for non-human users and for third-party permission failures, and it is evaluated inside execute_flow, which is inside the CreateNoteService#execute block. CreateNoteService yields, and on an error response calls mark_failed(note, response.message) and returns the response rather than raising, so the mention loop in Notes::PostProcessService stays intact and sibling triggers are not re-run on NewNoteWorker retry. That is exactly the failure shape wanted here.

Once this lands, the two MergeRequest guards in recommend_reviewers/v1 can be reconsidered. The additional_context_resolver guard also covers the bespoke CreateAndStartWorkflowService path, so it may still be worth keeping; the goal template guard becomes redundant for the trigger path.

Scope note

Adding the attribute changes trigger behaviour for every flow that adopts it, which is why it was left out of !248388 (merged) - that MR's job was making the trigger path work at all. Raised by @kinsingh in review: !248388 (comment 3672261843)

Epic: gitlab-org#23019