Skip to content

fix: cache detection findings and show digest progress#35

Merged
karngyan merged 1 commit into
mainfrom
fix/findings-cache-progress
Jul 17, 2026
Merged

fix: cache detection findings and show digest progress#35
karngyan merged 1 commit into
mainfrom
fix/findings-cache-progress

Conversation

@karngyan

Copy link
Copy Markdown
Contributor

Claim

Every findings-consuming command — patterns, digest, mutations propose, mutations synthesize, and status --with-findings — re-ran a full deterministic detection pass over every stored event on each invocation. On the user's 69,400-event database that is ~45s of silent recomputation every time, with no output until it finishes, so the command appears hung. This is derived, deterministic work: recomputing an unchanged result is pure waste.

This PR (1) persists a detection-findings cache so repeated invocations at unchanged inputs are instant, and (2) reports concise progress to stderr when a fresh pass actually runs.

Design

  • New migration 0002_findings_cache.sql (store schema v1 → v2; migrations stay ordered and immutable). One findings_cache table: cache_key (32-byte validity key), generation (32-byte global corpus stamp for GC), report_json (opaque serialized report), created_at.
  • Content-addressed validity key, no explicit invalidation. cache_key = SHA-256(detector spec version ‖ min_occurrences ‖ min_sessions ‖ min_support_ratio_bps ‖ project filter ‖ event_count ‖ max_row_id ‖ import_watermark). The fingerprint (event_count, max(row_id), max(imported_at)) is read from indexed columns only — never by deserializing event JSON — so the validity check is orders of magnitude cheaper than the pass it guards. Any import/delete/prune moves at least one field, so a stale key simply misses; nothing has to remember to invalidate. Bumping DETECTION_SPEC_VERSION invalidates every entry, so a future signature-normalization change can't serve an outdated report. A reindex touches only the derived search projection (not the events detection reads), so it correctly leaves the cache valid.
  • Exact evidence IDs retained. The cached payload is the full serialized DetectionReport; its findings carry the same exact evidence identifiers a fresh pass produces (round-trip is byte-for-byte identical — verified by test).
  • Store stays type-agnostic. Detection types live in autophagy-patterns (which depends on the store), so the store treats the payload as opaque JSON and exposes only fingerprint/generation/get-put primitives; the CLI (detection.rs) computes the key and (de)serializes. One-way dependency direction preserved.
  • Bounded growth. Each write drops every entry from an older generation, then inserts its own — superseded corpus states never accumulate; current-generation entries at different thresholds/projects coexist.
  • Minimal surface. patterns/digest gain --recompute to force a fresh pass; the other three sites always read through the cache.
  • Progress → stderr only. When a fresh pass runs: digesting 69,400 events across 320 sessions… before, digested in X.Xs — N finding(s) after. Cache hits are silent. stdout stays a clean report so JSON output mode is untouched.
  • ADR 0013 records the stored-schema addition and its privacy posture; docs/architecture/database-schema.md documents the table.

Evidence

Automated tests:

  • autophagy-store findings_cache_round_trips_and_collects_stale_generations — get/put round-trips; a new generation collects prior-generation entries.
  • autophagy-store detection_fingerprint_tracks_the_scanned_corpus — fingerprint advances with the corpus and honors the project filter.
  • autophagy-cli detection_findings_are_cached_and_report_progress — first pass reports progress; the second is a cache hit with byte-for-byte identical findings and no fresh pass; a threshold change, a new import, and --recompute each force a fresh pass; the corpus re-warms after an invalidating import.

Hands-on (per the task): imported evals/fixtures/generic-jsonl/demo.jsonl, ran patterns twice → second run silent, stdout identical. On a 36,000-event / 400-session synthetic corpus (debug build):

run detection wall (time -p real) stderr
1 (fresh) 0.6s 0.97s digesting 36,000 events across 400 sessions…
2 (cache hit) 0.21s (silent)

Detection cost is eliminated on the repeat and output is identical; --output json keeps stdout pure JSON with progress on stderr.

mise run check (fmt + clippy -D warnings/pedantic/missing_docs + tests + docs + actionlint): passing (exit 0).

Privacy

None. The cache is derived, deterministic, local-only data holding no new source text — only the serialized report, whose findings carry the exact evidence identifiers the detectors already produce from events already on disk. It never leaves the machine, adds no network path, and is fully reconstructable at any time by deleting every row (or --recompute).

🤖 Generated with Claude Code

https://claude.ai/code/session_015LY7xtercMc3NtLeX2z4YV

Every findings-consuming command (patterns, digest, mutations propose,
mutations synthesize, status --with-findings) re-ran a full deterministic
detection pass over every stored event on each invocation — ~45s of silent
recomputation on a 69,400-event database, with no output until it finished.

Persist a derived detection-findings cache (migration 0002, ADR 0013) keyed
by a content fingerprint of the exact inputs — detector spec version,
effective thresholds, project filter, event count, max row id, and a monotonic
import watermark — so an unchanged corpus at unchanged thresholds returns
instantly and any import/delete/prune misses without explicit invalidation.
Cached reports keep their exact evidence identifiers. When a fresh pass runs,
a concise before/after progress line is written to stderr; stdout stays a
clean report so JSON output is untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@karngyan
karngyan merged commit 5e8856e into main Jul 17, 2026
1 check passed
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