fix(resolution): clean up processed refs by row id so batch boundaries can't drop sibling call sites (#1269) (#1270)

Post-batch cleanup deleted resolved refs (and parked failed ones) by
(from_node_id, reference_name, reference_kind) — no line/col. When one
caller had several call sites to the same callee and a batch boundary
split them, the first batch's cleanup removed every row with that key,
including later-batch siblings that were never attempted — their edges
were silently never created. On nlohmann/json this ate 422 real call
edges (write_cbor's 38 to_char_type calls indexed as 11).

Refs loaded from unresolved_refs now carry their row id through
resolution, and all three persist paths (sync resolveAndPersist, the
yielding retry pass, the batched drain loop) delete / mark-failed by
exactly that id. The key-tuple methods remain only as the fallback for
hand-built refs from the public API. Failed-parking gains the same
precision: outcome can differ per call site (receiver inference reads
the ref's line), so a sibling must not inherit another row's failure.

Also untracks the zz-scratch local test files that slipped into #1268
and gitignores the pattern.

Validation: red-green regression test (5 sites, batch size 2 — old code
kept 2 edges, fix keeps 5); nlohmann/json re-index is a strict superset
of the previous edge set (0 lost, 422 recovered, spot-checked against
source).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-12 20:09:03 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 6103f5e228
commit e871c49a31
11 changed files with 277 additions and 145 deletions
+5 -4
View File
@@ -918,10 +918,11 @@ export function matchDottedCallChain(
// CRITICAL: resolve the TARGET via a synthetic bare-name ref, but return the
// match tied to the ORIGINAL `ref` (referenceName `inner().method`). The
// batched resolver (resolveAndPersistBatched) reads unresolved rows from
// offset 0 every pass and relies on deleteSpecificResolvedReferences —
// keyed on referenceName — to clear each resolved row so the batch empties.
// If we propagated the synthetic ref's bare `method` as `.original`, the
// delete would never match the stored `inner().method` row, the batch would
// offset 0 every pass and relies on the post-batch cleanup (row-id delete
// for DB-loaded refs, referenceName-keyed delete otherwise, #1269) to
// clear each resolved row so the batch empties. If we propagated the
// synthetic ref's bare `method` as `.original`, a key-based delete
// would never match the stored `inner().method` row, the batch would
// never drain, and the loop would re-resolve + re-insert forever (a runaway
// that grew gin's graph to 5M edges / 1.4 GB before this fix).
const bareRef = { ...ref, referenceName: method };