Skip to content

fix(graph): incremental global pass5/6 runs pass4; preserve stub callee role (#352)#368

Merged
HumanBean17 merged 2 commits into
masterfrom
fix/incremental-pass4-divergence
Jul 4, 2026
Merged

fix(graph): incremental global pass5/6 runs pass4; preserve stub callee role (#352)#368
HumanBean17 merged 2 commits into
masterfrom
fix/incremental-pass4-divergence

Conversation

@HumanBean17

Copy link
Copy Markdown
Owner

What

increment produced 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_global via pass1pass5pass6 with no pass4 (routes/EXPOSES). 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 (e.g. on bank-chat /chat/joinOperator Feign edges flipped unresolvedphantom).

Fix: run pass4_routes on tables_for_global between pass1 and pass5 — exactly what main() does for a full rebuild.

Divergence #2callee_declaring_role collapsed for out-of-scope stubs

_load_existing_types loaded out-of-scope types as annotation-less TypeDecl stubs without their persisted role, so _write_nodes_impl recomputed role via resolve_role_and_capabilities on the empty stub and collapsed CONTROLLER/SERVICE/… to the default OTHER. That wrong role was stored in type_role_by_node_id[stub_id], which feeds _callee_declaring_role_at_write → every CALLS edge into the type got the wrong callee_declaring_role after any increment.

Fix: _load_existing_types now loads s.role and seeds type_role_by_node_id for stubs; _write_nodes_impl trusts the persisted role for loaded_from_db stubs instead of overwriting it (the staged stub row is already filtered out of the write via stub_ids, so this only corrects the in-memory role map).

Test

Strengthened test_incremental_bulk_write_equivalent_to_full_rebuild:

  • _graph_state now also dumps the HTTP_CALLS / ASYNC_CALLS match histograms and the CALLS callee_declaring_role multiset (previously only node count, edge counts, and type roles — so both divergences shipped green).
  • New corpus: a @RestController Cal (route + CONTROLLER callee, left out of scope), a @FeignClient, and a Caller that is touched.

Divergence #2 is directly tested (TDD red → green): before the fix, incremental gave {'OTHER': 2} vs full {'CONTROLLER': 2} for callee_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 is phantom in both paths — the unresolvedphantom drift 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).

.venv/bin/python -m pytest tests/test_incremental_graph.py -q      # 33 passed
.venv/bin/python -m pytest tests/test_mcp_v2.py -q                 # 107 passed, 1 skipped
.venv/bin/python build_ast_graph.py --source-root tests/bank-chat-system --ladybug-path /tmp/cg.lbug   # exit 0

Notes

  • No re-index required and no ontology_version bump — full-rebuild semantics are unchanged; this makes increment match the canonical full index. No env-var change.

Closes #352.

🤖 Generated with Claude Code

HumanBean17 and others added 2 commits July 3, 2026 23:48
…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>
@HumanBean17

Copy link
Copy Markdown
Owner Author

Addressed a review finding: the http_calls_match histogram couldn't trip on divergence #1 — the corpus has no microservice, so the Feign edge resolves to 'phantom' in both the incremental and full paths, making that assertion non-load-bearing despite its message.

  • Added test_incremental_global_pass_runs_pass4_on_global_tables: a spy on pass4_routes asserting it runs on a DISTINCT global tables instance (the full-source rebuild) alongside the changed-files tables. Removing the global pass4 call drops the count to 1 → failure. Verified red→green.
  • Corrected the equivalence test's http_calls_match message to state plainly it's a consistency guard pointing at the structural test for divergence AST by Opus #1.

@HumanBean17 HumanBean17 merged commit f902fb7 into master Jul 4, 2026
1 check passed
@HumanBean17 HumanBean17 deleted the fix/incremental-pass4-divergence branch July 4, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

incremental rebuild diverges from full rebuild (skips pass4) → HTTP_CALLS match drift + stale callee_declaring_role

1 participant