Phase 4.5: cleanup — polish, branching, embeddings, lifecycle, deep-link #7
Reference in New Issue
Block a user
Delete Branch "phase-4.5"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Burns down 23 of the 24 items in the Phase 4.5 / 5 backlog (T103–T118). T115 (sqlite-vec swap) deferred to Phase 5 — host Python lacks
enable_load_extension.What shipped
Wave 1 (polish):
Wave 2: T109 schema 0014 (memories.event_id for deep-link)
Wave 3: T110 drawer bundle — event_id guard, html.escape, Jinja partial, bulk significance re-rate
Wave 4: T111 search UX — FTS snippet highlighting + turn deep-link
Wave 5: T112 real embedding swap — LLMClient.embed Protocol + Mock impl + routing + --re-embed-all backfill (FeatherlessClient.embed raises NotImplementedError; documented gap)
Wave 6: T113 branching read-side filter — active_branch_event_ids helper applied to read_recent_dialogue, scene_summarize, search_memories, meanwhile
Wave 7: T114 regenerate lifecycle rollback — triggered_by_assistant_turn_id back-ref + event_status_reverted event kind
Wave 9: T116 CannedQueue fixture builder + T117 cross-feature integration tests + T118 docs sweep
Surfaced for Phase 5
Schema
13 → 14 (one migration: 0014_phase45_schema.sql adds memories.event_id)
Tests
413 (Phase 4 baseline) → 457 passed (44 new, full suite green)
Test plan
Add ``m.event_id`` (T109's nullable column from migration 0014) to ``search_all_memories``'s SELECT, propagate it through the route's template context, and have ``search.html`` build result links as ``/chats/{chat_id}#turn-{event_id}`` — matching the ``id="turn-{event_id}"`` anchor that Phase 3.5 T86 stamps on each turn DOM node so the chat page scrolls to the originating turn on load. Memory rows projected before the 0014 migration ran read NULL ``event_id``; the template falls back to a chat-level link in that case so we never emit ``#turn-None``. Pre-existing tests that asserted on the bare ``href="/chats/{chat_id}"`` contract are updated to assert on the ``href="/chats/{chat_id}#turn-`` prefix to reflect the new deep-link.Adds the inverse projection used by T114.3's regenerate rollback. The new ``event_status_reverted`` event kind carries ``{event_id, prior_status}`` and the handler unconditionally sets ``events.status = prior_status`` for the row. Unlike the forward transitions (event_started / event_completed / event_cancelled), this handler does NOT guard against terminal statuses — its entire purpose is to reverse a transition, including walking back from a terminal status to a non-terminal one. Without that, rolling back an event_completed (status='completed' is terminal for the forward handlers) would silently no-op and leave the row in the post-superseded state. The handler registers via the existing ``@on(kind)`` decorator pattern in chat/eventlog/projector.py, so future replays of an event_log that contains event_status_reverted rows pick it up automatically. Test exercises completed→active, active→planned, and cancelled→active round-trips.Closes the T83.4 gap: when ``regenerate_assistant_turn`` supersedes an assistant_turn that already produced lifecycle transitions, it now emits an ``event_status_reverted`` (T114.2) for each transition tagged with ``triggered_by_assistant_turn_id == original_assistant_event_id`` (T114.1 back-reference) before the regenerated narrative is reclassified. Mapping from forward kind to ``prior_status`` lives in ``_PRIOR_STATUS_MAP``: - event_started → planned - event_completed → active - event_cancelled → active (best-effort default; cancellation can fire from either planned or active, but detect_event_transitions only surfaces currently-active rows so 'active' is the realistic prior) Backward compatibility: lifecycle rows authored before T114.1 lack the back-reference field. Those are skipped (DEBUG log per row) and collected into a legacy WARNING that preserves the T83.4 observability contract — operators still see un-rolled-back transitions, just from older logs. The classify-and-emit pass below the rollback now operates against an events projection that has already been reverted, so re-firing ``event_started``/``event_completed``/``event_cancelled`` for the regenerated narrative is safe — no double-emit of promotion artifacts. Spec tests: - ``test_regenerate_rolls_back_event_started_from_superseded_turn`` - ``test_regenerate_rolls_back_event_completed_to_active`` (also exercises the multi-rollback loop: a turn that fired both a start and a completion gets two event_status_reverted rows in id order, with active as the final projection — matching the per-row replay semantics of the projector) - ``test_regenerate_skips_events_without_back_reference`` (pins the legacy compatibility path with both DEBUG and WARNING expectations)