Skip to content

fix(test-linker): fingerprint-keyed test-edge cache + staleness + reroute (TAP-4080)#194

Merged
wtthornton merged 1 commit into
masterfrom
tap-4080-fingerprint-staleness
Jul 2, 2026
Merged

fix(test-linker): fingerprint-keyed test-edge cache + staleness + reroute (TAP-4080)#194
wtthornton merged 1 commit into
masterfrom
tap-4080-fingerprint-staleness

Conversation

@wtthornton

Copy link
Copy Markdown
Owner

What

Finishes TAP-4080 correctly. PR #193 shipped a reduced-scope version that an independent verifier refuted: it wrote an unconditional fixed-path cache (.tapps-mcp/test-edges.json) that was never fingerprint-keyed, never checked staleness (a stale cache from an old call graph was served as a hit — a correctness bug), and was dead code — the three diff_impact call sites still called build_test_edges directly, so the cache was never exercised by the path it was meant to speed up. This PR meets every acceptance criterion on the ADR-0029 substrate.

Changes

  • Fingerprint keying (AC1): cache moved to .tapps-mcp/test-edges-index.json and the payload embeds the call-graph fingerprint (index.fingerprint), the same fingerprint the call-graph cache uses.
  • Staleness (AC2): load_test_edges_cache validates the cached fingerprint with FingerprintStaleness — same fingerprint => hit; mismatch => miss + rebuild + rewrite. A stale cache is never served.
  • Reroute (AC / diff_impact): all three diff_impact.py sites (analyze_diff_impact, per-symbol lookup, export_test_map) route through the new load_or_build_test_edges_for_index(project_root, index) fast path — no redundant graph rebuild; force_rebuild threaded through export_test_map.
  • Eager stats registration (AC3): register_cache_stats("test_edges", ...) now fires via a side-effect import at the end of test_linker.py, which is imported on the diff_impact / call-graph paths a normal tapps_stats run exercises. "test_edges" now appears in tapps_stats.caches without a prior load_or_build call. Added _reset_test_edges_stats() to conftest cache reset.
  • Tests (AC4): existing save/load + load_or_build tests updated to the new path/signature; added test_cache_invalidates_on_fingerprint_change (old fp => hit, changed fp => miss + fresh rebuild) and test_stats_registered_in_collect_cache_stats.

Reuses AtomicJsonCache + FingerprintStaleness (ADR-0029) — no parallel atomic-write or staleness code.

Tests

  • uv run pytest packages/tapps-mcp/tests/unit/test_test_linker.py -o addopts="" -v7 passed (incl. the new invalidation + stats tests).
  • uv run pytest packages/tapps-mcp/tests -k 'diff_impact' -m 'not slow' -o addopts=""15 passed.
  • export_test_map / call-graph tests — 3 passed.
  • ruff clean on all changed files. No test skipped/xfailed; no gate constant loosened.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

…oute (TAP-4080)

The reduced-scope #193 shipped an UNCONDITIONAL fixed-path cache that was
never fingerprint-keyed, never checked staleness (serving edges from an old
call graph as a hit — a correctness bug), and was dead code: the three
diff_impact call sites still called build_test_edges directly, and the
"test_edges" stats provider only registered on a lazy import inside
load_or_build. This finishes the spec on the ADR-0029 substrate.

- Cache at .tapps-mcp/test-edges-index.json now embeds the call-graph
  fingerprint (index.fingerprint) and validates it with FingerprintStaleness
  on load: same fingerprint => hit, changed => miss + rebuild + rewrite. No
  stale edges are ever served.
- New load_or_build_test_edges_for_index(project_root, index) is the fast path
  for callers already holding the index (no redundant graph rebuild).
- All three diff_impact call sites (analyze_diff_impact, per-symbol lookup,
  export_test_map) route through the cached path; force_rebuild threaded
  through export_test_map.
- register_cache_stats("test_edges", ...) fires eagerly via a side-effect
  import at the end of test_linker.py, which is imported on the diff_impact /
  call-graph paths a normal tapps_stats run exercises — so "test_edges" now
  appears in tapps_stats.caches without a prior load_or_build call.
- Added _reset_test_edges_stats() to conftest cache reset.
- Tests: cache save/load and load_or_build updated to the new path/signature;
  added test_cache_invalidates_on_fingerprint_change (old fp => hit, changed
  fp => miss + fresh rebuild) and test_stats_registered_in_collect_cache_stats.

Reuses AtomicJsonCache + FingerprintStaleness (ADR-0029); no parallel
atomic-write or staleness code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wtthornton wtthornton merged commit f155deb into master Jul 2, 2026
4 checks passed
@wtthornton wtthornton deleted the tap-4080-fingerprint-staleness branch July 2, 2026 17:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1faef9f3ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +129 to 131
cached = load_test_edges_cache(project_root, fingerprint)
if cached is not None:
return cached

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rebuild test edges for forced-fresh indexes

When tapps_diff_impact(force_rebuild=True) rebuilds the call graph, analyze_diff_impact() still reaches this hit path with force_rebuild=False. In git checkouts the call-graph fingerprint is based on HEAD plus the set of dirty file names, so editing the contents of an already-dirty test file can produce a fresh index with the same index.fingerprint as the old TESTS-edge cache; this returns stale edges from the previous test body even though the caller explicitly rebuilt the graph. Please either propagate the force flag through analyze_diff_impact or validate the cached edges against the supplied index instead of returning on fingerprint alone.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant