fix(token_optimizer): include content in CacheEntry.to_dict — closes #770 cache-loss defect

Pre-fix to_dict omitted the required field content, so _load's
CacheEntry(**entry_data) raised TypeError on every reload and the
blanket except silently discarded the whole cache on every restart.

Frozen regression contract tests/test_token_optimizer_cache.py
(3 tests: save_persists_content, roundtrip_restores_content,
corrupt_storage_degrades_to_empty_cache). Full suite 2250/0.
This commit is contained in:
Nick committed 2026-09-30 12:18:35 +01:00
1 parent 6d40f94f18
commit f1e63b703e
2 files changed
+67

No files matched your search

+5
View File
@@ -44,8 +44,13 @@ class CacheEntry:
hit_count: int = 0
def to_dict(self):
# `content` is required by CacheEntry; omitting it here made the
# persisted JSON unloadable (CacheEntry(**entry_data) raised on
# _load), which the blanket except in _load turned into silent
# total cache loss on every restart. See issue #770.
return {
"key": self.key,
"content": self.content,
"tokens": self.tokens,
"last_used": self.last_used,
"use_count": self.use_count,
+62
View File
@@ -0,0 +1,62 @@
"""Cache roundtrip contract for TokenOptimizer.
Rebase w2 disposition evidence for issue #770: CacheEntry.to_dict omitted
`content`, so the persisted JSON could never satisfy CacheEntry(**entry_data)
on _load — the blanket except in _load silently discarded the whole cache on
every process restart. These tests freeze the repaired contract.
"""
import json
from rune.token_optimizer import CacheEntry, TokenOptimizer
def _entry(key: str = "static", content: str = "constitution text") -> CacheEntry:
return CacheEntry(
key=key,
content=content,
tokens=4,
last_used="2026-01-01T00:00:00+00:00",
use_count=1,
hit_count=0,
)
def _seed(opt: TokenOptimizer, key: str = "static", content: str = "constitution text"):
"""Seed a cache entry via the real mutation contract (every mutator
pairs its change with _mark_dirty — direct assignment alone never
reaches the save path)."""
opt.cache[key] = _entry(key, content)
opt._mark_dirty()
return opt
def test_save_persists_content(tmp_path):
path = tmp_path / "tok.json"
opt = TokenOptimizer(storage_path=str(path))
_seed(opt)
opt.flush()
data = json.loads(path.read_text())
assert data["cache"]["static"]["content"] == "constitution text"
def test_roundtrip_restores_content(tmp_path):
path = str(tmp_path / "tok.json")
seeder = TokenOptimizer(storage_path=path)
_seed(seeder)
seeder.flush()
reloaded = TokenOptimizer(storage_path=path)
assert "static" in reloaded.cache
assert reloaded.cache["static"].content == "constitution text"
assert reloaded.cache["static"].tokens == 4
def test_corrupt_storage_degrades_to_empty_cache(tmp_path):
"""The blanket except in _load is a deliberate corrupt-file recovery path:
the optimizer must start empty, never crash."""
path = tmp_path / "tok.json"
path.write_text("{not json at all")
opt = TokenOptimizer(storage_path=str(path))
assert opt.cache == {}