5151 parse_java ,
5252)
5353from graph_enrich import (
54+ _load_config_cross_service_resolution ,
5455 collect_annotation_meta_chain ,
5556 load_brownfield_overrides ,
5657 microservice_for_path ,
@@ -254,6 +255,7 @@ class GraphTables:
254255 parse_errors : int = 0
255256 skipped_files : int = 0
256257 pass3_skipped_cross_service : int = 0
258+ cross_service_resolution : str = "auto"
257259
258260
259261# ---------- file walk (see `path_filtering.iter_java_source_files`) ----------
@@ -1276,6 +1278,7 @@ def pass4_routes(
12761278 prs = str (source_root .resolve ())
12771279 except OSError :
12781280 prs = str (source_root )
1281+ tables .cross_service_resolution = _load_config_cross_service_resolution (prs )
12791282 meta_chain = collect_annotation_meta_chain (prs )
12801283
12811284 for ast in asts .values ():
@@ -1411,6 +1414,7 @@ def pass5_imperative_edges(
14111414 prs = str (source_root .resolve ())
14121415 except OSError :
14131416 prs = str (source_root )
1417+ tables .cross_service_resolution = _load_config_cross_service_resolution (prs )
14141418 meta_chain = collect_annotation_meta_chain (prs )
14151419 routes_by_id = {r .id : r for r in tables .routes_rows }
14161420 existing_route_ids = set (routes_by_id )
@@ -1648,6 +1652,30 @@ def _match_call_edge(
16481652 return "cross_service" , candidates
16491653
16501654
1655+ _BROWNFIELD_LAYERS = frozenset ({
1656+ "layer_c_source" ,
1657+ "layer_b_ann" ,
1658+ "layer_b_fqn" ,
1659+ "layer_a_meta" ,
1660+ })
1661+
1662+
1663+ def _is_brownfield_sourced (
1664+ call_strategy : str ,
1665+ candidates : list [RouteRow ],
1666+ ) -> bool :
1667+ """Both sides must come from brownfield layers for an edge to count as
1668+ authoritative under brownfield_only mode."""
1669+ if not candidates :
1670+ return False
1671+ if call_strategy not in _BROWNFIELD_LAYERS :
1672+ return False
1673+ return all (
1674+ getattr (c , "source_layer" , "builtin" ) in _BROWNFIELD_LAYERS
1675+ for c in candidates
1676+ )
1677+
1678+
16511679def pass6_match_edges (
16521680 tables : GraphTables ,
16531681 * ,
@@ -1670,6 +1698,11 @@ def pass6_match_edges(
16701698 tables .call_edge_stats .async_calls_match_breakdown .clear ()
16711699 tables .call_edge_stats .cross_service_calls_total = 0
16721700
1701+ brownfield_only = tables .cross_service_resolution == "brownfield_only"
1702+ suppressed_auto_cross_http : list [str ] = []
1703+ suppressed_auto_cross_async : list [str ] = []
1704+ suppressed_auto_cross_count = 0
1705+
16731706 def _micro_factor (member : MemberEntry | None ) -> float :
16741707 return 1.0 if (member and member .microservice ) else 0.85
16751708
@@ -1679,10 +1712,18 @@ def _micro_factor(member: MemberEntry | None) -> float:
16791712 member = member_by_id .get (row .symbol_id )
16801713 base = row .confidence / max (1e-9 , (0.3 * _micro_factor (member )))
16811714 src_route = route_by_id .get (row .route_id )
1715+ # Declared Feign client methods use `http_consumer` routes; synthetic phantoms from
1716+ # imperative clients are `http_endpoint` even when `feign_name` is populated from
1717+ # `@CodebaseClient.targetService` / YAML hints — those must path-match like RestTemplate.
1718+ _feign_like = (
1719+ src_route is not None
1720+ and src_route .kind == "http_consumer"
1721+ and bool (src_route .feign_name )
1722+ )
16821723 call = OutgoingCallDecl (
16831724 method_fqn = f"{ member .parent_fqn } #{ member .decl .signature } " if member else "" ,
16841725 method_sig = member .decl .signature if member else "" ,
1685- client_kind = "feign_method" if ( src_route and src_route . feign_name ) else "rest_template" ,
1726+ client_kind = "feign_method" if _feign_like else "rest_template" ,
16861727 channel = "http" ,
16871728 feign_target_name = src_route .feign_name if src_route else "" ,
16881729 feign_target_url = src_route .feign_url if src_route else "" ,
@@ -1700,6 +1741,16 @@ def _micro_factor(member: MemberEntry | None) -> float:
17001741 end_line = member .decl .end_line if member else 0 ,
17011742 )
17021743 outcome , candidates = _match_call_edge (call , all_routes , member .microservice if member else "" )
1744+ if (
1745+ brownfield_only
1746+ and outcome == "cross_service"
1747+ and not _is_brownfield_sourced (row .strategy , candidates )
1748+ ):
1749+ outcome = "unresolved"
1750+ candidates = []
1751+ suppressed_auto_cross_count += 1
1752+ if len (suppressed_auto_cross_http ) < 5 :
1753+ suppressed_auto_cross_http .append (call .method_fqn )
17031754 if outcome in VALID_CALL_MATCHES :
17041755 row .match = outcome
17051756 if outcome in ("cross_service" , "intra_service" ) and len (candidates ) == 1 :
@@ -1736,6 +1787,16 @@ def _micro_factor(member: MemberEntry | None) -> float:
17361787 end_line = member .decl .end_line if member else 0 ,
17371788 )
17381789 outcome , candidates = _match_call_edge (call , all_routes , member .microservice if member else "" )
1790+ if (
1791+ brownfield_only
1792+ and outcome == "cross_service"
1793+ and not _is_brownfield_sourced (row .strategy , candidates )
1794+ ):
1795+ outcome = "unresolved"
1796+ candidates = []
1797+ suppressed_auto_cross_count += 1
1798+ if len (suppressed_auto_cross_async ) < 5 :
1799+ suppressed_auto_cross_async .append (call .method_fqn )
17391800 if outcome in VALID_CALL_MATCHES :
17401801 row .match = outcome
17411802 if outcome in ("cross_service" , "intra_service" ) and len (candidates ) == 1 :
@@ -1760,6 +1821,18 @@ def _micro_factor(member: MemberEntry | None) -> float:
17601821 )
17611822
17621823 if verbose :
1824+ if brownfield_only :
1825+ n_bf = tables .call_edge_stats .cross_service_calls_total
1826+ first_http = ", " .join (suppressed_auto_cross_http )
1827+ first_async = ", " .join (suppressed_auto_cross_async )
1828+ print (
1829+ f"[pass6] cross_service_resolution=brownfield_only:\n "
1830+ f" { n_bf } cross_service edges from brownfield layers,\n "
1831+ f" { suppressed_auto_cross_count } auto-cross-service candidates suppressed -> unresolved\n "
1832+ f" (first 5 http: { first_http } )\n "
1833+ f" (first 5 async: { first_async } )" ,
1834+ file = sys .stderr ,
1835+ )
17631836 print (
17641837 f"[pass6] http_match={ dict (sorted (tables .call_edge_stats .http_calls_match_breakdown .items ()))} , "
17651838 f"async_match={ dict (sorted (tables .call_edge_stats .async_calls_match_breakdown .items ()))} , "
@@ -1805,7 +1878,8 @@ def _micro_factor(member: MemberEntry | None) -> float:
18051878 "http_calls_match_breakdown STRING, "
18061879 "async_calls_match_breakdown STRING, "
18071880 "cross_service_calls_total INT64, "
1808- "pass3_skipped_cross_service INT64"
1881+ "pass3_skipped_cross_service INT64, "
1882+ "cross_service_resolution STRING"
18091883 ")"
18101884)
18111885
@@ -1925,6 +1999,11 @@ def _write_nodes(
19251999 meta_chain : dict [str , frozenset [str ]] | None ,
19262000) -> None :
19272001 overrides = load_brownfield_overrides (project_root )
2002+ try :
2003+ prs = str (project_root .resolve ())
2004+ except OSError :
2005+ prs = str (project_root )
2006+ tables .cross_service_resolution = _load_config_cross_service_resolution (prs )
19282007 mch = meta_chain
19292008 # packages
19302009 for pkg , pid in tables .packages .items ():
@@ -2183,7 +2262,8 @@ def _write_meta(conn: kuzu.Connection, tables: GraphTables, source_root: Path) -
21832262 "http_calls_match_breakdown: $http_calls_match_breakdown, "
21842263 "async_calls_match_breakdown: $async_calls_match_breakdown, "
21852264 "cross_service_calls_total: $cross_service_calls_total, "
2186- "pass3_skipped_cross_service: $pass3_skipped_cross_service})" ,
2265+ "pass3_skipped_cross_service: $pass3_skipped_cross_service, "
2266+ "cross_service_resolution: $cross_service_resolution})" ,
21872267 {
21882268 "k" : "graph" ,
21892269 "ov" : ONTOLOGY_VERSION ,
@@ -2209,6 +2289,7 @@ def _write_meta(conn: kuzu.Connection, tables: GraphTables, source_root: Path) -
22092289 "async_calls_match_breakdown" : json .dumps (async_match ),
22102290 "cross_service_calls_total" : int (call_stats .cross_service_calls_total ),
22112291 "pass3_skipped_cross_service" : int (tables .pass3_skipped_cross_service ),
2292+ "cross_service_resolution" : str (tables .cross_service_resolution ),
22122293 },
22132294 )
22142295
0 commit comments