resolver: tier-3 import boost reaches any file whose BASENAME STEM matches an import segment, across crate boundaries — a single-candidate phantom #134
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#134
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 first-hand on 2026-09-05 while implementing #127. Not introduced by it: #127 only made the pre-existing rule produce a reachable phantom, and
manifest_layering::no_resolved_edge_crosses_an_undeclared_crate_boundarycaught it immediately.The defect
index::reachable_predicate's own doc states the rule:A basename stem carries no crate, package or root. So an import of
code_index_daemon::server::RPC_METHODSmakes everyserver.rsin the workspace reachable from the importing file — includingcrates/mcp-server/src/server.rs, which lives in a cratecode-index-daemondoes not (and cannot) depend on.The reproduction, verbatim
crates/daemon/tests/rpc_coverage_e2e.rsimportsand (as of #127) calls
rpc.read_epoch(). There are fourread_epochdefinitions:crates/daemon/src/access.rs(IndexAccess::read_epoch)accessIndexAccess, notaccesscrates/daemon/src/rpc_index.rs(RpcIndex::read_epoch)rpc_indexRpcIndexcrates/daemon/src/local_index.rs(LocalIndex::read_epoch)local_indexcrates/mcp-server/src/server.rs(routing_tests::StubIndex::read_epoch)serverExactly one candidate is reachable, so tier 3 resolves it.
find_callerson the mcp-server symbol reports the bind directly:and the gate names the edge:
The two other
rpc.read_epoch()call sites (crates/daemon/tests/reader_epoch_e2e.rs) stayedname_fallback— that file's imports reach more than one candidate, so tier 3 declines. The phantom is an artefact of the reachable set collapsing to ONE, which is exactly the shape the tier-3 gate is supposed to trust.Why it is worse than one bad edge
resolution: "resolved"with aresolved_bythat reads like provenance.server.rs,common.rs,util.rs,config.rs,handle.rs,error.rsall repeat across crates in this workspace and in every customer repo.What was NOT done, and why
The obvious fix — require the candidate file to be in a crate the ref's crate declares — is not available to the resolver: it has no Cargo knowledge, and legitimate cross-crate resolution (
mcp-server->daemon) is a large part of what tier 3 buys. A same-root guard would cause real recall loss. Filed rather than patched: reproduce the failure mode before changing the rule.#127's lane worked around the single instance by making the one call site UFCS (
IndexAccess::read_epoch(&rpc)), which makes the ref QUALIFIED and therefore structurally outside tier 3 (st.qualified = 0inrun_tier3_reachability). The comment at that call site says so and points here. That is a workaround for one site, not a fix.Candidate directions
precision_gate.#[cfg(test)]-module symbols from the cross-file EXPORTED pool. The winning candidate here isrouting_tests::StubIndex::read_epoch, a test double. This is one clause and structural, but it changes recall for every legitimate test-helper bind.file_keysa root/package column and require a stem match to agree on it. Needs a notion of "crate" the index does not have today.Direction 1 or 2 needs a measured before/after (bind-for-bind, per the "inspect binds, not deltas" rule), not a count.
ruby_package_parityis RED at integration HEAD: #134's tier-3 origin gate reads a manifest relation the packaged leg structurally cannot have #167LineIndexphantoms survive on rust-analyzer because tier 1Q anchors on a file STEM — directions 1 and 2 (package_root_of/lib) measured and REFUSED, direction 3 is what remains #168private_class_method :namemarks the INSTANCE method private, not the class method #102ruby_package_parityis RED at integration HEAD: #134's tier-3 origin gate reads a manifest relation the packaged leg structurally cannot have #167Two follow-ups from the #165/#167 lane that bear directly on this issue's own reasoning. Both are corrections in your favour.
1. The gate you added was already there, and already broken
#165 reads as "a defect in #134 as merged". It is not. The
IMPORT_SEGMENTS GLOB ('*' || fp.pkg_tail || '.*')comparison — a kebab-case package DIRECTORY tail tested against a snake_case import module — entered on tier 1b's name-import and container-import arms in722fbf6(I023, 2026-07-20) and has shipped in 64 releases since v0.5.15.2f16e22copied I046's gate into tier 3's Edge 2, exactly as its comment says it does, and that is what finally made the loss visible: onrust-analyzeronlytier3_import_boost(10321 → 9617) andtier1q_pass1/tier1r_receivermoved with your commit, whiletier1b_name_import(5084) andtier1b_container_import(1490) were identical before and after it — and the separator fix moves those two by +2106 and +150.So this issue's direction-3 ("give
file_keysa root/package column and require a stem match to agree on it") was right, and the reason it looked like a −58% recall loss was a spelling bug two months older than the gate. With the fold in place the gate is a net +15 246 on rust-analyzer, 99.8 % of it into hyphenated crates, and theCrate@327bind you would want removed stays removed whileHirFileId@331comes back.2. Direction 3 has a second half nobody has evidence for
temp.file_pkg.pkg_dirspells two different facts the same way: "the nearest manifest is at the repository root" and "no manifest covers me at all". The origin gate's middle disjunct reads''as the first, so in any tree where the walker finds no manifest,COALESCE(fpr.pkg_dir,'') = fp.pkg_diris true of every pair in the tree and this gate is silently off. #167 is that state reached through a harness; gitignored, oversized or simply absent manifests reach it in production.I implemented the fix and refused it, because splitting the two spellings is only half — the other half is a fallback partition for manifest-less trees, and there is no ground truth to pick one:
package_root_of(the parent directory) breaks three tier-3 fixtures, by the same split I046 measured as 67 lost ripgrep resolutions;OrphanOp, the C# monorepo);Every fixture disagrees because this gate has never been exercised in a manifest-less fixture: the old
'' = ''kept it switched off in all of them. Recorded inindex::nearest_package_dirand in_prdoc/records/84-P4-parity-deltas.md; what would settle it is a manifest-less corpus repo, which is a corpus change rather than a resolver one.3. Your fixture's blind spot, closed
tier3_origin_gate.rsis a good fixture and it could not have caught #165: its crates arealphaandbeta. Neither could the tier-1 corpus — ripgrep's crates arecore,globset,grep,ignore,matcher,pcre2,printer,regex,searcher,cli. The precision gate now stages a hyphenated package for rust (crates/hyphen-pkgimported ashyphen_pkg) and javascript (packages/ui-kitrequired asui-kit, where the two spellings agree), and the pair is load-bearing: folding only the segment side leaves the rust probe GREEN and the javascript one RED.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
LineIndexphantoms survive on rust-analyzer because tier 1Q anchors on a file STEM — directions 1 and 2 (package_root_of/lib) measured and REFUSED, direction 3 is what remains #168Triage 2026-09-06: CLOSING. Direction 3 landed, the origin gate is one clause in two funnels, and the precision gate is 7/7 with
phantoms=0— verified with the corpus actually mounted.Verified against master, not from a lane report. Analysis in the (closed) #165; fix in
2f16e22andefccc89.What landed, and why it is not a heuristic
2f16e22records the diagnosis: this was duplicated-rule drift, not the tradeoff the issue feared. Tier 1b's file-key arm has carried the package-origin gate since I046, andimport_key_rel's own column comment said outright that tier 3 carried no origin gate at all. Tier 3's Edge 2 now reads the same three disjuncts from the same relation —index::reachable_predicate,crates/indexer/src/index.rs:2211. No new rule, no new heuristic, and none of the three risky directions this issue named.efccc89then found the older half: a package directory (hir-expand) and an import (hir_expand) name the same package differently, so the gate computed"hir_expand".ends_with("hir-expand")and refused every legitimate cross-crate bind into a hyphenated crate. One clause, two funnels —fold_pkg_sepatindex.rs:1850,fold_pkg_sep_sqlat:1194— applied once at thetemp.file_pkginsert so every reader inherits it. That defect was two months old (I023, shipped in 64 releases); #134's gate is what made it measurable.Measured bind-for-bind, as this issue demanded — not by counting
rust-analyzer, against the pre-fix binary, binds diffed and read at source: +16717 gained, −1471 withdrawn, net +15246 (127867 → 143113). 99.8% of gains target a hyphenated crate. Withdrawals are the good direction (549 were
.clone()on an arbitrary receiver bound tosyntax::Parse<T>::clone), and all 109 re-pointings moved a call off the wrong crate's same-named method onto the one itsusenames. Phantoms bound from an oracle outside the resolver (each crate's ownCargo.toml): 16444 of the gains cross a declared boundary, 229 of the remaining 231 travel a real re-export, and the last two are phantoms — filed as #168. Undeclared-cross-crate rate 3.383% → 2.534%.The other eight pinned repos are byte-identical, bind-for-bind and rule-for-rule.
The gate was structurally blind, and now is not
No fixture and no tier-1 corpus repo had a hyphenated package directory, which is why a 58% recall loss reached a bless decision.
write_hyphen_pkg_decoys—crates/daemon/tests/precision_gate.rs:505— stages one for rust (directory and import spelled differently) and javascript (spelled the same). The pair is load-bearing: folding only the segment side leaves the rust probe green and the javascript one red.Runs (all exit 0, corpus mounted)
Baseline md5s match #165's closing comment exactly:
baseline.json 914dda1a…,tier3-baseline.json 94abf592…,stage-baseline.json 7d9695be….Three residuals, none hidden
crates/daemon/tests/rpc_coverage_e2e.rs:341(IndexAccess::read_epoch(&rpc)), and its comment at:329-340still calls itself a workaround for one site.manifest_layeringcovers the class repo-wide, but reverting that one call torpc.read_epoch()is the single strongest available proof and it has not been taken. Cheap, and worth doing.""spells both "covered by the root manifest" and "covered by no manifest", and the middle disjunct reads it as the first. Documented onindex::nearest_package_dir,index.rs:1979-2015. Two fallback partitions were implemented and refused for lack of ground truth — recorded in_prdoc/records/84-P4-parity-deltas.md:232-241, not guessed at.package_root_ofcounting a top-levellib/as a source dir (SRC_DIRS,index.rs:2044). Owned elsewhere.Also recorded rather than fixed: the C# extension pass still carries no origin gate, noted at the site because narrowing it owes its own measurement.
Closing on (1)-(3) being named and tracked, and the reported defect class being gated repo-wide with zero phantoms.
🤖 Triage lane, 2026-09-06, master
45cf6e4LineIndexphantoms survive on rust-analyzer because tier 1Q anchors on a file STEM — directions 1 and 2 (package_root_of/lib) measured and REFUSED, direction 3 is what remains #168require_relativecreates no file edge —temp.import_relselectsmodule GLOB '.*', and Ruby carries relativity in the KEYWORD #194LineIndexphantoms survive on rust-analyzer because tier 1Q anchors on a file STEM — directions 1 and 2 (package_root_of/lib) measured and REFUSED, direction 3 is what remains #168from .models importreaches everymodels.pyin the tree, and it produced 3 of #69's 5 measured phantoms #196build_recv_originbypassed #57 for four years of commits, and nothing could have told a reader #203build_recv_originbypassed #57 for four years of commits, and nothing could have told a reader #203build_recv_originbypassed #57 for four years of commits, and nothing could have told a reader #203package_root_ofreturns EMPTY when a SRC_DIRS name is the first path component, sosrc/- andlib/-headed trees can anchor no qualifier — #175's own repro still fails because of it #247