changed_symbols ships a bare ref_count: 0 where search_symbols says the zero was never measured — same symbol, same index, opposite honesty #213
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#213
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) against this repository, over the live daemon.The comparison, one symbol, one index, one generation
Symbol id 745588,
MEASURED_LOCK_NS_PER_GENERATION_ROW, apub constincrates/indexer/src/promotion.rs.search_symbols:with a
notespelling out the consequence:changed_symbols, same id, bothdetailedandconcise:No
name_fallback_count. Noname_fallback_unmeasured. No note.This is not a missing nicety — it is the tool's own documented contract
changed_symbols' description states the rule:Two states are documented:
name_fallback_countpresent (earned zero), or absent withname_fallback_unmeasured(nothing to count). The rows above are in a third, undocumented state: both absent, silently.And the index is not guessing about this.
project_overview.count_basisMEASURES it on this very index:1,916 rust consts on which a
ref_countof 0 was never measured against anything.search_symbolssays so.changed_symbolsdoes not.Why this is the worst place for the collapse
changed_symbolsandreview_diffare the tools an agent uses to size a change before making it. In that contextref_count: 0, direct_callers: 0reads as "nothing depends on this, it is safe to change or delete."For
MEASURED_LOCK_NS_PER_GENERATION_ROWthat is exactly wrong: it is consumed bylock_estimate, printed to operators byplugin enableas a predicted lock hold, and asserted against bybench_promotion_lock. It is one of the most load-bearing constants in the crate, and the review surface reports it with the same two zeros it would give genuinely dead code.kinds_without_use_channelon this index also covers rustmodule(627),impl(441) andstatic(89), plus php/typescript/ruby/csharp entries — so this is not one unlucky row. Every changed const, static, module and impl in every diff this tool has ever reported carried an unqualified zero.The fix
changed_symbols(andreview_diff, which shares the mapping) must emit the same three-state field disciplinesearch_symbols,get_symbolandfind_callersalready implement:name_fallback_countwhen earned,name_fallback_unmeasuredwith its reason when the population was empty. The machinery exists and is already correct one call away — this is a surface that did not adopt it, not a mechanism that needs inventing.Prefer the shared mechanism over a patch in
changed_symbols. If the counter and its disclosure were computed together in one place and every tool rendered that, the two surfaces could not have drifted apart. That is the fix worth making; makingchanged_symbolsemit one more field is the fix that lets the next surface drift again.direct_callers: 0needs the same treatment or an explicit statement of its own basis — for aconstthere is no call channel at all, so that zero is structural too.Mutations the fix must run
name_fallback_unmeasuredinchanged_symbols→ a test asserting the const row carries it must go RED. (Today that test does not exist; write it first and watch it fail against unfixed code.)fninstead of aconst→ it must go RED for the opposite cause, proving the fixture actually exercises a kind with no use channel. A test that passes on both aconstand afnis not measuring this.Verification that this is a rendering gap and not an index gap
The index holds the right answer: the same id, in the same generation, returns
name_fallback_unmeasured: "no_use_reference_channel"throughsearch_symbolsand throughget_symbol. Only the diff-shaped surfaces drop it.Related: #209, #210, #212 — all four are the same class, a surface reporting a state it did not measure. This one is distinctive in that the correct answer is already computed and simply not carried across.
Additional evidence, same symbol (id 745588), same index, same generation — a third surface, and it makes the case stronger than the original report did.
safe_delete(745588):safe_deletedoes not merely disclose the empty population — it changes its verdict because of it, and says so: "VERDICT DOWNGRADED:safe_deleteremoved nothing fromreasonsand addedevidence_incomplete, because its contract is never to report an absence verdict over incomplete evidence."So the tally on one symbol is now:
ref_countsearch_symbolsname_fallback_unmeasured: "no_use_reference_channel"+ explanatory noteget_symbolsafe_deleteunmeasured_population, and downgrades the verdictchanged_symbolsThat closes off the most likely objection to this issue, which is that the disclosure might be expensive or awkward to carry into a diff-shaped reply. Three surfaces already carry it, one of them (
safe_delete) computes it per-symbol on exactly this code path and then acts on it.changed_symbolsis alone, and it is the surface where a bare0does the most damage, because it is read as a go-ahead rather than as a report.It also sharpens the fix direction argued in the issue: with three correct implementations and one incorrect one, the problem is demonstrably that each surface renders the disclosure itself rather than receiving it alongside the count. The generic fix is to make the counter and its basis travel together as one value; anything else leaves a fifth surface free to drop it again.
Incidental confirmation from the same reply:
text_occurrence_filesincludes.forgejo/workflows/ci.yml, so the CI dot-directory allowlist (#33) is reaching dot-dirs correctly, andtext_candidate_windowreportscandidates_examined: 7, candidates_available: 7, saturated: false— the window cut nothing, so that list is the whole candidate set rather than a prefix.evidence_gaps.semanticstells the reader to consultpartial_sources, and no tool ever emits that field #216Fixed in
08e4f67— "a diff row that reports a zero now reports what the zero was measured against" — onmaster, shipping in v0.27.0.changed_symbolsreported a bareref_count: 0for symbol kinds this index measures as having no use channel, where four other surfaces disclosed the basis. The counter and its basis are now a single value, so no surface can render one without the other;direct_callersgained a basis of its own.The disclosure is carried in a flattened
NameFallback { count, unmeasured, shape_excluded }so the copy-back is whole by construction rather than by convention.Closing on merge.