From 0ac529f5f4a6e0ad7e89373ef77029ce670e7caa Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Sat, 10 Oct 2026 15:24:17 +0200 Subject: [PATCH] fix(cross-repo): no row count decides which calls are matched Each of the cross-repo scans -- a project's HTTP_CALLS, async and channel edges, a target's Route templates for fuzzy matching -- stopped after 4,096 rows and treated the rest as not there. A call examined after 4,096 others never got its CROSS_HTTP_CALLS edge even though the target serves its route; which edges existed depended on how many others the project held. No count may decide graph content. The scans now read every row; only a SQLite error counts as failure, as before. The project-cache guards (CR_MAX_PROJECTS, CR_MAX_CACHE_ENTRIES) stay: they fail the run visibly instead of dropping content. cross_repo_scan_bound_counts_examined_rows_not_matches asserted that the 4,097th call stays unmatched; as cross_repo_matches_calls_past_4096_rows it asserts the edge exists, and fails on the bounded scans. None of the 17 infra corpora has 4,096 rows in one of these scans, so their graphs are unchanged. Signed-off-by: Martin Vogel --- src/pipeline/pass_cross_repo.c | 42 +++++++++++----------------------- tests/test_cross_repo.c | 9 +++++--- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/pipeline/pass_cross_repo.c b/src/pipeline/pass_cross_repo.c index 09384a8e9..1f57d1c25 100644 --- a/src/pipeline/pass_cross_repo.c +++ b/src/pipeline/pass_cross_repo.c @@ -33,7 +33,6 @@ enum { CR_PATH_BUF = 4096, CR_QN_BUF = 512, CR_PROPS_BUF = 2048, - CR_MAX_EDGES = 4096, CR_DB_EXT_LEN = 3, /* strlen(".db") */ CR_INIT_CAP = 32, CR_MAX_PROJECTS = 4096, @@ -481,9 +480,9 @@ static bool cr_path_matches_template(const char *concrete, const char *templ) { * the route QN into out_qn and return the handler id; returns 0 on no match. * * COST: this scans every Route node of the target project once per unmatched - * HTTP_CALLS edge — O(calls × routes) per project pair. Acceptable while both - * factors stay small (calls are capped at CR_MAX_EDGES and it only runs for - * edges the exact lookup missed); revisit with a prepared template index if + * HTTP_CALLS edge — O(calls × routes) per project pair. It only runs for edges + * the exact lookup missed; no row count stops a scan (a cap decided which + * calls were matched at all); revisit with a prepared template index if * cross-repo matching ever shows up in profiles. (#523) */ static int64_t find_route_handler_fuzzy(cbm_store_t *target_store, const char *concrete_path, const char *method, char *out_qn, size_t out_qn_sz, @@ -504,11 +503,8 @@ static int64_t find_route_handler_fuzzy(cbm_store_t *target_store, const char *c return 0; } int64_t found = 0; - int scanned = 0; int step_rc = SQLITE_DONE; - while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) && - (step_rc = sqlite3_step(s)) == SQLITE_ROW) { - scanned++; + while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) { const char *qn = (const char *)sqlite3_column_text(s, 0); if (!qn || strncmp(qn, "__route__", CR_ROUTE_PREFIX_LEN) != 0) { continue; @@ -550,7 +546,7 @@ static int64_t find_route_handler_fuzzy(cbm_store_t *target_store, const char *c break; } } - if (!cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) { + if (!cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) { *failed = true; } if (sqlite3_finalize(s) != SQLITE_OK) { @@ -713,12 +709,9 @@ static cr_match_result_t match_http_routes(cbm_store_t *src_store, const char *s } int count = 0; - int scanned = 0; int step_rc = SQLITE_DONE; bool failed = false; - while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) && - (step_rc = sqlite3_step(s)) == SQLITE_ROW) { - scanned++; + while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) { int64_t caller_id = sqlite3_column_int64(s, 0); int64_t route_id = sqlite3_column_int64(s, SKIP_ONE); const char *props = (const char *)sqlite3_column_text(s, PAIR_LEN); @@ -772,7 +765,7 @@ static cr_match_result_t match_http_routes(cbm_store_t *src_store, const char *s count++; } - if (!failed && !cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) { + if (!failed && !cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) { failed = true; } if (sqlite3_finalize(s) != SQLITE_OK) { @@ -804,12 +797,9 @@ static cr_match_result_t match_async_routes(cbm_store_t *src_store, const char * } int count = 0; - int scanned = 0; int step_rc = SQLITE_DONE; bool failed = false; - while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) && - (step_rc = sqlite3_step(s)) == SQLITE_ROW) { - scanned++; + while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) { int64_t caller_id = sqlite3_column_int64(s, 0); int64_t route_id = sqlite3_column_int64(s, SKIP_ONE); const char *props = (const char *)sqlite3_column_text(s, PAIR_LEN); @@ -854,7 +844,7 @@ static cr_match_result_t match_async_routes(cbm_store_t *src_store, const char * } count++; } - if (!failed && !cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) { + if (!failed && !cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) { failed = true; } if (sqlite3_finalize(s) != SQLITE_OK) { @@ -969,12 +959,9 @@ static cr_match_result_t match_channels(cbm_store_t *src_store, const char *src_ } int count = 0; - int scanned = 0; int step_rc = SQLITE_DONE; bool failed = false; - while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) && - (step_rc = sqlite3_step(s)) == SQLITE_ROW) { - scanned++; + while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) { const char *channel_name = (const char *)sqlite3_column_text(s, SKIP_ONE); const char *channel_qn = (const char *)sqlite3_column_text(s, PAIR_LEN); if (!channel_name || !channel_qn) { @@ -1007,7 +994,7 @@ static cr_match_result_t match_channels(cbm_store_t *src_store, const char *src_ count++; } } - if (!failed && !cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) { + if (!failed && !cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) { failed = true; } if (sqlite3_finalize(s) != SQLITE_OK) { @@ -1084,12 +1071,9 @@ static cr_match_result_t match_typed_routes(cbm_store_t *src_store, const char * } int count = 0; - int scanned = 0; int step_rc = SQLITE_DONE; bool failed = false; - while (scanned < CR_MAX_EDGES && !cr_cancel_requested(ctx) && - (step_rc = sqlite3_step(s)) == SQLITE_ROW) { - scanned++; + while (!cr_cancel_requested(ctx) && (step_rc = sqlite3_step(s)) == SQLITE_ROW) { int64_t caller_id = sqlite3_column_int64(s, 0); int64_t route_id = sqlite3_column_int64(s, SKIP_ONE); const char *props = (const char *)sqlite3_column_text(s, PAIR_LEN); @@ -1139,7 +1123,7 @@ static cr_match_result_t match_typed_routes(cbm_store_t *src_store, const char * } count++; } - if (!failed && !cr_cancel_requested(ctx) && scanned < CR_MAX_EDGES && step_rc != SQLITE_DONE) { + if (!failed && !cr_cancel_requested(ctx) && step_rc != SQLITE_DONE) { failed = true; } if (sqlite3_finalize(s) != SQLITE_OK) { diff --git a/tests/test_cross_repo.c b/tests/test_cross_repo.c index 23d74fb5b..9d318f8c0 100644 --- a/tests/test_cross_repo.c +++ b/tests/test_cross_repo.c @@ -574,7 +574,10 @@ static bool cross_repo_seed_bounded_scan(const cross_repo_fixture_t *fixture, return ok; } -TEST(cross_repo_scan_bound_counts_examined_rows_not_matches) { +/* No row count decides which calls are matched: the scans stopped after 4,096 + * rows, so a call examined after 4,096 others never got its CROSS_HTTP_CALLS + * edge even though the target serves its route. */ +TEST(cross_repo_matches_calls_past_4096_rows) { cross_repo_fixture_t fixture; bool setup = cross_repo_fixture_begin(&fixture) && cross_repo_seed_bounded_scan(&fixture, "bounded-source", "bounded-target"); @@ -589,7 +592,7 @@ TEST(cross_repo_scan_bound_counts_examined_rows_not_matches) { ASSERT_FALSE(result.failed); ASSERT_EQ(result.projects_scanned, 1); - ASSERT_EQ(result.http_edges, 0); + ASSERT_EQ(result.http_edges, 1); PASS(); } @@ -952,7 +955,7 @@ SUITE(cross_repo) { RUN_TEST(cross_repo_named_pre768_target_fails_before_cleanup_issue2133); RUN_TEST(cross_repo_status_distinguishes_never_run_from_ran_without_links); RUN_TEST(cross_repo_wildcard_reports_skipped_pre768_store); - RUN_TEST(cross_repo_scan_bound_counts_examined_rows_not_matches); + RUN_TEST(cross_repo_matches_calls_past_4096_rows); RUN_TEST(cross_repo_propagates_delete_failure); 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);