Verifier layer: grounding + consistency over the §5 geometry / reasoning ops
The disposes half of the propose->verify loop. Catches the plausible lie a grammar check never sees: fluent, confident, wrong. GROUNDING (anti-hallucination): fit a claim point against every real evidence neighborhood via engram_reason_point_fit; grounded iff best fit clears an absolute threshold. Off-model orthogonal residual is the hallucination signal. Distinct from abduction: asks 'is there any real support at all' and may say no. CONSISTENCY (contradiction): (a) polarity/negation inversion -- claim lands on the opposite side of a real polarity axis from the grounded truth (the reassurance->accusation catch: 'never fought'->'argued'); (b) geometric -- claim inside a forbidden region or beyond a max-distance constraint. Pure C11, read-only, composes existing primitives only. 29 constructed-case checks, 0 failures across PERF and ASan/UBSan; macOS leaks 0. el-exposure deferred (point/variadic-set inputs -- matches reasoning-agent precedent). Reach checks (formal/causal/predictive) not started; documented.
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/* engram_verify.c — the VERIFIER layer. Pure compositions over engram_reason.h +
|
||||
* engram_geometry.h. stdlib + libm only; READ-ONLY over its inputs; touches no
|
||||
* store/index/activation. See engram_verify.h for the design and the frame contract. */
|
||||
#include "engram_verify.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
/* ── small float-vector helpers (mirror engram_reason.c) ────────────────────── */
|
||||
static double vdot(const float* a, const float* b, int dim) {
|
||||
double s = 0; for (int i = 0; i < dim; i++) s += (double)a[i] * (double)b[i]; return s;
|
||||
}
|
||||
static double l2(const float* a, const float* b, int dim) {
|
||||
double s = 0; for (int i = 0; i < dim; i++) { double d = (double)a[i] - (double)b[i]; s += d * d; }
|
||||
return sqrt(s);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════ GROUNDING ══════════ */
|
||||
int engram_verify_grounding(const float* claim, int dim,
|
||||
const GeoDescriptor* const* evidence, int n_evidence,
|
||||
double ext_floor, double ground_threshold,
|
||||
GeoGrounding* out) {
|
||||
if (!claim || dim <= 0 || !evidence || n_evidence < 1 || !out) return -1;
|
||||
if (!(ext_floor > 0)) ext_floor = 1.0;
|
||||
if (!(ground_threshold > 0 && ground_threshold < 1)) ground_threshold = 0.5;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->n_evidence = n_evidence;
|
||||
out->best = -1;
|
||||
out->nearest_centroid_l2 = INFINITY;
|
||||
out->scores = malloc((size_t)n_evidence * sizeof(double));
|
||||
if (!out->scores) return -1;
|
||||
|
||||
double best = -1;
|
||||
for (int i = 0; i < n_evidence; i++) {
|
||||
const GeoDescriptor* e = evidence[i];
|
||||
GeoFit f;
|
||||
if (!e || e->dim != dim || !e->centroid ||
|
||||
engram_reason_point_fit(e, claim, ext_floor, &f) != 0) {
|
||||
out->scores[i] = 0.0;
|
||||
continue;
|
||||
}
|
||||
out->scores[i] = f.score;
|
||||
double cl2 = l2(claim, e->centroid, dim);
|
||||
if (cl2 < out->nearest_centroid_l2) out->nearest_centroid_l2 = cl2;
|
||||
if (out->best < 0 || f.score > best) {
|
||||
best = f.score;
|
||||
out->best = i;
|
||||
out->grounding = f.score;
|
||||
out->best_distance = f.distance;
|
||||
out->best_ortho = f.ortho_residual;
|
||||
}
|
||||
}
|
||||
if (out->best < 0) { out->grounding = 0.0; out->best_distance = INFINITY; }
|
||||
out->grounded = (out->grounding >= ground_threshold) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
void engram_verify_grounding_free(GeoGrounding* out) {
|
||||
if (!out) return;
|
||||
free(out->scores); out->scores = NULL;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════ CONSISTENCY ════════ */
|
||||
int engram_verify_consistency(const float* claim, int dim,
|
||||
const GeoDescriptor* context,
|
||||
const GeoDescriptor* pole_pos, const GeoDescriptor* pole_neg,
|
||||
const GeoDescriptor* forbidden,
|
||||
double ext_floor, double deadzone_frac,
|
||||
double forbidden_thresh, double max_distance,
|
||||
GeoConsistency* out) {
|
||||
if (!claim || dim <= 0 || !out) return -1;
|
||||
if (!(ext_floor > 0)) ext_floor = 1.0;
|
||||
if (!(deadzone_frac >= 0 && deadzone_frac < 1)) deadzone_frac = 0.10;
|
||||
if (!(forbidden_thresh > 0 && forbidden_thresh < 1)) forbidden_thresh = 0.5;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->verdict = GEO_CONSIST_OK;
|
||||
out->consistency = 1.0;
|
||||
|
||||
int do_polarity = (pole_pos && pole_neg);
|
||||
int do_distance = (max_distance > 0);
|
||||
if ((do_polarity || do_distance) &&
|
||||
(!context || context->dim != dim || !context->centroid)) return -1;
|
||||
if (do_polarity && (pole_pos->dim != dim || pole_neg->dim != dim ||
|
||||
!pole_pos->centroid || !pole_neg->centroid)) return -1;
|
||||
if (forbidden && (forbidden->dim != dim || !forbidden->centroid)) return -1;
|
||||
|
||||
double pol_score = 1.0, geo_score = 1.0;
|
||||
|
||||
/* ── (a) POLARITY / negation inversion ─────────────────────────────────── */
|
||||
if (do_polarity) {
|
||||
/* axis p = (c_pos − c_neg); midpoint o = ½(c_pos + c_neg). */
|
||||
float* p = malloc((size_t)dim * sizeof(float));
|
||||
float* o = malloc((size_t)dim * sizeof(float));
|
||||
if (!p || !o) { free(p); free(o); return -1; }
|
||||
double pn2 = 0;
|
||||
for (int i = 0; i < dim; i++) {
|
||||
double dpos = (double)pole_pos->centroid[i], dneg = (double)pole_neg->centroid[i];
|
||||
p[i] = (float)(dpos - dneg);
|
||||
o[i] = (float)(0.5 * (dpos + dneg));
|
||||
pn2 += (dpos - dneg) * (dpos - dneg);
|
||||
}
|
||||
double pn = sqrt(pn2);
|
||||
out->polarity_separation = 0.5 * pn;
|
||||
if (pn > 1e-12) {
|
||||
/* signed positions along the axis (projection of (x − o) onto unit p). */
|
||||
float* cdo = malloc((size_t)dim * sizeof(float)); /* claim − o */
|
||||
float* rdo = malloc((size_t)dim * sizeof(float)); /* context − o */
|
||||
if (!cdo || !rdo) { free(p); free(o); free(cdo); free(rdo); return -1; }
|
||||
for (int i = 0; i < dim; i++) {
|
||||
cdo[i] = (float)((double)claim[i] - (double)o[i]);
|
||||
rdo[i] = (float)((double)context->centroid[i] - (double)o[i]);
|
||||
}
|
||||
double claim_side = vdot(cdo, p, dim) / pn; /* units: emb-space length */
|
||||
double ref_side = vdot(rdo, p, dim) / pn;
|
||||
out->polarity_claim = claim_side;
|
||||
out->polarity_reference = ref_side;
|
||||
double dz = deadzone_frac * out->polarity_separation; /* neutral band */
|
||||
if (fabs(claim_side) > dz && fabs(ref_side) > dz &&
|
||||
(claim_side > 0) != (ref_side > 0)) {
|
||||
out->inverted = 1;
|
||||
pol_score = 0.0; /* opposite poles ⇒ zero consistency */
|
||||
} else if (fabs(claim_side) <= dz || fabs(ref_side) <= dz) {
|
||||
pol_score = 0.5; /* neutral / undecided */
|
||||
} else {
|
||||
pol_score = 1.0; /* same pole ⇒ consistent */
|
||||
}
|
||||
free(cdo); free(rdo);
|
||||
}
|
||||
free(p); free(o);
|
||||
}
|
||||
|
||||
/* ── (b) GEOMETRIC contradiction ───────────────────────────────────────── */
|
||||
if (forbidden) {
|
||||
GeoFit f;
|
||||
if (engram_reason_point_fit(forbidden, claim, ext_floor, &f) == 0) {
|
||||
out->forbidden_fit = f.score;
|
||||
if (f.score >= forbidden_thresh) {
|
||||
out->geo_violation = 1;
|
||||
double g = 1.0 - f.score; if (g < 0) g = 0;
|
||||
if (g < geo_score) geo_score = g;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (do_distance) {
|
||||
out->context_distance = l2(claim, context->centroid, dim);
|
||||
if (out->context_distance > max_distance) {
|
||||
out->geo_violation = 1;
|
||||
geo_score = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── verdict + scalar (polarity is the headline; both flags stay visible) ─ */
|
||||
out->consistency = (pol_score < geo_score) ? pol_score : geo_score;
|
||||
if (out->inverted) out->verdict = GEO_CONSIST_POLARITY;
|
||||
else if (out->geo_violation) out->verdict = GEO_CONSIST_GEOMETRIC;
|
||||
else out->verdict = GEO_CONSIST_OK;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/* engram_verify.h — the VERIFIER layer: GROUNDING + CONSISTENCY over the live
|
||||
* geometry (engram_geometry.h) and reasoning (engram_reason.h) operators.
|
||||
*
|
||||
* The geometry PROPOSES (cheap, creative, sometimes wrong); the verifier DISPOSES.
|
||||
* This layer catches the class of failure a grammar check never sees: a fluent,
|
||||
* confident, WRONG output — the "plausible lie". The motivating case: a translation
|
||||
* that DELETED a negation so "you never fought" became "you argued" — reassurance
|
||||
* inverted into accusation, grammatical and invisible, catchable ONLY by the geometry.
|
||||
*
|
||||
* GROUNDING claim → is there ANY real structure that supports it, or is it
|
||||
* floating free of the manifold? (anti-hallucination gate)
|
||||
* CONSISTENCY claim → does it CONTRADICT the established structure? Two catches:
|
||||
* (a) POLARITY: the claim lands on the OPPOSITE side of a negation
|
||||
* axis from the grounded truth (the reassurance→accusation catch),
|
||||
* (b) GEOMETRIC: the claim sits inside a region it must be far from,
|
||||
* or violates a max-distance constraint to its context.
|
||||
*
|
||||
* PURE + READ-ONLY (stdlib + libm only): every function consumes a claim POINT
|
||||
* (float* in R^dim) plus GeoDescriptor(s), and NEVER touches the store, index, or
|
||||
* activation. All geometry is delegated to engram_reason_point_fit / engram_geo_*;
|
||||
* this file only composes and applies thresholds.
|
||||
*
|
||||
* FRAME CONTRACT (inherited): the claim point and every descriptor passed together
|
||||
* MUST share emb `dim` and the same `global_mean` frame — exactly the §5 operator
|
||||
* contract. A function returns <0 on a dim/frame mismatch or bad argument.
|
||||
*/
|
||||
#ifndef ENGRAM_VERIFY_H
|
||||
#define ENGRAM_VERIFY_H
|
||||
|
||||
#include "engram_geometry.h"
|
||||
#include "engram_reason.h"
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* GROUNDING — anti-hallucination. Score how well a claimed POINT is supported by
|
||||
* the ACTUAL structure: fit the claim against every real evidence neighborhood
|
||||
* (engram_reason_point_fit → in-distribution Mahalanobis + off-model orthogonal
|
||||
* residual) and take the BEST supporter. A claim that sits inside real structure
|
||||
* scores high (grounded); a claim floating far from every neighborhood scores low
|
||||
* on all of them → flagged UNGROUNDED (a hallucination).
|
||||
*
|
||||
* This is an ABSOLUTE-THRESHOLD gate, deliberately distinct from ABDUCTION (which
|
||||
* always RANKS and picks a winner among competing hypotheses): grounding asks the
|
||||
* prior question — "is there any real support at all?" — and is allowed to answer no.
|
||||
* The off-model `ortho_residual` is the sharpest hallucination signal: energy in a
|
||||
* direction the manifold does not even span.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
typedef struct {
|
||||
double grounding; /* ∈[0,1]: overall support = best fit score */
|
||||
int grounded; /* 1 iff grounding >= ground_threshold */
|
||||
int best; /* index of best-supporting evidence structure, or −1 */
|
||||
double best_distance; /* full point-to-manifold distance to the best */
|
||||
double best_ortho; /* off-model orthogonal residual of the best fit */
|
||||
double nearest_centroid_l2;/* raw L2 to the nearest evidence centroid (coarse) */
|
||||
int n_evidence;
|
||||
double* scores; /* per-evidence fit score, higher = better (owned)*/
|
||||
} GeoGrounding;
|
||||
/* ext_floor>0 guards zero-extent axes (default 1.0). ground_threshold∈(0,1): the
|
||||
* minimum best-fit score to call the claim grounded (default 0.5). */
|
||||
int engram_verify_grounding(const float* claim, int dim,
|
||||
const GeoDescriptor* const* evidence, int n_evidence,
|
||||
double ext_floor, double ground_threshold,
|
||||
GeoGrounding* out);
|
||||
void engram_verify_grounding_free(GeoGrounding* out);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* CONSISTENCY — contradiction detection. Does the claim contradict the established
|
||||
* structure? Two independent sub-checks (either can fire; both flags are reported):
|
||||
*
|
||||
* (a) POLARITY / negation inversion. A polarity axis p is defined by two REAL
|
||||
* poles — pole_pos (asserts X) and pole_neg (asserts ¬X):
|
||||
* p = (c_pos − c_neg)/‖·‖ , midpoint o = ½(c_pos + c_neg).
|
||||
* The claim's side = p·(claim − o); the reference's side = p·(c_context − o).
|
||||
* If the two sides have OPPOSITE sign AND both clear the neutral deadzone, the
|
||||
* claim asserts the polarity opposite to the grounded truth → INVERSION flagged.
|
||||
* This is the "you never fought"→"you argued" catch: the truth ("never fought")
|
||||
* sits on the negate pole, the claim ("argued") on the affirm pole → opposite
|
||||
* sides → flagged, though every word is grammatical.
|
||||
*
|
||||
* (b) GEOMETRIC contradiction. The claim sits INSIDE a `forbidden` region it must
|
||||
* be far from (point_fit score to forbidden ≥ forbidden_thresh), OR it violates
|
||||
* a max-distance constraint to its context centroid (L2 > max_distance).
|
||||
*
|
||||
* pole_pos/pole_neg may both be NULL to skip the polarity check; forbidden may be
|
||||
* NULL and max_distance≤0 to skip the geometric check. `context` (the grounded truth
|
||||
* region) is required whenever polarity or the distance constraint is used.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
typedef enum {
|
||||
GEO_CONSIST_OK = 0, /* consistent with context */
|
||||
GEO_CONSIST_POLARITY = 1, /* polarity/negation inversion (asserts ¬X where X) */
|
||||
GEO_CONSIST_GEOMETRIC = 2 /* geometric contradiction (in forbidden / too far) */
|
||||
} GeoConsistencyVerdict;
|
||||
typedef struct {
|
||||
GeoConsistencyVerdict verdict; /* headline (polarity takes precedence) */
|
||||
double consistency; /* ∈[0,1]: min over the checks (1 = fully consistent)*/
|
||||
/* polarity sub-check */
|
||||
int inverted; /* 1 iff a polarity inversion was detected */
|
||||
double polarity_claim; /* p·(claim − o) (signed position on the axis)*/
|
||||
double polarity_reference; /* p·(c_context − o) (the grounded truth's side) */
|
||||
double polarity_separation; /* ½‖c_pos − c_neg‖ (the axis half-length / scale)*/
|
||||
/* geometric sub-check */
|
||||
int geo_violation; /* 1 iff a geometric contradiction was detected */
|
||||
double forbidden_fit; /* claim's point_fit score to the forbidden region*/
|
||||
double context_distance; /* L2(claim, c_context) */
|
||||
} GeoConsistency;
|
||||
/* ext_floor>0 (default 1.0). deadzone_frac∈[0,1): a polarity side within
|
||||
* deadzone_frac·separation of the midpoint is "neutral" and never triggers inversion
|
||||
* (default 0.10). forbidden_thresh∈(0,1): fit-to-forbidden at/above which the claim
|
||||
* counts as inside the forbidden region (default 0.5). max_distance>0 enables the
|
||||
* distance constraint; ≤0 disables it. */
|
||||
int engram_verify_consistency(const float* claim, int dim,
|
||||
const GeoDescriptor* context,
|
||||
const GeoDescriptor* pole_pos, const GeoDescriptor* pole_neg,
|
||||
const GeoDescriptor* forbidden,
|
||||
double ext_floor, double deadzone_frac,
|
||||
double forbidden_thresh, double max_distance,
|
||||
GeoConsistency* out);
|
||||
|
||||
#endif /* ENGRAM_VERIFY_H */
|
||||
Reference in New Issue
Block a user