[mache-b094fa] feat(graph): public ref resolution — RefRange and a classified RefTarget - #627
Merged
Merged
Conversation
…assified RefTarget
Downstream consumers (modmap, a monorepo blast-radius tool) hand-write a
node_refs/_ast join for every reference range, and match call tokens against
node_refs.token, which cannot tell a local Join from filepath.Join. Both are
now first-class on the SQL backend, behind graph.RefResolver.
## Resolution is four-valued, not a boolean
Measured on this repo's own projection (123,117 refs):
resolved 13,353 10.8% exactly one local definition — follow it
external 16,285 13.2% qualified by an import leaving the projection;
ZERO local blast radius, which is an ANSWER
ambiguous 21,920 17.8% several defs, candidates returned
no-target 71,559 58.1% no local definition at all (builtins, locals,
field names — node_refs records every identifier)
A boolean "resolved?" puts 89% of references in one undifferentiated bucket.
The distinction between "leaves the module" and "genuinely uncertain" is the
whole value for a blast-radius consumer: the first has no radius, the second
needs a human.
NodeID is populated ONLY for RefResolved, so a caller cannot accidentally
consume a guess. Package narrowing is a filter, never a tiebreak — if it does
not reduce to exactly one, the result stays ambiguous.
Deliberately absent: any heuristic deciding whether an import path "looks
external" from its shape. Whether example.com/you is inside your module is
something the caller knows and mache does not, so mache reports the import
path and lets the caller judge.
## The bug only real data found
A node_id is NOT a unique key into node_refs. A qualified call dual-emits two
rows at the same node — `require.NoError` and `NoError` — and 37,236 node ids
in this projection carry more than one. node_defs is keyed on the BARE token,
so an arbitrary LIMIT 1 lands on the qualified row about half the time and
reports a false "no target" for a reference that resolves perfectly well.
LLO's table contract makes the choice deterministic: qualifier is set on the
bare-token row and NULL on the qualified one. Ordering on that selects the row
whose token can actually match a definition.
Verified against a real 851-file projection, not only fixtures:
leyline.CanonicalSourceRoot -> resolved via the internal import
fmt.Errorf -> external via "fmt"
t.Errorf -> no-target (a receiver variable is not an import)
The dual-emit regression was initially VACUOUS — its fixture used
Helper/helper.Helper, where uppercase sorts first and the bare token won by
accident, so the test passed with the fix reverted. Renamed to run/a.run so
alphabetical order picks the wrong row, and re-falsified.
Refs mache-b094fa. RefRange needed no new substrate; TargetNodeID's remaining
ambiguity is bounded by what the default build emits (no _lsp tables today).
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.
Downstream consumers (modmap — a monorepo blast-radius tool) hand-write a
node_refs/_astjoin for every reference range, and match call tokens againstnode_refs.token, which cannot tell a localJoinfromfilepath.Join. Both are now first-class behindgraph.RefResolver.Resolution is four-valued, and the data demanded it
Bucketing all 123,117 refs in this repo's own projection:
resolvedexternalambiguousno-targetA boolean
resolved?puts 89% into one undifferentiated bucket. For a blast-radius tool, "leaves the module" and "genuinely uncertain" are opposite answers — one has no radius, the other needs a human.NodeIDis populated only forresolved, so a caller cannot consume a guess by accident. Package narrowing is a filter, never a tiebreak: if it doesn't reduce to exactly one, the result stays ambiguous.Deliberately absent: any heuristic deciding whether an import path "looks external" from its shape. Whether
example.com/youis inside your module is something the caller knows and mache does not.The bug only real data found
A
node_idis not a unique key intonode_refs. A qualified call dual-emits two rows at the same node —require.NoErrorandNoError— and 37,236 node_ids here carry more than one.node_defsis keyed on the bare token, so an arbitraryLIMIT 1lands on the qualified row about half the time and reports a falseno-targetfor a reference that resolves fine.LLO's table contract makes the pick deterministic: qualifier is set on the bare-token row, NULL on the qualified one.
Worth knowing for anything else keying on node_id — including modmap's
internal/assembleid-prefixing.Verified against a real projection, not only fixtures
Two things the smell ratchet caught
lastSlashwas a hand-rolledstrings.LastIndexByte— and a byte-identical copy already existed ininternal/ingest/inner_scope_test.go:76. Two independent reimplementations of one stdlib call. Now stdlib;hasPathSegmentlikewise collapsed toslices.Contains.type Resolutioncollided withschema.Resolution(unrelated struct). RenamedRefResolution.Neither was caught by
go vet,golangci-lint, or tests.Test note
The dual-emit regression was initially vacuous: its fixture used
Helper/helper.Helper, where uppercase sorts first so the bare token won by accident — it passed with the fix reverted. Renamed torun/a.runso alphabetical order picks wrong, then re-falsified.Still open in mache-b094fa
Open-stays-lazy test (the ~100GB constraint — currently unlocked by any test)LookupDefreturns[]string/GetCallersreturns[]*Nodeasymmetry driving modmap'scallerResolverVerification
task cipassed via the pre-push gate;task smellsreturns 0;go test ./graph/green.🤖 Generated with Claude Code
https://claude.ai/code/session_01TtGhz7QzUHZi52a3FeNtEs