test: watcher history replay — drive the LIVE notify path across real git history, assert cold==incremental at every commit #39
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
h-dv/code-index#39
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
P1 — the single highest-expected-yield item in the corpus plan.
From
_prdoc/records/brainstorm-2026-07-28-oss-corpus-test-system.md§0 + revised §8.Why this is #1
The 2026-07-28 spike indexed 7 real OSS repos (ripgrep, flask, zod, express, guzzle, sinatra, Dapper; 170–580 files each) and cleared every axis it could reach:
rc=0,parse_errors=0, no panicsBut every one of those used one-shot
code-index index— the reconciler, not the livenotifywatcher.Every historical staleness defect in this project lived in the watcher:
relative_pathdrift (single-file-root watch matching)changed_symbols/review_diffproject_index_staleness_no_reconcile)So the corpus's value is concentrated precisely where the spike could not look.
What to build
A harness that, for a pinned repo with real history:
N, cold-indexes it, startscode-index watch(livenotify, notindex);Kcommits:git checkoutthe commit while the watcher is running, wait for quiescence, thenpath,name,kind,lang,start_line,start_col,end_line,end_col,qualified_name,visibilitypath,name,kind,start_line,start_col,roles,qualified,qualifier, target rendered as tf.path||'#'||ts.name||'@'||ts.start_line(rendering the target by path+name+line, never
target_id, so rowid ordering cannot mask a difference)Coverage requirements
Anti-vacuity (mandatory)
Per the §0 method note — a vacuity nearly produced a false green during the spike itself. Every assertion ships with a positive control: assert the tree actually changed between commits (file count / symbol count delta non-zero) before comparing. A replay where nothing changed must FAIL, not pass. See #44.
Acceptance
Landed —
crates/indexer/tests/corpus_watcher_replay.rsTwo tests, because the live path and the shipped contract are different promises and conflating them would have hidden the result.
corpus_watcher_live_path_only_misses_new_directories—reconcile_interval: None, so the safety net cannot paper over a dropped event. Asserts that the only commits the live path fails to absorb are those creating a new directory. A dropped modify, delete, or same-directory add fails.corpus_watcher_with_reconcile_converges_on_every_commit— the shipped configuration. Asserts every commit converges, including the new-directory ones. 0 divergences.What this measured
The long-open Linux dynamic re-watch gap is now reproduced on real data and quantified. Commit
be739c7 index: add grep-index cratecreatescrates/index/(6 files, directory absent in the parent); the live watcher misses all of it — inotify watches are per-directory and the files land before the new watch is armed. All 3 divergences were new-directory commits and nothing else, and the periodic reconcile converges exactly those 3.That is a much sharper statement than "the watcher sometimes misses things on Linux."
Design points that carry the test
WatchMode::Native, notAuto— a silent fallback to polling would hide the very inotify defects this exists to catch.Cargo.lock.interesting_commitsnow walks history keeping only commits that change an indexable extension, taken from the newest end.Deviations from the acceptance criteria — stated plainly
COSI_CORPUS_REPLAY_COMMITSraises it). Each step cold-indexes the tree; 25 already costs ~200s for the pair of tests. The effective number is what matters and it is lower still — 3 and 8 non-inert steps respectively.shapesproves add/modify/delete were exercised; the pinned ripgrep window contains noRand no directory deletion. Rather than fake them with synthetic edits in a test that claims to replay real history, they are left uncovered and named here.inertdiffers between the two tests (18 vs 16) purely as sampling noise — with the reconcile armed the DB is occasionally mid-update when sampled, so a step counts as converging rather than inert. It does not affect either assertion.Acceptance: boxes 2 and 4 met, 1 and 3 partially, 5 unmet (Linux only), 6 met (quiescence-based polling, no fixed sleeps; green on repeated runs).