fix: cache detection findings and show digest progress#35
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claim
Every findings-consuming command —
patterns,digest,mutations propose,mutations synthesize, andstatus --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
0002_findings_cache.sql(store schema v1 → v2; migrations stay ordered and immutable). Onefindings_cachetable:cache_key(32-byte validity key),generation(32-byte global corpus stamp for GC),report_json(opaque serialized report),created_at.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. BumpingDETECTION_SPEC_VERSIONinvalidates 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.DetectionReport; its findings carry the same exact evidence identifiers a fresh pass produces (round-trip is byte-for-byte identical — verified by test).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.generation, then inserts its own — superseded corpus states never accumulate; current-generation entries at different thresholds/projects coexist.patterns/digestgain--recomputeto force a fresh pass; the other three sites always read through the cache.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.docs/architecture/database-schema.mddocuments the table.Evidence
Automated tests:
autophagy-storefindings_cache_round_trips_and_collects_stale_generations— get/put round-trips; a new generation collects prior-generation entries.autophagy-storedetection_fingerprint_tracks_the_scanned_corpus— fingerprint advances with the corpus and honors the project filter.autophagy-clidetection_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--recomputeeach force a fresh pass; the corpus re-warms after an invalidating import.Hands-on (per the task): imported
evals/fixtures/generic-jsonl/demo.jsonl, ranpatternstwice → second run silent, stdout identical. On a 36,000-event / 400-session synthetic corpus (debug build):time -p real)digesting 36,000 events across 400 sessions…Detection cost is eliminated on the repeat and output is identical;
--output jsonkeeps 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