|
6 | 6 | */ |
7 | 7 | #include "test_framework.h" |
8 | 8 | #include <store/store.h> |
| 9 | +#include <foundation/constants.h> |
| 10 | +#include <cbm.h> |
9 | 11 | #include <sqlite3.h> |
10 | 12 | #include <string.h> |
11 | 13 | #include <stdlib.h> |
12 | 14 | #include <stdio.h> |
13 | 15 |
|
| 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 | + |
14 | 55 | /* ── Schema / Open / Close ──────────────────────────────────────── */ |
15 | 56 |
|
16 | 57 | TEST(store_open_memory) { |
@@ -1930,6 +1971,7 @@ SUITE(store_nodes) { |
1930 | 1971 | RUN_TEST(store_coverage_meta_zero_row_truncation_and_delete); |
1931 | 1972 | RUN_TEST(store_coverage_replace_rejects_invalid_row_arguments); |
1932 | 1973 | RUN_TEST(store_coverage_replace_rolls_back_when_shadow_rebuild_fails); |
| 1974 | + RUN_TEST(sql_label_allowlists_match_cbm_label_is_type_like); |
1933 | 1975 | RUN_TEST(store_open_memory); |
1934 | 1976 | RUN_TEST(store_close_null); |
1935 | 1977 | RUN_TEST(store_open_memory_twice); |
|
0 commit comments