Allow InputInterface to hold and run multiple guardrail sets
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3584849. -->
Reported by: [marcus_johansson](https://www.drupal.org/user/385947)
Related to !1492
>>>
<p>[Tracker]<br>
<strong>Update Summary: </strong>[One-line status update for stakeholders]<br>
<strong>Short Description: </strong>Change InputInterface::setGuardrailSet() to support multiple guardrail sets so they can be attached at different stages of the request lifecycle.<br>
<strong>Check-in Date: </strong>MM/DD/YYYY<br>
[/Tracker]</p>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p>Today <code>InputInterface::setGuardrailSet()</code> and <code>getGuardrailSet()</code> only support a single <code>AiGuardrailSetInterface</code> per input (see <code>src/OperationType/InputBase.php</code> and <code>src/OperationType/InputInterface.php</code>). Calling <code>setGuardrailSet()</code> a second time silently replaces the first one.</p>
<p>This is too limiting. Guardrails can legitimately be attached at different stages of the request lifecycle - by the caller that built the input, by a middleware-style subscriber, by an agent or tool wrapping the call, or by a future global guardrails setting (see follow-up issue). With the current API only one of those stages can win and the others get dropped.</p>
<p>The <code>GuardrailsEventSubscriber</code> already iterates a single set and aggregates <code>StopResult</code> scores against a stop threshold, so extending it to iterate multiple sets is a natural fit.</p>
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
<ul>
<li>Change <code>InputBase</code> to store guardrail sets as an array keyed by guardrail set ID.</li>
<li>Add <code>addGuardrailSet(AiGuardrailSetInterface $guardrails): void</code>, <code>addGuardrailSets(array $guardrail_sets): void</code> and <code>getGuardrailSets(): array</code> to <code>InputInterface</code>.</li>
<li>Keep <code>setGuardrailSet()</code> and <code>getGuardrailSet()</code> for backwards compatibility: <code>setGuardrailSet()</code> becomes equivalent to resetting the list to a single set; <code>getGuardrailSet()</code> returns the first set or NULL. Mark both as deprecated in favor of the new plural methods.</li>
<li>Update <code>GuardrailsEventSubscriber::applyPreGenerateGuardrails()</code> and <code>applyPostGenerateGuardrails()</code> to iterate all attached sets. Decide how score aggregation and stop thresholds compose across sets - likely aggregate per-set rather than across sets so each set's threshold is honored independently.</li>
<li>Update <code>AiGuardrailHelper</code> to use the new add API.</li>
<li>Make this a prerequisite for the "global guardrails" feature so global sets can coexist with per-request sets without clobbering each other.</li>
<li>Tests: attaching two sets runs both, results from both are recorded via <code>addGuardrailResult()</code>, stop threshold is evaluated per set, rewrite results from one set are visible to the next.</li>
<li>Document the API change and migration path from <code>setGuardrailSet()</code> to <code>addGuardrailSet()</code>.</li>
</ul>
<h3 id="summary-ai-usage">AI usage (if applicable)</h3>
<p>[x] AI Assisted Issue<br>
This issue was generated with AI assistance, but was reviewed and refined by the creator.</p>
<p>[ ] AI Assisted Code<br>
[x] AI Generated Code<br>
[ ] Vibe Coded</p>
<p>- <strong>This issue was created with the help of AI</strong></p>
issue