get_dependencies(direction: "in") answers total: 0 with no channel saying candidates were dropped for package-scope ambiguity — the matched_keys half is fixed, this half is not #174
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#174
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 while authoring hand-verified benchmark questions for #51. Traced to source.
Measured
The second is the one that settles it: most of Dapper's test suite carries
using Dapper;. A repository-wide zero for the most-imported file in the project is not a sparse answer, it is a dead code path.The first was the failing clause of a hand-verified benchmark question.
Dapper.ProviderTools/BulkCopy.cs:9isusing Dapper.ProviderTools.Internal;, and that file declares that namespace —file_outlineshows themodulesymbol. The importer exists and is one hop away.Mechanism (read at source)
crates/daemon/src/local_index.rs::matched_keyssplits the import module on.,:,/and\, and then requires a candidate key to equal one segment.The key taken from a C#
modulesymbol is the whole dotted namespace. So the comparison is:Dapper.ProviderTools.Internalcan never equal a segment of itself. Every C# namespace with a dot in it is unreachable through this path, and a namespace without a dot is the rare case.The tool's own description promises exactly the behaviour that does not happen: "so
using mainproject.Frameworkmatches files declaringnamespace mainproject.Framework".By the same code path this is expected to affect PHP (namespaces are
\-separated and the key is the whole namespace). That half is inferred from the shared function, not measured — I did not run a PHP repro.Why existing gates could not see it
The only e2e test on this path is
get_dependencies_in_finds_qualified_importers_rust. Rustmodkeys are single segments, sokey == segmentholds trivially for them. The test is green, has always been green, and exercises the one input shape for which the defect is invisible.That is this repository's own recorded failure class — a test whose fixture is one-sided, passing over a promise it does not exercise. The promise ("
mainproject.Frameworkmatchesnamespace mainproject.Framework") is stated in the tool description and graded by nothing.Repro
Two C# files:
a.csdeclaringnamespace Foo.Bar.Baz { class A {} }andb.cscontainingusing Foo.Bar.Baz;. Index, thenget_dependencies("a.cs", direction: "in")→total: 0. Change the namespace to a single segmentFooand the same call findsb.cs.Or on the pinned
cs-dappercorpus (sha72a54c475f75e18cb93cba0809d00a5e6e49efd9):get_dependencies("Dapper/SqlMapper.Async.cs", direction: "in").What must NOT be done to make this pass
Foo.Barwould then matchOther.Foo.Bar, and an importer list that is wrong in the permissive direction is worse than one that is empty — an empty answer is at least visibly useless.matched_keys.get_dependencies_in_finds_qualified_importers_rust. It is green because Rust keys are single-segment; adding assertions to it keeps the one-sided fixture. The test this needs is a multi-segment key per language that has one, and it should be built so that reverting the fix reddens it.Measured vs inferred
The three
get_dependenciesreadings,BulkCopy.cs:9, the declaredmodulesymbol, thematched_keyssplitting rule and the identity of the single e2e test are all measured or read at source. The PHP impact is inferred from the shared code path and is stated as a suspect, not a finding.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
CONFIRMED for one file, and the issue is WRONG about the other. There are TWO independent causes producing the same zero; one is fixed, one is a NAMED residual.
The two-causes question, settled by measurement
Measured on
cs-dapper@72a54c475fby replayingimporters_of_file's exact algorithm against the indexed DB and instrumenting each stage:ambiguousmatched_keysacceptedDapper.ProviderTools/BulkCopy.csBulkCopy,Dapper.ProviderToolsDapper.ProviderTools/Internal/DynamicBulkCopy.csDynamicBulkCopy,Dapper.ProviderTools.InternalDapper/SqlMapper.Async.csSqlMapper.Async,Dapper{Dapper}matched_keys, the issue's diagnosis. CONFIRMED and FIXED. It fires on the twoProviderToolsfiles. The ambiguity set is empty for both, so the scope guard drops nothing:matched_keysgenuinely accepted zero rows.matched_keysand that is wrong.SqlMapper.Async.cs's keyDapperis a single segment, somatched_keysalready matched 18using Dapper…rows before the fix. All 18 are then dropped becausenamespace Dapperis declared under three package roots —Dapper/(46 files),Dapper.Rainbow/(5),Dapper.SqlBuilder/(1), 54 files total — and no importer sits in the target's own root. The fix changes nothing for this file, by design.using Dapper;genuinely does not say which of 54 files it wants, so refusing is defensible. What is not defensible is that the reply saystotal: 0with no channel saying candidates were dropped for ambiguity. That disclosure is a separate change and was not made — it is recorded in theimporters_of_filedoc comment under "THE RESIDUAL, NAMED". The issue body should be corrected: only theProviderToolsmeasurement supports thematched_keysdiagnosis.The rule shipped — and why the suggested one was rejected
Keys now carry provenance (
MatchKey { text, module_path }): amodule-symbol name is a declared path, a file stem is a filename. Structural, not a language check — the function already builds keys from exactly those two places.The contiguous-subsequence rule suggested to the lane was measured and REJECTED:
The dominant false positive is not the ancestor case the issue flags but the descendant one: key
Dapper.Testsmatchingusing Dapper.Tests.Performance.Dashing;, andGuzzleHttp\Testsmatchinguse GuzzleHttp\Tests\SpyResponse;. And the PHP class-import case that would motivate a looser rule is already answered correctly by the stem key (Psr17SpyFactorystays at 3 importers before and after), so widening the namespace key buys nothing.False-positive shapes the shipped rule admits, stated:
cs-dapperoracle already assumes.import from "./probe.config"is still not found. Named, deliberately not pinned.Blast radius: strictly additive, zero rows lost anywhere. ts-zod, js-express, python-flask, py-django, ruby-sinatra, rust-ripgrep: 0 changed rows. The single-segment degeneration is exact — measured, not asserted.
Live, through the real tool over the pinned corpus:
Per-language multi-segment-key survey — MEASURED, and PHP is no longer inferred
module A; module Bemits separate single-segment symbolsmodnames are single segmentsTS/JS do have multi-segment keys, via the stem (177 files in zod, 63 in express). A stem key carries no language, so the third test grades it once on the reachable instance.
Mutations — real RED
M1 — revert
matched_keysto the pre-#174 body. All three e2e tests redden independently — which is why the test was split into three: the first failing assertion ends the body, so a single test would have let the C# arm redden while the PHP arm was never reached.Same three RED under
COSI_E2E_LEG=daemon.M2 — the permissive fix (contiguous subsequence).
M3 — provenance flag forced true.
A MUTATION SURVIVED, and it is a finding about the test rather than the fix. M3's first form asserted a TS stem
probe.configmust not matchprobe/config. It stayed GREEN. Cause: the model of the pipeline omitted the SQL prefilteri.module LIKE '%probe.config%', where.is a literal — verified,SELECT 'vitest/config' LIKE '%vitest.config%'→0. The cross-separator collision is unreachable in production, so that assertion could never fail. Replaced with the reachable case found by re-measuring: the .NET<Namespace>.csprojconvention. Confirmed live — with the flag forced true,get_dependencies("Dapper.ProviderTools/Dapper.ProviderTools.csproj", "in")returns 1, reporting a build file as the target of a C#using. That one row is the entire difference the flag makes across seven corpora. Both the code doc and the test doc record the deleted assertion and why.The description was right; the DOC COMMENT was the thing that lied
The issue says "do not soften the tool description" — correct, and it was not touched. But the
matched_keysdoc comment claimed: "This also keeps the C# case working (using mainproject.Framework↔namespace mainproject.Framework, whose segments includeFramework)". The key is the whole namespace, neverFramework. Corrected.Gates
cargo fmt -p code-index-daemon --check0 ·clippy -p code-index-daemon --all-targets -D warnings0 ·cargo test -p code-index-daemon --no-fail-fast0 (73 suites) · newqualified_importers_e2e0 (3 passed) · same underCOSI_E2E_LEG=daemon0 ·RUSTDOCFLAGS="-D warnings" cargo doc0 ·mcp_smoke(109) +disclosure_contract_e2e(38) + 4 more 0 ·agent_task_benchmark_cs_dapperwith the corpus env 0, 20 questions graded.clippyfirst failed ondoc_lazy_continuationandcargo doconprivate-intra-doc-links(public doc linking privatematched_keys); both fixed per CLAUDE.md, the link demoted to a citation keeping the module path.Records — not blessed
tests/corpus/baseline.jsoncannot move, and structurally rather than merely by not being run:corpus_ratchetlives incode-index-indexer, which does not linkcode-index-daemonat all. This is a read-path change toimporters_of_file.tests/bench/ratchet.jsoncs-dapper is now stale and was not edited:recall 0.7903 → 0.8548,correct → 53/62,tool_tokens 7742 → 7900(1.020x, inside the 1.05 ceiling; recall is a floor, so the gate is green). Only +1 of that is this fix (dapper.get_dependencies.DynamicBulkCopyflips to passing — the oracle already expects/importers/total == 1and the exact row, which is what the tool now returns). The other +3 is #172'sgeneric_namefix landingdapper.who_calls.CastResultat 3/3. The block should be re-recorded once all of this batch lands.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
STAYING OPEN, NARROWED — cause 1 fixed, cause 2 live, and the issue's own decisive example still returns
total: 0Close-out lane, master
552e3a2. Title corrected.Cause 1 is fixed and graded
crates/daemon/src/local_index.rs:6774—matched_keysnow takes&[MatchKey]with amodule_pathprovenance flag and applies two rules by key shape: a single segment matches any segment; a multi-segment module path must match whole.MatchKeyconstruction at:4020-4042. Three e2e tests pass in both legs (EXIT=0), with a real anti-vacuity guard insidea_multi_segment_file_stem_is_not_a_module_path.Dapper.ProviderTools/BulkCopy.csandDynamicBulkCopy.csnow answer 1 each.Cause 2 is live, undisclosed, and tracked nowhere else
The issue's second measured example — "the one that settles it" — still reproduces exactly as written on master:
namespace Dapperis a single segment and already matches 18using Dapper…rows; all 18 are dropped by the package-scope guard because three package roots declare that namespace.importers_of_file's own doc atlocal_index.rs:3984says what is wrong with that:I enumerated all open issues (paging past the 50-row API cap): nothing else covers it. Closing this would remove the only release-gate entry for a symptom that is still reproducible verbatim, in the same tool, in the same reply, as the same user-visible zero — which is why it stays here rather than being closed-and-refiled.
Corrected scope
Replace the Mechanism section with:
Also correct the Measured table: only the two
ProviderToolsrows ever supported thematched_keysdiagnosis.Do not close this by widening the guard. The guard's refusal is right; the missing thing is a channel in the payload — not prose in the tool description — saying candidates existed and were dropped for package-scope ambiguity.
get_dependencies(direction: "in") is structurally always empty for C# — matched_keys compares a whole dotted namespace against one path segment, and the only e2e test uses single-segment Rust keysto get_dependencies(direction: "in") answerstotal: 0with no channel saying candidates were dropped for package-scope ambiguity — the matched_keys half is fixed, this half is notCause 2 FIXED, merged as
cbeb655. Graded bycrates/mcp-server/tests/scope_ambiguity_e2e.rs(3/3).importers_of_filenow returns anImportersReplycarryingscope_ambiguity { dropped_importers, ambiguous_keys, declaring_package_roots, declaring_files }, counted inside the filter that makes the removals rather than reconstructed afterwards — so the number cannot drift from the thing it describes.Three-state, as required: the block is emitted only when the guard actually fired, so its absence beside
total: 0is the measured "nothing was dropped", andscope_ambiguity_unavailablecarries "this build did not report". Wire-compatible in both directions — ascope_disclosurerequest flag buys the object, old clients keep the 2-tuple, andRpcIndexdecodes both.Mutations run:
dropped_for_scope += 1deleted → RED on both legs, with the e2e printing{"next_cursor":null,"results":[],"total":0}— this issue's reported reply, byte for byte;scope_ambiguityemitted unconditionally → RED on the control on both legs (dropped_importers: 0, ambiguous_keys: []), which is what stops the disclosure from becoming vacuously present.Closing on cause 2, which is what this issue is titled for. If the other causes in the body remain live, they are worth their own numbers rather than keeping this one open indefinitely.