mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-09 09:11:00 +08:00
fix(pipeline): accept regenerated file-owned inbound edges
Allow exact-delta planning to handle inbound edges whose source node has no file owner when the edge itself is owned by a path in the delta batch and the replacement batch regenerates the same source/target/type edge. Keep the final planner authoritative so stale unowned-source edges still fall back to full/containment handling. Add paired pipeline canaries for regenerated versus stale file-owned unowned-source CALLS edges. Validation: build/c/test-runner rebuild rc=0; CBM_ONLY_SUITE=pipeline rc=0 (306/306); source-safety rc=0; escalated CBM_ONLY_SUITE=incremental CBM_ONLY_TEST=exact rc=0 (1/1); self-dogfood cap64 rc=0 moved first blocker to frontier_too_large; cap128/cap256 benchmark evidence recorded in notes. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -905,11 +905,11 @@ static bool delta_batch_contains_edge(const cbm_pipeline_file_delta_t *const *de
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool delta_unowned_inbound_edge_is_regenerated(
|
||||
static bool delta_inbound_edge_is_regenerated_by_batch(
|
||||
const cbm_store_inbound_edge_t *edge, const cbm_pipeline_file_delta_t *const *deltas,
|
||||
int delta_count) {
|
||||
return edge && edge->source_rel_path && edge->source_rel_path[0] == '\0' && edge->type &&
|
||||
strcmp(edge->type, cbm_delta_edge_contains_file) == 0 &&
|
||||
return edge && edge->source_rel_path && edge->source_rel_path[0] == '\0' &&
|
||||
delta_path_in_batch(edge->edge_rel_path, deltas, delta_count) &&
|
||||
delta_batch_contains_edge(deltas, delta_count, edge->source_qn, edge->target_qn,
|
||||
edge->type);
|
||||
}
|
||||
@@ -936,7 +936,7 @@ static bool delta_inbound_edges_supported(cbm_store_t *store,
|
||||
bool ok = true;
|
||||
for (int i = 0; i < edge_count; i++) {
|
||||
if (!delta_path_in_batch(edges[i].source_rel_path, deltas, delta_count) &&
|
||||
!delta_unowned_inbound_edge_is_regenerated(&edges[i], deltas, delta_count) &&
|
||||
!delta_inbound_edge_is_regenerated_by_batch(&edges[i], deltas, delta_count) &&
|
||||
!delta_owned_inbound_edge_is_deleted(&edges[i], delta)) {
|
||||
ok = false;
|
||||
break;
|
||||
|
||||
@@ -1022,6 +1022,13 @@ static bool incr_empty_source_inbound_edge_is_structure(const cbm_store_inbound_
|
||||
strcmp(edge->type, CBM_PIPELINE_EDGE_CONTAINS_FILE) == 0;
|
||||
}
|
||||
|
||||
static bool incr_inbound_edge_owner_is_exact_file(const cbm_store_inbound_edge_t *edge,
|
||||
const cbm_file_info_t *exact_files,
|
||||
int exact_count) {
|
||||
return edge && edge->source_rel_path && edge->source_rel_path[0] == '\0' &&
|
||||
incr_file_info_has_rel_path(exact_files, exact_count, edge->edge_rel_path);
|
||||
}
|
||||
|
||||
static int incr_expand_exact_inbound_frontier(cbm_store_t *store, const char *project,
|
||||
const cbm_file_info_t *all_files, int all_file_count,
|
||||
cbm_file_info_t *exact_files, int *exact_count,
|
||||
@@ -1052,7 +1059,8 @@ static int incr_expand_exact_inbound_frontier(cbm_store_t *store, const char *pr
|
||||
for (int i = 0; i < edge_count; i++) {
|
||||
const char *source_rel_path = edges[i].source_rel_path;
|
||||
if (!source_rel_path || source_rel_path[0] == '\0') {
|
||||
if (incr_empty_source_inbound_edge_is_structure(&edges[i])) {
|
||||
if (incr_empty_source_inbound_edge_is_structure(&edges[i]) ||
|
||||
incr_inbound_edge_owner_is_exact_file(&edges[i], exact_files, *exact_count)) {
|
||||
continue;
|
||||
}
|
||||
if (out_reason) {
|
||||
|
||||
@@ -3548,6 +3548,37 @@ static int pipeline_delta_seed_existing_ownership(cbm_store_t *s, const char *pr
|
||||
: CBM_STORE_ERR;
|
||||
}
|
||||
|
||||
static int pipeline_delta_seed_file_owned_unowned_source_edge(
|
||||
cbm_store_t *s, const char *project, const char *rel_path, const char *source_qn,
|
||||
const char *target_qn, const char *edge_type) {
|
||||
enum { PIPELINE_DELTA_TEST_BASE_GENERATION = 1 };
|
||||
int64_t target_id = pipeline_delta_seed_existing_ownership_id(s, project, rel_path, target_qn);
|
||||
if (target_id <= CBM_STORE_NO_NODE_ID) {
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
cbm_node_t source = {.project = (char *)project,
|
||||
.label = "Module",
|
||||
.name = "module",
|
||||
.qualified_name = (char *)source_qn,
|
||||
.file_path = "",
|
||||
.properties_json = "{}"};
|
||||
int64_t source_id = cbm_store_upsert_node(s, &source);
|
||||
if (source_id <= CBM_STORE_NO_NODE_ID) {
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
cbm_edge_t edge = {.project = (char *)project,
|
||||
.source_id = source_id,
|
||||
.target_id = target_id,
|
||||
.type = (char *)edge_type,
|
||||
.properties_json = "{}"};
|
||||
int64_t edge_id = cbm_store_insert_edge(s, &edge);
|
||||
if (edge_id <= CBM_STORE_NO_NODE_ID) {
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
return cbm_store_upsert_edge_owner(s, project, edge_id, rel_path, NULL,
|
||||
PIPELINE_DELTA_TEST_BASE_GENERATION);
|
||||
}
|
||||
|
||||
static int pipeline_delta_seed_project_node(cbm_store_t *s, const char *project) {
|
||||
cbm_node_t project_node = {.project = (char *)project,
|
||||
.label = "Project",
|
||||
@@ -4925,6 +4956,86 @@ TEST(pipeline_file_delta_plan_accepts_regenerated_structural_inbound_edge) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_file_delta_plan_accepts_regenerated_file_owned_unowned_source_edge) {
|
||||
const char *project = "test";
|
||||
const char *rel_path = "src/main.go";
|
||||
const char *source_qn = "test.src.module";
|
||||
const char *target_qn = "test.src.main.Run";
|
||||
cbm_store_t *s = cbm_store_open_memory();
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_EQ(cbm_store_upsert_project(s, project, "/tmp/test"), CBM_STORE_OK);
|
||||
ASSERT_EQ(pipeline_delta_seed_file_owned_unowned_source_edge(
|
||||
s, project, rel_path, source_qn, target_qn, "CALLS"),
|
||||
CBM_STORE_OK);
|
||||
|
||||
cbm_node_t replacement_node = {.project = (char *)project,
|
||||
.label = "Function",
|
||||
.name = "Run",
|
||||
.qualified_name = (char *)target_qn,
|
||||
.file_path = (char *)rel_path,
|
||||
.properties_json = "{}"};
|
||||
cbm_store_delta_edge_t replacement_edge = {.source_qn = source_qn,
|
||||
.target_qn = target_qn,
|
||||
.type = "CALLS",
|
||||
.properties_json = "{}",
|
||||
.derived_kind = CBM_STORE_DERIVED_KIND_DIRECT};
|
||||
cbm_pipeline_file_delta_t delta = {.delta = {.project = project,
|
||||
.rel_path = rel_path,
|
||||
.nodes = &replacement_node,
|
||||
.node_count = 1,
|
||||
.edges = &replacement_edge,
|
||||
.edge_count = 1}};
|
||||
cbm_file_hash_t hash = {0};
|
||||
cbm_file_state_t state = {0};
|
||||
pipeline_delta_attach_test_metadata(&delta, &hash, &state);
|
||||
|
||||
cbm_pipeline_file_delta_plan_t plan = {0};
|
||||
ASSERT_EQ(cbm_pipeline_plan_file_delta(s, &delta, CBM_SZ_4, &plan), CBM_STORE_OK);
|
||||
ASSERT_EQ(plan.route, CBM_PIPELINE_DELTA_ROUTE_EXACT_CANDIDATE);
|
||||
ASSERT_STR_EQ(plan.reason, "candidate");
|
||||
ASSERT_EQ(pipeline_delta_plan_contains_path(&plan, rel_path), 1);
|
||||
|
||||
cbm_pipeline_file_delta_plan_free(&plan);
|
||||
cbm_store_close(s);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_file_delta_plan_falls_back_on_stale_file_owned_unowned_source_edge) {
|
||||
const char *project = "test";
|
||||
const char *rel_path = "src/main.go";
|
||||
const char *source_qn = "test.src.module";
|
||||
const char *target_qn = "test.src.main.Run";
|
||||
cbm_store_t *s = cbm_store_open_memory();
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_EQ(cbm_store_upsert_project(s, project, "/tmp/test"), CBM_STORE_OK);
|
||||
ASSERT_EQ(pipeline_delta_seed_file_owned_unowned_source_edge(
|
||||
s, project, rel_path, source_qn, target_qn, "CALLS"),
|
||||
CBM_STORE_OK);
|
||||
|
||||
cbm_node_t replacement_node = {.project = (char *)project,
|
||||
.label = "Function",
|
||||
.name = "Run",
|
||||
.qualified_name = (char *)target_qn,
|
||||
.file_path = (char *)rel_path,
|
||||
.properties_json = "{}"};
|
||||
cbm_pipeline_file_delta_t delta = {.delta = {.project = project,
|
||||
.rel_path = rel_path,
|
||||
.nodes = &replacement_node,
|
||||
.node_count = 1}};
|
||||
cbm_file_hash_t hash = {0};
|
||||
cbm_file_state_t state = {0};
|
||||
pipeline_delta_attach_test_metadata(&delta, &hash, &state);
|
||||
|
||||
cbm_pipeline_file_delta_plan_t plan = {0};
|
||||
ASSERT_EQ(cbm_pipeline_plan_file_delta(s, &delta, CBM_SZ_4, &plan), CBM_STORE_OK);
|
||||
ASSERT_EQ(plan.route, CBM_PIPELINE_DELTA_ROUTE_FALLBACK);
|
||||
ASSERT_STR_EQ(plan.reason, "inbound_edges_require_full");
|
||||
|
||||
cbm_pipeline_file_delta_plan_free(&plan);
|
||||
cbm_store_close(s);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_file_delta_plan_falls_back_without_file_metadata) {
|
||||
cbm_store_t *s = cbm_store_open_memory();
|
||||
ASSERT_NOT_NULL(s);
|
||||
@@ -11920,6 +12031,8 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_file_delta_plan_falls_back_on_new_folder_structure_edge);
|
||||
RUN_TEST(pipeline_file_delta_apply_inserts_and_prunes_new_folder_context);
|
||||
RUN_TEST(pipeline_file_delta_plan_accepts_regenerated_structural_inbound_edge);
|
||||
RUN_TEST(pipeline_file_delta_plan_accepts_regenerated_file_owned_unowned_source_edge);
|
||||
RUN_TEST(pipeline_file_delta_plan_falls_back_on_stale_file_owned_unowned_source_edge);
|
||||
RUN_TEST(pipeline_file_delta_plan_falls_back_without_file_metadata);
|
||||
RUN_TEST(pipeline_file_delta_plan_falls_back_on_unsupported_edges);
|
||||
RUN_TEST(pipeline_file_delta_plan_falls_back_on_delete);
|
||||
|
||||
Reference in New Issue
Block a user