b667a21c99
When a regenerate replaces an assistant_turn that already produced lifecycle transitions (``event_started`` / ``event_completed`` / ``event_cancelled``), those transitions are NOT rolled back before ``detect_event_transitions`` re-runs against the new text. A regenerate-after-completion can therefore double-emit promotion artifacts. Phase 3.5 first cut (per the task plan): documentation + WARNING log naming the affected event_log ids. The actual undo pass is invasive (re-projection / inverse-handler dispatch) and is deferred to Phase 4. Implementation: - TODO docstring block at the top of ``regenerate_assistant_turn``. - Module-level ``_log = logging.getLogger(__name__)``. - Scan immediately after the original assistant_turn row is located: joins event_log lifecycle rows to the events table on event_id so we can scope by chat (lifecycle payloads carry only ``event_id``, not ``chat_id``). Filter ``id > original_assistant_event_id`` as the positional linkage to "transitions emitted as part of (or after) this turn's processing." Decision (asked in the brief): the scan uses the ``id > original`` heuristic rather than scanning for explicit references. Lifecycle event payloads do not carry a back-pointer to the assistant_turn that triggered them — the linkage is positional in the event log. A tighter linkage would require either adding a payload field on lifecycle events (cross-cutting schema change) or threading the just-appended assistant_turn id into ``detect_event_transitions``'s emit calls (narrow but still cross-cutting). The positional heuristic is loose but conservative: a turn that emits no lifecycle events produces no warning, and the warning's purpose is operator-visible breadcrumbs not an exact rollback set. Test: test_regenerate_with_prior_lifecycle_logs_warning seeds a turn that produced ``event_started`` + ``event_completed`` rows and asserts the WARNING fires with the expected ids.