Add a 'During generate' (streaming) section to the Guardrail Set form and let guardrails declare their supported modes
## Summary Add a **"During generate" (streaming) section** to the Guardrail Set form (`src/Form/AiGuardrailSetForm.php`) so site builders can explicitly pick streaming guardrails, and add a way for a guardrail plugin to **declare which of the three modes** (`pre` / `post` / `during`) it supports so each section only lists the guardrails that belong there. Target branch: **1.5.x**. ## Problem The docs (`docs/developers/guardrails.md`) describe three guardrail modes via `AiGuardrailModeEnum`: | Mode | Enum case | Value | | --- | --- | --- | | Pre-generate | `AiGuardrailModeEnum::PreGenerate` | `pre` | | Post-generate | `AiGuardrailModeEnum::PostGenerate` | `post` | | During-generate | `AiGuardrailModeEnum::DuringGenerate` | `during` | But the UI and data model don't reflect this: 1. **No "during" section in the form.** `AiGuardrailSetForm::form()` only builds two sections — `pre_generate_guardrails` and `post_generate_guardrails`. There is no way to pick a streaming guardrail as a first-class "during" choice. Today, per `docs/developers/guardrails.md` ("Registration", lines 321–323), a `StreamableGuardrailInterface` guardrail must be placed on the **post-generate list**, and `GuardrailsEventSubscriber` auto-detects it at runtime (`if ($streaming_iterator !== NULL && $guardrail instanceof StreamableGuardrailInterface)`) and registers it with the stream iterator instead of running `processOutput()`. That implicit "drop it in post and hope it's detected" behaviour is confusing and undiscoverable. 2. **The `during` mode is effectively dead.** `AiGuardrailModeEnum::DuringGenerate` is defined but **never referenced anywhere** in `src/` — only `PreGenerate` and `PostGenerate` are used (in `GuardrailsEventSubscriber::addGuardrailResult()` calls). The `AiGuardrailSet` config entity only persists `pre_generate_guardrails` and `post_generate_guardrails` (see the entity annotation `config_export` and `config/schema/ai.schema.yml:218,226`); there is no `during_generate_guardrails` list. 3. **A guardrail cannot declare which modes it applies to.** The `#[AiGuardrail]` attribute (`src/Attribute/AiGuardrail.php`) only carries `id`, `label`, `description`, `deriver` — nothing about supported modes. `AiGuardrailSetForm::buildGuardrailSection()` calls `AiGuardrail::loadMultiple()` and lists **every** guardrail in **every** section dropdown, regardless of whether the plugin meaningfully implements `processInput()` (pre), `processOutput()` (post), or `StreamableGuardrailInterface` (during). A streaming-only guardrail still shows up under "Pre generate" and vice-versa, with no signal to the site builder about where it actually belongs. Who benefits: site builders configuring guardrail sets in the admin UI (`/admin/config/ai/guardrails/guardrail-sets`), and module developers writing guardrails who want their plugin to appear only in the relevant section. ## Proposed solution *(optional)* Two related pieces of work — both should land together so the new "during" section can be filtered correctly: **A. Add a "During generate" section to the Guardrail Set form** * Add a third fieldset (e.g. `during_generate_guardrails`) to `AiGuardrailSetForm::form()`, built via the existing `buildGuardrailSection()` helper. * Add a `during_generate_guardrails` property to the `AiGuardrailSet` entity, its `config_export` annotation, `config/schema/ai.schema.yml`, and a `getDuringGenerateGuardrails()` getter mirroring the pre/post getters. * Wire `AiGuardrailModeEnum::DuringGenerate` through `GuardrailsEventSubscriber` so streaming guardrails are sourced from the new "during" list and registered with the stream iterator from there, instead of being implicitly auto-detected on the post-generate list. * Decide on backwards compatibility / migration: existing sets that have a `StreamableGuardrailInterface` guardrail sitting on the post list should keep working (a `post_update` hook to move them, or continued auto-detection as a fallback). Update `docs/developers/guardrails.md` ("Registration" section) once the picker exists. **B. Let a guardrail declare its supported modes and filter the form accordingly** * Add a way for a guardrail to declare which of the three modes it supports. Two candidate approaches (maintainer's choice): * **Explicit:** add a `modes` field (array of `AiGuardrailModeEnum`) to the `#[AiGuardrail]` attribute, defaulting to `[pre, post]` for back-compat. * **Derived:** infer supported modes from the plugin — `StreamableGuardrailInterface` ⇒ `during`; an overridden/meaningful `processInput()` ⇒ `pre`; `processOutput()` ⇒ `post`. * Update `AiGuardrailSetForm::buildGuardrailSection()` to filter the per-section dropdown to only guardrails that support that section's mode, so streaming guardrails appear only under "During generate", input-only guardrails only under "Pre generate", etc. ## Workaround *(optional)* Streaming guardrails currently have to be added to the **post-generate** list, where `GuardrailsEventSubscriber` auto-detects `StreamableGuardrailInterface` and registers them with the stream iterator. It works but is undiscoverable, and every guardrail is offered in every section regardless of relevance. ## Affected modules / components *(optional)* - `ai` core module: `src/Form/AiGuardrailSetForm.php`, `src/Entity/AiGuardrailSet.php`, `src/Entity/AiGuardrailModeEnum.php`, `src/Attribute/AiGuardrail.php`, `src/EventSubscriber/GuardrailsEventSubscriber.php`, `config/schema/ai.schema.yml`, `docs/developers/guardrails.md`.
issue