fix(sessions): pinned-skip note counts pinned rows, not spared ancestors
_note_pinned_skipped diffed count_prune_matches(include_pinned=True/False) with whole_lineages on for prune. Excluding a pinned tip turns it into an unmatched continuation, so its unpinned compression ancestors drop out of the "without" count too and the note reported them as pinned (chain a -> a-2 -> a-3 with only a-3 pinned said "3 pinned sessions"). Count plain filter matches for the note; the delete itself is unchanged. count_prune_matches has no other caller that needs whole_lineages, so the keyword goes back to base's signature.
This commit is contained in:
@@ -639,8 +639,9 @@ def _note_pinned_skipped(db, filters, action):
|
||||
"""Tell the user how many pinned rows bulk prune/archive spared (pin = durable keep; only
|
||||
`prune --include-pinned` opts in, archive always spares them)."""
|
||||
_base = {k: v for k, v in filters.items() if k != "include_pinned"}
|
||||
with_pinned, without = (int(db.count_prune_matches(**_base, include_pinned=flag, whole_lineages=action == "prune"))
|
||||
for flag in (True, False))
|
||||
# Count matching pinned rows only: whole-lineage selection would also drop the unpinned
|
||||
# ancestors a pinned tip spares, and report them as pinned.
|
||||
with_pinned, without = (int(db.count_prune_matches(**_base, include_pinned=flag)) for flag in (True, False))
|
||||
skipped = max(with_pinned - without, 0)
|
||||
if not skipped:
|
||||
return
|
||||
|
||||
@@ -250,10 +250,10 @@ class SessionMaintenanceMixin:
|
||||
FROM sessions s WHERE {where}
|
||||
ORDER BY last_active ASC, s.started_at ASC""", params)]
|
||||
|
||||
def count_prune_matches(self, older_than_days: Optional[float] = None, source: str = None, *,
|
||||
whole_lineages: bool = False, **filters) -> int:
|
||||
def count_prune_matches(self, older_than_days: Optional[float] = None, source: str = None,
|
||||
**filters) -> int:
|
||||
"""Count-only :meth:`list_prune_candidates` (CLI reports spared pinned sessions)."""
|
||||
where, params = self._prune_where(older_than_days, source, filters, whole_lineages=whole_lineages)
|
||||
where, params = self._prune_where(older_than_days, source, filters)
|
||||
return int(self._read_one(f"SELECT COUNT(*) FROM sessions s WHERE {where}", params)[0])
|
||||
|
||||
def count_open_prune_matches(self, older_than_days: Optional[float] = None, source: str = None, *,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import time
|
||||
from contextlib import closing
|
||||
import pytest
|
||||
from hermes_cli.sessions_cmd import _note_pinned_skipped
|
||||
from hermes_state import SessionDB
|
||||
|
||||
|
||||
@@ -47,7 +48,7 @@ def test_prune_sessions_respects_touch_session_activity(tmp_path):
|
||||
assert db.get_session("active_by_heartbeat") is not None
|
||||
|
||||
|
||||
def test_prune_keeps_the_compressed_segments_of_a_chat_still_in_use(tmp_path):
|
||||
def test_prune_keeps_the_compressed_segments_of_a_chat_still_in_use(tmp_path, capsys):
|
||||
"""A rotated conversation ages as one: its old compressed-away segments stay while a later segment
|
||||
is still in use, and go together once the whole chat is idle. The CLI/dashboard preview lists what prune deletes."""
|
||||
with closing(SessionDB(tmp_path / "state.db")) as db:
|
||||
@@ -74,6 +75,13 @@ def test_prune_keeps_the_compressed_segments_of_a_chat_still_in_use(tmp_path):
|
||||
db._conn.commit()
|
||||
|
||||
idle = {"idle", "idle-2", "idle-3"}
|
||||
# The CLI pinned-skip note counts pinned rows, not the unpinned ancestors a pinned tip spares.
|
||||
db._conn.execute("UPDATE sessions SET pinned = 1 WHERE id = 'idle-3'")
|
||||
db._conn.commit()
|
||||
_note_pinned_skipped(db, {"older_than_days": 90, "lineage_tips_only": False}, "prune")
|
||||
assert "Note: 1 pinned session also match" in capsys.readouterr().out
|
||||
db._conn.execute("UPDATE sessions SET pinned = 0 WHERE id = 'idle-3'")
|
||||
db._conn.commit()
|
||||
assert {c["id"] for c in db.list_prune_candidates(older_than_days=90, whole_lineages=True)} == idle
|
||||
assert db.prune_sessions(older_than_days=90) == len(idle)
|
||||
assert db.get_compression_lineage("live-3") == ["live", "live-2", "live-3"]
|
||||
|
||||
Reference in New Issue
Block a user