Skip to content

Commit 63eda54

Browse files
DeusDatawin4r
andcommitted
fix(store): route label allowlists through the canonical type-like set
cbm.h documents cbm_label_is_type_like() as the single source of truth for type-like labels, "so adding a new type-like label (e.g. Struct for Rust/Go/Swift/D structs) updates them all at once instead of scattering || strcmp(label,"Struct")==0 across the tree". A SQL string literal cannot call it. Four queries in store.c and the BM25 ranking in mcp.c carried their own hardcoded label lists, so they silently opted out of that contract and stopped matching the moment Struct, Interface, Enum, Type and Trait began being emitted: store.c:5279 arch_boundaries ('Function','Method','Class') store.c:5422 arch_packages_from_qn ('Function','Method','Class') store.c:6689 arch_clusters ('Function','Method','Class') store.c:7742 cbm_store_vector_search ('Function','Method','Class') mcp.c:2759 BM25 ranking ('Class','Interface','Type','Enum') Effect on main today: get_architecture (boundaries, packages, clusters) and vector search drop every Struct, Interface, Enum, Type and Trait node in the project, and search_code under-ranks structs -- for Rust, Go, Swift and D, where struct is the primary type declaration. The BM25 list had drifted differently again, omitting Struct and Trait but not Interface. The fix is not a wider literal. CBM_SQL_TYPE_LIKE_LABELS and CBM_SQL_CALLABLE_OR_TYPE_LABELS live next to each other in constants.h and are pinned to cbm_label_is_type_like() by a test that checks both directions: every label the C predicate accepts appears in the SQL fragment, and nothing it rejects is smuggled in. Adding a type-like label without updating the SQL now fails that test instead of quietly shrinking query results. No new labels are introduced. Every label added here is one the extractors already emit. Reported by @win4r in #568, whose audit found four of the five sites. Their patch widened the literals in place and added Actor, a label main does not emit; this routes through the canonical set instead and also covers Interface, Type and Trait, which that patch still omitted. Co-Authored-By: win4r <win4r@users.noreply.github.com> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1 parent d698db8 commit 63eda54

4 files changed

Lines changed: 62 additions & 5 deletions

File tree

