feat(mcp): report incremental containment reasons

Add pipeline-owned publish_reason metadata and surface it in index_repository responses when incremental indexing falls back to containment. The reason is set on existing exact-route skip/fallback paths and cleared on exact publish/full run reset, so MCP and CLI JSON callers can distinguish a safe containment rebuild from an opaque incremental result.

Update the affected-frontier benchmark harness to prefer structured publish_reason while keeping stderr parsing for older binaries. Add focused pipeline assertions and an MCP tool test for incremental_containment with missing_existing_ownership.

Validated with git diff --check, bash scripts/check-source-safety.sh, bash scripts/test-source-safety.sh, python3 -m py_compile scripts/benchmark-incremental-speed.py, escalated make -j8 -f Makefile.cbm build/c/test-runner, escalated CBM_ONLY_SUITE=pipeline build/c/test-runner, escalated CBM_ONLY_SUITE=mcp build/c/test-runner, escalated make -j8 -f Makefile.cbm cbm, and escalated persistent MCP affected-frontier matrix 8/8 pass.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
Andrew Hundt
2026-07-01 16:15:51 -04:00
parent a35f58fcb0
commit ec83b08c2e
8 changed files with 141 additions and 9 deletions
+7 -1
View File
@@ -274,6 +274,11 @@ def response_publish_kind(data: dict[str, Any]) -> str:
return publish_kind if isinstance(publish_kind, str) else ""
def response_publish_reason(data: dict[str, Any]) -> str:
publish_reason = data.get("publish_reason")
return publish_reason if isinstance(publish_reason, str) else ""
def is_incremental_publish_kind(publish_kind: str) -> bool:
return publish_kind in {
PUBLISH_INCREMENTAL_NOOP,
@@ -337,6 +342,7 @@ def build_index_result(
"incremental_done": parse_logged_elapsed_ms(stderr, "incremental.done"),
}
indexed_ms = indexed_work_elapsed_ms(logged_elapsed_ms)
publish_reason = response_publish_reason(data)
result: dict[str, Any] = {
"elapsed_ms": elapsed_ms_int,
"indexed_work_elapsed_ms": indexed_ms,
@@ -357,7 +363,7 @@ def build_index_result(
or is_incremental_publish_kind(publish_kind),
},
"logged_elapsed_ms": logged_elapsed_ms,
"exact_reason": parse_exact_reason(stderr),
"exact_reason": publish_reason or parse_exact_reason(stderr),
"stderr_tail": log_tail(stderr),
}
if include_logs:
+4
View File
@@ -4969,6 +4969,7 @@ static char *handle_index_repository(cbm_mcp_server_t *srv, const char *args) {
int rc = cbm_pipeline_run(p);
bool graph_changed = cbm_pipeline_graph_changed(p);
cbm_pipeline_publish_kind_t publish_kind = cbm_pipeline_publish_kind(p);
const char *publish_reason = cbm_pipeline_publish_reason(p);
srv->active_pipeline = NULL;
cbm_pipeline_unlock();
@@ -4997,6 +4998,9 @@ static char *handle_index_repository(cbm_mcp_server_t *srv, const char *args) {
yyjson_mut_obj_add_str(doc, root, "status", rc == 0 ? "indexed" : "error");
yyjson_mut_obj_add_str(doc, root, "publish_kind",
cbm_pipeline_publish_kind_name(publish_kind));
if (publish_reason && publish_reason[0]) {
yyjson_mut_obj_add_str(doc, root, "publish_reason", publish_reason);
}
yyjson_mut_obj_add_bool(doc, root, "graph_changed", graph_changed);
if (rc == 0) {
+17
View File
@@ -118,6 +118,7 @@ struct cbm_pipeline {
int committed_edges;
bool graph_changed;
cbm_pipeline_publish_kind_t publish_kind;
char *publish_reason;
};
/* ── Global pkgmap (one active pipeline at a time) ─────────────── */
@@ -189,6 +190,7 @@ cbm_pipeline_t *cbm_pipeline_new(const char *repo_path, const char *db_path,
p->committed_edges = -1;
p->graph_changed = false;
p->publish_kind = CBM_PIPELINE_PUBLISH_NONE;
p->publish_reason = NULL;
atomic_init(&p->cancelled, 0);
return p;
@@ -325,6 +327,7 @@ void cbm_pipeline_free(cbm_pipeline_t *p) {
free(p->repo_path);
free(p->db_path);
free(p->project_name);
free(p->publish_reason);
cbm_discover_free_excluded(p->excluded_dirs, p->excluded_count);
p->excluded_dirs = NULL;
p->excluded_count = 0;
@@ -403,6 +406,10 @@ cbm_pipeline_publish_kind_t cbm_pipeline_publish_kind(const cbm_pipeline_t *p) {
return p ? p->publish_kind : CBM_PIPELINE_PUBLISH_NONE;
}
const char *cbm_pipeline_publish_reason(const cbm_pipeline_t *p) {
return p ? p->publish_reason : NULL;
}
const char *cbm_pipeline_publish_kind_name(cbm_pipeline_publish_kind_t kind) {
switch (kind) {
case CBM_PIPELINE_PUBLISH_NONE:
@@ -432,6 +439,15 @@ void cbm_pipeline_set_publish_kind(cbm_pipeline_t *p, cbm_pipeline_publish_kind_
}
}
void cbm_pipeline_set_publish_reason(cbm_pipeline_t *p, const char *reason) {
if (!p) {
return;
}
char *next = (reason && reason[0]) ? cbm_strdup(reason) : NULL;
free(p->publish_reason);
p->publish_reason = next;
}
static bool resolve_db_path_buf(const cbm_pipeline_t *p, char *path, size_t path_sz) {
if (!p || !path || path_sz == 0) {
return false;
@@ -1272,6 +1288,7 @@ int cbm_pipeline_run(cbm_pipeline_t *p) {
}
p->graph_changed = false;
p->publish_kind = CBM_PIPELINE_PUBLISH_NONE;
cbm_pipeline_set_publish_reason(p, NULL);
p->committed_nodes = -1;
p->committed_edges = -1;
+1
View File
@@ -142,6 +142,7 @@ bool cbm_pipeline_graph_changed(const cbm_pipeline_t *p);
* that need derived-view policy decisions; it does not change graph contents. */
cbm_pipeline_publish_kind_t cbm_pipeline_publish_kind(const cbm_pipeline_t *p);
const char *cbm_pipeline_publish_kind_name(cbm_pipeline_publish_kind_t kind);
const char *cbm_pipeline_publish_reason(const cbm_pipeline_t *p);
/* ── Index lock (prevents concurrent pipeline runs on same DB) ──── */
+32 -8
View File
@@ -881,6 +881,7 @@ static int incr_try_exact_delete_route(cbm_pipeline_t *p, cbm_store_t *store, co
const char *rel_path = deleted[0];
if (!rel_path || !rel_path[0]) {
cbm_pipeline_set_publish_reason(p, "missing_rel_path");
cbm_log_info("incremental.exact.delete.fallback", "reason", "missing_rel_path");
return CBM_STORE_OK;
}
@@ -901,6 +902,7 @@ static int incr_try_exact_delete_route(cbm_pipeline_t *p, cbm_store_t *store, co
CBM_PIPELINE_EXACT_DELTA_MAX_AFFECTED_PATHS,
&plan);
if (rc != CBM_STORE_OK || plan.route != CBM_PIPELINE_DELTA_ROUTE_EXACT_CANDIDATE) {
cbm_pipeline_set_publish_reason(p, plan.reason ? plan.reason : "plan_error");
cbm_log_info("incremental.exact.delete.fallback", "reason",
plan.reason ? plan.reason : "plan_error");
cbm_pipeline_file_delta_plan_free(&plan);
@@ -911,6 +913,7 @@ static int incr_try_exact_delete_route(cbm_pipeline_t *p, cbm_store_t *store, co
int64_t generation = 0;
rc = cbm_store_reserve_index_generation(store, project, NULL, NULL, &generation);
if (rc != CBM_STORE_OK || generation <= 0) {
cbm_pipeline_set_publish_reason(p, "reserve_generation");
cbm_log_info("incremental.exact.delete.fallback", "reason", "reserve_generation", "rc",
itoa_buf_incr(rc));
return CBM_STORE_OK;
@@ -921,6 +924,7 @@ static int incr_try_exact_delete_route(cbm_pipeline_t *p, cbm_store_t *store, co
CBM_PIPELINE_EXACT_DELTA_MAX_AFFECTED_PATHS, &plan);
if (rc != CBM_STORE_OK || plan.route != CBM_PIPELINE_DELTA_ROUTE_EXACT_CANDIDATE) {
incr_mark_generation_failed(store, project, generation);
cbm_pipeline_set_publish_reason(p, plan.reason ? plan.reason : "apply_error");
cbm_log_info("incremental.exact.delete.fallback", "reason",
plan.reason ? plan.reason : "apply_error");
cbm_pipeline_file_delta_plan_free(&plan);
@@ -932,6 +936,7 @@ static int incr_try_exact_delete_route(cbm_pipeline_t *p, cbm_store_t *store, co
cbm_store_count_edges(store, project));
cbm_pipeline_set_graph_changed(p, true);
cbm_pipeline_set_publish_kind(p, CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT);
cbm_pipeline_set_publish_reason(p, NULL);
if (cbm_pipeline_repo_path(p) && cbm_artifact_exists(cbm_pipeline_repo_path(p))) {
(void)cbm_artifact_export(db_path, cbm_pipeline_repo_path(p), project, CBM_ARTIFACT_FAST);
}
@@ -954,14 +959,15 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
if (deleted_count != 0 || changed_count > CBM_PIPELINE_EXACT_DELTA_MAX_CHANGED_PATHS ||
cbm_pipeline_get_mode(p) < CBM_MODE_FAST ||
changed_count > CBM_PIPELINE_EXACT_DELTA_MAX_AFFECTED_PATHS) {
cbm_log_info("incremental.exact.skip", "reason",
deleted_count != 0
? "has_deletes"
: (changed_count > CBM_PIPELINE_EXACT_DELTA_MAX_CHANGED_PATHS
? "changed_batch_too_large"
: (cbm_pipeline_get_mode(p) < CBM_MODE_FAST
? "global_derived_edges"
: "frontier_too_large")));
const char *reason =
deleted_count != 0
? "has_deletes"
: (changed_count > CBM_PIPELINE_EXACT_DELTA_MAX_CHANGED_PATHS
? "changed_batch_too_large"
: (cbm_pipeline_get_mode(p) < CBM_MODE_FAST ? "global_derived_edges"
: "frontier_too_large"));
cbm_pipeline_set_publish_reason(p, reason);
cbm_log_info("incremental.exact.skip", "reason", reason);
return CBM_STORE_OK;
}
@@ -983,11 +989,13 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
scratch = cbm_gbuf_new(project, cbm_pipeline_repo_path(p));
registry = cbm_registry_new();
if (!changed_paths || !deltas || !delta_ptrs || !result_cache || !scratch || !registry) {
cbm_pipeline_set_publish_reason(p, "alloc");
cbm_log_info("incremental.exact.fallback", "reason", "alloc");
goto cleanup;
}
for (int i = 0; i < changed_count; i++) {
if (!changed_files[i].rel_path) {
cbm_pipeline_set_publish_reason(p, "missing_rel_path");
cbm_log_info("incremental.exact.fallback", "reason", "missing_rel_path");
goto cleanup;
}
@@ -999,6 +1007,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
store, scratch, registry, project, changed_paths, changed_count);
CBM_PROF_END_N("incremental_exact", "1_seed_scratch", t_exact_seed, changed_count);
if (rc != CBM_STORE_OK) {
cbm_pipeline_set_publish_reason(p, "scratch_seed");
cbm_log_info("incremental.exact.fallback", "reason", "scratch_seed", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1030,6 +1039,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
rc = cbm_pipeline_ensure_file_structure(scratch, project, structure_root_qn,
changed_files[i].rel_path, NULL);
if (rc != 0) {
cbm_pipeline_set_publish_reason(p, "ensure_structure");
cbm_log_info("incremental.exact.fallback", "reason", "ensure_structure", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1041,6 +1051,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
CBM_PROF_END_N("incremental_exact", "3_extract_resolve", t_exact_extract_resolve,
changed_count);
if (rc != 0) {
cbm_pipeline_set_publish_reason(p, "extract_resolve");
cbm_log_info("incremental.exact.fallback", "reason", "extract_resolve", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1049,6 +1060,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
rc = cbm_pipeline_pass_k8s(&ctx, changed_files, changed_count);
CBM_PROF_END_N("incremental_exact", "4_k8s", t_exact_k8s, changed_count);
if (rc != 0) {
cbm_pipeline_set_publish_reason(p, "k8s");
cbm_log_info("incremental.exact.fallback", "reason", "k8s", "rc", itoa_buf_incr(rc));
goto cleanup;
}
@@ -1056,6 +1068,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
rc = run_postpasses(&ctx, changed_files, changed_count, project);
CBM_PROF_END_N("incremental_exact", "5_postpasses", t_exact_postpasses, changed_count);
if (rc != 0) {
cbm_pipeline_set_publish_reason(p, "postpasses");
cbm_log_info("incremental.exact.fallback", "reason", "postpasses", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1067,6 +1080,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
rc = cbm_pipeline_pass_httplinks(&ctx);
CBM_PROF_END_N("incremental_exact", "7_httplinks", t_exact_httplinks, changed_count);
if (rc != 0) {
cbm_pipeline_set_publish_reason(p, "httplinks");
cbm_log_info("incremental.exact.fallback", "reason", "httplinks", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1080,6 +1094,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
rc = cbm_pipeline_build_file_delta_from_gbuf(scratch, project, changed_files[i].rel_path,
CBM_PIPELINE_COMPAT_GENERATION, &deltas[i]);
if (rc != CBM_STORE_OK) {
cbm_pipeline_set_publish_reason(p, "build_delta");
cbm_log_info("incremental.exact.fallback", "reason", "build_delta", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1088,6 +1103,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
&changed_files[i],
pass_fingerprint);
if (rc != CBM_STORE_OK) {
cbm_pipeline_set_publish_reason(p, "metadata");
cbm_log_info("incremental.exact.fallback", "reason", "metadata", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1101,6 +1117,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
CBM_PIPELINE_EXACT_DELTA_MAX_AFFECTED_PATHS, &plan);
CBM_PROF_END_N("incremental_exact", "10_plan_delta", t_exact_plan, changed_count);
if (rc != CBM_STORE_OK || plan.route != CBM_PIPELINE_DELTA_ROUTE_EXACT_CANDIDATE) {
cbm_pipeline_set_publish_reason(p, plan.reason ? plan.reason : "plan_error");
cbm_log_info("incremental.exact.fallback", "reason",
plan.reason ? plan.reason : "plan_error");
goto cleanup;
@@ -1111,6 +1128,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
rc = cbm_store_reserve_index_generation(store, project, NULL, NULL, &generation);
CBM_PROF_END("incremental_exact", "11_reserve_generation", t_exact_reserve);
if (rc != CBM_STORE_OK || generation <= 0) {
cbm_pipeline_set_publish_reason(p, "reserve_generation");
cbm_log_info("incremental.exact.fallback", "reason", "reserve_generation", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1120,6 +1138,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
rc = cbm_pipeline_file_delta_stamp_generation(&deltas[i], generation);
if (rc != CBM_STORE_OK) {
incr_mark_generation_failed(store, project, generation);
cbm_pipeline_set_publish_reason(p, "stamp_generation");
cbm_log_info("incremental.exact.fallback", "reason", "stamp_generation", "rc",
itoa_buf_incr(rc));
goto cleanup;
@@ -1133,6 +1152,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
CBM_PROF_END_N("incremental_exact", "13_apply_delta", t_exact_apply, changed_count);
if (rc != CBM_STORE_OK || plan.route != CBM_PIPELINE_DELTA_ROUTE_EXACT_CANDIDATE) {
incr_mark_generation_failed(store, project, generation);
cbm_pipeline_set_publish_reason(p, plan.reason ? plan.reason : "apply_error");
cbm_log_info("incremental.exact.fallback", "reason",
plan.reason ? plan.reason : "apply_error");
goto cleanup;
@@ -1142,6 +1162,7 @@ static int incr_try_exact_upsert_route(cbm_pipeline_t *p, cbm_store_t *store, co
cbm_store_count_edges(store, project));
cbm_pipeline_set_graph_changed(p, true);
cbm_pipeline_set_publish_kind(p, CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT);
cbm_pipeline_set_publish_reason(p, NULL);
if (cbm_pipeline_repo_path(p) && cbm_artifact_exists(cbm_pipeline_repo_path(p))) {
(void)cbm_artifact_export(db_path, cbm_pipeline_repo_path(p), project, CBM_ARTIFACT_FAST);
}
@@ -1526,6 +1547,9 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
if (persist_rc == 0) {
cbm_pipeline_set_graph_changed(p, true);
cbm_pipeline_set_publish_kind(p, CBM_PIPELINE_PUBLISH_INCREMENTAL_CONTAINMENT);
if (!cbm_pipeline_publish_reason(p)) {
cbm_pipeline_set_publish_reason(p, "containment_rebuild");
}
}
incr_classification_free(&cls);
cbm_gbuf_free(existing);
+1
View File
@@ -758,6 +758,7 @@ atomic_int *cbm_pipeline_cancelled_ptr(cbm_pipeline_t *p);
void cbm_pipeline_set_committed_counts(cbm_pipeline_t *p, int nodes, int edges);
void cbm_pipeline_set_graph_changed(cbm_pipeline_t *p, bool changed);
void cbm_pipeline_set_publish_kind(cbm_pipeline_t *p, cbm_pipeline_publish_kind_t kind);
void cbm_pipeline_set_publish_reason(cbm_pipeline_t *p, const char *reason);
/* Parse a gRPC stub call "<service-stub>.<method>" into the canonical proto
* service name + method. Returns true ONLY when a recognized gRPC stub/client
+77
View File
@@ -10,6 +10,7 @@
#include "test_framework.h"
#include <cli/cli.h>
#include <mcp/mcp.h>
#include <pipeline/pipeline.h>
#include <store/store.h>
#include <sqlite3.h>
#include <yyjson/yyjson.h>
@@ -1647,6 +1648,81 @@ TEST(tool_index_repository_missing_path) {
PASS();
}
TEST(tool_index_repository_reports_incremental_containment_reason) {
char *repo_tmp = th_mktempdir("cbm_mcp_publish_reason_repo");
if (!repo_tmp) {
PASS();
}
char repo[CBM_PATH_MAX];
int n = snprintf(repo, sizeof(repo), "%s", repo_tmp);
ASSERT(n >= 0 && (size_t)n < sizeof(repo));
char *cache_tmp = th_mktempdir("cbm_mcp_publish_reason_cache");
ASSERT_NOT_NULL(cache_tmp);
char cache[CBM_PATH_MAX];
n = snprintf(cache, sizeof(cache), "%s", cache_tmp);
ASSERT(n >= 0 && (size_t)n < sizeof(cache));
const char *saved = getenv("CBM_CACHE_DIR");
char *saved_copy = saved ? cbm_strdup(saved) : NULL;
cbm_setenv("CBM_CACHE_DIR", cache, 1);
cbm_config_t *cfg = cbm_config_open(cache);
ASSERT_NOT_NULL(cfg);
ASSERT_EQ(cbm_config_set(cfg, CBM_CONFIG_INCREMENTAL_REINDEX, "always"), 0);
ASSERT_EQ(th_write_file(TH_PATH(repo, "go.mod"), "module example.com/pubreason\n\ngo 1.22\n"),
0);
ASSERT_EQ(th_write_file(TH_PATH(repo, "main.go"), "package main\n\nfunc main() {}\n"), 0);
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
ASSERT_NOT_NULL(srv);
cbm_mcp_server_set_config(srv, cfg);
char req[CBM_SZ_4K];
n = snprintf(req, sizeof(req),
"{\"jsonrpc\":\"2.0\",\"id\":41,\"method\":\"tools/call\","
"\"params\":{\"name\":\"index_repository\","
"\"arguments\":{\"repo_path\":\"%s\",\"mode\":\"fast\"}}}",
repo);
ASSERT(n >= 0 && (size_t)n < sizeof(req));
char *resp = cbm_mcp_server_handle(srv, req);
ASSERT_NOT_NULL(resp);
ASSERT_NOT_NULL(strstr(resp, "indexed"));
free(resp);
ASSERT_EQ(th_write_file(TH_PATH(repo, "pkg/file_created.go"),
"package pkg\n\nfunc Created() int {\n\treturn 7\n}\n"),
0);
n = snprintf(req, sizeof(req),
"{\"jsonrpc\":\"2.0\",\"id\":42,\"method\":\"tools/call\","
"\"params\":{\"name\":\"index_repository\","
"\"arguments\":{\"repo_path\":\"%s\",\"mode\":\"fast\"}}}",
repo);
ASSERT(n >= 0 && (size_t)n < sizeof(req));
resp = cbm_mcp_server_handle(srv, req);
ASSERT_NOT_NULL(resp);
char *inner = extract_text_content(resp);
ASSERT_NOT_NULL(inner);
ASSERT_NOT_NULL(strstr(inner, "\"publish_kind\":\"incremental_containment\""));
ASSERT_NOT_NULL(strstr(inner, "\"publish_reason\":\"missing_existing_ownership\""));
free(inner);
free(resp);
cbm_mcp_server_free(srv);
cbm_config_close(cfg);
if (saved_copy) {
cbm_setenv("CBM_CACHE_DIR", saved_copy, 1);
free(saved_copy);
} else {
cbm_unsetenv("CBM_CACHE_DIR");
}
th_cleanup(repo);
th_cleanup(cache);
PASS();
}
TEST(tool_get_code_snippet_missing_qn) {
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
@@ -3457,6 +3533,7 @@ SUITE(mcp) {
/* Pipeline-dependent tool handlers */
RUN_TEST(tool_index_repository_missing_path);
RUN_TEST(tool_index_repository_reports_incremental_containment_reason);
RUN_TEST(tool_get_code_snippet_missing_qn);
RUN_TEST(tool_get_code_snippet_not_found);
RUN_TEST(tool_search_code_missing_pattern);
+2
View File
@@ -8694,6 +8694,7 @@ TEST(incremental_fast_falls_back_for_inbound_transitive_complexity_and_matches_f
ASSERT(strstr(logs, "msg=incremental.exact.fallback reason=inbound_edges_require_full") !=
NULL);
ASSERT_EQ(cbm_pipeline_publish_kind(p), CBM_PIPELINE_PUBLISH_INCREMENTAL_CONTAINMENT);
ASSERT_STR_EQ(cbm_pipeline_publish_reason(p), "inbound_edges_require_full");
cbm_pipeline_free(p);
char diff_err[CBM_SZ_8K] = {0};
@@ -8760,6 +8761,7 @@ TEST(incremental_fast_three_file_batch_falls_back_to_full_rebuild_parity) {
cbm_pipeline_apply_config(p, cfg);
ASSERT_EQ(cbm_pipeline_run(p), 0);
ASSERT_EQ(cbm_pipeline_publish_kind(p), CBM_PIPELINE_PUBLISH_INCREMENTAL_CONTAINMENT);
ASSERT_STR_EQ(cbm_pipeline_publish_reason(p), "changed_batch_too_large");
cbm_pipeline_free(p);
ASSERT(pipeline_store_has_function_name(g_incr_dbpath, project, "NewMain"));