search: query-time candidate-set resolver over raw edges (#86) - #93
Merged
IceRhymers merged 4 commits intoJul 23, 2026
Merged
Conversation
Two-query resolver (edge sites, then a SQL-window-bounded candidate scan per name) that resolves raw reference_edges.target_name to ranked symbols candidates without a self-join, mirroring symbols.py's auto-correlation avoidance. Ranking is membership-preserving so ambiguity is never silently collapsed, and candidate_count always carries the true pre-cap total even when the fetch is capped. Serve-only: no migration, no extraction-time symbol FK, no indexer change.
Additive app.service builders over the new resolver: find_references_payload (corpus-wide, call edges) and list_imports_payload (repo-scoped, import edges, structured repo_known miss). Repo-id-to-name resolution runs in a separate post-leg transaction, mirroring search_code_payload's _repo_name_map handling. MCP tool registration is a later child (#87).
Offline AC4 measurement CLI reusing the resolver's own build_candidate_count_select/classify_resolution so the reported distribution agrees with the serve path by construction. call edges are the headline metric compared against the epic's baseline; import edges are reported separately as informational.
…book (#86) Adds §4 covering the resolver's two-query design, candidate-set/resolution semantics, the exact-dotted-match import decision, repo_known for list_imports, branch-scoping parity with search_code/get_file, and the measurement script with a recorded distribution from self-indexing this repo's own tracked source.
IceRhymers
merged commit Jul 23, 2026
548c1a1
into
integration/knowledge-graph-reference-edges
4 checks passed
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Part of #82 / umbrella #89. Closes #86.
Adds a query-time resolver that turns raw
reference_edgesrows (#83/#84/#85: unresolved call/import sites, no FK tosymbols) into ranked candidate-set matches againstsymbols, entirely at serve time — no migration, no indexer change, no extraction-time symbol FK, noINDEX_SEMANTICS_VERSIONbump. MCP tool registration (find_references/list_importsas callable tools) is a separate, later child (#87) — this PR ships the resolver module and theapp.servicepayload builders it backs, not the tool wiring.Caveat: since #87 hasn't landed yet,
find_references_payload/list_imports_payloadare not reachable from any MCP tool or the web UI in this PR — they're exercised only by the unit/integration test suites and the offline measurement script below. #87 will wire them up.app/search/references.py(new): two-query resolver (edge sites, then a SQL-window-bounded candidate scan per name) — deliberately not one joined query, avoiding the same self-join auto-correlation hazardapp/search/symbols.pyavoids forsym:. Candidate fetch is bounded in SQL per name (ROW_NUMBER() OVER (PARTITION BY symbols.name ...), cap 32), whileCOUNT(*) OVERcarries the TRUE pre-cap count soresolution(unique/ambiguous/unresolved) is never wrong even when the returned list is capped. Ranking (same-repo → kind-appropriate → same-file → deterministic tiebreak) is membership-preserving: ambiguity is never silently collapsed to one answer. Import edges resolve on the exact full dotted path only (no last-segment split) — a deliberate, documented decision (external/stdlib imports should read asunresolved, not manufacture false ambiguity).app/service.py: additivefind_references_payload(corpus-wide,calledges) andlist_imports_payload(repo-required,importedges, structuredrepo_knownmiss — never a silent empty). Repo-id→name resolution runs in its own post-leg transaction, mirroringsearch_code_payload's_repo_name_maphandling.scripts/measure_reference_resolution.py(new): offline AC4 measurement CLI that reuses the resolver's ownbuild_candidate_count_select/classify_resolution, so the reported distribution agrees with the serve path by construction rather than re-implementing the join.docs/runbooks/reference-edges.md: new §4 documenting the resolver design, the import-as-external decision,repo_knownsemantics, branch-scoping parity withsearch_code/get_file, and the recorded measurement below.tests/unit/test_references.py,tests/unit/test_measure_reference_resolution.py,tests/integration/test_references.py, plus extensions totests/unit/test_service.py/tests/integration/test_service.py.Gates (fresh run against this diff)
make lint(ruff check + format --check + mypyapp indexer webui): cleanmake test(unit + observability): 1028 passedmake test-integration: new/touched tests (test_references.py×16,test_service.py×19 incl. 3 new) all pass against a real local Postgres. The suite's pre-existing failures/errors (lakebase_tokenizer/lakebase_ann/lakebase_bm25extensions absent from a vanilla Postgres image, and role-provisioning-dependent grant tests) are documented, environment-only gaps unrelated to this change — confirmed pre-existing by reproducing one (test_commit_search.py::test_scoped_commit_equals_repo_branch_query_non_default) on a clean stash of this branch.test_references.pyto actually verify no last-segment splitting and a distinct aliased secondfilesjoin in the count builder).Measurement (AC4)
Self-indexed this repo's own git-tracked source (206 files, 2,947 symbols, 15,412 reference edges across Python/JS/TS/TSX) into a local Postgres via the real indexer pipeline, then ran
scripts/measure_reference_resolution.py --edge-kind both --use-resolver:The re-measured
call-edge distribution tracks the prior-art baseline closely (within ~4 points on every bucket);importedges resolve at ~2% total, confirming they're overwhelmingly external/stdlib targets, matching the exact-dotted-match design (D3).Acceptance-criteria mapping
_rank_candidates+ truecandidate_countfromCOUNT(*) OVER; unit ambiguity/cap tests, integration cross-repo/same-repo-duplicate/hot-name tests.QueryTooBroadError;row_cap/candidates_truncatedflags; ORDER BY total order ending in a PK tiebreak on both queries; integration timeout + determinism tests.search_code): shared_branch_predicate, byte-identical toget_file_payload's, applied to both the edge site's file and each candidate's file; unit byte-identical-fragment assertions + integration branch-parity tests.scripts/measure_reference_resolution.py+ the real self-indexed measurement above, recorded indocs/runbooks/reference-edges.md§4.Test plan
make lintmake testmake test-integration(new/touched suites, against a real Postgres)