‎src/foundation/constants.h‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,19 @@ enum {
9494
/* Common offset constants (used across many files). */
9595
enum { SKIP_ONE = 1, PAIR_LEN = 2 };
9696

97+
/* ── Label allowlists for SQL ────────────────────────────────────
98+
* SQL mirror of cbm_label_is_type_like() (internal/cbm/helpers.c). That
99+
* function is documented as the single source of truth for type-like labels
100+
* "instead of scattering `|| strcmp(label,\"Struct\")==0` across the tree" —
101+
* but a SQL string literal cannot call it, so several queries hardcoded
102+
* ('Function','Method','Class') and silently stopped matching the moment
103+
* Struct/Interface/Enum/Type/Trait began being emitted.
104+
*
105+
* Use these instead of inlining a label list. tests/test_store_nodes.c pins
106+
* them against cbm_label_is_type_like(), so adding a type-like label there
107+
* without updating these fails CI rather than quietly shrinking query results. */
108+
#define CBM_SQL_TYPE_LIKE_LABELS "'Class','Struct','Interface','Enum','Type','Trait'"
109+
#define CBM_SQL_CALLABLE_LABELS "'Function','Method'"
110+
#define CBM_SQL_CALLABLE_OR_TYPE_LABELS CBM_SQL_CALLABLE_LABELS "," CBM_SQL_TYPE_LIKE_LABELS
111+
97112
#endif /* CBM_CONSTANTS_H */

‎src/mcp/mcp.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2863,7 +2863,7 @@ static char *bm25_search(cbm_store_t *store, const char *project, const char *qu
28632863
" (fts.base_rank "
28642864
" - CASE WHEN n.label IN ('Function','Method') THEN 10.0 "
28652865
" WHEN n.label = 'Route' THEN 8.0 "
2866-
" WHEN n.label IN ('Class','Interface','Type','Enum') THEN 5.0 "
2866+
" WHEN n.label IN (" CBM_SQL_TYPE_LIKE_LABELS ") THEN 5.0 "
28672867
" ELSE 0.0 END) AS rank "
28682868
"FROM ("
28692869
" SELECT rowid, bm25(nodes_fts) AS base_rank"

‎src/store/store.c‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5276,7 +5276,7 @@ static int arch_boundaries(cbm_store_t *s, const char *project, const char *path
52765276
char nsqlbuf[ST_SQL_BUF];
52775277
const char *nbase =
52785278
"SELECT id, qualified_name, file_path FROM nodes WHERE project=?1 AND label IN "
5279-
"('Function','Method','Class')";
5279+
"(" CBM_SQL_CALLABLE_OR_TYPE_LABELS ")";
52805280
if (scoped) {
52815281
snprintf(nsqlbuf, sizeof(nsqlbuf), "%s%s ORDER BY id", nbase, arch_path_scope_sql());
52825282
} else {
@@ -5419,7 +5419,7 @@ static int arch_packages_from_qn(cbm_store_t *s, const char *project, const char
54195419
bool scoped = arch_path_prepare(path, norm, sizeof(norm), like, sizeof(like));
54205420
char qsqlbuf[ST_SQL_BUF];
54215421
const char *qbase = "SELECT qualified_name FROM nodes WHERE project=?1 AND label IN "
5422-
"('Function','Method','Class')";
5422+
"(" CBM_SQL_CALLABLE_OR_TYPE_LABELS ")";
54235423
if (scoped) {
54245424
snprintf(qsqlbuf, sizeof(qsqlbuf), "%s%s", qbase, arch_path_scope_sql());
54255425
} else {
@@ -6686,7 +6686,7 @@ static int arch_clusters(cbm_store_t *s, const char *project, const char *path,
66866686
bool scoped = arch_path_prepare(path, norm, sizeof(norm), like, sizeof(like));
66876687
char nsqlbuf[ST_SQL_BUF];
66886688
const char *nbase = "SELECT id, name, qualified_name, file_path FROM nodes "
6689-
"WHERE project=?1 AND label IN ('Function','Method','Class')";
6689+
"WHERE project=?1 AND label IN (" CBM_SQL_CALLABLE_OR_TYPE_LABELS ")";
66906690
if (scoped) {
66916691
snprintf(nsqlbuf, sizeof(nsqlbuf), "%s%s ORDER BY id LIMIT ?4", nbase,
66926692
arch_path_scope_sql());
@@ -7760,7 +7760,7 @@ int cbm_store_vector_search(cbm_store_t *s, const char *project, const char **ke
77607760
" FROM node_vectors v"
77617761
" INNER JOIN nodes n ON n.id = v.node_id"
77627762
" WHERE v.project = ?2"
7763-
" AND n.label IN ('Function','Method','Class')"
7763+
" AND n.label IN (" CBM_SQL_CALLABLE_OR_TYPE_LABELS ")"
77647764
" ORDER BY score DESC"
77657765
" LIMIT ?3";
77667766

‎tests/test_store_nodes.c‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,52 @@
66
*/
77
#include "test_framework.h"
88
#include <store/store.h>
9+
#include <foundation/constants.h>
10+
#include <cbm.h>
911
#include <sqlite3.h>
1012
#include <string.h>
1113
#include <stdlib.h>
1214
#include <stdio.h>
1315

16+
/* ── Label allowlist / SQL drift guard ──────────────────────────── */
17+
18+
/* CONTRACT PIN. `cbm_label_is_type_like()` is documented in cbm.h as the single
19+
* source of truth for type-like labels, "instead of scattering
20+
* `|| strcmp(label,\"Struct\")==0` across the tree". A SQL string literal cannot
21+
* call it, so four queries in store.c and the BM25 ranking in mcp.c hardcoded
22+
* their own label lists — and silently stopped matching once Struct (Rust, Go,
23+
* Swift, D) began being emitted. get_architecture and vector search dropped
24+
* every struct in the project; search_code under-ranked them.
25+
*
26+
* This pins the SQL mirrors to the C predicate in BOTH directions, so the next
27+
* type-like label fails here instead of quietly shrinking query results. */
28+
TEST(sql_label_allowlists_match_cbm_label_is_type_like) {
29+
/* Every label the C predicate accepts must appear in the SQL fragment. */
30+
static const char *const type_like[] = {"Class", "Struct", "Interface",
31+
"Enum", "Type", "Trait"};
32+
for (size_t i = 0; i < sizeof(type_like) / sizeof(type_like[0]); i++) {
33+
ASSERT_TRUE(cbm_label_is_type_like(type_like[i]));
34+
char quoted[64];
35+
snprintf(quoted, sizeof(quoted), "'%s'", type_like[i]);
36+
ASSERT_NOT_NULL(strstr(CBM_SQL_TYPE_LIKE_LABELS, quoted));
37+
ASSERT_NOT_NULL(strstr(CBM_SQL_CALLABLE_OR_TYPE_LABELS, quoted));
38+
}
39+
/* And nothing the predicate rejects may be smuggled into the type-like
40+
* fragment — otherwise the SQL would widen past the C contract. */
41+
static const char *const not_type_like[] = {"Function", "Method", "Module",
42+
"File", "Folder", "Variable"};
43+
for (size_t i = 0; i < sizeof(not_type_like) / sizeof(not_type_like[0]); i++) {
44+
ASSERT_FALSE(cbm_label_is_type_like(not_type_like[i]));
45+
char quoted[64];
46+
snprintf(quoted, sizeof(quoted), "'%s'", not_type_like[i]);
47+
ASSERT_NULL(strstr(CBM_SQL_TYPE_LIKE_LABELS, quoted));
48+
}
49+
/* The callable fragment carries exactly Function and Method on top. */
50+
ASSERT_NOT_NULL(strstr(CBM_SQL_CALLABLE_OR_TYPE_LABELS, "'Function'"));
51+
ASSERT_NOT_NULL(strstr(CBM_SQL_CALLABLE_OR_TYPE_LABELS, "'Method'"));
52+
PASS();
53+
}
54+
1455
/* ── Schema / Open / Close ──────────────────────────────────────── */
1556

1657
TEST(store_open_memory) {
@@ -1930,6 +1971,7 @@ SUITE(store_nodes) {
19301971
RUN_TEST(store_coverage_meta_zero_row_truncation_and_delete);
19311972
RUN_TEST(store_coverage_replace_rejects_invalid_row_arguments);
19321973
RUN_TEST(store_coverage_replace_rolls_back_when_shadow_rebuild_fails);
1974+
RUN_TEST(sql_label_allowlists_match_cbm_label_is_type_like);
19331975
RUN_TEST(store_open_memory);
19341976
RUN_TEST(store_close_null);
19351977
RUN_TEST(store_open_memory_twice);

0 commit comments

Comments
 (0)