ai_observability: streaming OTel spans drop final token usage (span ended on un-consumed iterator)
## Problem
Streaming chat OTel spans record no final token usage.
`AiOtelSpansEventSubscriber` sends both `PostGenerateResponseEvent` and
`PostStreamingResponseEvent` to one handler. It ends the span on the first
event, before the stream is consumed, so token usage is empty. The terminal
event carries the real usage, but the span is already ended and OTel ignores
attributes set after `end()`. The stored span is also never unset, so
`$otelSpans` grows unbounded.
Two related gaps on the terminal event:
- `operationType` and `configuration` are empty. `attachStreamMetadata()` sets
`providerConfiguration`, which `triggerEvent()` never reads. This also leaves
the metrics `operation_type` dimension empty.
- `reconstructChatOutput()` does not copy `finishReason` onto the final message.
`ReplayedChatMessageIterator` and `AssistantStreamIterator` override
`getIterator()` without dispatching the terminal event, so spans can leak.
## Fix
- Finalize the span on `PostStreamingResponseEvent`, then unset it.
- Add a shutdown guard for iterators that never dispatch the terminal event.
- Populate `operationType` and `configuration` on the terminal event.
- Preserve `finishReason` in the reconstructed output.
## Done
- Streaming spans carry final input/output token usage.
- No unbounded `$otelSpans` growth.
- Terminal event has non-empty `operationType` and `configuration`.
- No span leak for non-dispatching subclasses.
- `finishReason` recoverable from the final output.
- Test simulates the real event order, and fixes the mislabeled dispatch in
`AiOtelMetricsEventSubscriberTest`.
## Related
Umbrella #3586445. Layer 2 envelope #3586453. Prerequisite for #3586479.
AI-Generated: Yes (Claude Code; verified against drupal/ai 1.4.0 with a real OpenTelemetry SDK.)
issue