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
|
return None
|
||||||
if not isinstance(entries, list):
|
if not isinstance(entries, list):
|
||||||
return None
|
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 = []
|
rows = []
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
if not isinstance(entry, dict):
|
if not isinstance(entry, dict):
|
||||||
continue
|
continue
|
||||||
child_sid = _safe_first(entry.get("session_id"))
|
child_sid = _safe_first(entry.get("session_id"))
|
||||||
if (
|
if not child_sid or not is_safe_session_id(child_sid):
|
||||||
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"))
|
|
||||||
):
|
|
||||||
continue
|
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)
|
rows.append(entry)
|
||||||
|
if persisted_sidecar_ids - indexed_ids - seen_ids:
|
||||||
|
return None
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def _child_rows_from_sidecars(seen_ids: set[str]) -> list:
|
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()
|
memory_seen_ids: set[str] = set()
|
||||||
rows = _child_rows_from_memory(memory_seen_ids)
|
rows = _child_rows_from_memory(memory_seen_ids)
|
||||||
index_seen_ids = set(memory_seen_ids)
|
index_rows = _child_rows_from_index(memory_seen_ids)
|
||||||
index_rows = _child_rows_from_index(index_seen_ids)
|
|
||||||
if index_rows is not None:
|
if index_rows is not None:
|
||||||
indexed_result = _resolve_from_rows(rows + index_rows)
|
return _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)
|
|
||||||
|
|
||||||
rows.extend(_child_rows_from_sidecars(memory_seen_ids))
|
rows.extend(_child_rows_from_sidecars(memory_seen_ids))
|
||||||
return _resolve_from_rows(rows)
|
return _resolve_from_rows(rows)
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ SESSIONS_JS = ROOT / "static" / "sessions.js"
|
|||||||
|
|
||||||
|
|
||||||
def _write_sidecar(session_dir: Path, sid: str, **overrides):
|
def _write_sidecar(session_dir: Path, sid: str, **overrides):
|
||||||
|
messages = overrides.pop("messages", [])
|
||||||
payload = {
|
payload = {
|
||||||
"session_id": sid,
|
"session_id": sid,
|
||||||
"title": sid,
|
"title": sid,
|
||||||
"created_at": 100.0,
|
"created_at": 100.0,
|
||||||
"updated_at": 100.0,
|
"updated_at": 100.0,
|
||||||
"profile": "work",
|
"profile": "work",
|
||||||
"messages": [],
|
|
||||||
}
|
}
|
||||||
payload.update(overrides)
|
payload.update(overrides)
|
||||||
|
payload["messages"] = messages
|
||||||
(session_dir / f"{sid}.json").write_text(json.dumps(payload), encoding="utf-8")
|
(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,
|
"created_at": 200.0,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
+ [{"session_id": f"noise{idx:08d}", "profile": "work"} for idx in range(50)]
|
||||||
),
|
),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
@@ -182,6 +184,128 @@ def test_empty_indexed_continuation_lookup_falls_back_to_sidecars(tmp_path, monk
|
|||||||
assert loaded == ["childempty01"]
|
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):
|
def test_indexed_continuation_lookup_follows_snapshot_hops_without_scanning(tmp_path, monkeypatch):
|
||||||
"""Repeated compression can resolve through index-backed snapshot descendants."""
|
"""Repeated compression can resolve through index-backed snapshot descendants."""
|
||||||
from api import routes, config
|
from api import routes, config
|
||||||
|
|||||||
Reference in new issue
Block a user