fix(graph_buffer): merge duplicate-edge properties commutatively - #1471
Conversation
confidence, strategy and via are not part of the edge dedup key
(source:target:type), and the same logical edge is legitimately minted by more
than one strategy carrying different values — pass_calls emits CALLS edges from
both LSP resolution and registry-textual matching. cbm_gbuf_insert_edge
replaced the stored blob unconditionally, and per-worker edge buffers merge in
worker-slot order, so which strategy's attributes survived was a function of
thread scheduling: the edge set stayed stable while its attributes flickered
run to run.
Replace the overwrite with a total order over the two candidates, which makes
the result commutative and associative and therefore independent of arrival
order:
1. higher "confidence" wins, which also enforces the intended precedence
that an LSP-resolved call outranks a textual match;
2. on equal confidence, the lexicographically greater blob wins, giving a
deterministic choice between otherwise equal candidates.
An empty or absent incoming blob still never displaces stored properties.
Adds three tests: order independence for the same pair of blobs inserted both
ways, the higher-confidence winner regardless of arrival, and the existing
empty-blob guard. The first two fail before this change.
Signed-off-by: Andrew Studnicky <a.j.studnicky@gmail.com>
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
|
Merged — thank you @Studnicky. The key insight is the one most fixes in this area miss: making the merge a total order (confidence, then lexicographic) rather than picking a "better" arrival makes it commutative and associative by construction, so worker scheduling cannot influence the outcome. And it doubles as the precedence rule we actually want — an LSP-resolved edge outranks a textual match. The both-orders test is exactly the right shape. This feeds directly into a known issue we have had open since the 0.9.1 RC: node counts are byte-stable across runs but edge totals drift 0.01–1.3% on resolution-heavy languages. Non-key edge attributes being decided by merge order is one concrete source of that, now closed. |
Fixes #1469
What does this PR do?
On a duplicate edge key,
cbm_gbuf_insert_edgereplaced the stored property blob unconditionally ("just replace for now").confidence,strategyandviaare not part of the dedup key, and per-worker edge buffers merge in worker-slot order, so which strategy's attributes survived was a function of thread scheduling.This replaces the overwrite with a total order over the two candidates, making the merge commutative and associative and therefore independent of arrival order:
confidencewins, which also enforces the intended precedence that an LSP-resolved call outranks a textual match;An empty or absent incoming blob still never displaces stored properties.
Why
Duplicates carrying different attributes genuinely occur —
pass_callsmintsCALLSedges from both LSP resolution and registry-textual matching. The edge set was stable while its attributes flickered run to run, so an MCP consumer readingCALLSconfidence could see a different value for the same commit across two indexes of the same tree.It also means the precedence
pass_parallel.cdocuments — "LSP-resolved calls take precedence over registry-textual matching" — was only honoured when the LSP edge happened to arrive second. Under this rule it holds regardless of arrival.The node path already works this way:
cbm_gbuf_upsert_nodepicks the survivor of a same-QN collision by a canonical content rule "so the pick is commutative and scheduling-free" (#923). This applies the same idea to edge attributes.Testing
Three new tests in
test_graph_buffer.c:gbuf_edge_props_merge_is_order_independent— the same pair of blobs inserted in both orders must leave identical stored properties.gbuf_edge_props_merge_prefers_higher_confidence— the higher-confidence blob wins even when it arrives first.gbuf_edge_props_merge_keeps_existing_on_empty— guards the{}case.The first two fail on an unpatched tree (51 passed, 2 failed), with the low-confidence registry blob having overwritten the high-confidence LSP one. All three pass with the change.
scripts/test.sh: 438 passed acrossgraph_buffer configlink simhash semantic parallel pipeline. The full suite matches the pre-change baseline exactly — the two failures (cli,py_lsp_scale) reproduce identically on an unpatched tree.scripts/lint.sh: no new findings.graph_buffer.creports the same 8 pre-existing items as before the change.Note this is invisible to
tests/repro/repro_parallel_determinism.c, which compares(source_qn, type, target_qn)triples and so cannot observe attribute churn — hence unit coverage rather than a corpus repro.Risks
Edge identity and edge count are unchanged; only which blob survives on a duplicate key differs. Where duplicates disagreed, the stored attributes may now differ from a given previous run — but previously that value was scheduling-dependent, so there was no stable prior value to preserve.
edge_props_confidenceparses"confidence":withstrtod; a blob without one reads as-1.0, so any edge stating a confidence outranks one that does not. Blobs that carry no confidence at all fall through to the lexicographic tie-break, which is deterministic but arbitrary — deliberately, since there is no semantic basis to prefer either.Scope is limited to the duplicate-key path in
cbm_gbuf_insert_edge; the key format, the store schema, and the MCP surface are untouched.Links
Checklist
git commit -s)