Skip to content

add cross-service resolution mode flag for pass6 matching#30

Merged
HumanBean17 merged 3 commits into
masterfrom
feat/cross-service-resolution-flag
May 6, 2026
Merged

add cross-service resolution mode flag for pass6 matching#30
HumanBean17 merged 3 commits into
masterfrom
feat/cross-service-resolution-flag

Conversation

@HumanBean17
Copy link
Copy Markdown
Owner

Summary

  • add cached .lancedb-mcp.yml reader for cross_service_resolution (auto/brownfield_only) and thread the mode into graph build tables
  • gate pass6 cross-service promotion in brownfield_only so only fully brownfield-sourced caller+route matches remain cross-service, with suppression logging
  • persist and surface the mode via GraphMeta / KuzuGraph.meta() / MCP graph_meta, bump ontology to 8, and document the new config in README
  • add PR-G1 tests for mode behavior, unknown-value fallback, and old-graph meta() fallback

Test plan

  • ruff check .
  • pytest tests/test_cross_service_resolution_flag.py tests/test_call_edge_matching.py -v --tb=short
  • pytest tests -q --tb=line
  • pytest tests/test_mcp_tools.py -q --tb=line -k graph_meta

Made with Cursor

HumanBean17 and others added 2 commits May 6, 2026 09:40
Introduce `cross_service_resolution` (auto|brownfield_only), persist it in GraphMeta, and gate pass6 cross-service promotion so brownfield-only mode keeps only fully brownfield-sourced edges.

Co-authored-by: Cursor <cursoragent@cursor.com>
@HumanBean17
Copy link
Copy Markdown
Owner Author

Review: PR-G1 — cross_service_resolution config flag

Verdict: Approved ✅

Clean, scope-disciplined implementation of the brownfield-first opt-in flag exactly as specified in plans/PLAN-CROSS-SERVICE-RESOLUTION-FLAG.md. All 8 plan-mandated tests pass, manual evidence reproduces, ontology bump is justified, and the gate logic is symmetric across HTTP and async loops. No leakage into PR-F1 (Feign-not-an-exposer) or PR-H1 (CLIENT rename) territory.

Scope discipline (out-of-scope checks)

Sentinel Status
FEIGN_CLIENT ✅ 0 hits — no leakage from PR-H1
HTTP_CLIENT ✅ 0 hits — no capability work
_TYPE_ANN_TO_CAPABILITY ✅ 0 hits — ast_java.py capability table untouched
EXPOSES / feign_not_an_exposer ✅ 0 hits — no leakage from PR-F1
incremental.rebuild / rebuild_partial ✅ 0 hits — Tier-2 untouched
@mcp.tool registrations ✅ 0 new tools — surfaced via existing graph_meta
REST_CONTROLLER ✅ 0 hits — controller vocabulary untouched
New annotation types ✅ 0 — reuses existing @CodebaseRoute / @CodebaseClient

Plan compliance

# Step from plan Verified
1 _load_config_cross_service_resolution reader, mirrors _load_config_microservice_roots graph_enrich.py:147 — same caching/error-handling/warn-and-fall-back
2 cross_service_resolution: str = "auto" field on GraphTables build_ast_graph.py:258
3 Field populated at the three load_brownfield_overrides call sites build_ast_graph.py:1281, 1417, 2002
4 _is_brownfield_sourced helper near pass6_match_edges build_ast_graph.py:1655-1676 with both-sides-must-be-brownfield logic and defensive empty-candidates guard
5 Gate inside both HTTP and async loops of pass6_match_edges build_ast_graph.py:1746-1753 (HTTP) and 1791-1798 (async); identical structure
6 GraphMeta schema extended with cross_service_resolution STRING build_ast_graph.py:1878
7 ONTOLOGY_VERSION 7 → 8 with phase-comment update ast_java.py:74 (with "Phase 6" comment)
8 KuzuGraph.meta() extended with None fallback for old graphs kuzu_queries.py:350 (full mode) + :426 (default to None when not in tier) — the existing tiered _META_FULL/_META_PRE_E3/_META_LEGACY fallback chain absorbs the new column cleanly
9 Verbose log line per propose §2.5 (count + first 5 examples) build_ast_graph.py:1822-1830 — capped at 5
10 README brownfield section updated ✅ Mentions ontology_version 8, both modes, the strict both-sides rule, and the rebuild requirement

Tests

274 passed, 4 skipped in 101.29s

Plan target was 280 = 266 baseline + 8 (this PR) + 6 (PR-F1, not yet shipped). Math: 266 + 8 = 274 ✅. The full +14 target lands once PR-F1 merges.

