#134's origin gate compares a kebab-case directory against a snake_case import, so hyphenated crates lose 58% of their cross-crate binds #165
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#165
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 refusing to bless the tier-3 corpus baseline on a mechanism argument and diffing the actual binds instead. This is a defect in #134 as merged (branch
integration, commit2f16e226), caught before release.The comparison
crates/indexer/src/index.rs, in theimport_key_relbuild:dottedis the dot-wrapped import module (.hir_expand.).tailis the package directory tail (hir-expand)."hir_expand".ends_with("hir-expand")is false, sopkg_tail_ok = 0.pkg_tail_ok = 1is the only disjunct of the tier-3 origin gate that admits a cross-package bind:So every legitimate cross-crate reference into a hyphenated crate is gated off. Cargo derives the code identifier from the package name by folding
-to_; nothing here does.Measured on rust-analyzer (two full indexes, same corpus, base vs integration)
Base =
6f75e58(pre-#125/#134). Both indexes reproduce the ratchet exactly (refs_resolved127555 → 127867).Resolved type binds: 52751 → 51288.
Split by whether the target crate directory is hyphenated:
hir-expand,hir-def, …)syntax,ide, …)A 17× disparity, which is the signature.
A lost bind read at source — it is correct, not a phantom
crates/hir-def/src/attrs/docs.rs:331—file_id: HirFileIdlost its bind tocrates/hir-expand/src/lib.rs.The file explicitly imports the symbol, and
hir-def/Cargo.tomldeclareshir-expand.workspace = true. The import vouches for the origin exactly as the gate intends; the gate cannot see it because of the spelling.The gate is right about other things in the same file.
Crateatdocs.rs:327lost a bind tocrates/hir/src/lib.rsand that removal is correct —Crateis imported frombase_db(line 14), and hir-def does not depend onhirat all (that edge runs the other way). So #134 is doing real work; this defect is riding along with it.Why no existing gate caught it
The tier-1 corpus structurally cannot see it. ripgrep's crates are
core,globset,grep,ignore,matcher,pcre2,printer,regex,searcher,cli— not one is hyphenated. That is why the lane's bind inspection of ripgrep found only 2 correct binds lost and reasonably concluded the change was a precision gain. It was, on that corpus. rust-analyzer is the first pinned repo with kebab-case crate directories, and it is in the tier-3 suite, which is why the tier-3 ratchet is what went red.This repo is affected too:
crates/mcp-server,crates/plugin-host,crates/plugin-supervisorare all hyphenated, so cosi-mcp degrades its own resolution.Suggested fix — one rule, both ends
Fold
-to_on both sides of the comparison, at the two points where the two vocabularies meet: whenpkg_tailis stored intotemp.file_pkg, and in the segment-wrapping that producesIMPORT_SEGMENTS/QUALIFIER_SEGMENTS. Then every comparison site (tier 1b's file-key arm, tier 1Q, tier 3 Edge 2, tier 1R) inherits it without its own patch — this should be one structural clause, not a fix per tier.It is not Rust-only, which is part of why it belongs at the vocabulary boundary rather than in a language arm: Python distributions are routinely
python-dateutil→dateutil,scikit-learn→sklearn; Ruby hasactivesupport→active_support.Bound the claim honestly: folding can only add matches, so it cannot remove a bind — but it can admit one, and a
foo-bar/foo_barpair in one tree would collide. State that in the code rather than assuming it away.Required before this is called fixed
baseline.jsonre-record onintegration(8e15f983…) was blessed against the pre-fix delta and must be redone, not amended in place.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
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 #168Fixed, measured bind-for-bind against both the pre-#134 binary and the pre-fix
integrationbinary. One correction to the framing first, because it changes who owns this.This is not a defect in #134. It is 47 days and 64 releases older.
The kebab/snake comparison entered on tier 1b's name-import and container-import arms in I023 and has shipped in every release since.
2f16e22(#134) only gave tier 3's Edge 2 the same gate. The rule histogram says so directly —tier1b_name_importandtier1b_container_importare identical pre-#134 and atintegration, and the fold moves them:6f75e58348924atier1b_file_key_importtier1b_name_importtier1b_container_importtier1q_pass1tier3_import_boosttier1q_tiebreaktier1r_receiverOnly
tier3_import_boost(10321 → 9617) andtier1q_pass1/tier1rare #134's. Everything else was already losing binds before it. #134 is what made the loss measurable, by putting the gate somewhere the tier-3 ratchet could see it.The mechanism, and why it is one clause
fold_pkg_sep(s) = s.replace('-', "_"), applied in exactly two funnels so no comparison site carries its own copy:temp.file_pkg.pkg_tail, folded once at the insert, so every reader (tiers 1b, 1Q, 1R, 3,influence) inherits it;wrap_segmentsfor the Rust paths (all three of its callers do exactly one thing with the result: test a package tail against it) andPKG_SEGMENTS/PKG_QUALIFIER_SEGMENTSfor the SQL ones.It is deliberately not applied to file KEYS or to the segment expressions used to match them. A file stem is not translated between vocabularies:
packages/my-lib/src/my-file.jsis imported asmy-file, hyphen intact, and folding one side there would lose that match rather than gain one. Three consequential readers that compare the stored tail against un-folded text are folded to meet it —qseg_pkg's suffix enumeration,influence::anchor_arm's package-root arm, andwriter::path_evidence_keys(which must record BOTH spellings, or a hyphenated package can appear or disappear without re-deciding the qualified refs it anchors).Measured, on rust-analyzer, read at source
ci-fixedagainstintegrationHEAD, full bind digest(ref path|line|col|name|kind) -> (target path|name|kind|line):+16 717 gained, −1 471 withdrawn (1 362 to unresolved, 109 re-pointed), net +15 246 = 127 867 → 143 113.
99.8 % of the gains target a hyphenated crate — hir-def 6072, ide-db 4317, hir-expand 2151, hir-ty 1612, base-db 1440. The bind you named is back, to the right target, and the one #134 correctly removed stays removed:
The withdrawals are the good direction, sampled and read at source: 549 of the 1 362 were
.clone()on an arbitrary receiver bound tosyntax::Parse<T>::clone; 81 were.len()bound toline-index/hir-ty. All 109 re-pointings moved a call off the wrong crate's same-named method onto the one itsuseactually names —f.edition()fromhir::Crate::editiontohir_ty::HirFormatter::edition,data.is_const()fromhir::Functiontohir_def::FunctionSignature,path.display(db, ed)fromImportAlias::display(one arg) toModPath::display(two).tier1r_receiverreads −120 because 310 of its binds were re-attributed to earlier tiers on the same target, not lost.The other eight pinned repos are BYTE-IDENTICAL, bind-for-bind and rule-for-rule. None has a hyphenated package directory — the blindness you named.
Phantom bound: 2 of 16 717 (0.012 %), found by an oracle outside the resolver
Each ref crate's own
Cargo.toml: 16 444 of the gains cross a boundary Cargo declares, 42 are same-crate, and 229 of the remaining 231 travel the real re-export atcrates/ide-db/src/lib.rs:75(pub use base_db::{self, ..}). The last two are phantoms and are filed as #168 —use ide_db::line_index::LineIndexincrates/ide/src/lib.rs:142andview_syntax_tree.rs:4bind tocrates/rust-analyzer/src/line_index.rsinstead of the re-exportedlib/line-index/src/lib.rs. Not caused by the fold:package_root_ofcounts a top-levellibas a source directory, solib/line-indexhas an empty package tail and cannot anchor. The undeclared-cross-crate rate falls 3.383 % → 2.534 %.One real recall cost, also named: 18
proc_macro_srv::span::Spanrefs inproc-macro-srv-clistopped resolving, because the qualifier now correctly anchors on theproc-macro-srvpackage and that package holds severalSpanaliases of its own.The gate that was structurally blind now is not
write_hyphen_pkg_decoysstages a hyphenated package into the precision-gate tempdir for two languages, and the pair is the point — one alone grades only half the fold:crates/hyphen-pkg/use hyphen_pkg::HyphenBadge, each package with its ownCargo.tomlsopkg_dirdiffers and the tail is the only origin evidence. This is #165 itself.packages/ui-kit/require('ui-kit/widget'), directory and specifier spelled the same. It resolved before the fold and must still resolve after: the control for "folding both operands only adds matches".Mutations RUN, both RED, and each catches what the other cannot:
Plus
pkg_segment_consts_are_the_folded_twins(derivation through the shippedfold_pkg_sep_sql, SQLite-vs-Rust agreement, and a source gate that all fourfp.pkg_tailGLOB sites use the folded expression — mutations RED at both arms) anda_hyphenated_package_tail_is_recorded_in_both_vocabularies(mutation RED).Baselines
Both re-recorded after the bind inspection, each with a reason naming the mechanism, and a third one that nobody had noticed was stale:
tests/corpus/baseline.json534084b8…→914dda1aa1eea933f72a20d2287031f0tests/corpus/tier3-baseline.json210e7e1f…→94abf592dabe654166b38c46a845fd11tests/corpus/stage-baseline.jsona3e90a1e…→7d9695befe2e5968ac4ab05d403104acbaseline.jsonandstage-baseline.jsonmove for #134's reasons, not #165's, and the bless reasons say so with the measurement (identical bind sets AND identical rule histograms on every tier-1 repo, before vs after the fold).crates/indexer/tests/corpus_tier3_ratchet.rscited the oldbaseline.jsonmd5 as a live invariant; that claim is now scoped to the commit range where it held and names the new value.stage-baseline.jsonhad been red under three separate "green"cargo test --workspaceruns, because that suite reportsunavailableand passes withoutCOSI_CORPUS_DIR. Worth knowing for anyone reading a green workspace run as coverage.Gates
fmt 0·clippy -D warnings 0·rustdoc -D warnings 0·windows-gnu check -D warnings 0·corpus_ratchet 0·corpus_tier3_ratchet 0·ruby_package_parity 0(executed=1) ·precision_gate7/7, recall 1.000, phantoms 0 on every language ·COSI_E2E_LEG=daemon -p code-index-mcp 0· workspace suite green.Two things the gates caught in my own work and I am reporting rather than quietly fixing: the doc-citation gate refused two test names I had cited but not written, and my first version of a #167 test stated a mutation that ran GREEN — the fixture was admitted by
pkg_tail_ok, so the test graded nothing.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
ruby_package_parityis RED at integration HEAD: #134's tier-3 origin gate reads a manifest relation the packaged leg structurally cannot have #167Closing — fixed, merged, and verified independently of the lane's report
Merged to master as part of
1a99623; master isf6a878a.Independent verification (not the lane's numbers):
fold_pkg_sepandfold_pkg_sep_sqlare present incrates/indexer/src/index.rs(:1850, :1194) — the two funnels, so no comparison site carries its own copy.WS_EXIT=0, 3233 passed, 0 failed, 301 blocks. Earlier the same command on the same tree shape reported 6 failures; all six cleared.f6a878a, includingOSS corpus (tier 1), which had been red for the whole session.baseline.json914dda1a,tier3-baseline.json94abf592,stage-baseline.json7d9695be.The correction to this issue's own title and body, for the record
I filed this as "a defect in #134 as merged". That was wrong, and the lane disproved it rather than accepting my framing.
The kebab-vs-snake comparison entered tier 1b's name-import and container-import arms in
722fbf6(I023, 2026-07-20) and had shipped in 64 releases. Those two rules are byte-identical before and after2f16e22, and folding the separator moves them by +2106/+150 on their own. What #134 did was copy I046's origin gate into tier 3 — which turned a two-month-old silent recall loss into a measurable one.My bisect landed on
2f16e22and I stopped there. A bisect on a gate's output finds the commit that made the gate able to see, not the commit that broke the thing. Blaming the exposing change is worse than a mis-attribution: it argues for reverting the thing that restored your sight.Why the corpus could not see it
rust-ripgrep's crates arecore,globset,grep,ignore,matcher,pcre2,printer,regex,searcher,cli— not one is hyphenated. The tier-1 corpus was blind to this defect by construction, which is why a rigorous bind inspection on ripgrep reached a correct conclusion about the wrong population. rust-analyzer, in the tier-3 suite, is the first pinned repo with kebab-case crate directories.Residual, named not hidden
#168 — 2 phantoms in 16,444 admitted binds (0.012%), bounded by an oracle outside the resolver (each referencing crate's own
Cargo.toml), caused bypackage_root_ofcounting a top-levellib/as a source dir. Open, and assigned.Closing.
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 #168