Skip to content

fix(graph): incremental correctness + perf follow-ups (PR-P5)#344

Merged
HumanBean17 merged 1 commit into
masterfrom
fix/incremental-followups
Jun 22, 2026
Merged

fix(graph): incremental correctness + perf follow-ups (PR-P5)#344
HumanBean17 merged 1 commit into
masterfrom
fix/incremental-followups

Conversation

@HumanBean17

Copy link
Copy Markdown
Owner

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

Follow-up Type Outcome
P5b Pull annotation-definition users into incremental scope ✅ correctness fix real bug, fixed
P5c Convert global-step Route write to bulk ⚡ perf behavior-preserving conversion
P5a OVERRIDES duplication check 🔬 investigation not a bug — invariant guard added

P5b — annotation users not pulled into incremental scope (correctness)

@interface Foo is a node property (annotations STRING[]) on its users, not a Symbol->Symbol edge. _find_dependents walks edges, so when an annotation definition changed (e.g. gained a @Service meta-annotation that shifts the Layer-A chain in resolve_role_and_capabilities), every type carrying @Foo was left out of the incremental scope → its role/capabilities went stale until the next full rebuild. Violates the incremental≡full invariant.

Fix: _find_annotation_dependents(conn, changed_node_ids) finds files whose annotations reference a changed kind='annotation' node; merged into dependent_files so users are re-parsed and role-re-SET. The expansion cap bounds scope.

  • Handles add/change and delete (changed_files = added | changed | removed; nodes queried before deletion).
  • Known limitation: direct usage only. A user of @A where @A is 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).
  • Regression test 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_ID constant + COPY new route ids / SET existing ones. routes_rows already carries unique ids (_append_route dedups), 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 by test_incremental_route_merge_dedup_preserved and 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_rows derives pairs via _direct_supertype_ids, which reads tables.extends_rows/implements_rows — populated only by pass2_edges over 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 own parent_id/node_id (no edge lookup) and so could duplicate. No guard shipped (would be dead code). Added test_incremental_overrides_not_duplicated_for_non_scope_subtype as 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 -q850 passed, 14 skipped (+2 new tests)
  • Red/green verified for the P5b regression test; all 20 TestIncrementalOrchestrator tests pass
  • No ontology bump, no schema change, no re-index, no public-surface change

🤖 Generated with Claude Code

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>
@HumanBean17 HumanBean17 force-pushed the fix/incremental-followups branch from 328ab31 to 87a2ccf Compare June 22, 2026 19:51
@HumanBean17 HumanBean17 changed the base branch from fix/incremental-node-property-refresh to master June 22, 2026 19:51
@HumanBean17 HumanBean17 merged commit b21fcd4 into master Jun 22, 2026
1 check passed
@HumanBean17 HumanBean17 deleted the fix/incremental-followups branch June 22, 2026 19:58
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>
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.

1 participant