All 8 plan-enumerated tests are present in tests/test_cross_service_resolution_flag.py (273 LOC) and pass in 1.03s on their own.

Manual evidence reproduced

Auto mode (default, on the cross_service_smoke fixture):

[pass6] http_match={'ambiguous': 1, 'cross_service': 1, 'intra_service': 1, 'phantom': 1, 'unresolved': 1},
        async_match={'cross_service': 1}, cross_service_calls_total=2

brownfield_only mode (after dropping .lancedb-mcp.yml with the flag):

[pass6] cross_service_resolution=brownfield_only:
        0 cross_service edges from brownfield layers,
        2 auto-cross-service candidates suppressed -> unresolved
[pass6] http_match={'ambiguous': 1, 'intra_service': 1, 'phantom': 1, 'unresolved': 2},
        async_match={'unresolved': 1}, cross_service_calls_total=0

meta() round-trip:

auto:  'auto'
bo:    'brownfield_only'

Determinism: ✅ two consecutive auto-mode builds produced identical sorted route IDs (9/9 match).

Notes that earned my trust

  • Gate symmetry. HTTP and async loops use identical 4-line guard blocks. No copy-paste drift in the suppressed_auto_cross_count or suppressed_auto_cross accumulator handling.
  • Defensive empty-candidates guard. _is_brownfield_sourced returns False when candidates is empty — this is the correct (and conservative) interpretation when both sides are unknown, even though _match_call_edge rarely returns cross_service with empty candidates today.
  • Reset of cross_service_calls_total at the top of pass6. Future-proofs against incremental rebuild (Tier-2). The inline comment makes the intent explicit, which is exactly the right note for a follow-up implementer.
  • Tiered meta() fallback absorbed the new column without a new tier — the existing _META_PRE_E3 / _META_LEGACY chain does the work, and old graphs return None via the meta_mode == "pre_e3" branch's lack of column read.
  • Test add brownfield route overrides and codebase route composition (PR-A3) #8 (test_brownfield_client_with_auto_route_does_not_match) explicitly exercises the asymmetric case the propose [TBD-2] resolved — this is the test that protects the "both sides must be brownfield" decision against future relaxation by accident.
  • README update documents the rebuild requirement and the strict both-sides rule prominently. Users who upgrade and skip rebuild will read it immediately because it sits inside the brownfield section.

