Skip to content

v1.108.178 — a cached negative must prove it still describes the subject

Choose a tag to compare

@jgravelle jgravelle released this 26 Jul 15:59

Item 3 of the #377 post-ship review by @mightydanp: a cached negative must prove it still describes the subject it was measured against.

The search_symbols result cache keys on the index's indexed_at, so it invalidates on a reindex and on nothing else. That is fine for a positive result, whose rows really were in the index at that generation. It is not fine for a negative one:

1. the query is absent against snapshot A
2. the result and its fresh verdict are cached
3. the source changes without the index being rebuilt
4. the same query hits the cache
5. the old fresh/absent verdict is replayed, and an absence proof is
   minted over a state nobody scanned

New retrieval/subject_state.py captures what the answer depends on at cache-write time (index generation, .db mtime, live git HEAD, and for an absence the working-tree fingerprint) and re-checks it at cache-read time. When something moved, the verdict discloses revalidated, an absent state is downgraded to degraded, and any evidence token minted on the earlier pass is stripped, because it was issued against the state that just failed to hold. The refusal names what moved rather than falling through to a generic "the verdict was degraded".

Only a negative pays for the expensive signal. git status runs only when the cached verdict is absent, the one answer a working-tree edit can falsify while the index and its generation sit still. A cached positive costs a few stats and keeps serving with disclosure. That split is the reviewer's and it is right.

Unknown is never reported as changed. A non-git checkout knows none of this and did not change either; treating unknown as moved would refuse every absence there.

Second defect fixed in passing: _result_cache_get copied the result and its _meta but not the nested verdict, so the evidence_ref the dispatcher attaches after the tool returns landed in the stored entry and was replayed to every later hit.

Verified on a real checkout, not only on fixtures: the same absent query is replayed unchanged when nothing moves, and downgrades the moment an untracked file appears. Whole-scope working-tree semantics for a live absence is item 5 and is not in this release.