feat: branching read-side filter — event readers consult active branch range (T113)
Wire the active branch's [origin_event_id, head_event_id] window into every user-facing event/memory reader so switching branches actually changes what dialogue and memories the user sees. Phase 4 T89/T94 shipped branches as metadata-only — this closes the loop. Helper: - chat/state/branches.py: add `active_branch_event_ids(conn)` returning the active branch's id range, with two defensive fall-throughs to `(0, BIG_INT)`: (a) no active branch row at all, and (b) the bootstrap "main" sentinel (name="main", origin=0, head=0). Production never bumps main's head_event_id today, so this preserves existing reader behaviour for every test that doesn't explicitly switch. Readers updated (all user-facing dialogue / retrieval surfaces): - chat/services/turn_common.py::read_recent_dialogue — chat-history prompt context + the chat-view template path (via web/turns.py + web/chat.py). - chat/services/scene_summarize.py::_read_recent_dialogue — scene-close per-POV summary input. - chat/state/memory.py::search_memories — FTS leg filters via m.event_id (T109's column); legacy NULL event_id rows are *included* unconditionally so the filter doesn't break pre-0014 retrieval. The fused (FTS + RRF + vector) path also drops vector hits whose event_id falls outside the branch window. - chat/web/meanwhile.py::_read_recent_meanwhile_dialogue — meanwhile prompt context. Projector queries (chat/state/world.py et al.) and admin/management surfaces (drawer hide-panel, cross-chat search, regenerate's row lookups by id) are intentionally NOT branch-filtered: projection must see the full log to build state correctly, and the admin surfaces operate across branches by design. Tests (10 new, 446 total): - tests/test_branches_state.py: 3 tests for `active_branch_event_ids` itself (bootstrap-main, no-active-branch, non-main literal range). - tests/test_branching.py: 7 cross-feature tests covering the spec's five required scenarios plus scene_summarize and meanwhile readers.
This commit is contained in:
@@ -144,23 +144,36 @@ def _read_recent_dialogue(
|
||||
``id >= since_event_id`` so callers needing a scene-scoped view (e.g.
|
||||
thread detection on close) don't pull turns that landed before the
|
||||
closing scene's ``scene_opened`` event.
|
||||
|
||||
T113: also clamps by the active branch's ``[origin, head]`` event-id
|
||||
range so scene-summary inputs respect the user's current branch.
|
||||
Bootstrap-main and "no active branch" fall through to ``(0, BIG_INT)``
|
||||
so existing flows are unchanged.
|
||||
"""
|
||||
from chat.state.branches import active_branch_event_ids
|
||||
|
||||
origin, head = active_branch_event_ids(conn)
|
||||
if since_event_id is None:
|
||||
cur = conn.execute(
|
||||
"SELECT kind, payload_json FROM event_log "
|
||||
"WHERE kind IN ('user_turn', 'assistant_turn') "
|
||||
" AND superseded_by IS NULL AND hidden = 0 "
|
||||
" AND id BETWEEN ? AND ? "
|
||||
"ORDER BY id DESC LIMIT ?",
|
||||
(limit,),
|
||||
(origin, head, limit),
|
||||
)
|
||||
else:
|
||||
# Compose ``since_event_id`` with the branch lower bound — readers
|
||||
# want the tightest ``id >= max(since, origin)`` clamp without an
|
||||
# extra Python pass.
|
||||
lower = max(origin, since_event_id)
|
||||
cur = conn.execute(
|
||||
"SELECT kind, payload_json FROM event_log "
|
||||
"WHERE kind IN ('user_turn', 'assistant_turn') "
|
||||
" AND superseded_by IS NULL AND hidden = 0 "
|
||||
" AND id >= ? "
|
||||
" AND id BETWEEN ? AND ? "
|
||||
"ORDER BY id DESC LIMIT ?",
|
||||
(since_event_id, limit),
|
||||
(lower, head, limit),
|
||||
)
|
||||
rows = list(reversed(cur.fetchall()))
|
||||
out: list[dict] = []
|
||||
|
||||
Reference in New Issue
Block a user