Observations (non-blocking)

  1. _load_config_cross_service_resolution redefines yaml import locally (graph_enrich.py:158). The sibling _load_config_microservice_roots does the same, so this is faithful mirroring — not a regression. If yaml is ever promoted to a top-level import in this file, both should change together.
  2. suppressed_auto_cross accumulator is shared between HTTP and async loops. This means the "first 5 examples" line could end up mixing channels. Probably fine, but if a future debug session needs to know which channel a suppressed FQN came from, splitting into suppressed_auto_cross_http / suppressed_auto_cross_async is a 4-LOC follow-up.
  3. getattr(c, "source_layer", "builtin") in _is_brownfield_sourced — the default "builtin" is correct (it's not in _BROWNFIELD_LAYERS, so it short-circuits to False), but RouteRow.source_layer is a declared field on master, so the getattr defensive default is dead code today. Leave it for forward compat with synthetic phantoms.
  4. Test Document Kuzu MAP-as-STRING pattern in plan + prompts #6 inlines a 26-line CREATE NODE TABLE GraphMeta(...) schema for the legacy fixture. If the schema changes again (e.g. PR-F1 adds another column), this test will need to be updated. Acceptable trade-off — it's documenting the "what does the v7 schema look like" contract for the fallback path.
  5. No CLI override (--cross-service-resolution=...) per plan §Out-of-scope. Confirmed — only YAML reads it. Listed as follow-up AST by Opus #1.

Plan deltas needed

None. The plan and PR are in lockstep.


Ready to merge. Next: PR-F1 (Feign-not-an-exposer) per plans/PLAN-FEIGN-NOT-AN-EXPOSER.md. Once both F1 and G1 are in, PR-H1 (CLIENT role rename, propose merged in #28) can land on top with ontology 8→9.

Keep brownfield-only suppression counts unchanged but log separate first-five examples for HTTP and async to make pass6 debugging clearer.

Co-authored-by: Cursor <cursoragent@cursor.com>
@HumanBean17 HumanBean17 merged commit bb5a8df into master May 6, 2026
HumanBean17 added a commit that referenced this pull request May 6, 2026
…ty (#32)

Implements propose merged in #28. Sequences after PR-F1 (#31, merged) and
PR-G1 (#30, merged). Hard rename — no deprecation alias, MCP bundle has
no users yet and breaking changes are explicitly allowed.

Single PR, ~115 LOC, ontology bump 8→9. Single source-of-truth flips at
ast_java.py:91 (role) and ast_java.py:114 (HTTP_CLIENT capability);
VALID_ROLES / VALID_CAPABILITIES are auto-derived so no java_ontology.py
edit needed.

9 new tests in tests/test_client_role_rename.py target ~290 passed,
4 skipped (281 baseline + 9).

Plan delta from propose: test #4 asserts warn-and-drop behaviour
(stderr warning + override silently dropped) matching actual
graph_enrich.py:443-447, 481-486, NOT raised ValueError as the propose's
example suggested.

Out of scope: async role/capability changes (MESSAGE_PRODUCER already
covers Kafka/Rabbit/JMS), auto-promoting RestTemplate/WebClient to
HTTP_CLIENT (brownfield-only opt-in), backwards-compat alias,
RegisterRestClient (followup #3).
HumanBean17 added a commit that referenced this pull request May 6, 2026
* chore: tidy completed plans/proposes and refresh stale docs

Move completed plans to plans/completed/:
- PLAN-CLIENT-ROLE-RENAME.md (PR #33 merged)
- PLAN-CROSS-SERVICE-RESOLUTION-FLAG.md (PR #30 merged)
- PLAN-FEIGN-NOT-AN-EXPOSER.md (PR #31 merged)

Move completed proposes to propose/completed/:
- CLIENT-ROLE-RENAME-PROPOSE.md (PR #28 merged)
- CROSS-SERVICE-RESOLUTION-FLAG-PROPOSE.md (PR #26 merged)
- FEIGN-NOT-AN-EXPOSER-PROPOSE.md (PR #25 merged)

Refresh active docs:
- README.md "Deferred" section: trace_request_flow, find_route_callers,
  HTTP_CALLS/ASYNC_CALLS are shipped (not deferred). Add explicit pointers
  to the still-active TIER2-INCREMENTAL-REBUILD and REFRESH-CODE-INDEX-AUTO-MODE
  proposes for the incremental Kuzu work.
- CODEBASE_REQUIREMENTS.md A.3: drop the stale 'ontology version 3' literal
  (now 9) and fix references to PLAN-CAPABILITIES-MODEL and CALL-GRAPH-PROPOSE
  to use their completed/ paths. Tense matches reality (call-graph layer is
  shipped, not deferred).
- CODEBASE_REQUIREMENTS.md B.9: same fix for the propose/DEFERRED-CALL-GRAPH-PROPOSE.md
  reference; the propose lives under propose/completed/CALL-GRAPH-PROPOSE.md.

No code changes. Test baseline unchanged: 290 passed, 4 skipped.

* docs: add inline Java stubs for @CodebaseRoute / @CodebaseClient / @CodebaseProducer

Per pushback on PR #34: the route, client, and producer brownfield
annotations were mentioned 4x in README + CODEBASE_REQUIREMENTS but
their @interface stubs were never shown inline. Users had to spelunk
through tests/fixtures/ to know what to copy into their project.

README §5 'Brownfield overrides — Last resort — source stubs' now has
three explicit subsections:

  - 3a. Roles & capabilities — @CodebaseRole / @CodebaseCapability
        / @CodebaseCapabilities (class-level), with usage example.
  - 3b. Routes — @CodebaseRoute / @CodebaseRoutes +
        CodebaseRouteFrameworkKind / CodebaseRouteKind (method-level),
        with HTTP-endpoint and Kafka-consumer usage examples.
  - 3c. Clients & producers — @CodebaseClient / @CodebaseClients and
        @CodebaseProducer / @CodebaseProducers (method-level), with
        rest_template + kafka_send usage examples.

Stub Java in the doc matches the verbatim sources under
tests/fixtures/brownfield_route_stubs/ and brownfield_client_stubs/
(also referenced for copy-paste). Enum values mirror VALID_ROUTE_*
and VALID_CLIENT_KINDS in java_ontology.py.

CODEBASE_REQUIREMENTS.md A.2.1 updated to enumerate all three
annotation families (roles, routes, clients/producers) and link to
the matching README sections instead of only mentioning role stubs.

No code change. Test baseline unchanged: 290 passed, 4 skipped.
@HumanBean17 HumanBean17 deleted the feat/cross-service-resolution-flag branch May 10, 2026 21:18
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