Prove continuation index completeness before trusting it
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
eda4c50cf5
commit
d68760b288
2 files changed
+144
-15
No files matched your search
+19
-14
@@ -6787,20 +6787,30 @@ def _pre_compression_continuation_session_id(session) -> str | None:
|
||||
return None
|
||||
if not isinstance(entries, list):
|
||||
return None
|
||||
try:
|
||||
persisted_sidecar_ids = {
|
||||
path.stem
|
||||
for path in SESSION_DIR.glob("*.json")
|
||||
if not path.name.startswith("_") and is_safe_session_id(path.stem)
|
||||
}
|
||||
except Exception:
|
||||
return None
|
||||
indexed_ids: set[str] = set()
|
||||
row_seen_ids = set(seen_ids)
|
||||
rows = []
|
||||
for entry in entries:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
child_sid = _safe_first(entry.get("session_id"))
|
||||
if (
|
||||
not child_sid
|
||||
or child_sid in seen_ids
|
||||
or not is_safe_session_id(child_sid)
|
||||
or not _safe_first(entry.get("parent_session_id"))
|
||||
):
|
||||
if not child_sid or not is_safe_session_id(child_sid):
|
||||
continue
|
||||
seen_ids.add(child_sid)
|
||||
indexed_ids.add(child_sid)
|
||||
if child_sid in row_seen_ids or not _safe_first(entry.get("parent_session_id")):
|
||||
continue
|
||||
row_seen_ids.add(child_sid)
|
||||
rows.append(entry)
|
||||
if persisted_sidecar_ids - indexed_ids - seen_ids:
|
||||
return None
|
||||
return rows
|
||||
|
||||
def _child_rows_from_sidecars(seen_ids: set[str]) -> list:
|
||||
@@ -6880,14 +6890,9 @@ def _pre_compression_continuation_session_id(session) -> str | None:
|
||||
|
||||
memory_seen_ids: set[str] = set()
|
||||
rows = _child_rows_from_memory(memory_seen_ids)
|
||||
index_seen_ids = set(memory_seen_ids)
|
||||
index_rows = _child_rows_from_index(index_seen_ids)
|
||||
index_rows = _child_rows_from_index(memory_seen_ids)
|
||||
if index_rows is not None:
|
||||
indexed_result = _resolve_from_rows(rows + index_rows)
|
||||
if indexed_result:
|
||||
return indexed_result
|
||||
rows.extend(_child_rows_from_sidecars(set(memory_seen_ids)))
|
||||
return _resolve_from_rows(rows)
|
||||
return _resolve_from_rows(rows + index_rows)
|
||||
|
||||
rows.extend(_child_rows_from_sidecars(memory_seen_ids))
|
||||
return _resolve_from_rows(rows)
|
||||
|
||||
@@ -10,15 +10,16 @@ SESSIONS_JS = ROOT / "static" / "sessions.js"
|
||||
|
||||
|
||||
def _write_sidecar(session_dir: Path, sid: str, **overrides):
|
||||
messages = overrides.pop("messages", [])
|
||||
payload = {
|
||||
"session_id": sid,
|
||||
"title": sid,
|
||||
"created_at": 100.0,
|
||||
"updated_at": 100.0,
|
||||
"profile": "work",
|
||||
"messages": [],
|
||||
}
|
||||
payload.update(overrides)
|
||||
payload["messages"] = messages
|
||||
(session_dir / f"{sid}.json").write_text(json.dumps(payload), encoding="utf-8")
|
||||
|
||||
|
||||
@@ -128,6 +129,7 @@ def test_continuation_lookup_uses_index_without_scanning_sidecars(tmp_path, monk
|
||||
"created_at": 200.0,
|
||||
}
|
||||
]
|
||||
+ [{"session_id": f"noise{idx:08d}", "profile": "work"} for idx in range(50)]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
@@ -182,6 +184,128 @@ def test_empty_indexed_continuation_lookup_falls_back_to_sidecars(tmp_path, monk
|
||||
assert loaded == ["childempty01"]
|
||||
|
||||
|
||||
def test_stale_index_with_existing_candidate_falls_back_to_newer_sidecar(tmp_path, monkeypatch):
|
||||
"""A complete-looking result is not trusted when another sidecar is absent from the index."""
|
||||
from api import routes, config, models
|
||||
|
||||
class _S:
|
||||
def __init__(self, sid, profile, snap=False):
|
||||
self.session_id = sid
|
||||
self.profile = profile
|
||||
self.parent_session_id = None
|
||||
self.pre_compression_snapshot = snap
|
||||
self.updated_at = 100.0
|
||||
self.created_at = 100.0
|
||||
|
||||
snapshot = _S("snapstale001", "work", snap=True)
|
||||
index_file = tmp_path / "_index.json"
|
||||
_write_sidecar(
|
||||
tmp_path,
|
||||
"oldstale001",
|
||||
parent_session_id="snapstale001",
|
||||
updated_at=200.0,
|
||||
created_at=150.0,
|
||||
)
|
||||
_write_sidecar(
|
||||
tmp_path,
|
||||
"newstale001",
|
||||
parent_session_id="snapstale001",
|
||||
updated_at=400.0,
|
||||
created_at=350.0,
|
||||
)
|
||||
index_file.write_text(
|
||||
json.dumps(
|
||||
[
|
||||
{
|
||||
"session_id": "oldstale001",
|
||||
"profile": "work",
|
||||
"parent_session_id": "snapstale001",
|
||||
"updated_at": 200.0,
|
||||
"created_at": 150.0,
|
||||
}
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(config, "SESSION_DIR", tmp_path, raising=False)
|
||||
monkeypatch.setattr(models, "SESSION_DIR", tmp_path, raising=False)
|
||||
monkeypatch.setattr(routes, "SESSION_DIR", tmp_path, raising=False)
|
||||
monkeypatch.setattr(routes, "SESSION_INDEX_FILE", index_file, raising=False)
|
||||
monkeypatch.setattr(routes, "SESSIONS", collections.OrderedDict(), raising=False)
|
||||
|
||||
assert routes._pre_compression_continuation_session_id(snapshot) == "newstale001"
|
||||
|
||||
|
||||
def test_stale_index_multihop_falls_back_to_missing_descendant_sidecar(tmp_path, monkeypatch):
|
||||
"""An indexed snapshot ancestor must not hide a newer descendant omitted from the index."""
|
||||
from api import routes, config, models
|
||||
|
||||
class _S:
|
||||
def __init__(self, sid, profile, snap=False):
|
||||
self.session_id = sid
|
||||
self.profile = profile
|
||||
self.parent_session_id = None
|
||||
self.pre_compression_snapshot = snap
|
||||
self.updated_at = 100.0
|
||||
self.created_at = 100.0
|
||||
|
||||
snapshot = _S("snapstale002", "work", snap=True)
|
||||
index_file = tmp_path / "_index.json"
|
||||
_write_sidecar(
|
||||
tmp_path,
|
||||
"oldstale002",
|
||||
parent_session_id="snapstale002",
|
||||
updated_at=200.0,
|
||||
created_at=150.0,
|
||||
)
|
||||
_write_sidecar(
|
||||
tmp_path,
|
||||
"midstale002",
|
||||
parent_session_id="snapstale002",
|
||||
pre_compression_snapshot=True,
|
||||
updated_at=300.0,
|
||||
created_at=250.0,
|
||||
)
|
||||
_write_sidecar(
|
||||
tmp_path,
|
||||
"newstale002",
|
||||
parent_session_id="midstale002",
|
||||
updated_at=500.0,
|
||||
created_at=450.0,
|
||||
)
|
||||
index_file.write_text(
|
||||
json.dumps(
|
||||
[
|
||||
{
|
||||
"session_id": "oldstale002",
|
||||
"profile": "work",
|
||||
"parent_session_id": "snapstale002",
|
||||
"updated_at": 200.0,
|
||||
"created_at": 150.0,
|
||||
},
|
||||
{
|
||||
"session_id": "midstale002",
|
||||
"profile": "work",
|
||||
"parent_session_id": "snapstale002",
|
||||
"pre_compression_snapshot": True,
|
||||
"updated_at": 300.0,
|
||||
"created_at": 250.0,
|
||||
},
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(config, "SESSION_DIR", tmp_path, raising=False)
|
||||
monkeypatch.setattr(models, "SESSION_DIR", tmp_path, raising=False)
|
||||
monkeypatch.setattr(routes, "SESSION_DIR", tmp_path, raising=False)
|
||||
monkeypatch.setattr(routes, "SESSION_INDEX_FILE", index_file, raising=False)
|
||||
monkeypatch.setattr(routes, "SESSIONS", collections.OrderedDict(), raising=False)
|
||||
|
||||
assert routes._pre_compression_continuation_session_id(snapshot) == "newstale002"
|
||||
|
||||
|
||||
def test_indexed_continuation_lookup_follows_snapshot_hops_without_scanning(tmp_path, monkeypatch):
|
||||
"""Repeated compression can resolve through index-backed snapshot descendants."""
|
||||
from api import routes, config
|
||||
|
||||
Reference in new issue
Block a user