refactor(sessions): make whole_lineages an explicit prune keyword

_prune_where popped whole_lineages out of **filters and prune_sessions
injected it by mutating that dict, so a filter-shaped kwarg silently
changed the SQL shape and a typo'd flag would be ignored rather than
rejected. It is now a keyword-only argument of _prune_where and the
list/count prune helpers; prune_sessions passes whole_lineages=True and
the CLI and dashboard previews pass it explicitly. Behaviour unchanged.
This commit is contained in:
kshitijk4poor
2026-09-26 21:54:34 +05:30
committed by kshitij
parent 2759f0fe97
commit 257f70db28
3 changed files with 20 additions and 21 deletions

View File

@@ -220,11 +220,10 @@ class SessionMaintenanceMixin:
clauses.append("COALESCE(s.pinned, 0) = 0")
return " AND ".join(clauses), params
def _prune_where(self, older_than_days, source, filters) -> Tuple[str, list]:
def _prune_where(self, older_than_days, source, filters, *, whole_lineages: bool = False) -> Tuple[str, list]:
"""Translate the legacy age window into the shared activity filter, then build WHERE.
``whole_lineages`` (prune) keeps a compression ancestor while any continuation after it
is unmatched."""
whole_lineages = filters.pop("whole_lineages", False)
if (older_than_days is not None and filters.get("last_active_before") is None
and filters.get("started_before") is None):
if older_than_days < 0:
@@ -239,11 +238,11 @@ class SessionMaintenanceMixin:
# segment stays, deleting it would cut the start off a chat that is still in use.
return f"{where} AND s.id NOT IN ({_continued_ancestors_sql(where)})", [*params, *params]
def list_prune_candidates(self, older_than_days: Optional[float] = None, source: str = None,
**filters) -> List[Dict[str, Any]]:
def list_prune_candidates(self, older_than_days: Optional[float] = None, source: str = None, *,
whole_lineages: bool = False, **filters) -> List[Dict[str, Any]]:
"""Dry-run: sessions a matching prune/archive would touch, oldest first (``older_than_days``
= inactivity threshold: freshest of ``last_activity_at`` / latest message / ``started_at``)."""
where, params = self._prune_where(older_than_days, source, filters)
where, params = self._prune_where(older_than_days, source, filters, whole_lineages=whole_lineages)
return [dict(row) for row in self._read_all(
f"""SELECT s.id, s.source, s.title, s.model, s.started_at,
{_LAST_ACTIVE_SQL} AS last_active,
@@ -251,16 +250,16 @@ 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,
**filters) -> int:
def count_prune_matches(self, older_than_days: Optional[float] = None, source: str = None, *,
whole_lineages: bool = False, **filters) -> int:
"""Count-only :meth:`list_prune_candidates` (CLI reports spared pinned sessions)."""
where, params = self._prune_where(older_than_days, source, filters)
where, params = self._prune_where(older_than_days, source, filters, whole_lineages=whole_lineages)
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,
**filters) -> int:
def count_open_prune_matches(self, older_than_days: Optional[float] = None, source: str = None, *,
whole_lineages: bool = False, **filters) -> int:
"""Count open sessions a matching prune skips (``ended_at`` guard inverted); visibility-only."""
where, params = self._prune_where(older_than_days, source, filters)
where, params = self._prune_where(older_than_days, source, filters, whole_lineages=whole_lineages)
ended_guard = "s.ended_at IS NOT NULL"
if not where.startswith(ended_guard):
raise RuntimeError("prune filter lost its ended-session safety guard")
@@ -303,8 +302,7 @@ class SessionMaintenanceMixin:
``exclude_active_write_guards`` (automatic maintenance) skips rows under a live turn lease
or compression lock while expired/dead holders are reclaimed and fenced. A compression
ancestor is deleted only together with every continuation after it (``whole_lineages``)."""
filters["whole_lineages"] = True
where, where_params = self._prune_where(older_than_days, source, filters)
where, where_params = self._prune_where(older_than_days, source, filters, whole_lineages=True)
removed_ids: list[str] = []
def _do(conn):
cursor = conn.execute(f"SELECT s.id FROM sessions s WHERE {where}", where_params)