@@ -7956,11 +7956,11 @@ typedef struct {
79567956 const char *file_path;
79577957} compare_node_spec_t;
79587958
7959- static bool compare_write_coverage (cbm_store_t *store, const char *project,
7960- const char *generation) {
7959+ static bool compare_write_coverage_mode (cbm_store_t *store, const char *project,
7960+ const char *generation, const char *index_mode ) {
79617961 cbm_coverage_meta_t meta = {
79627962 .generation = generation,
7963- .index_mode = "full" ,
7963+ .index_mode = index_mode ,
79647964 .recorded_at = "2026-08-28T00:00:00Z",
79657965 .recording_status = "complete",
79667966 .ignored_files_stored = 0,
@@ -7971,6 +7971,11 @@ static bool compare_write_coverage(cbm_store_t *store, const char *project,
79717971 return cbm_store_coverage_replace_ex(store, project, NULL, 0, &meta) == CBM_STORE_OK;
79727972}
79737973
7974+ static bool compare_write_coverage(cbm_store_t *store, const char *project,
7975+ const char *generation) {
7976+ return compare_write_coverage_mode(store, project, generation, "full");
7977+ }
7978+
79747979static bool compare_write_fixture_store(const char *path, const char *project, bool target) {
79757980 static const compare_node_spec_t base_nodes[] = {
79767981 {"pkg.common", "Function", "src/common.c"}, {"pkg.base", "Function", "src/base.c"},
@@ -8248,10 +8253,11 @@ static bool compare_write_single_node_store(const char *path, const char *projec
82488253}
82498254
82508255static bool compare_write_identity_store(const char *path, const char *project,
8251- const char *generation, const char *source_qn,
8252- const char *source_label, const char *source_file,
8253- const char *target_qn, const char *target_label,
8254- const char *target_file, const char *edge_type) {
8256+ const char *generation, const char *index_mode,
8257+ const char *source_qn, const char *source_label,
8258+ const char *source_file, const char *target_qn,
8259+ const char *target_label, const char *target_file,
8260+ const char *edge_type) {
82558261 cbm_store_t *store = cbm_store_open_path(path);
82568262 if (!store ||
82578263 cbm_store_upsert_project(store, project, "/tmp/compare-identity-525") != CBM_STORE_OK) {
@@ -8296,7 +8302,7 @@ static bool compare_write_identity_store(const char *path, const char *project,
82968302 };
82978303 ok = cbm_store_insert_edge(store, &edge) > 0;
82988304 }
8299- ok = ok && compare_write_coverage (store, project, generation) &&
8305+ ok = ok && compare_write_coverage_mode (store, project, generation, index_mode ) &&
83008306 cbm_store_prepare_for_publish(store) == CBM_STORE_OK;
83018307 cbm_store_close(store);
83028308 return ok;
@@ -8309,10 +8315,10 @@ TEST(tool_compare_graphs_normalizes_legacy_path_separators_issue525) {
83098315 char target_path[768];
83108316 snprintf(base_path, sizeof(base_path), "%s/sepbase525.db", fixture.cache);
83118317 snprintf(target_path, sizeof(target_path), "%s/septarget525.db", fixture.cache);
8312- ASSERT_TRUE(compare_write_identity_store(base_path, "sepbase525", "sep-base", "pkg.source ",
8313- "Function", "src\\same.c", "pkg.target", "Function ",
8314- "src\\target.c", "CALLS"));
8315- ASSERT_TRUE(compare_write_identity_store(target_path, "septarget525", "sep-target",
8318+ ASSERT_TRUE(compare_write_identity_store(base_path, "sepbase525", "sep-base", "full ",
8319+ "pkg.source", " Function", "src\\same.c", "pkg.target",
8320+ "Function", " src\\target.c", "CALLS"));
8321+ ASSERT_TRUE(compare_write_identity_store(target_path, "septarget525", "sep-target", "full",
83168322 "pkg.source", "Function", "src/same.c", "pkg.target",
83178323 "Function", "src/target.c", "CALLS"));
83188324
@@ -8358,18 +8364,26 @@ TEST(tool_compare_graphs_sanitizes_legacy_invalid_utf8_issue525) {
83588364 "bad.c";
83598365 static const char replacement_type[] = "\xEF\xBF\xBD"
83608366 "EDGE";
8367+ static const char invalid_generation[] = "utf-\xFF"
8368+ "generation";
8369+ static const char invalid_index_mode[] = "\xFE"
8370+ "full";
8371+ static const char replacement_generation[] = "utf-\xEF\xBF\xBD"
8372+ "generation";
8373+ static const char replacement_index_mode[] = "\xEF\xBF\xBD"
8374+ "full";
83618375
83628376 compare_graphs_fixture_t fixture;
83638377 ASSERT_TRUE(compare_graphs_fixture_open(&fixture));
83648378 char base_path[768];
83658379 char target_path[768];
83668380 snprintf(base_path, sizeof(base_path), "%s/utfbase525.db", fixture.cache);
83678381 snprintf(target_path, sizeof(target_path), "%s/utftarget525.db", fixture.cache);
8368- ASSERT_TRUE(compare_write_identity_store(base_path, "utfbase525", "utf-base", NULL, NULL , NULL,
8369- NULL, NULL, NULL, NULL));
8370- ASSERT_TRUE(compare_write_identity_store(target_path, "utftarget525", "utf-target", invalid_qn,
8371- invalid_label, invalid_file, safe_qn, "Function" ,
8372- "src/sink.c", invalid_type));
8382+ ASSERT_TRUE(compare_write_identity_store(base_path, "utfbase525", "utf-base", "full" , NULL,
8383+ NULL, NULL, NULL, NULL, NULL, NULL ));
8384+ ASSERT_TRUE(compare_write_identity_store(
8385+ target_path, "utftarget525", invalid_generation, invalid_index_mode, invalid_qn ,
8386+ invalid_label, invalid_file, safe_qn, "Function", "src/sink.c", invalid_type));
83738387
83748388 cbm_mcp_server_t *server = cbm_mcp_server_new(NULL);
83758389 ASSERT_NOT_NULL(server);
@@ -8384,6 +8398,9 @@ TEST(tool_compare_graphs_sanitizes_legacy_invalid_utf8_issue525) {
83848398 yyjson_doc *doc = inner ? yyjson_read(inner, strlen(inner), 0) : NULL;
83858399 ASSERT_NOT_NULL(doc);
83868400 yyjson_val *root = yyjson_doc_get_root(doc);
8401+ yyjson_val *target = yyjson_obj_get(root, "target");
8402+ ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(target, "generation")), replacement_generation);
8403+ ASSERT_STR_EQ(yyjson_get_str(yyjson_obj_get(target, "index_mode")), replacement_index_mode);
83878404 yyjson_val *items =
83888405 yyjson_obj_get(yyjson_obj_get(yyjson_obj_get(root, "nodes"), "added"), "items");
83898406 bool found_node = false;
@@ -8486,6 +8503,52 @@ TEST(tool_compare_graphs_midscan_cancel_restores_store_state_issue525) {
84868503 PASS();
84878504}
84888505
8506+ TEST(tool_compare_graphs_progress_cancel_clears_handler_issue525) {
8507+ compare_graphs_fixture_t fixture;
8508+ ASSERT_TRUE(compare_graphs_fixture_open(&fixture));
8509+ cbm_mcp_server_t *server = cbm_mcp_server_new(NULL);
8510+ ASSERT_NOT_NULL(server);
8511+
8512+ cbm_store_compare_test_cancel_from_progress(true);
8513+ char *cancelled = compare_graphs_call(server, NULL);
8514+ ASSERT_NOT_NULL(cancelled);
8515+ ASSERT_NOT_NULL(strstr(cancelled, "\"isError\":true"));
8516+ ASSERT_NOT_NULL(strstr(cancelled, "cancelled"));
8517+ char *inner = extract_text_content(cancelled);
8518+ ASSERT_NOT_NULL(inner);
8519+ ASSERT_NULL(strstr(inner, "\"nodes\""));
8520+ free(inner);
8521+ free(cancelled);
8522+
8523+ cbm_store_t *base_store = cbm_store_open_path_query(fixture.base_path);
8524+ cbm_store_t *target_store = cbm_store_open_path_query(fixture.target_path);
8525+ ASSERT_NOT_NULL(base_store);
8526+ ASSERT_NOT_NULL(target_store);
8527+ cbm_graph_compare_result_t result = {0};
8528+ ASSERT_EQ(cbm_store_compare_graphs(base_store, "base525", target_store, "target525", 100, NULL,
8529+ NULL, NULL, NULL, &result),
8530+ CBM_STORE_CANCELLED);
8531+ ASSERT_EQ(result.nodes_added_total, 0);
8532+ ASSERT_EQ(result.nodes_removed_total, 0);
8533+ ASSERT_EQ(result.edges_added_total, 0);
8534+ ASSERT_EQ(result.edges_removed_total, 0);
8535+
8536+ static const char expensive_query[] =
8537+ "WITH RECURSIVE sequence(value) AS (VALUES(0) UNION ALL "
8538+ "SELECT value+1 FROM sequence WHERE value<5000) SELECT sum(value) FROM sequence;";
8539+ int base_query_rc = cbm_store_exec(base_store, expensive_query);
8540+ int target_query_rc = cbm_store_exec(target_store, expensive_query);
8541+ cbm_store_compare_test_cancel_from_progress(false);
8542+ cbm_store_close(target_store);
8543+ cbm_store_close(base_store);
8544+ ASSERT_EQ(base_query_rc, CBM_STORE_OK);
8545+ ASSERT_EQ(target_query_rc, CBM_STORE_OK);
8546+
8547+ cbm_mcp_server_free(server);
8548+ ASSERT_TRUE(compare_graphs_fixture_close(&fixture));
8549+ PASS();
8550+ }
8551+
84898552TEST(tool_compare_graphs_enforces_encoded_budget_issue525) {
84908553 compare_graphs_fixture_t fixture;
84918554 ASSERT_TRUE(compare_graphs_fixture_open(&fixture));
@@ -12295,6 +12358,7 @@ SUITE(mcp) {
1229512358 RUN_TEST(tool_compare_graphs_sanitizes_legacy_invalid_utf8_issue525);
1229612359 RUN_TEST(tool_compare_graphs_bind_failures_are_atomic_issue525);
1229712360 RUN_TEST(tool_compare_graphs_midscan_cancel_restores_store_state_issue525);
12361+ RUN_TEST(tool_compare_graphs_progress_cancel_clears_handler_issue525);
1229812362 RUN_TEST(tool_compare_graphs_enforces_encoded_budget_issue525);
1229912363 RUN_TEST(tool_compare_graphs_validation_and_scan_cap_are_atomic_issue525);
1230012364 RUN_TEST(tool_compare_graphs_cancel_and_readonly_handles_release_issue525);
0 commit comments