fix(graph): incremental correctness + perf follow-ups (PR-P5)#344
Merged
Conversation
Three follow-ups to the init/increment-perf plan (PR-P1..P4): PR-P5b (correctness): pull annotation-definition users into incremental scope. Annotation usage is a node property (annotations STRING[]), not a Symbol->Symbol edge, so _find_dependents never pulled annotation users in — when an @interface definition changed (e.g. gained a meta-annotation shifting the Layer-A chain in resolve_role_and_capabilities), types carrying it went stale until the next full rebuild. Add _find_annotation_dependents; merge its results into dependent_files so users are re-parsed and role-re-SET (the expansion cap bounds scope). Direct usage only; transitive composition documented as a known limitation. PR-P5c (perf): convert the last per-row graph write — the global pass 5-6 Route MERGE upsert — to bulk, mirroring _write_nodes_impl's Symbol pattern (_SET_ROUTE_BY_ID + COPY-new/SET-existing). Behavior-preserving; routes_rows already carries unique ids. PR-P5a (investigation, no code): confirmed OVERRIDES does NOT duplicate on increment — loaded stub types carry no supertype edges, so _populate_overrides_rows can't re-derive their pairs (unlike DECLARES, fixed in PR-P4, which is derived from a member's own fields). No guard shipped; added an invariant guard test pinning the behavior. Co-Authored-By: Claude <noreply@anthropic.com>
328ab31 to
87a2ccf
Compare
HumanBean17
added a commit
that referenced
this pull request
Jun 22, 2026
) All of the init/increment-perf work has landed — the original plan (PR-P1..P3: #340 cached ignore, #341 _write_edges bulk, #342 nodes/routes bulk) and the post-review follow-ups (PR-P4 #343 dependent refresh + DECLARES dedup, PR-P5 #344 annotation-scope fix + route bulk + overrides invariant), plus its proposal (#338). Relocate the plan, agent-prompts, and proposal from active/ to completed/, matching the Ladybug/INDEX-OUTPUT close-out convention (pure rename, no content edits). Co-authored-by: Claude <noreply@anthropic.com>
Merged
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.
Follow-ups to the init/increment-perf plan (PR-P1..P4). Stacked on #343 — merge #343 first, then rebase this onto master (or change the base). The diff below shows only these follow-ups.
Summary
P5b — annotation users not pulled into incremental scope (correctness)
@interface Foois a node property (annotations STRING[]) on its users, not aSymbol->Symboledge._find_dependentswalks edges, so when an annotation definition changed (e.g. gained a@Servicemeta-annotation that shifts the Layer-A chain inresolve_role_and_capabilities), every type carrying@Foowas left out of the incremental scope → itsrole/capabilitieswent stale until the next full rebuild. Violates the incremental≡full invariant.Fix:
_find_annotation_dependents(conn, changed_node_ids)finds files whoseannotationsreference a changedkind='annotation'node; merged intodependent_filesso users are re-parsed and role-re-SET. The expansion cap bounds scope.changed_files = added | changed | removed; nodes queried before deletion).@Awhere@Ais meta-annotated with the changed@B(transitive composition) is NOT pulled in — the reverse-chain walk is left to a future hardening pass. The direct case covers the dominant real-world shape (stereotype annotations applied directly).test_incremental_pulls_in_annotation_users_on_def_change: verified red without the fix (dependents_reprocessed=0), green with it. The user has no edge to any changed node, so it isolates the scope-tracking fix from the PR-P4 preserved-dependent refresh.P5c — global-step Route MERGE → bulk (perf)
The last per-row graph write —
_write_clients_producers_and_calls's Route MERGE upsert (explicitly deferred in the plan as "future optimization") — converted to bulk, mirroring_write_nodes_impl's Symbol pattern: new_SET_ROUTE_BY_IDconstant +COPYnew route ids /SETexisting ones.routes_rowsalready carries unique ids (_append_routededups), so the COPY can't hit a PK violation. Behavior-preserving (the old MERGE's no-delete-of-stale-routes behavior is kept exactly). Guarded bytest_incremental_route_merge_dedup_preservedand the incremental≡full equivalence test.P5a — OVERRIDES does not duplicate (investigation)
The suspected cross-file OVERRIDES duplication turned out not to be a bug.
_populate_overrides_rowsderives pairs via_direct_supertype_ids, which readstables.extends_rows/implements_rows— populated only bypass2_edgesover parsed files. Cross-file resolution stubs (loaded_from_db) load nodes, not edges, so a loaded subtype has no derivable supertype and never produces an OVERRIDES pair. This differs from DECLARES (fixed in PR-P4), which is derived from a member's ownparent_id/node_id(no edge lookup) and so could duplicate. No guard shipped (would be dead code). Addedtest_incremental_overrides_not_duplicated_for_non_scope_subtypeas an invariant guard — it pins the behavior and would catch duplication if stub edge-loading is ever added.Validation
.venv/bin/ruff check .— clean.venv/bin/python -m pytest tests -q— 850 passed, 14 skipped (+2 new tests)TestIncrementalOrchestratortests pass🤖 Generated with Claude Code