fix(cache): prevent stale same-tick stat-index hits - #2385
Conversation
Revalidate recent and legacy stat-index values, confirm warm-hit stats, and require consistent content snapshots before caching file hashes. Keep hash and word-count volatility independent and cover reload, promotion, portability, future-mtime, and race paths.
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR reworks the stat-based fastpath in graphify/cache.py for file_hash and cached_word_count. It introduces a timestamp "stability window" (2 seconds) so recently modified files bypass the stat-only cache hit until their timestamps settle, plus new helpers for validating stat signatures, checking stability, reading content confirmed by consecutive equal stat snapshots, and dropping stale entries. It also adds per-field "volatile" flags (hashes_volatile, word_count_volatile) so a refreshed hash doesn't promote a stale sibling word-count and vice versa. The test surface is broad, touching test_cache.py, test_stat_index_portability.py, and test_word_count_cache.py — covering cache roundtrips, invalidation, portability across roots, future-mtime/ctime fallback behavior, and same-stat rewrite scenarios.
No blocking issues surfaced. 10 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 963 functions depend on the 261 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
file_hash()— 43 callers, 8 callees - worse:
cached_word_count()— 6 callers, 6 callees
Verification — 963 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 631 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below; 1 more finding(s) on lines outside this diff (see the check run).
| raw, st = confirm, confirm_st | ||
| return raw, None | ||
|
|
||
| def file_hash(path: Path, root: Path = Path("."), cache_root: "Path | None" = None) -> str: |
There was a problem hiding this comment.
file_hash()
fans out to 8 callees (efferent coupling); 43 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Problem
The persisted stat index could reuse a stale file hash or word count when a rapid rewrite kept the same size and
mtime_ns. That could replay semantic cache data for the previous contents.Changes
Verification
uv run --frozen --all-extras pytest -q: 3984 passed, 3 skippedgraphify/cache.py: 0 errorsgit diff --check: pass