fix(read-lifecycle): harden CCR persistence (is None + import guard + try/except)

Address Copilot review feedback:

- content_router.py: explicit `is None` check (truthiness override would
  discard falsy test doubles); guard the `get_compression_store` import so
  a stripped build without the module still runs read_lifecycle, matching
  smart_crusher's pattern.
- read_lifecycle.py: wrap `store.store(...)` in try/except with a
  precomputed fallback hash so a transient backend failure can't break
  compress(), mirroring read_maturation.py.

22 tests pass; end-to-end via headroom.compress() still hits the store.
This commit is contained in:
Kiryu Tsukimiya 2026-06-27 14:28:02 +09:00
parent dde424783f
commit 55a0dfde67
No known key found for this signature in database
2 changed files with 29 additions and 16 deletions

View file

@ -2417,12 +2417,22 @@ class ContentRouter(Transform):
"""
# Pre-process: Read lifecycle management (stale/superseded detection)
if self.config.read_lifecycle.enabled:
from ..cache.compression_store import get_compression_store
from .read_lifecycle import ReadLifecycleManager
# is None (not truthiness) so falsy test doubles are honored;
# guarded import keeps read_lifecycle running in stripped builds.
injected_store = kwargs.get("compression_store")
if injected_store is None:
try:
from ..cache.compression_store import get_compression_store
injected_store = get_compression_store()
except ImportError:
pass
lifecycle_mgr = ReadLifecycleManager(
self.config.read_lifecycle,
compression_store=kwargs.get("compression_store") or get_compression_store(),
compression_store=injected_store,
)
lifecycle_result = lifecycle_mgr.apply(
messages,

View file

@ -474,21 +474,24 @@ class ReadLifecycleManager:
if content_bytes < self.config.min_size_bytes:
return False, content, None
# Store original in CCR if available
ccr_hash = None
# Best-effort CCR persistence (mirrors read_maturation.py): a store
# failure must not break compress().
ccr_hash = hashlib.sha256(content.encode()).hexdigest()[:24]
if self.store is not None:
ccr_hash = self.store.store(
original=content,
compressed="",
tool_name="Read",
tool_call_id=classification.tool_call_id,
compression_strategy=f"read_lifecycle:{classification.state.value}",
)
# Generate marker
if ccr_hash is None:
# No CCR store — generate a content hash for reference
ccr_hash = hashlib.sha256(content.encode()).hexdigest()[:24]
try:
ccr_hash = self.store.store(
original=content,
compressed="",
tool_name="Read",
tool_call_id=classification.tool_call_id,
compression_strategy=f"read_lifecycle:{classification.state.value}",
)
except Exception as e: # noqa: BLE001 - storage failure must not break the request
logger.warning(
"read_lifecycle: CCR store failed for %s: %s",
classification.tool_call_id,
e,
)
file_display = classification.file_path or "unknown"