fix(graph): incremental global pass5/6 runs pass4; preserve stub callee role (#352)#368
Merged
Merged
Conversation
…ee role (#352) Incremental rebuild diverged from a full rebuild in two ways: 1. The global pass5/6 step rebuilt its tables via pass1 only — no pass4 — so tables_for_global.exposes_rows/routes_rows stayed empty. pass5 links Feign HTTP_CALLS to routes via exposes_rows and pass6 matches against routes_rows, so the HTTP_CALLS match outcome drifted from the full rebuild. Run pass4 on tables_for_global (mirrors main()). 2. _load_existing_types loaded out-of-scope types as annotation-less stubs but not their persisted role, so _write_nodes_impl recomputed role via resolve_role_and_capabilities on the stub and collapsed CONTROLLER/SERVICE/... to OTHER — corrupting callee_declaring_role on CALLS edges into the type. Load s.role and seed type_role_by_node_id; for loaded_from_db stubs trust the persisted role instead of overwriting it. Strengthens the incremental==full equivalence test to also assert HTTP_CALLS / ASYNC_CALLS match histograms and the CALLS callee_declaring_role multiset, on a corpus with a route, a Feign client, and an out-of-scope @RestController callee. Co-Authored-By: Claude <noreply@anthropic.com>
The equivalence test's http_calls_match histogram claimed to guard "missing pass4 on global pass5/6 tables" but could not trip on it: the synthetic corpus has no microservice, so the Feign route is filtered out in pass6 and resolves to 'phantom' in both the incremental and full paths. The assertion was a non-load-bearing consistency check. - Add test_incremental_global_pass_runs_pass4_on_global_tables: a spy on pass4_routes that asserts it runs on a DISTINCT global tables instance (the full-source rebuild) alongside the changed-files tables. Removing the global pass4 call (incremental_rebuild step 6) drops the call count to 1 -> failure. Verified red->green. - Correct the equivalence test's http_calls_match message to state plainly that it is a consistency guard, and point to the structural test for divergence #1. Co-Authored-By: Claude <noreply@anthropic.com>
Owner
Author
|
Addressed a review finding: the
|
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.
What
incrementproduced a graph that differed from a clean full rebuild in two ways, both in the incremental global pass5/6 step (#352).Divergence #1 — missing pass4 → HTTP_CALLS match drift
The global pass5/6 step rebuilt
tables_for_globalviapass1→pass5→pass6with no pass4 (routes/EXPOSES). Sotables_for_global.exposes_rows/routes_rowsstayed empty; pass5 links Feign HTTP_CALLS to routes viaexposes_rowsand pass6 matches againstroutes_rows, so the HTTP_CALLS match outcome drifted from the full rebuild (e.g. on bank-chat/chat/joinOperatorFeign edges flippedunresolved→phantom).Fix: run
pass4_routesontables_for_globalbetween pass1 and pass5 — exactly whatmain()does for a full rebuild.Divergence #2 —
callee_declaring_rolecollapsed for out-of-scope stubs_load_existing_typesloaded out-of-scope types as annotation-lessTypeDeclstubs without their persistedrole, so_write_nodes_implrecomputed role viaresolve_role_and_capabilitieson the empty stub and collapsedCONTROLLER/SERVICE/… to the defaultOTHER. That wrong role was stored intype_role_by_node_id[stub_id], which feeds_callee_declaring_role_at_write→ every CALLS edge into the type got the wrongcallee_declaring_roleafter anyincrement.Fix:
_load_existing_typesnow loadss.roleand seedstype_role_by_node_idfor stubs;_write_nodes_impltrusts the persisted role forloaded_from_dbstubs instead of overwriting it (the staged stub row is already filtered out of the write viastub_ids, so this only corrects the in-memory role map).Test
Strengthened
test_incremental_bulk_write_equivalent_to_full_rebuild:_graph_statenow also dumps the HTTP_CALLS / ASYNC_CALLS match histograms and the CALLScallee_declaring_rolemultiset (previously only node count, edge counts, and type roles — so both divergences shipped green).@RestController Cal(route + CONTROLLER callee, left out of scope), a@FeignClient, and aCallerthat is touched.Divergence #2 is directly tested (TDD red → green): before the fix, incremental gave
{'OTHER': 2}vs full{'CONTROLLER': 2}forcallee_declaring_role; after, they match.Divergence #1 is fixed by mirroring
main()exactly. The histogram-equality assertion is a non-trivial regression guard (the corpus has a real HTTP_CALLS edge), but this minimal corpus's Feign outcome isphantomin both paths — theunresolved→phantomdrift in the issue depends on bank-chat's microservice-configured Feign match, which isn't reproducible inline. The fix is the canonical correct behavior (run pass4 on the global tables).Notes
ontology_versionbump — full-rebuild semantics are unchanged; this makesincrementmatch the canonical full index. No env-var change.Closes #352.
🤖 Generated with Claude Code