Merge pull request #2318 from DeusData/fix/issue-1133

fix(cross-repo): fail explicitly when zero target projects resolve (#1133)
This commit is contained in:
Martin Vogel authored and GitHub committed 2026-09-30 22:17:47 +02:00
commit aa53278b29
5 files changed
+103 -3

No files matched your search

+8 -2
View File
@@ -9947,10 +9947,16 @@ static char *handle_cross_repo_mode(cbm_mcp_server_t *srv, const char *repo_path
free(lease_keys);
yyjson_doc_free(jdoc);
if (result.failed) {
if (result.no_targets || result.failed) {
free(project);
return cbm_mcp_text_result(
"cross-repo source or target project is missing, invalid, or not indexed", true);
result.no_targets
? "cross-repo-intelligence resolved zero target projects: no indexed project "
"other than the source matched target_projects. Index the other service first "
"(run list_projects to see what is indexed); existing cross-repo edges were "
"left unchanged."
: "cross-repo source or target project is missing, invalid, or not indexed",
true);
}
int total = result.http_edges + result.async_edges + result.channel_edges + result.grpc_edges +
+17 -1
View File
@@ -1239,17 +1239,33 @@ cbm_cross_repo_result_t cbm_cross_repo_match_cancellable(const char *project,
result.failed = !result.cancelled;
return result;
}
int other_targets = 0;
for (int i = 0; i < resolved_count; i++) {
if (cr_cancel_requested(&run)) {
result.cancelled = true;
free_project_list(resolved, resolved_count);
return result;
}
if (strcmp(resolved[i], project) != 0 && !cr_project_exists(resolved[i])) {
if (strcmp(resolved[i], project) == 0) {
continue;
}
if (!cr_project_exists(resolved[i])) {
result.failed = true;
free_project_list(resolved, resolved_count);
return result;
}
other_targets++;
}
/* Nothing but the source itself to match against (a self-only list, or
* ["*"] in a store holding only the source). Reporting that as success
* with projects_scanned:0 is indistinguishable from "these services share
* no routes", and the source's existing CROSS_* edges would be wiped below
* with nothing to rebuild them. Fail before any write. (#1133) */
if (other_targets == 0) {
result.failed = true;
result.no_targets = true;
free_project_list(resolved, resolved_count);
return result;
}
/* Every input is known to exist before destructive source cleanup. The
+1
View File
@@ -20,6 +20,7 @@ typedef struct {
int projects_scanned;
double elapsed_ms;
bool failed; /* source/target validation, open, or allocation failed */
bool no_targets; /* failed: nothing but the source project resolved (#1133) */
bool cancelled; /* stopped at a bounded cancellation checkpoint */
bool partial_results; /* committed writes before cancellation were retained */
} cbm_cross_repo_result_t;
+23
View File
@@ -15501,6 +15501,28 @@ TEST(cli_build_args_json_array_flag_accepts_json_literal) {
PASS();
}
/* #1133: `index_repository --target-projects '["*"]'` (the form the help
* documents) must reach the cross-repo matcher as a one-element array, not as
* a single string holding the literal text -- that shape resolved to zero
* targets and returned a silent "success" with projects_scanned:0. */
TEST(cli_build_args_json_target_projects_literal_issue1133) {
char *err = NULL;
char *argv[] = {"--repo-path", "/r", "--mode", "cross-repo-intelligence",
"--target-projects", "[\"*\"]"};
char *json = cbm_cli_build_args_json("index_repository", 6, argv, &err);
ASSERT_NOT_NULL(json);
ASSERT_NULL(err);
ASSERT(strstr(json, "\"target_projects\":[\"*\"]") != NULL);
free(json);
char *argv2[] = {"--repo-path", "/r", "--target-projects", "[\"svc-a\",\"svc-b\"]"};
json = cbm_cli_build_args_json("index_repository", 4, argv2, &err);
ASSERT_NOT_NULL(json);
ASSERT(strstr(json, "\"target_projects\":[\"svc-a\",\"svc-b\"]") != NULL);
free(json);
PASS();
}
/* An unknown flag for a KNOWN tool must be rejected loudly, not silently
* typed as a string and dropped server-side (#997). GF1 eval: `trace_path
* --max-depth 1` was accepted, the real --depth stayed at default 3, and
@@ -16738,6 +16760,7 @@ SUITE(cli) {
RUN_TEST(cli_build_args_json_integer_flag_issue680);
RUN_TEST(cli_build_args_json_bare_boolean_issue680);
RUN_TEST(cli_build_args_json_array_flag_accepts_json_literal);
RUN_TEST(cli_build_args_json_target_projects_literal_issue1133);
RUN_TEST(cli_build_args_json_unknown_flag_rejected);
RUN_TEST(cli_build_args_json_repeated_array_issue680);
RUN_TEST(cli_build_args_json_array_literal);
+54
View File
@@ -469,6 +469,58 @@ TEST(cross_repo_pre_cancel_preserves_existing_cross_edges) {
PASS();
}
/* #1133: a run whose targets resolve to nothing but the source project must
* fail explicitly. It used to report "success" with projects_scanned:0 --
* indistinguishable from "these services share no routes" -- and, worse, it
* had already wiped the source's existing CROSS_* edges before noticing there
* was nothing to match against. */
TEST(cross_repo_self_only_target_fails_and_keeps_edges_issue1133) {
cross_repo_fixture_t fixture;
bool setup =
cross_repo_fixture_begin(&fixture) &&
cross_repo_seed_http_pair(&fixture, "self-source", "self-target", "/self-only", "self");
if (!setup) {
cross_repo_fixture_end(&fixture);
FAIL("failed to seed self-only fixture");
}
const char *target = "self-target";
cbm_cross_repo_result_t initial = cbm_cross_repo_match("self-source", &target, 1);
int before = cross_repo_count_edges(&fixture, "self-source", "CROSS_HTTP_CALLS");
const char *self = "self-source";
cbm_cross_repo_result_t result = cbm_cross_repo_match("self-source", &self, 1);
int after = cross_repo_count_edges(&fixture, "self-source", "CROSS_HTTP_CALLS");
cross_repo_fixture_end(&fixture);
ASSERT_FALSE(initial.failed);
ASSERT_TRUE(before > 0);
ASSERT_TRUE(result.failed);
ASSERT_TRUE(result.no_targets);
ASSERT_EQ(result.projects_scanned, 0);
ASSERT_EQ(after, before);
PASS();
}
/* #1133: ["*"] in a store that holds only the source project resolves to zero
* targets -- same contract as an explicit self-only list. */
TEST(cross_repo_wildcard_with_no_other_project_fails_issue1133) {
cross_repo_fixture_t fixture;
if (!cross_repo_fixture_begin(&fixture) ||
!cross_repo_create_project(&fixture, "lonely-source")) {
cross_repo_fixture_end(&fixture);
FAIL("failed to create isolated source project");
}
const char *targets[] = {"*"};
cbm_cross_repo_result_t result = cbm_cross_repo_match("lonely-source", targets, 1);
cross_repo_fixture_end(&fixture);
ASSERT_TRUE(result.failed);
ASSERT_TRUE(result.no_targets);
ASSERT_EQ(result.projects_scanned, 0);
PASS();
}
/* Add the internal "<name>::missed" miss-graph row that indexing writes into
* the SAME db whenever a file parses partially. */
static bool cross_repo_add_missed_shadow(const cross_repo_fixture_t *fixture, const char *project) {
@@ -528,4 +580,6 @@ SUITE(cross_repo) {
RUN_TEST(cross_repo_failed_bidirectional_insert_is_not_counted);
RUN_TEST(cross_repo_cancel_mid_run_keeps_completed_target_and_stops_before_later_target);
RUN_TEST(cross_repo_pre_cancel_preserves_existing_cross_edges);
RUN_TEST(cross_repo_self_only_target_fails_and_keeps_edges_issue1133);
RUN_TEST(cross_repo_wildcard_with_no_other_project_fails_issue1133);
}