doctor index freshness counts never-indexable files as staleness — 35/35 false positives on this repo #210
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#210
Loading…
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?
Found by dogfooding v0.27.0-rc (
caa62fe). Sibling of #209; same file, same surface, different rule broken.The symptom
After a full, clean, completed reconcile —
resolve_scope: Full, 0 drifted, 0 gone from disk —doctorstill says:This WARN is permanent. Those files will never have a row. It cannot be cleared by waiting, re-indexing, or anything else an operator can do.
The product already knows the right answer, and
doctordoes not askindex_coverageon the very first path in that list:So two surfaces of the same binary disagree about the same path: one proves it can never be indexed, the other counts it as the index being behind.
The population is 100% false positive, measured
Derived independently of
doctor(index rows vsgit ls-files, so a different code path from the one under test):The two derivations reconcile: my 40 minus the 5 dot-files
doctor's walk excludes = the 35 it reports.Not one of the 40 is a genuine staleness case. Every extension in that list is one no compiled-in language and no installed package claims.
.hC shims,.wasmgrammars,.expectedgolden files,.rbxfixtures,Cargo.lock.The cause
crates/cli/src/doctor.rs:1204incheck_index_freshness:The comment says "files on disk the walker admits" — and that is exactly the bug. Walker admission is about ignore rules and directory pruning; it says nothing about whether any extractor could ever claim the extension. A
.wasmis admitted by the walk and then correctly refused atclassify.doctoronly consults the first stage, so every file that dies at the second stage is counted as the index being behind.behindthen drives the severity, so a repository with any binary fixture warns forever.Why this matters beyond the noise
This is the I034 brick shape — a never-indexable file read as "the index is behind" — in the surface an operator actually reads. I034 fixed that class in the freshness barrier by classifying with a walker-PROVED
not_indexed.doctornever got the same treatment.And the practical cost is the one that always follows a permanent warning: real drift is now invisible. A genuinely missing row would appear as
36 on disk with no rowand nobody would notice.What the fix has to do
Split the count by the classifier, not by the walk. Three populations, each said separately:
behindshould mean, and the only thing that should drive the WARN.ineligible_extension,too_large,auto_generated, …). Reuse the same classifierindex_coverageuses so the two surfaces cannot disagree again.An empty "genuine staleness" list must remain a measurement, not an absence.
Mutations the fix must run
.wasmas coverable → the WARN-is-OK assertion fails.The test must assert on a fixture containing BOTH a real missing row and an ineligible file, or it cannot tell the two populations apart — which is the defect itself.
changed_symbolsships a bareref_count: 0wheresearch_symbolssays the zero was never measured — same symbol, same index, opposite honesty #213Additional evidence: two other surfaces already handle this population correctly, so
doctoris the outlier rather than the classifier being unavailable.list_files(path_glob="crates/plugin-host/tests/fixtures/*")— the same three.wasmfilesdoctorcounts as staleness:Three things it does right that
doctordoes not:not_indexedis its own list; they never enterresultsand never contribute to a count that means "behind".not_walker_excludedmeans the walk alone proved nothing — exactly the distinctiondoctormisses by treating walker admission as coverability.index_coverage(path)is the full per-path verdict" — it defers rather than guessing.So the per-path picture across three surfaces on identical paths:
rustc_guest.wasmindex_coveragenever/ineligible_extension/stage: classify, with proof,hint: "Permanent"list_filesnot_indexedpopulation,not_walker_excluded, defers toindex_coveragedoctorfreshnessbehind, drives a permanent WARNThis closes off the "the classifier is not reachable from
doctor" objection:list_filesreaches it from the daemon, andindex_coverageis the same crate's own function.doctorconsultscode_index_indexer::walker::walkand stops.It also sharpens the fix direction in the issue.
list_files' three-way shape — indexed / provably-never / could-not-classify, each named — is the shapedoctor's disk side should have, and reusing that classification rather than re-deriving it is what stops the two from disagreeing again. Notelist_filesis not itself complete (it reportsnot_walker_excludedrather than the classify-stage reason), so the fix should push both toward theindex_coverageverdict rather than copyinglist_filesas-is.evidence_gaps.semanticstells the reader to consultpartial_sources, and no tool ever emits that field #216doctorcannot proveneveron a project with packages installed — 35/35 land in NOT CLASSIFIED whereindex_coverageproves them permanent #218Eligibility::from_builtinsdegradesauto-generated (plugin-excluded), which is package-independent by construction #219Eligibility::from_builtinsdegradesauto-generated (plugin-excluded), which is package-independent by construction #219Fixed in
c566f87, onmaster, shipping in v0.27.0.The freshness check counted never-indexable files as staleness — 35 of 35 on this repository — producing a permanent WARN that hid real drift. It now splits the disk side by the same classifier
index_coverageuses, and asks the project's own extractor set, so the two surfaces give the same verdict. Live after the fix:Closing on merge.