Files
codebase-memory-mcp/tests/resolver_quality.h
T

764 lines
40 KiB
C

#ifndef CBM_TEST_RESOLVER_QUALITY_H
#define CBM_TEST_RESOLVER_QUALITY_H
/* Independent, bounded fixture oracle. Each listed (file, caller, relation)
* closes that caller's relation domain: list all its genuine occurrences.
* Other callers/files are outside coverage, except a row at an exact golden
* site is always checked for a rebound caller or role. No resolver output or
* capability flag creates expectations. RAW sites may have an explicit,
* independently authored line-preserving preprocessing map; every other
* covered expanded/embedded row still fails rather than borrowing raw spans.
* Include this header in tests only; no production resolver behavior changes. */
#include "cbm.h"
#include "callable_sig.h"
#include "resolve/policy.h"
#include "store/store.h"
#include "foundation/sha256.h"
#include "sqlite3.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
const char *path, *text;
/* Fixture-authored bytes/configuration, never populated from resolver output. */
const char *preprocessed_text, *preprocessed_configuration_identity;
} RQ_Source;
typedef struct {
const char *file, *caller, *needle;
unsigned occurrence_index;
/* Without needle, supply an exact nonempty raw byte interval. */
uint32_t start_byte, end_byte;
/* Optional alternate coordinates for this SAME raw occurrence. The bytes
* and source line must agree; zero/zero means no preprocessing map. */
uint32_t preprocessed_start_byte, preprocessed_end_byte;
/* Optional authored caller variant, never inferred from the merged node.
* All three fields are required; the exact stored variant must agree. */
unsigned caller_variant_start_line, caller_variant_end_line;
const char *caller_variant_declaration;
} RQ_Site;
typedef enum {
RQ_TARGET_PROJECT = 0,
RQ_TARGET_EXTERNAL,
RQ_TARGET_CONFIGURED_FOLDER
} RQ_TargetDomain;
typedef struct {
const char *file, *suffix, *label;
int declaration_line;
const char *declaration_text;
/* For project targets, NULL suffix means a known identity not materialized;
* it never matches a graph target. The file/source anchor remains real. */
RQ_TargetDomain domain;
/* External builtins have an exact repository ontology identity, never a
* fabricated project declaration or a suffix-based target match. */
const char *external_identity;
/* Only CONFIGURED_FOLDER uses a separate literal configuration anchor.
* file/suffix still identify the exact structural Folder; its 0/0 range
* never substitutes for a declaration range on an ordinary project node. */
const char *configuration_file;
} RQ_Target;
typedef enum { RQ_REQUIRED, RQ_KNOWN_GAP } RQ_Mode;
typedef struct {
const char *case_id;
RQ_Site site;
const char *relation;
RQ_Target target;
RQ_Mode mode;
const char *gap_id, *why, *tried;
/* Preserve gap history while promoting recovery to a required positive. */
bool expected_recovered;
/* Explicit source semantics may call several targets at one occurrence,
* e.g. an Ansible notify topic. Never infer this group from observed rows. */
const char *dispatch_group;
} RQ_Expected;
typedef struct {
const char *case_id;
RQ_Site site;
const char *relation;
RQ_Target target;
bool any_target;
} RQ_Forbidden;
typedef struct {
size_t genuine, found, missing, wrong, projection_errors;
size_t required_missing, recovered_gaps, invalid_oracle, metadata_errors;
} RQ_Report;
typedef struct {
const char *case_id, *relation;
const RQ_Site *site;
const RQ_Target *target;
uint32_t start, end;
int line;
bool negative, any_target, found;
const char *dispatch_group;
uint32_t preprocessed_start, preprocessed_end;
char preprocessed_content_identity[CBM_SHA256_HEX_LEN + 1];
const char *preprocessed_configuration_identity;
} RQ_State;
enum { RQ_MAX_EXPECTATIONS = 256 };
static inline bool rq_text(const char *text) {
return text && text[0];
}
static inline bool rq_equal(const char *left, const char *right) {
return left && right && !strcmp(left, right);
}
static inline bool rq_suffix(const char *actual, const char *name, const char *wanted) {
if (!rq_text(actual) || !rq_text(wanted))
return false;
size_t length = cbm_qn_callable_base_len_named(actual, name);
size_t expected = cbm_qn_callable_base_len(wanted);
return expected && length >= expected &&
(!wanted[expected] || !strcmp(actual + length, wanted + expected)) &&
(length == expected || actual[length - expected - 1] == '.') &&
!memcmp(actual + length - expected, wanted, expected);
}
static inline const RQ_Source *rq_source_input(const RQ_Source *sources, size_t count,
const char *path) {
for (size_t i = 0; i < count; i++)
if (rq_equal(sources[i].path, path))
return sources + i;
return NULL;
}
static inline const char *rq_source(const RQ_Source *sources, size_t count, const char *path) {
const RQ_Source *source = rq_source_input(sources, count, path);
return source ? source->text : NULL;
}
static inline bool rq_anchor(RQ_State *state, const RQ_Source *sources, size_t count) {
const RQ_Site *site = state->site;
const RQ_Source *input = rq_source_input(sources, count, site->file);
const char *source = input ? input->text : NULL;
if (!source || !rq_text(site->caller) || strlen(source) > UINT32_MAX ||
site->occurrence_index > strlen(source))
return false;
if (rq_text(site->needle)) {
const char *found = source;
for (unsigned i = 0; i <= site->occurrence_index; i++) {
found = strstr(found, site->needle);
if (!found)
return false;
if (i != site->occurrence_index)
found++;
}
state->start = (uint32_t)(found - source);
state->end = state->start + (uint32_t)strlen(site->needle);
if (site->end_byte && (site->start_byte != state->start || site->end_byte != state->end))
return false;
} else {
state->start = site->start_byte;
state->end = site->end_byte;
}
if (state->start >= state->end || state->end > strlen(source))
return false;
state->line = 1;
for (uint32_t i = 0; i < state->start; i++)
state->line += source[i] == '\n';
if (site->preprocessed_start_byte || site->preprocessed_end_byte) {
const char *expanded = input->preprocessed_text;
uint32_t start = site->preprocessed_start_byte, end = site->preprocessed_end_byte;
if (!expanded || !rq_text(input->preprocessed_configuration_identity) ||
strlen(expanded) > UINT32_MAX || start >= end || end > strlen(expanded) ||
end - start != state->end - state->start ||
memcmp(expanded + start, source + state->start, end - start))
return false;
int line = 1;
for (uint32_t i = 0; i < start; i++)
line += expanded[i] == '\n';
if (line != state->line)
return false;
state->preprocessed_start = start;
state->preprocessed_end = end;
state->preprocessed_configuration_identity = input->preprocessed_configuration_identity;
cbm_sha256_hex(expanded, strlen(expanded), state->preprocessed_content_identity);
}
return true;
}
/* Bounded external ontology: internal/cbm/lsp/py_builtins.c materializes
* these Python symbols at this synthetic path. The expectation supplies the
* exact identity and label independently of any observed resolver output.
* Kotlin adds only Any.hashCode and Int.toString. Their semantics are defined
* by the Kotlin stdlib API (-any/hash-code.html and -int/to-string.html); Int
* explicitly overrides toString. <kotlin-builtins> is the repository ontology
* path. An absent Int node is a quality gap, never authority for Any.toString. */
static inline bool rq_external_target(const RQ_Target *target) {
if (target->domain != RQ_TARGET_EXTERNAL || target->suffix || target->declaration_line ||
target->declaration_text || target->configuration_file ||
!rq_text(target->external_identity))
return false;
if (rq_equal(target->file, "<kotlin-builtins>"))
return rq_equal(target->label, "Method") &&
(rq_equal(target->external_identity, "kotlin.Any.hashCode") ||
rq_equal(target->external_identity, "kotlin.Int.toString"));
if (!rq_equal(target->file, "<python-builtins>"))
return false;
static const struct {
const char *identity, *label;
} builtins[] = {{"builtins.len", "Function"}, {"builtins.print", "Function"},
{"builtins.str", "Class"}, {"builtins.int", "Class"},
{"builtins.list", "Class"}, {"builtins.dict", "Class"},
{"builtins.range", "Class"}, {"builtins.str.upper", "Method"},
{"builtins.str.lower", "Method"}, {"builtins.list.append", "Method"},
{"builtins.list.pop", "Method"}, {"builtins.dict.get", "Method"}};
for (size_t i = 0; i < sizeof(builtins) / sizeof(builtins[0]); i++)
if (rq_equal(target->external_identity, builtins[i].identity) &&
rq_equal(target->label, builtins[i].label))
return true;
return false;
}
static inline const char *rq_target_identity(const RQ_Target *target) {
return target->domain == RQ_TARGET_EXTERNAL ? target->external_identity : target->suffix;
}
static inline bool rq_configured_folder(const RQ_Target *target) {
if (target->domain != RQ_TARGET_CONFIGURED_FOLDER || target->external_identity ||
!rq_equal(target->label, "Folder") || !rq_text(target->file) || !rq_text(target->suffix) ||
!rq_text(target->configuration_file) || target->file[0] == '/' ||
strchr(target->file, '\\'))
return false;
const char *segment = target->file;
for (const char *cursor = target->file;; cursor++) {
if (*cursor != '/' && *cursor)
continue;
size_t length = (size_t)(cursor - segment);
if (!length || (length == 1 && segment[0] == '.') ||
(length == 2 && segment[0] == '.' && segment[1] == '.'))
return false;
if (!*cursor)
break;
segment = cursor + 1;
}
size_t length = strlen(target->file);
if (strlen(target->suffix) != length)
return false;
for (size_t i = 0; i < length; i++)
if (target->suffix[i] != (target->file[i] == '/' ? '.' : target->file[i]))
return false;
return true;
}
static inline bool rq_target_anchor(const RQ_Target *target, const RQ_Source *sources,
size_t count) {
if (target->domain == RQ_TARGET_EXTERNAL)
return rq_external_target(target);
bool folder = rq_configured_folder(target);
if ((!folder && target->domain != RQ_TARGET_PROJECT) || target->external_identity ||
(!folder && target->configuration_file))
return false;
const char *text =
rq_source(sources, count, folder ? target->configuration_file : target->file);
if (!text || target->declaration_line <= 0 || !rq_text(target->declaration_text))
return false;
for (int line = 1; line < target->declaration_line; line++) {
text = strchr(text, '\n');
if (!text)
return false;
text++;
}
size_t length = strcspn(text, "\n");
if (length > 0 && text[length] == '\n' && text[length - 1] == '\r')
length--;
return strlen(target->declaration_text) == length &&
!memcmp(text, target->declaration_text, length);
}
static inline bool rq_target(const RQ_Target *target, const char *file, const char *qn,
const char *name, const char *label, int first, int last) {
if (target->domain == RQ_TARGET_EXTERNAL)
return rq_external_target(target) && rq_equal(target->file, file) &&
rq_equal(target->external_identity, qn) && rq_equal(target->label, label);
if (target->domain == RQ_TARGET_CONFIGURED_FOLDER)
return rq_configured_folder(target) && rq_equal(file, target->file) &&
rq_equal(label, "Folder") && rq_suffix(qn, name, target->suffix) && first == 0 &&
last == 0;
bool source_range = first <= target->declaration_line && last >= target->declaration_line;
/* File and file-level Module nodes identify the whole source, so their
* persisted range is 0/0. Exact file, qualified identity, label and the
* independently checked source anchor still bind the expected target. */
bool structural_file = (rq_equal(target->label, "File") || rq_equal(target->label, "Module")) &&
rq_equal(target->label, label) && first == 0 && last == 0;
return target->domain == RQ_TARGET_PROJECT && !target->external_identity &&
!target->configuration_file && rq_equal(target->file, file) &&
rq_suffix(qn, name, target->suffix) &&
(!target->label || rq_equal(target->label, label)) && (source_range || structural_file);
}
static inline bool rq_generation(sqlite3 *db, const char *project) {
sqlite3_stmt *stmt = NULL;
bool ok =
sqlite3_prepare_v2(db, "SELECT rules_version FROM resolver_generations WHERE project=?1",
-1, &stmt, NULL) == SQLITE_OK &&
sqlite3_bind_text(stmt, 1, project, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
sqlite3_step(stmt) == SQLITE_ROW &&
sqlite3_column_int(stmt, 0) == CBM_RESOLVER_RULES_VERSION &&
sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
return ok;
}
static inline bool rq_snapshot(sqlite3 *db, const char *project, const RQ_Source *source) {
sqlite3_stmt *stmt = NULL;
char digest[CBM_SHA256_HEX_LEN + 1];
cbm_sha256_hex(source->text, strlen(source->text), digest);
bool ok = sqlite3_prepare_v2(db,
"SELECT raw_content_identity,raw_length,"
"preprocessed_content_identity,preprocessed_length,"
"configuration_identity FROM "
"resolver_file_summaries WHERE project=?1 AND file_path=?2",
-1, &stmt, NULL) == SQLITE_OK &&
sqlite3_bind_text(stmt, 1, project, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
sqlite3_bind_text(stmt, 2, source->path, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
sqlite3_step(stmt) == SQLITE_ROW &&
rq_equal((const char *)sqlite3_column_text(stmt, 0), digest) &&
sqlite3_column_int64(stmt, 1) == (sqlite3_int64)strlen(source->text);
if (ok && source->preprocessed_text) {
cbm_sha256_hex(source->preprocessed_text, strlen(source->preprocessed_text), digest);
ok = rq_equal((const char *)sqlite3_column_text(stmt, 2), digest) &&
sqlite3_column_int64(stmt, 3) == (sqlite3_int64)strlen(source->preprocessed_text) &&
rq_equal((const char *)sqlite3_column_text(stmt, 4),
source->preprocessed_configuration_identity);
}
ok = ok && sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
return ok;
}
static inline bool rq_prepare(RQ_State *states, const RQ_Expected *expected, size_t count,
const RQ_Forbidden *forbidden, size_t negative_count,
const RQ_Source *sources, size_t source_count, RQ_Report *report,
FILE *log) {
for (size_t i = 0; i < count + negative_count; i++) {
RQ_State *state = states + i;
if (i < count) {
const RQ_Expected *item = expected + i;
*state = (RQ_State){.case_id = item->case_id,
.relation = item->relation,
.site = &item->site,
.target = &item->target,
.dispatch_group = item->dispatch_group};
bool mode = item->mode == RQ_REQUIRED || item->mode == RQ_KNOWN_GAP;
bool gap = item->mode != RQ_KNOWN_GAP ||
(rq_text(item->gap_id) && rq_text(item->why) && rq_text(item->tried));
bool required = item->mode == RQ_REQUIRED || item->expected_recovered;
if (!mode || !gap || (required && !rq_text(rq_target_identity(&item->target))) ||
(item->dispatch_group &&
(!rq_text(item->dispatch_group) ||
(!rq_equal(item->relation, "CALLS") && !rq_equal(item->relation, "USAGE")))))
report->invalid_oracle++;
} else {
const RQ_Forbidden *item = forbidden + i - count;
*state = (RQ_State){.case_id = item->case_id,
.relation = item->relation,
.site = &item->site,
.target = &item->target,
.negative = true,
.any_target = item->any_target};
}
bool valid = rq_text(state->case_id) && rq_text(state->relation) &&
rq_anchor(state, sources, source_count) &&
(state->any_target || rq_target_anchor(state->target, sources, source_count));
const RQ_Site *site = state->site;
if (site->caller_variant_start_line || site->caller_variant_end_line ||
site->caller_variant_declaration) {
const RQ_Target declaration = {.file = site->file,
.declaration_line = (int)site->caller_variant_start_line,
.declaration_text = site->caller_variant_declaration};
const char *text = rq_source(sources, source_count, site->file);
size_t length = text ? strlen(text) : 0;
unsigned lines = length && text[length - 1] != '\n' ? 1 : 0;
unsigned occurrence_end_line = 1;
for (size_t at = 0; at < length; at++) {
lines += text[at] == '\n';
if (state->end && at < state->end - 1)
occurrence_end_line += text[at] == '\n';
}
valid = valid && site->caller_variant_start_line > 0 &&
site->caller_variant_end_line >= site->caller_variant_start_line &&
site->caller_variant_end_line <= lines &&
(unsigned)state->line >= site->caller_variant_start_line &&
(unsigned)state->line <= site->caller_variant_end_line &&
occurrence_end_line <= site->caller_variant_end_line &&
rq_target_anchor(&declaration, sources, source_count);
}
for (size_t j = 0; j < i; j++) {
const RQ_State *prior = states + j;
bool same_site = !state->negative && !prior->negative &&
rq_equal(prior->site->file, state->site->file) &&
rq_equal(prior->relation, state->relation) &&
prior->start == state->start && prior->end == state->end;
bool distinct_target = prior->target->domain != state->target->domain ||
!rq_equal(prior->target->file, state->target->file);
if (!distinct_target && prior->target->domain == RQ_TARGET_EXTERNAL)
distinct_target =
!rq_equal(prior->target->external_identity, state->target->external_identity);
if (!distinct_target && prior->target->domain == RQ_TARGET_PROJECT &&
rq_text(prior->target->suffix) && rq_text(state->target->suffix))
distinct_target = !rq_suffix(prior->target->suffix, NULL, state->target->suffix) &&
!rq_suffix(state->target->suffix, NULL, prior->target->suffix);
bool multicast = rq_text(state->dispatch_group) &&
rq_equal(prior->dispatch_group, state->dispatch_group) &&
rq_equal(prior->site->caller, state->site->caller) && distinct_target;
if (rq_equal(prior->case_id, state->case_id) || (same_site && !multicast))
valid = false;
/* One transformed site must not recover different raw occurrences.
* Multicast may share it only when the raw occurrence also agrees. */
bool same_expanded = state->preprocessed_end && prior->preprocessed_end &&
rq_equal(prior->site->file, state->site->file) &&
rq_equal(prior->relation, state->relation) &&
prior->preprocessed_start == state->preprocessed_start &&
prior->preprocessed_end == state->preprocessed_end;
if (same_expanded && (!same_site || !multicast) && !state->negative && !prior->negative)
valid = false;
}
if (!valid) {
report->invalid_oracle++;
if (log)
fprintf(log, "[RQ] INVALID_ORACLE case=%s\n",
state->case_id ? state->case_id : "?");
}
}
for (size_t i = 0; i < count; i++) {
if (!states[i].dispatch_group)
continue;
bool companion = false;
for (size_t j = 0; j < count; j++)
companion |= i != j && rq_equal(states[i].dispatch_group, states[j].dispatch_group) &&
rq_equal(states[i].site->file, states[j].site->file) &&
rq_equal(states[i].site->caller, states[j].site->caller) &&
rq_equal(states[i].relation, states[j].relation) &&
states[i].start == states[j].start && states[i].end == states[j].end;
if (!companion)
report->invalid_oracle++;
}
return report->invalid_oracle == 0;
}
/* A merged conditional declaration may have a different canonical range.
* Accept only one exact variant matching independently authored source bounds;
* a broad, duplicated, absent or malformed variant cannot authorize a caller. */
static inline bool rq_caller_variant(sqlite3_stmt *row, int properties_column,
const RQ_State *state) {
const RQ_Site *site = state->site;
if (!site->caller_variant_start_line || !site->caller_variant_end_line ||
!rq_text(site->caller_variant_declaration))
return false;
const char *properties = (const char *)sqlite3_column_text(row, properties_column);
if (!properties)
return false;
sqlite3_stmt *query = NULL;
const char *sql = "SELECT COUNT(*) FROM json_each(?1,'$.variants') v "
"WHERE json_type(?1,'$.variants')='array' AND v.type='object' "
"AND (SELECT COUNT(*) FROM json_each(?1) r WHERE r.key='variants')=1 "
"AND json_type(v.value,'$.file_path')='text' "
"AND (SELECT COUNT(*) FROM json_each(v.value) k "
"WHERE k.key IN ('file_path','start_line','end_line'))=3 "
"AND json_type(v.value,'$.start_line')='integer' "
"AND json_type(v.value,'$.end_line')='integer' "
"AND json_extract(v.value,'$.file_path')=?2 "
"AND json_extract(v.value,'$.start_line')=?3 "
"AND json_extract(v.value,'$.end_line')=?4";
bool ok = sqlite3_prepare_v2(sqlite3_db_handle(row), sql, -1, &query, NULL) == SQLITE_OK &&
sqlite3_bind_text(query, 1, properties, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
sqlite3_bind_text(query, 2, site->file, -1, SQLITE_TRANSIENT) == SQLITE_OK &&
sqlite3_bind_int64(query, 3, site->caller_variant_start_line) == SQLITE_OK &&
sqlite3_bind_int64(query, 4, site->caller_variant_end_line) == SQLITE_OK &&
sqlite3_step(query) == SQLITE_ROW && sqlite3_column_int(query, 0) == 1 &&
sqlite3_step(query) == SQLITE_DONE;
sqlite3_finalize(query);
return ok;
}
/* Column layout shared by the row and graph checks: file/scope/relation,
* span/origin, target identity and source declaration, then graph counterpart. */
static inline bool rq_occurrence(sqlite3_stmt *stmt, RQ_State *states, size_t count,
bool target_in_domain, RQ_Report *report, FILE *log) {
const char *file = (const char *)sqlite3_column_text(stmt, 0);
const char *scope = (const char *)sqlite3_column_text(stmt, 1);
const char *relation = (const char *)sqlite3_column_text(stmt, 2);
sqlite3_int64 start = sqlite3_column_int64(stmt, 3), end = sqlite3_column_int64(stmt, 4);
int origin = sqlite3_column_int(stmt, 5);
const char *target = (const char *)sqlite3_column_text(stmt, 6);
const char *target_file = (const char *)sqlite3_column_text(stmt, 7);
const char *source_file = (const char *)sqlite3_column_text(stmt, 12);
const char *source_name = (const char *)sqlite3_column_text(stmt, 15);
bool covered = false, matched = false, forbidden = false, unprojected = false;
bool mapped_origin = false;
size_t positive_matches = 0;
for (size_t i = 0; i < count; i++) {
RQ_State *state = states + i;
bool caller = rq_suffix(scope, source_name, state->site->caller);
bool same_file = rq_equal(file, state->site->file);
bool raw_site = same_file && start == state->start && end == state->end;
bool expanded_site = same_file && state->preprocessed_end &&
start == state->preprocessed_start && end == state->preprocessed_end;
bool expanded_identity = expanded_site && origin == CBM_SOURCE_ORIGIN_PREPROCESSED &&
rq_equal((const char *)sqlite3_column_text(stmt, 19),
state->preprocessed_content_identity) &&
rq_equal((const char *)sqlite3_column_text(stmt, 20),
state->preprocessed_configuration_identity);
bool same_site = origin == CBM_SOURCE_ORIGIN_RAW ? raw_site : expanded_identity;
mapped_origin |= expanded_identity;
covered |= raw_site || expanded_site ||
(target_in_domain && (same_file || rq_equal(source_file, state->site->file)) &&
caller && rq_equal(relation, state->relation));
bool source_range = sqlite3_column_int(stmt, 13) <= state->line &&
sqlite3_column_int(stmt, 14) >= state->line;
bool structural_file = rq_equal((const char *)sqlite3_column_text(stmt, 18), "File") &&
sqlite3_column_int(stmt, 13) == 0 &&
sqlite3_column_int(stmt, 14) == 0;
bool variant = rq_caller_variant(stmt, 21, state);
bool source_identity = variant || (rq_equal(source_file, state->site->file) &&
(source_range || structural_file));
/* An explicit variant expectation must be independently verified even
* if the canonical declaration happens to overlap the occurrence. */
if (state->site->caller_variant_start_line)
source_identity = variant;
if (!same_site || !caller || !source_identity || !rq_equal(relation, state->relation))
continue;
bool target_ok = state->any_target ||
rq_target(state->target, target_file, target,
(const char *)sqlite3_column_text(stmt, 10),
(const char *)sqlite3_column_text(stmt, 11),
sqlite3_column_int(stmt, 8), sqlite3_column_int(stmt, 9));
if (!state->any_target && state->target->domain == RQ_TARGET_EXTERNAL &&
(sqlite3_column_type(stmt, 17) == SQLITE_NULL ||
sqlite3_column_int(stmt, 17) != CBM_RESOLVE_DOMAIN_EXTERNAL))
target_ok = false;
if (state->negative && target_ok)
forbidden = true;
if (!state->negative && target_ok) {
positive_matches++;
if (sqlite3_column_int(stmt, 16)) {
state->found = true;
matched = true;
} else {
unprojected = true;
}
}
}
/* One emitted target cannot recover two supposedly distinct targets,
* even if suffix aliases or overlapping declaration anchors escaped the
* static expectation checks. */
if (positive_matches > 1) {
report->invalid_oracle++;
return false;
}
if (covered && origin != CBM_SOURCE_ORIGIN_RAW && !mapped_origin) {
report->metadata_errors++;
if (log)
fprintf(log, "[RQ] UNMAPPED_SOURCE_ORIGIN file=%s origin=%d\n", file, origin);
return false;
}
if (covered && (!matched || forbidden)) {
if (unprojected && !forbidden)
report->projection_errors++;
else
report->wrong++;
if (log)
fprintf(log,
"[RQ] WRONG_OCCURRENCE file=%s scope=%s span=%lld-%lld role=%s target=%s\n",
file, scope, (long long)start, (long long)end, relation, target);
return false;
}
return true;
}
static inline bool rq_projection(sqlite3_stmt *stmt, const RQ_State *states, size_t count,
bool target_in_domain, RQ_Report *report, FILE *log) {
const char *file = (const char *)sqlite3_column_text(stmt, 0);
const char *scope = (const char *)sqlite3_column_text(stmt, 1);
const char *relation = (const char *)sqlite3_column_text(stmt, 2);
const char *target = (const char *)sqlite3_column_text(stmt, 3);
const char *target_file = (const char *)sqlite3_column_text(stmt, 4);
const char *source_name = (const char *)sqlite3_column_text(stmt, 9);
/* This oracle covers the seven resolver relations. Inheritance and
* override facts between the same nodes have a separate contract. */
static const char *const relations[] = {"IMPORTS", "CALLS", "CALL_REFERENCE", "USAGE",
"READS", "WRITES", "USES_TYPE"};
bool resolver_relation = false;
for (size_t i = 0; i < sizeof(relations) / sizeof(relations[0]); i++)
resolver_relation |= rq_equal(relation, relations[i]);
if (!resolver_relation)
return true;
bool covered = false, matched = false;
for (size_t i = 0; i < count; i++) {
const RQ_State *state = states + i;
bool variant = rq_caller_variant(stmt, 11, state);
if ((!rq_equal(file, state->site->file) && !variant) ||
!rq_suffix(scope, source_name, state->site->caller))
continue;
bool target_ok = state->any_target ||
rq_target(state->target, target_file, target,
(const char *)sqlite3_column_text(stmt, 7),
(const char *)sqlite3_column_text(stmt, 8),
sqlite3_column_int(stmt, 5), sqlite3_column_int(stmt, 6));
/* A known target under a different role is also inside the audit. */
covered |= (target_in_domain && rq_equal(relation, state->relation)) ||
(!state->any_target && target_ok);
if (!rq_equal(relation, state->relation) || !target_ok)
continue;
if (!state->negative && state->found && sqlite3_column_int(stmt, 10))
matched = true;
}
/* Graph edges aggregate sites. A forbidden site is checked in rows; an
* independent valid site may legitimately project the same caller/target. */
if (covered && !matched) {
report->projection_errors++;
if (log)
fprintf(log, "[RQ] UNATTESTED_GRAPH_EDGE file=%s scope=%s role=%s target=%s\n", file,
scope, relation, target);
return false;
}
return true;
}
static inline bool rq_query(sqlite3 *db, const char *project, const char *sql, bool projection,
RQ_State *states, size_t count, const RQ_Source *project_sources,
size_t project_source_count, RQ_Report *report, FILE *log) {
sqlite3_stmt *stmt = NULL;
bool ok = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK &&
sqlite3_bind_text(stmt, 1, project, -1, SQLITE_TRANSIENT) == SQLITE_OK;
int step = SQLITE_ERROR;
if (ok)
while ((step = sqlite3_step(stmt)) == SQLITE_ROW) {
const char *target_file = (const char *)sqlite3_column_text(stmt, projection ? 4 : 7);
bool target_in_domain =
!project_sources ||
rq_source_input(project_sources, project_source_count, target_file);
bool valid = projection
? rq_projection(stmt, states, count, target_in_domain, report, log)
: rq_occurrence(stmt, states, count, target_in_domain, report, log);
ok = valid && ok;
}
if (step != SQLITE_DONE) {
report->metadata_errors++;
ok = false;
}
sqlite3_finalize(stmt);
return ok;
}
static inline bool rq_finish(const char *suite, const RQ_Expected *expected, size_t count,
const RQ_State *states, RQ_Report *report, FILE *log) {
for (size_t i = 0; i < count; i++) {
const RQ_Expected *item = expected + i;
bool required = item->mode == RQ_REQUIRED || item->expected_recovered;
report->found += states[i].found;
if (!states[i].found) {
report->missing++;
report->required_missing += required;
if (log)
fprintf(log,
"[RQ] QUALITY_MISS case=%s gap=%s file=%s role=%s target=%s:%s "
"WHY=%s TRIED=%s\n",
item->case_id, item->gap_id ? item->gap_id : "required", item->site.file,
item->relation, item->target.file,
rq_target_identity(&item->target) ? rq_target_identity(&item->target)
: "<identity-gap>",
item->why ? item->why : "required supported relationship",
item->tried ? item->tried : "independent source golden");
} else if (!required) {
report->recovered_gaps++;
if (log)
fprintf(log, "[RQ] PROMOTE_TO_REQUIRED case=%s gap=%s\n", item->case_id,
item->gap_id);
}
}
if (log)
fprintf(log,
"[RQ] %s genuine=%zu found=%zu missing=%zu wrong=%zu projection_errors=%zu "
"required_missing=%zu recovered_gaps=%zu metadata_errors=%zu invalid_oracle=%zu\n",
suite, report->genuine, report->found, report->missing, report->wrong,
report->projection_errors, report->required_missing, report->recovered_gaps,
report->metadata_errors, report->invalid_oracle);
return !report->wrong && !report->projection_errors && !report->required_missing &&
!report->recovered_gaps && !report->metadata_errors && !report->invalid_oracle;
}
/* Run before closing the fixture store. NULL log suppresses diagnostics only;
* it never changes accounting or gates. A non-NULL report is always populated. */
static inline bool rq_check_domain(cbm_store_t *store, const char *project, const char *suite,
const RQ_Source *sources, size_t source_count,
const RQ_Expected *expected, size_t count,
const RQ_Forbidden *forbidden, size_t negative_count,
RQ_Report *report, FILE *log, bool project_declarations_only) {
if (!report)
return false;
*report = (RQ_Report){.genuine = count};
if (!store || !rq_text(project) || !rq_text(suite) || !sources || !source_count ||
(!expected && count) || (!forbidden && negative_count) || count > RQ_MAX_EXPECTATIONS ||
negative_count > RQ_MAX_EXPECTATIONS - count || count + negative_count == 0) {
report->invalid_oracle++;
return false;
}
RQ_State *states = calloc(count + negative_count, sizeof(*states));
if (!states) {
report->metadata_errors++;
return false;
}
bool valid = rq_prepare(states, expected, count, forbidden, negative_count, sources,
source_count, report, log);
sqlite3 *db = cbm_store_get_db(store);
if (!db || !rq_generation(db, project))
report->metadata_errors++;
for (size_t i = 0; i < source_count; i++) {
bool expanded = sources[i].preprocessed_text != NULL;
if (!rq_text(sources[i].path) || !sources[i].text ||
expanded != (sources[i].preprocessed_configuration_identity != NULL) ||
(expanded && (!rq_text(sources[i].preprocessed_text) ||
!rq_text(sources[i].preprocessed_configuration_identity)))) {
report->invalid_oracle++;
continue;
}
if (!db || !rq_snapshot(db, project, sources + i)) {
report->metadata_errors++;
if (log)
fprintf(log, "[RQ] SOURCE_SNAPSHOT_MISMATCH file=%s\n", sources[i].path);
}
}
const char *rows =
"SELECT r.file_path,r.scope,r.relation,r.start_byte,r.end_byte,r.source_origin,r.target,"
"t.file_path,t.start_line,t.end_line,t.name,t.label,s.file_path,s.start_line,s.end_line,"
"s.name,EXISTS(SELECT 1 FROM edges e WHERE e.project=r.project AND e.source_id=s.id "
"AND e.target_id=t.id AND e.type=r.relation),r.domain,s.label,"
"v.content_identity,v.configuration_identity,s.properties FROM resolver_occurrences r "
"LEFT JOIN resolver_occurrence_evidence v ON v.project=r.project AND v.ordinal=r.ordinal "
"LEFT JOIN nodes s ON s.project=r.project AND s.qualified_name=r.scope "
"LEFT JOIN nodes t ON t.project=r.project AND t.qualified_name=r.target "
"WHERE r.project=?1 AND r.emitted=1";
const char *edges =
"SELECT s.file_path,s.qualified_name,e.type,t.qualified_name,t.file_path,t.start_line,"
"t.end_line,t.name,t.label,s.name,EXISTS(SELECT 1 FROM resolver_occurrences r "
"WHERE r.project=e.project AND r.emitted=1 AND r.scope=s.qualified_name "
"AND r.target=t.qualified_name AND r.relation=e.type),s.properties FROM edges e "
"JOIN nodes s ON s.id=e.source_id JOIN nodes t ON t.id=e.target_id WHERE e.project=?1";
if (valid && !report->metadata_errors) {
const RQ_Source *domain = project_declarations_only ? sources : NULL;
(void)rq_query(db, project, rows, false, states, count + negative_count, domain,
source_count, report, log);
(void)rq_query(db, project, edges, true, states, count + negative_count, domain,
source_count, report, log);
}
bool ok = valid && rq_finish(suite, expected, count, states, report, log);
free(states);
return ok;
}
/* Default: every target in each covered caller/relation remains audited. */
static inline bool rq_check(cbm_store_t *store, const char *project, const char *suite,
const RQ_Source *sources, size_t source_count,
const RQ_Expected *expected, size_t count,
const RQ_Forbidden *forbidden, size_t negative_count, RQ_Report *report,
FILE *log) {
return rq_check_domain(store, project, suite, sources, source_count, expected, count, forbidden,
negative_count, report, log, false);
}
/* Opt-in project declaration domain: source paths are authored inputs, never
* inferred from emitted targets. Exact golden spans still audit EVERY target,
* including external namesakes. Elsewhere in a covered caller/relation, all
* project-file targets remain covered while unrelated external builtin sites
* are outside this oracle's denominator and target domain. */
static inline bool rq_check_project_declarations(cbm_store_t *store, const char *project,
const char *suite, const RQ_Source *sources,
size_t source_count, const RQ_Expected *expected,
size_t count, const RQ_Forbidden *forbidden,
size_t negative_count, RQ_Report *report,
FILE *log) {
return rq_check_domain(store, project, suite, sources, source_count, expected, count, forbidden,
negative_count, report, log, true);
}
#endif