From cdb7a293b0b98564f373f6552dfb34151c3c0dfe Mon Sep 17 00:00:00 2001 From: DavidHLP Date: Mon, 9 Nov 2026 20:55:52 -0800 Subject: [PATCH] fix(pipeline): exclude project identity from service patterns Signed-off-by: DavidHLP --- internal/cbm/service_patterns.c | 11 +++++++++ internal/cbm/service_patterns.h | 3 +++ src/pipeline/pass_calls.c | 16 +++++++++---- src/pipeline/pass_parallel.c | 41 ++++++++++++++++++--------------- tests/test_infrascan.c | 6 +++++ tests/test_pipeline.c | 2 +- 6 files changed, 55 insertions(+), 24 deletions(-) diff --git a/internal/cbm/service_patterns.c b/internal/cbm/service_patterns.c index 04249bd22..bf9a9d43c 100644 --- a/internal/cbm/service_patterns.c +++ b/internal/cbm/service_patterns.c @@ -1170,6 +1170,17 @@ cbm_svc_kind_t cbm_service_pattern_match(const char *resolved_qn) { return result; } +cbm_svc_kind_t cbm_service_pattern_match_in_project(const char *resolved_qn, const char *name, + const char *project_name) { + if (resolved_qn && project_name && project_name[0]) { + size_t len = strlen(project_name); + if (strncmp(resolved_qn, project_name, len) == 0 && resolved_qn[len] == '.') { + resolved_qn += len + 1; + } + } + return cbm_service_pattern_match_named(resolved_qn, name); +} + cbm_svc_kind_t cbm_service_pattern_match_named(const char *resolved_qn, const char *name) { if (!resolved_qn || !name) { return cbm_service_pattern_match(resolved_qn); diff --git a/internal/cbm/service_patterns.h b/internal/cbm/service_patterns.h index d8ca2359f..4da8df04b 100644 --- a/internal/cbm/service_patterns.h +++ b/internal/cbm/service_patterns.h @@ -35,6 +35,9 @@ void cbm_service_patterns_init(void); * "project.venv.requests.api.get"). Import-alias transparent. */ cbm_svc_kind_t cbm_service_pattern_match(const char *resolved_qn); cbm_svc_kind_t cbm_service_pattern_match_named(const char *resolved_qn, const char *name); +/* For graph-qualified symbols: project identity is not library evidence. */ +cbm_svc_kind_t cbm_service_pattern_match_in_project(const char *resolved_qn, const char *name, + const char *project_name); /* True for a bare, unqualified call to the native `fetch()` API. Deliberately * NOT part of the substring tables above: those are matched unconditionally diff --git a/src/pipeline/pass_calls.c b/src/pipeline/pass_calls.c index 37ed3d446..89a35f699 100644 --- a/src/pipeline/pass_calls.c +++ b/src/pipeline/pass_calls.c @@ -852,8 +852,11 @@ static bool emit_classified_edge(cbm_pipeline_ctx_t *ctx, const CBMCall *call, const char **imp_keys, const char **imp_vals, int imp_count, bool suppress_plain_calls, const char *route_mount, CBMLanguage lang) { - cbm_svc_kind_t svc = - cbm_service_pattern_match_named(res->qualified_name, target ? target->name : NULL); + cbm_svc_kind_t svc = cbm_service_pattern_match_in_project( + res->qualified_name, target ? target->name : NULL, + target && res->qualified_name && strcmp(target->qualified_name, res->qualified_name) == 0 + ? ctx->project_name + : NULL); if (svc == CBM_SVC_ROUTE_REG && route_reg_path(call, lang) != NULL) { return handle_route_registration(ctx, call, source, module_qn, imp_keys, imp_vals, imp_count, route_mount, lang); @@ -1387,12 +1390,15 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM * the missing target must NOT drop the call — otherwise no HTTP_CALLS edge * is written and cross-repo matching finds nothing (#523). Emit directly * when the call carries a URL/topic first argument. */ - if (calls_emit_service_call(ctx, call, source_node, - cbm_service_pattern_match(res.qualified_name), &res, evidence)) { + const cbm_gbuf_node_t *target_node = cbm_gbuf_find_by_qn(ctx->gbuf, res.qualified_name); + if (calls_emit_service_call( + ctx, call, source_node, + cbm_service_pattern_match_in_project(res.qualified_name, NULL, + target_node ? ctx->project_name : NULL), + &res, evidence)) { return SKIP_ONE; } - const cbm_gbuf_node_t *target_node = cbm_gbuf_find_by_qn(ctx->gbuf, res.qualified_name); if (!target_node || source_node->id == target_node->id) { return 0; } diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index d44f7b473..707616fad 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -2866,12 +2866,15 @@ static void emit_trpc_edge(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source, cons static bool emit_service_edge(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source, const cbm_gbuf_node_t *target, const CBMCall *call, const cbm_resolution_t *res, const char *module_qn, - const cbm_registry_t *registry, const cbm_gbuf_t *main_gbuf, - const char **imp_keys, const char **imp_vals, int imp_count, - bool suppress_plain_calls, const char *route_mount, - CBMLanguage lang) { - cbm_svc_kind_t svc = - cbm_service_pattern_match_named(res->qualified_name, target ? target->name : NULL); + const char *project_name, const cbm_registry_t *registry, + const cbm_gbuf_t *main_gbuf, const char **imp_keys, + const char **imp_vals, int imp_count, bool suppress_plain_calls, + const char *route_mount, CBMLanguage lang) { + cbm_svc_kind_t svc = cbm_service_pattern_match_in_project( + res->qualified_name, target ? target->name : NULL, + target && res->qualified_name && strcmp(target->qualified_name, res->qualified_name) == 0 + ? project_name + : NULL); const char *arg = call->first_string_arg; /* Also detect route registration by callee name suffix alone (handles unresolved @@ -3620,8 +3623,8 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB .strategy = "service_pattern"}; site_evidence->calls_emitted |= emit_service_edge(ws->local_edge_buf, source_node, source_node, call, &svc_res, - module_qn, rc->registry, rc->main_gbuf, imp_keys, imp_vals, - imp_count, false, route_mount, lang); + module_qn, rc->project_name, rc->registry, rc->main_gbuf, + imp_keys, imp_vals, imp_count, false, route_mount, lang); continue; } } @@ -3643,8 +3646,8 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB site_evidence->candidate_qn = count == SKIP_ONE ? target->qualified_name : NULL; site_evidence->calls_emitted |= emit_service_edge(ws->local_edge_buf, source_node, target, call, &selected, - module_qn, rc->registry, rc->main_gbuf, imp_keys, - imp_vals, imp_count, false, route_mount, lang); + module_qn, rc->project_name, rc->registry, rc->main_gbuf, + imp_keys, imp_vals, imp_count, false, route_mount, lang); ws->calls_resolved++; } continue; @@ -3680,8 +3683,8 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB * route/HTTP/service classification is unchanged. */ site_evidence->calls_emitted |= emit_service_edge(ws->local_edge_buf, source_node, source_node, call, &fake_res, - module_qn, rc->registry, rc->main_gbuf, imp_keys, imp_vals, - imp_count, true, route_mount, lang); + module_qn, rc->project_name, rc->registry, rc->main_gbuf, + imp_keys, imp_vals, imp_count, true, route_mount, lang); } else if (cbm_service_pattern_is_global_fetch(call->callee_name)) { /* Native `fetch()` (#856): only the global API once resolution * has failed to find a local/imported `fetch`. Call the low-level @@ -3790,9 +3793,10 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB (u[0] == '/' || strstr(u, "://") != NULL || (psvc == CBM_SVC_ASYNC && strlen(u) > PP_ESC_SPACE)); if (url_or_topic) { - site_evidence->calls_emitted |= emit_service_edge( - ws->local_edge_buf, source_node, NULL, call, &res, module_qn, rc->registry, - rc->main_gbuf, imp_keys, imp_vals, imp_count, false, route_mount, lang); + site_evidence->calls_emitted |= + emit_service_edge(ws->local_edge_buf, source_node, NULL, call, &res, + module_qn, rc->project_name, rc->registry, rc->main_gbuf, + imp_keys, imp_vals, imp_count, false, route_mount, lang); ws->calls_resolved++; } } @@ -3800,9 +3804,10 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB } site_evidence->candidate_qn = target_node->qualified_name; _rc_t0 = extract_now_ns(); - site_evidence->calls_emitted |= emit_service_edge( - ws->local_edge_buf, source_node, target_node, call, &res, module_qn, rc->registry, - rc->main_gbuf, imp_keys, imp_vals, imp_count, drop_plain_call, route_mount, lang); + site_evidence->calls_emitted |= + emit_service_edge(ws->local_edge_buf, source_node, target_node, call, &res, module_qn, + rc->project_name, rc->registry, rc->main_gbuf, imp_keys, imp_vals, + imp_count, drop_plain_call, route_mount, lang); atomic_fetch_add_explicit(&rc->time_ns_rc_emit, extract_now_ns() - _rc_t0, memory_order_relaxed); ws->calls_resolved++; diff --git a/tests/test_infrascan.c b/tests/test_infrascan.c index 40068852b..d68ac672f 100644 --- a/tests/test_infrascan.c +++ b/tests/test_infrascan.c @@ -184,6 +184,12 @@ TEST(infrascan_service_pattern_match_rejects_ids_inside_words) { }; ASSERT_EQ(svc_case_mismatches(cases), 0); ASSERT_EQ(cbm_service_pattern_match_named("proj.worker.run(_:Alamofire)", "run"), CBM_SVC_NONE); + ASSERT_EQ(cbm_service_pattern_match_in_project("Dio.src.nav.navigateTo", "navigateTo", "Dio"), + CBM_SVC_NONE); + ASSERT_EQ(cbm_service_pattern_match_in_project("Dio.src.axios.get", "get", "Dio"), + CBM_SVC_HTTP); + ASSERT_EQ(cbm_service_pattern_match_in_project("axios.get", NULL, "Dio"), CBM_SVC_HTTP); + ASSERT_EQ(cbm_service_pattern_match_in_project("Dio.client.get", "get", "Di"), CBM_SVC_HTTP); ASSERT_EQ(cbm_service_pattern_match_named("proj.Alamofire.request(_:String)", "request"), CBM_SVC_HTTP); /* A field whose name contains a call keeps its complete QN. */ diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index 251afc3b9..a1e424b34 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -16065,7 +16065,7 @@ TEST(pipeline_arg_url_skips_client_navigation) { free(saved_single); FAIL("save environment"); } - char tmp[256] = "/tmp/cbm_arg_url_nav_XXXXXX"; + char tmp[256] = "/tmp/cbm_arg_url_nav_Dio_XXXXXX"; if (!cbm_mkdtemp(tmp)) { free(saved_workers); free(saved_single);