Grounding is the edge's weight, and the weight is a vector
El SDK CI - dev / build-and-test (pull_request) Failing after 3m59s

A relation that keeps holding up strengthens; one that stops corresponding
decays. That is not analogous to grounding, it IS grounding — so it belongs on
the edge, not in a subsystem beside it. The graph was already the grounding
structure; this stops modelling it as something else.

Deleted, not refactored:
  - cog_ground_edge and the `grounded-by` relation type. A grounded-by edge
    models grounding as a relation BETWEEN nodes when it is a property OF a
    relation. #147 fixed which endpoints that edge landed on and left the wrong
    idea intact. Measured on the live store: the old path scored two nodes with
    ZERO edges between them at 0.925237 and wrote an edge for it.
  - ground() writing. It was a read that wrote — the eg_vindex_sync defect.
    Three identical calls produced three writes to the same edge id.
  - keystone_write_blocked. Its measured cost was 0.00% brier reduction over
    n_trials 0 on the keystone: the loop never ran, so the self was never
    calibrated and never falsifiable. Nothing replaces it — non-circularity of
    the reference frame is temporal, not a permission.
  - a graph predicate for "evidence downstream of itself", built and then
    withdrawn. Reachability from the self region covers 89.2% of the live graph
    (10,580 of 11,861 nodes), so any topological predicate marks nearly all
    evidence tainted and degenerates into the total block censorship began as.

The vector, carried in a GRD1 block on the edge's own metadata:
factual, relational, associative (the existing hebb), polarity (SIGNED — near
zero is "no support", negative is "actively contradicts"; `inhibitory` is that
distinction crushed to one bit), provenance class, and a timestamp. Confidence,
recency, staleness and volatility are DERIVED at read and never serialized.

Decay is one model, not two: cog_decay_factor is the single implementation and
engram_temporal_decay now delegates to it — proven bit-identical over 24
(age, reinforcement) points.

Values reference: thirteen regions, aggregate MIN, binding value named. Measured
— the 13 have pairwise centroid cosine min 0.1525 / mean 0.5199 / max 0.9278, so
they demonstrably are not one region, and a mean would let agreement with twelve
mask a violation of the thirteenth.

Supersession versions the whole vector jointly, gated by consequence and
salience with no epsilon anywhere: floor crossings and sign changes only.
Polarity flips and provenance-class changes are inherently significant and
bypass the salience gate.

Also fixed: the frame contract. Descriptors are built over L2-normalized member
embeddings; think() and the grounding path were fitting RAW vectors against them.
Measured on the self region, same data, same 106 members:
  magnitude 0.00283443 -> 0.536134, spread 18.7565 -> 0.930163.
Every fit score sat three decimal places below the 0.5 floors that gate on them.

assert() gates on both floors and computes still_held instead of returning a
hardcoded `true` — the old build reported still_held for a node that does not
exist.
This commit is contained in:
Neuron
2026-08-16 13:18:50 -05:00
parent d41645388a
commit 7a1501d097
7 changed files with 1444 additions and 135 deletions
+548 -87
View File
@@ -9740,18 +9740,17 @@ el_val_t engram_consolidate_permanence(el_val_t node_id){
* Explicit per-node temporal_decay_rate still overrides lambda (77 nodes carry
* one) that path is untouched and remains the escape hatch for content that
* genuinely should expire fast. */
#define ENGRAM_DECAY_FLOOR 0.25
/* 2026-08-16: the body moved to cog_decay_factor (engram_cognition.c) so that
* NODE decay and EDGE-GROUNDING decay are one implementation with one set of
* constants, rather than a decay model and a parallel copy of it. The mapping is
* exact reinforcements := activation_count, lambda_override :=
* temporal_decay_rate so this path is bit-identical to what it replaced.
* COG_T_HALF_HOURS / COG_DECAY_LAMBDA / COG_DECAY_FLOOR carry the same values
* ENGRAM_T_HALF_HOURS / ENGRAM_DECAY_LAMBDA / 0.25 carried here. */
static double engram_temporal_decay(const EngramNode* n, int64_t now_ms) {
int64_t age_ms = now_ms - n->last_activated;
if (age_ms <= 0) return 1.0;
double lambda = (n->temporal_decay_rate > 0.0) ? n->temporal_decay_rate
: ENGRAM_DECAY_LAMBDA;
double age_hours = (double)age_ms / 3600000.0;
double t_half = ENGRAM_T_HALF_HOURS *
(1.0 + log(1.0 + (double)n->activation_count));
double factor = exp(-lambda * age_hours / t_half);
if (factor < ENGRAM_DECAY_FLOOR) factor = ENGRAM_DECAY_FLOOR;
return factor;
return cog_decay_factor(now_ms - n->last_activated,
(double)n->activation_count,
n->temporal_decay_rate);
}
/* Activation dampening: high activation_count nodes are "well-known" context
@@ -14425,9 +14424,30 @@ el_val_t engram_think_json(el_val_t seeds, el_val_t faculty) {
if (idx >= 0 && idx < eg->node_count) {
EngramNode* n = &eg->nodes[idx];
if (n->emb && n->emb_dim == g->dim) {
/* NORMALIZE INTO THE DESCRIPTOR'S FRAME (2026-08-16).
* This copied the RAW vector, but engram_geometry
* builds every descriptor over L2-normalized member
* embeddings, so the anchor was being fitted against
* an ellipsoid at a radius it was never fitted over.
* Measured on the self region: magnitude 0.00283443
* raw vs 0.521837 in-frame on the same pair a 180x
* error, and the reason every fit score sat three
* decimal places below the 0.5 floors that gate on
* them. The frame contract is stated in
* engram_verify.h; this call site did not honour it.
* Idempotent when the stored vector is already unit. */
anchor = malloc(sizeof(float) * (size_t)g->dim);
if (anchor) memcpy(anchor, n->emb,
sizeof(float) * (size_t)g->dim);
if (anchor) {
double s2 = 0;
for (int i = 0; i < g->dim; i++)
s2 += (double)n->emb[i] * (double)n->emb[i];
double nn = sqrt(s2);
if (nn > 1e-12)
for (int i = 0; i < g->dim; i++)
anchor[i] = (float)((double)n->emb[i] / nn);
else
memcpy(anchor, n->emb, sizeof(float) * (size_t)g->dim);
}
}
}
free(id);
@@ -14455,88 +14475,522 @@ el_val_t engram_think_json(el_val_t seeds, el_val_t faculty) {
return el_wrap_str(b.buf);
}
/* engram_ground_json(claim_csv, evidence_csv, for_whom) — grounding as a RELATION.
* Turns the DORMANT verifier inward for real: verifies the claim region's centroid
* against the evidence region, then writes a grounded-by edge (weight = grounding,
* grounded-for-whom). Additive. */
/* ═══════════════════════════════════════════════════════════════════════════
* §7 WIRING GROUNDING IS THE EDGE'S WEIGHT, AND THE WEIGHT IS A VECTOR
* (2026-08-16; spec correspondence-and-censorship.md @ 2b7e4ba.)
*
* What was here minted a `grounded-by` edge on every call and returned a float.
* Every part of that was wrong, and #147 only fixed the endpoints:
* - grounding is a property OF a relation, not a relation BETWEEN nodes, so
* there was nothing for a new edge to carry;
* - the call was a READ that wrote the eg_vindex_sync defect;
* - one scalar cannot separate "true and meaningful" from "true and misapplied",
* nor "no support" from "actively contradicts".
*
* ground() is now pure: it reads the vector the RELATION already carries, decays
* it analytically to now on the runtime's one decay model, computes what the
* current geometry would say, and reports whether that move is consequential
* without recording it. Recording is a separate, explicitly named write.
* */
/* The values reference: THIRTEEN regions, discovered from the graph rather than
* hardcoded, so a fourteenth value is picked up without a code change. They are
* the nodes the values root `contains`. Measured on the live store: exactly 13,
* pairwise centroid cosine min 0.1525 / mean 0.5199 / max 0.9278 not one region. */
#define EG_VALUES_ROOT_DEFAULT "kn-5b606390-a52d-4ca2-8e0e-eba141d13440"
#define EG_MAX_VALUES 32
typedef struct {
int n;
char* ids[EG_MAX_VALUES];
GeoDescriptor* g[EG_MAX_VALUES];
} EgValueRef;
static void eg_values_release(EgValueRef* v) {
if (!v) return;
for (int i = 0; i < v->n; i++) { free(v->ids[i]); if (v->g[i]) engram_geo_free(v->g[i]); }
v->n = 0;
}
static int eg_values_build(EgValueRef* out) {
if (!out || !g_engram_store) return -1;
memset(out, 0, sizeof *out);
const char* root = getenv("ENGRAM_VALUES_ROOT");
if (!root || !*root) root = EG_VALUES_ROOT_DEFAULT;
StoreEdge* edges = NULL; size_t ne = 0;
if (store_get_edges_from(g_engram_store, root, &edges, &ne) < 0) return -1;
for (size_t i = 0; i < ne && out->n < EG_MAX_VALUES; i++) {
if (edges[i].tombstoned) continue;
if (!edges[i].relation || strcmp(edges[i].relation, "contains") != 0) continue;
if (!edges[i].to_id) continue;
GeoDescriptor* g = eg_geo_build_desc(edges[i].to_id);
if (!g) continue;
out->ids[out->n] = strdup(edges[i].to_id);
out->g[out->n] = g;
out->n++;
}
store_edges_free(edges, ne);
return out->n;
}
/* THE REFERENCE FRAME IS BUILT ONCE AND HELD. Two reasons, and the second is the
* real one:
* - cost: thirteen descriptors over the whole node vector per call made a
* single /api/ground take tens of seconds;
* - correctness: a reference frame that is rebuilt on every read moves under
* the measurement, which is the defect the whole spec is about. Holding it
* for the process lifetime is the closest this layer can come to "the frame
* updates when it is not being used to act" without owning that decision —
* which belongs to the dreamer, not here. ENGRAM_VALUES_NOCACHE=1 forces a
* rebuild per call for tests that deliberately move a value node. */
static EgValueRef _eg_values_cache;
static int _eg_values_cached = 0;
static const EgValueRef* eg_values_ref(void) {
const char* nc = getenv("ENGRAM_VALUES_NOCACHE");
if (nc && nc[0] && nc[0] != '0') {
if (_eg_values_cached) { eg_values_release(&_eg_values_cache); _eg_values_cached = 0; }
}
if (!_eg_values_cached) {
if (eg_values_build(&_eg_values_cache) <= 0) return NULL;
_eg_values_cached = 1;
}
return &_eg_values_cache;
}
/* Copy a node's embedding INTO THE DESCRIPTOR'S FRAME (never borrow — g->nodes is
* realloc'd on append, so a borrowed EngramNode* dangles across any concurrent write).
*
* THE FRAME CONTRACT IS LOAD-BEARING AND WAS BEING VIOLATED. engram_verify.h states
* it plainly: the claim point and the descriptor must share the same frame.
* engram_geometry builds every descriptor over L2-NORMALIZED member embeddings
* `centroid` is the mean of normcopy()'d vectors and the principal axes are computed
* against that while the raw `n->emb` in the resident store is not necessarily unit.
* Fitting a raw vector against a unit-frame descriptor puts the point at a radius the
* ellipsoid was never fitted over, so the distance is dominated by the norm mismatch
* and every fit score collapses toward zero. Normalizing here is idempotent when the
* stored vector is already unit, so it can only help. */
static float* eg_node_emb_copy(const char* id, int dim) {
EngramStore* eg = engram_get();
if (!eg || !id || dim <= 0) return NULL;
int64_t idx = engram_find_node_index(id);
if (idx < 0 || idx >= eg->node_count) return NULL;
EngramNode* n = &eg->nodes[idx];
if (!n->emb || n->emb_dim != dim) return NULL;
float* p = malloc(sizeof(float) * (size_t)dim);
if (!p) return NULL;
double s = 0;
for (int i = 0; i < dim; i++) s += (double)n->emb[i] * (double)n->emb[i];
double nn = sqrt(s);
if (nn > 1e-12) for (int i = 0; i < dim; i++) p[i] = (float)((double)n->emb[i] / nn);
else memcpy(p, n->emb, sizeof(float) * (size_t)dim);
return p;
}
/* Salience of a relation = the salience of its endpoints, reusing the fields the
* runtime already keeps (node salience and working-memory weight). Consolidation
* is gated by salience that is why you remember the argument and not the
* commute and this deliberately reads existing state rather than introducing a
* threshold of its own. */
static double eg_node_salience(const char* id) {
EngramStore* eg = engram_get();
if (!eg || !id) return 0.0;
int64_t idx = engram_find_node_index(id);
if (idx < 0 || idx >= eg->node_count) return 0.0;
EngramNode* n = &eg->nodes[idx];
double s = n->salience;
if (n->working_memory_weight > s) s = n->working_memory_weight;
if (n->background_activation > s) s = n->background_activation;
return s;
}
static double eg_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;
}
/* Signed projection of a unit gradient onto unit(target x): positive means the
* descent direction still points toward the target. Frame-independent, so it
* stays comparable across versions even if the region's principal axes rotate
* which is what makes the DIRECTION_REVERSAL test meaningful over time. */
static double eg_proj_toward(const float* dir, const float* x, const float* target, int dim) {
double s = 0, n2 = 0;
for (int i = 0; i < dim; i++) { double d = (double)target[i] - (double)x[i]; s += (double)dir[i] * d; n2 += d * d; }
double n = sqrt(n2);
return n > 1e-12 ? s / n : 0.0;
}
/* RELATIONAL grounding of a point: the MIN fit over the thirteen value regions,
* and the NAME of the value that binds. Min, not mean, because a mean lets strong
* agreement with twelve values mask a violation of the thirteenth which is the
* mechanism of rationalization, not a scoring detail. */
static int eg_relational_read(const float* x, const EgValueRef* V,
double* out_score, int* out_idx, float** out_dir) {
if (!x || !V || V->n <= 0) return -1;
double worst = 2.0; int wi = -1;
for (int i = 0; i < V->n; i++) {
GeoFit f;
if (cog_warped_fit(V->g[i], x, NULL, &f) != 0) continue;
if (f.score < worst) { worst = f.score; wi = i; }
}
if (wi < 0) return -1;
if (out_score) *out_score = worst;
if (out_idx) *out_idx = wi;
if (out_dir) {
GeoGradient gr;
if (engram_think(V->g[wi], x, NULL, &gr) == 0) {
*out_dir = gr.direction; gr.direction = NULL; /* take ownership */
engram_gradient_free(&gr);
} else *out_dir = NULL;
}
return 0;
}
/* Find the live relation between two nodes, in either orientation, and return
* the NEWEST recorded version of it. Returns 1 on hit. */
static int eg_find_relation(const char* a, const char* b, StoreEdge* out, char* base_id, size_t cap) {
if (!g_engram_store || !a || !b) return 0;
for (int dir = 0; dir < 2; dir++) {
const char* from = dir == 0 ? a : b;
const char* to = dir == 0 ? b : a;
StoreEdge* edges = NULL; size_t ne = 0;
if (store_get_edges_from(g_engram_store, from, &edges, &ne) < 0) continue;
for (size_t i = 0; i < ne; i++) {
if (edges[i].tombstoned) continue;
if (!edges[i].to_id || strcmp(edges[i].to_id, to) != 0) continue;
if (edges[i].id && strchr(edges[i].id, '#')) continue; /* a version, not a root */
snprintf(base_id, cap, "%s", edges[i].id ? edges[i].id : "");
store_edges_free(edges, ne);
if (cog_grounding_head(g_engram_store, base_id, out, 64) >= 0) return 1;
return 0;
}
store_edges_free(edges, ne);
}
return 0;
}
/* The two-axis observation of one relation from the CURRENT geometry. Pure.
* factual = how well the far endpoint sits in the near endpoint's neighbourhood
* a real correspondence measurement of the relation itself;
* relational = min fit over the thirteen value regions, with the binding name;
* cos_angle = the cosine between the two full-dimensional gradients. */
typedef struct {
int ok;
double factual, relational, cos_angle, fac_proj, rel_proj;
char binding[128];
} EgObservation;
static int eg_observe_relation(const char* from_id, const char* to_id,
const EgValueRef* V, EgObservation* out) {
memset(out, 0, sizeof *out);
GeoDescriptor* R = eg_geo_build_desc(from_id);
if (!R) return -1;
int dim = R->dim;
float* x = eg_node_emb_copy(to_id, dim);
if (!x) { engram_geo_free(R); return -1; }
GeoFit f;
if (cog_warped_fit(R, x, NULL, &f) != 0) { free(x); engram_geo_free(R); return -1; }
out->factual = f.score;
GeoGradient fg;
float* fac_dir = NULL;
if (engram_think(R, x, NULL, &fg) == 0) { fac_dir = fg.direction; fg.direction = NULL; engram_gradient_free(&fg); }
int vi = -1; float* rel_dir = NULL;
if (eg_relational_read(x, V, &out->relational, &vi, &rel_dir) == 0 && vi >= 0)
snprintf(out->binding, sizeof out->binding, "%s", V->ids[vi]);
if (fac_dir && R->centroid) out->fac_proj = eg_proj_toward(fac_dir, x, R->centroid, dim);
if (rel_dir && vi >= 0 && V->g[vi]->centroid) out->rel_proj = eg_proj_toward(rel_dir, x, V->g[vi]->centroid, dim);
if (fac_dir && rel_dir) out->cos_angle = eg_vdot(fac_dir, rel_dir, dim);
free(fac_dir); free(rel_dir); free(x); engram_geo_free(R);
out->ok = 1;
return 0;
}
/* Fold an observation into the vector as it would stand now. The accrual is a
* running MEAN over reinforcements the same form the beat's calibration
* already uses (brier_sum / n_trials) so no rate constant is introduced. */
static void eg_fold_observation(const CogGrounding* prev, const EgObservation* obs,
CogProvClass prov, double floor, double rel_floor,
int64_t now, CogGrounding* out) {
*out = *prev;
double n = prev->reinforcements;
out->factual = prev->present ? prev->factual + (obs->factual - prev->factual) / (n + 1.0) : obs->factual;
out->relational = prev->present ? prev->relational + (obs->relational - prev->relational) / (n + 1.0) : obs->relational;
out->fac_proj = obs->fac_proj; out->rel_proj = obs->rel_proj; out->cos_angle = obs->cos_angle;
out->agreement = obs->cos_angle > 0 ? 1 : (obs->cos_angle < 0 ? -1 : 0);
out->reinforcements = n + 1.0;
out->ts = now;
if (prov != COG_PROV_UNSET) out->prov = prov;
out->floor_at_record = floor; out->rel_floor_at_record = rel_floor;
snprintf(out->binding_value, sizeof out->binding_value, "%s", obs->binding);
/* DERIVED, recomputed at the instant — never carried over from prev. */
out->age_ms = 0; out->decay = 1.0;
out->factual_now = out->factual; out->relational_now = out->relational;
out->associative_now = out->associative;
out->stale = 0;
}
static void eg_emit_vector(JsonBuf* b, const char* key, const CogGrounding* g) {
char t[768];
snprintf(t, sizeof t,
"\"%s\":{\"established\":%s,"
"\"factual\":%.6g,\"relational\":%.6g,\"associative\":%.6g,\"polarity\":%.6g,"
"\"provenance\":\"%s\",\"ts\":%lld,\"seq\":%lld,\"reinforcements\":%.6g,"
"\"cos_angle\":%.6g,\"agreement\":%d,\"fac_proj\":%.6g,\"rel_proj\":%.6g,"
"\"binding_value\":\"%s\","
"\"derived\":{\"age_s\":%lld,\"decay\":%.6g,\"factual_now\":%.6g,"
"\"relational_now\":%.6g,\"associative_now\":%.6g,\"stale\":%s}}",
key, g->present ? "true" : "false",
g->factual, g->relational, g->associative, g->polarity,
cog_prov_name(g->prov), (long long)g->ts, (long long)g->seq, g->reinforcements,
g->cos_angle, g->agreement, g->fac_proj, g->rel_proj,
g->binding_value[0] ? g->binding_value : "-",
(long long)(g->age_ms / 1000), g->decay, g->factual_now,
g->relational_now, g->associative_now, g->stale ? "true" : "false");
jb_puts(b, t);
}
/* engram_ground_json(claim, evidence, for_whom) — a READ. Never writes. */
el_val_t engram_ground_json(el_val_t claim, el_val_t evidence, el_val_t for_whom) {
if (!g_engram_store) return eg_geo_err("store unavailable");
GeoDescriptor* C = eg_geo_build_desc(EL_CSTR(claim));
GeoDescriptor* E = eg_geo_build_desc(EL_CSTR(evidence));
if (!C || !E) { if (C) engram_geo_free(C); if (E) engram_geo_free(E); return eg_geo_err("geometry unavailable"); }
const GeoDescriptor* ev[1] = { E };
GeoGrounding gr;
int rc = engram_verify_grounding(C->centroid, C->dim, ev, 1, 1.0, 0.5, &gr);
double grounding = (rc == 0) ? gr.grounding : 0.0;
if (rc == 0) engram_verify_grounding_free(&gr);
const char* fw = EL_CSTR(for_whom); if (fw && !*fw) fw = NULL;
/* GROUND THE NODE ASKED ABOUT, AND SAY WHAT WAS RESOLVED (2026-08-16
* self-review). This wrote the grounded-by edge between the two REGION
* HUBS and then echoed those hubs back in the "claim"/"evidence" fields
* as though they were the caller's input. Three consequences, all measured
* against the live store:
*
* 1. The edge landed on a node the caller never named. Asking to ground
* 3b9ced5d against 6edf8c79 wrote an edge on 6edf8c79 -> d0406dfd,
* because those were the hubs of the two regions.
* 2. When both seeds resolve into the same region, the hubs coincide and
* the call grounds a node against ITSELF, returning grounding = 1
* a perfect score with no evidence behind it. Two independent agents
* hit this and reported 0.885 / 0.909 self-groundings as confident.
* 3. The echo concealed both, because the response looked exactly like a
* successful grounding of the ids that were passed in.
*
* The region is HOW a claim is evaluated; it is not WHAT the claim is
* about. So the edge attaches to the requested ids, and the resolved hubs
* are reported separately under claim_region / evidence_region. When the
* two regions coincide, the grounding is degenerate by construction and is
* reported as such rather than as a confident 1.0. */
const char* cid = EL_CSTR(claim);
const char* eid = EL_CSTR(evidence);
const char* chub = C->hub_id ? C->hub_id : cid;
const char* ehub = E->hub_id ? E->hub_id : eid;
/* Degeneracy is broader than chub == ehub. Three circular shapes, each of
* which yields a high score for structural reasons rather than evidential
* ones, and all three were previously invisible:
* same-region both seeds resolve to one region grounding a thing
* against itself.
* claim-in-ev the claim's region hub IS the evidence node: the evidence
* sits at the centre of the claim's own neighbourhood.
* ev-in-claim the mirror case.
* Measured: grounding 3b9ced5d against 6edf8c79 scored 0.98883 purely
* because 6edf8c79 is the hub of 3b9ced5d's region. */
const char* degenerate = NULL;
if (chub && ehub && strcmp(chub, ehub) == 0) degenerate = "same-region";
else if (chub && eid && strcmp(chub, eid) == 0) degenerate = "claim-region-is-evidence";
else if (ehub && cid && strcmp(ehub, cid) == 0) degenerate = "evidence-region-is-claim";
if (degenerate) grounding = 0.0; /* circular support is not support */
const char* fw = EL_CSTR(for_whom); if (fw && !*fw) fw = NULL;
int64_t now = engram_now_ms();
/* Do not write an edge for a grounding that is degenerate by construction. */
int wr = degenerate ? -1 : cog_ground_edge(g_engram_store, cid, eid, grounding, fw);
JsonBuf b; jb_init(&b); char t[512];
snprintf(t, sizeof t, "{\"relation\":\"grounded-by\",\"claim\":\"%s\",\"evidence\":\"%s\","
"\"claim_region\":\"%s\",\"evidence_region\":\"%s\",\"degenerate\":%s%s%s,"
"\"for_whom\":\"%s\",\"grounding\":%.6g,\"written\":%s}",
cid ? cid : "", eid ? eid : "", chub ? chub : "", ehub ? ehub : "",
degenerate ? "\"" : "false", degenerate ? degenerate : "", degenerate ? "\"" : "",
fw ? fw : "-", grounding, wr == 0 ? "true" : "false");
StoreEdge cur; char base_id[192] = "";
JsonBuf b; jb_init(&b); char t[768];
if (!eg_find_relation(cid, eid, &cur, base_id, sizeof base_id)) {
/* THE HONEST ANSWER. There is nothing to ground a claim "against" that is
* not already a relation. This previously minted one and scored it and
* when both seeds fell in one region the score came back 1.0 with no
* evidence behind it. If the two are unrelated, say so and write nothing. */
snprintf(t, sizeof t,
"{\"claim\":\"%s\",\"evidence\":\"%s\",\"for_whom\":\"%s\",\"related\":false,"
"\"grounding\":null,\"written\":false,"
"\"note\":\"no relation between these nodes; grounding is a property of a relation, not a score minted between nodes\"}",
cid ? cid : "", eid ? eid : "", fw ? fw : "-");
jb_puts(&b, t);
return el_wrap_str(b.buf);
}
CogGrounding rec; cog_grounding_parse(&cur, now, &rec);
const EgValueRef* V = eg_values_ref();
EgObservation obs; memset(&obs, 0, sizeof obs);
int nv = V ? V->n : 0;
if (nv > 0 && cur.from_id && cur.to_id) eg_observe_relation(cur.from_id, cur.to_id, V, &obs);
double floor = 0.5, rel_floor = 0.5;
CogGrounding ng = rec;
if (obs.ok) eg_fold_observation(&rec, &obs, COG_PROV_UNSET, floor, rel_floor, now, &ng);
CogSignificance sig = obs.ok ? cog_grounding_significant(&rec, &ng, floor, rel_floor)
: COG_SIG_NONE;
double sal = cur.from_id && cur.to_id
? (eg_node_salience(cur.from_id) > eg_node_salience(cur.to_id)
? eg_node_salience(cur.from_id) : eg_node_salience(cur.to_id))
: 0.0;
CogTrajectory tr; memset(&tr, 0, sizeof tr);
cog_grounding_trajectory(g_engram_store, base_id, now, &tr);
jb_putc(&b, '{');
snprintf(t, sizeof t,
"\"claim\":\"%s\",\"evidence\":\"%s\",\"for_whom\":\"%s\",\"related\":true,"
"\"relation\":\"%s\",\"edge\":\"%s\",\"edge_root\":\"%s\",\"from\":\"%s\",\"to\":\"%s\","
"\"value_regions\":%d,\"aggregate\":\"min\",\"salience\":%.6g,",
cid ? cid : "", eid ? eid : "", fw ? fw : "-",
cur.relation ? cur.relation : "", cur.id ? cur.id : "", base_id,
cur.from_id ? cur.from_id : "", cur.to_id ? cur.to_id : "", nv > 0 ? nv : 0, sal);
jb_puts(&b, t);
engram_geo_free(C); engram_geo_free(E);
eg_emit_vector(&b, "recorded", &rec);
jb_putc(&b, ',');
if (obs.ok) eg_emit_vector(&b, "observed", &ng);
else jb_puts(&b, "\"observed\":null");
/* Volatility and drift are computed here and stored nowhere — the series
* exists only because nothing was destroyed. */
snprintf(t, sizeof t,
",\"trajectory\":{\"n_versions\":%d,\"factual_volatility\":%.6g,"
"\"relational_volatility\":%.6g,\"factual_drift\":%.6g,\"relational_drift\":%.6g,"
"\"stayed_true_became_wrong\":%s}"
",\"would_record\":%s,\"significance\":\"%s\",\"inherent\":%s,\"written\":false}",
tr.n_versions, tr.factual_volatility, tr.relational_volatility,
tr.factual_drift, tr.relational_drift,
tr.stayed_true_became_wrong ? "true" : "false",
sig != COG_SIG_NONE ? "true" : "false", cog_significance_name(sig),
cog_significance_inherent(sig) ? "true" : "false");
jb_puts(&b, t);
store_edge_free(&cur);
return el_wrap_str(b.buf);
}
/* engram_assert_json(claim_id, for_whom, floor) — the honesty floor as a QUERY at
* ASSERTION time only (holding is never gated). Reads the claim's grounded-by
* edges (for the observer) and returns whether assertion is permitted. */
el_val_t engram_assert_json(el_val_t claim_id, el_val_t for_whom, el_val_t floor) {
/* engram_ground_record_json — the WRITE half, named as one. Recomputes the
* vector, applies the consolidation gate (salience + per-dimension consequence),
* and on significance supersedes the EDGE, versioning the WHOLE vector jointly.
* The predecessor is never touched.
*
* There is no provenance predicate here and nothing takes its place. An earlier
* pass built one a bounded traversal over a grounding-chain graph, refusing
* evidence downstream of the region being calibrated. It is deleted. Circularity
* of the reference frame is TEMPORAL, not topological: you cannot recalibrate the
* ruler while measuring with it, so that update happens when the frame is not
* being used to act, which is a fact about engagement and belongs to the dreamer.
* The measurement that settles it: reachability from the self region reaches
* 89.2% of the live graph, so any topological predicate marks nearly all evidence
* tainted and degenerates into the total block censorship started as. */
el_val_t engram_ground_record_json(el_val_t claim, el_val_t evidence,
el_val_t provenance, el_val_t floor_v) {
if (!g_engram_store) return eg_geo_err("store unavailable");
const char* cid = EL_CSTR(claim);
const char* eid = EL_CSTR(evidence);
double floor = atof(EL_CSTR(floor_v)); if (!(floor > 0)) floor = 0.5;
double rel_floor = floor;
CogProvClass prov = cog_prov_parse(EL_CSTR(provenance));
int64_t now = engram_now_ms();
StoreEdge cur; char base_id[192] = "";
JsonBuf b; jb_init(&b); char t[768];
if (!eg_find_relation(cid, eid, &cur, base_id, sizeof base_id)) {
snprintf(t, sizeof t, "{\"claim\":\"%s\",\"evidence\":\"%s\",\"related\":false,\"written\":false}",
cid ? cid : "", eid ? eid : "");
jb_puts(&b, t); return el_wrap_str(b.buf);
}
CogGrounding rec; cog_grounding_parse(&cur, now, &rec);
const EgValueRef* V = eg_values_ref();
EgObservation obs; memset(&obs, 0, sizeof obs);
int nv = V ? V->n : 0;
if (nv <= 0 || !cur.from_id || !cur.to_id ||
eg_observe_relation(cur.from_id, cur.to_id, V, &obs) != 0 || !obs.ok) {
store_edge_free(&cur);
return eg_geo_err("geometry unavailable for this relation");
}
CogGrounding ng;
eg_fold_observation(&rec, &obs, prov, floor, rel_floor, now, &ng);
CogSignificance sig = cog_grounding_significant(&rec, &ng, floor, rel_floor);
/* CONSOLIDATION GATE. Significance says the move would change a decision;
* salience says it is worth making durable. The two INHERENT moves a
* polarity sign flip and a provenance class change bypass salience because
* they are discrete changes of state rather than drift. */
double sal = eg_node_salience(cur.from_id) > eg_node_salience(cur.to_id)
? eg_node_salience(cur.from_id) : eg_node_salience(cur.to_id);
int salient = (sal > 0.0);
int consolidate = (sig != COG_SIG_NONE) && (cog_significance_inherent(sig) || salient);
char written_id[224] = "";
int seq = -1;
if (consolidate) seq = cog_grounding_record(g_engram_store, &cur, &ng, written_id, sizeof written_id);
jb_putc(&b, '{');
snprintf(t, sizeof t,
"\"claim\":\"%s\",\"evidence\":\"%s\",\"edge\":\"%s\",\"edge_root\":\"%s\","
"\"value_regions\":%d,\"aggregate\":\"min\",\"floor\":%.4g,\"rel_floor\":%.4g,"
"\"salience\":%.6g,\"salient\":%s,",
cid ? cid : "", eid ? eid : "", cur.id ? cur.id : "", base_id,
nv, floor, rel_floor, sal, salient ? "true" : "false");
jb_puts(&b, t);
eg_emit_vector(&b, "previous", &rec); jb_putc(&b, ',');
eg_emit_vector(&b, "observed", &ng);
snprintf(t, sizeof t,
",\"significance\":\"%s\",\"inherent\":%s,\"consolidated\":%s,"
"\"written\":%s,\"version\":%d,\"version_id\":\"%s\"}",
cog_significance_name(sig), cog_significance_inherent(sig) ? "true" : "false",
consolidate ? "true" : "false",
(consolidate && seq > 0) ? "true" : "false", seq > 0 ? seq : 0, written_id);
jb_puts(&b, t);
store_edge_free(&cur);
return el_wrap_str(b.buf);
}
/* engram_ground_trajectory_json(claim, evidence) — the supersession chain read as
* a TIME SERIES OF VECTORS. Not only what the grounding is but which way it has
* been moving and how fast a derivative obtained for free from immutability,
* because the points were never destroyed. */
el_val_t engram_ground_trajectory_json(el_val_t claim, el_val_t evidence) {
if (!g_engram_store) return eg_geo_err("store unavailable");
StoreEdge cur; char base_id[192] = "";
if (!eg_find_relation(EL_CSTR(claim), EL_CSTR(evidence), &cur, base_id, sizeof base_id))
return eg_geo_err("no relation between these nodes");
store_edge_free(&cur);
int64_t now = engram_now_ms();
JsonBuf b; jb_init(&b); char t[768];
jb_puts(&b, "{\"edge_root\":\""); jb_puts(&b, base_id); jb_puts(&b, "\",\"versions\":[");
int emitted = 0;
for (int v = 0; v <= 64; v++) {
char vid[224];
if (v == 0) snprintf(vid, sizeof vid, "%s", base_id);
else snprintf(vid, sizeof vid, "%s#%d", base_id, v);
StoreEdge e;
if (store_get_edge(g_engram_store, vid, &e) != 1) { if (v) break; else continue; }
CogGrounding g; cog_grounding_parse(&e, now, &g);
if (emitted) jb_putc(&b, ',');
snprintf(t, sizeof t,
"{\"version\":%d,\"id\":\"%s\",\"established\":%s,\"ts\":%lld,"
"\"factual\":%.6g,\"relational\":%.6g,\"associative\":%.6g,\"polarity\":%.6g,"
"\"provenance\":\"%s\",\"cos_angle\":%.6g,\"agreement\":%d,"
"\"binding_value\":\"%s\",\"prev\":\"%s\","
"\"derived\":{\"age_s\":%lld,\"decay\":%.6g,\"factual_now\":%.6g,\"stale\":%s}}",
v, vid, g.present ? "true" : "false", (long long)g.ts,
g.factual, g.relational, g.associative, g.polarity,
cog_prov_name(g.prov), g.cos_angle, g.agreement,
g.binding_value[0] ? g.binding_value : "-", g.prev_edge,
(long long)(g.age_ms / 1000), g.decay, g.factual_now, g.stale ? "true" : "false");
jb_puts(&b, t); emitted++;
store_edge_free(&e);
}
CogTrajectory tr; memset(&tr, 0, sizeof tr);
cog_grounding_trajectory(g_engram_store, base_id, now, &tr);
snprintf(t, sizeof t,
"],\"n_versions\":%d,\"derived\":{\"factual_volatility\":%.6g,"
"\"relational_volatility\":%.6g,\"factual_drift\":%.6g,\"relational_drift\":%.6g,"
"\"stayed_true_became_wrong\":%s}}",
emitted, tr.factual_volatility, tr.relational_volatility,
tr.factual_drift, tr.relational_drift,
tr.stayed_true_became_wrong ? "true" : "false");
jb_puts(&b, t);
return el_wrap_str(b.buf);
}
/* engram_assert_json(claim_id, for_whom, floor, rel_floor) — the honesty floor as
* a QUERY at ASSERTION time only (holding is never gated), now gating on BOTH
* axes. A well-evidenced claim must not earn the right to be asserted regardless
* of whether it means the right thing.
*
* `still_held` was a HARDCODED `true` in this format string a temporal property
* named in the API and answered without consulting anything, which is invariant
* §8.1 violated in one literal. It is now derived: the claim's node is read and
* the field reports whether the content is present and live. Holding remains
* unconditional; what decays is the GROUNDING, and a relation whose decayed
* grounding has fallen below its floor stops being assertable on its own,
* without anyone having to remember to check. */
el_val_t engram_assert_json(el_val_t claim_id, el_val_t for_whom, el_val_t floor, el_val_t rel_floor) {
if (!g_engram_store) return eg_geo_err("store unavailable");
const char* fw = EL_CSTR(for_whom); if (fw && !*fw) fw = NULL;
double fl = atof(EL_CSTR(floor)); if (!(fl > 0)) fl = 0.5;
int gate = cog_assert_gate(g_engram_store, EL_CSTR(claim_id), fw, fl);
JsonBuf b; jb_init(&b); char t[192];
snprintf(t, sizeof t, "{\"claim\":\"%s\",\"for_whom\":\"%s\",\"floor\":%.4g,\"may_assert\":%s,\"still_held\":true}",
EL_CSTR(claim_id), fw ? fw : "-", fl, gate == 1 ? "true" : "false");
double fl = atof(EL_CSTR(floor)); if (!(fl > 0)) fl = 0.5;
double rfl = atof(EL_CSTR(rel_floor)); if (!(rfl > 0)) rfl = fl;
CogAssertion a;
if (cog_assert_two_axis(g_engram_store, EL_CSTR(claim_id), fl, rfl, engram_now_ms(), &a) != 0)
return eg_geo_err("assert failed");
JsonBuf b; jb_init(&b); char t[640];
snprintf(t, sizeof t,
"{\"claim\":\"%s\",\"for_whom\":\"%s\",\"floor\":%.4g,\"rel_floor\":%.4g,"
"\"may_assert\":%s,\"still_held\":%s,\"found\":%s,\"n_relations\":%d,"
"\"factual\":%.6g,\"relational\":%.6g,\"relational_established\":%s,"
"\"cos_angle\":%.6g,\"agreement\":%d,\"binding_value\":\"%s\",\"best_relation\":\"%s\","
"\"refused_because\":\"%s\"}",
EL_CSTR(claim_id), fw ? fw : "-", fl, rfl,
a.may_assert ? "true" : "false", a.still_held ? "true" : "false",
a.found ? "true" : "false", a.n_edges,
a.factual, a.relational, a.relational_established ? "true" : "false",
a.cos_angle, a.agreement,
a.binding_value[0] ? a.binding_value : "-", a.best_edge,
a.may_assert ? "-" :
!a.found ? "no-relation" :
!a.relational_established ? "relational-axis-never-established" :
(a.factual < fl) ? "below-factual-floor" :
(a.relational < rfl) ? "below-relational-floor" : "-");
jb_puts(&b, t);
return el_wrap_str(b.buf);
}
@@ -14636,15 +15090,22 @@ el_val_t engram_correspondence_beat_json(el_val_t seeds, el_val_t faculty, el_va
JsonBuf b; jb_init(&b); char t[384];
snprintf(t, sizeof t,
"{\"faculty\":\"%s\",\"stance_id\":\"%s\",\"region_hub\":\"%s\",\"dim\":%d,\"n_axes\":%d,\"signal_axes\":%d,"
/* `keystone_write_blocked` is GONE from this response. It reported that the
* beat had refused to learn about the reference frame, and the measured
* cost of that refusal was 0.00% brier reduction over n_trials 0 on the
* keystone region the loop never ran, so nothing about the self was ever
* calibrated OR falsifiable. Nothing replaces the flag: non-circularity of
* the reference frame is temporal, not a permission (spec §5.2). The
* `keystone` field is retained as a label on the region, and it no longer
* gates anything. */
"\"resumed\":%s,\"keystone\":%s,\"probes\":%d,\"epochs\":%d,"
"\"brier_before\":%.6g,\"brier_after\":%.6g,\"reduction_pct\":%.2f,"
"\"reliability\":%.6g,\"n_trials\":%lld,\"stance_written\":%s,\"keystone_write_blocked\":%s}",
"\"reliability\":%.6g,\"n_trials\":%lld,\"stance_written\":%s}",
EL_CSTR(faculty), sid, g->hub_id ? g->hub_id : "region", dim, na, signal,
resumed ? "true" : "false", st.keystone ? "true" : "false", NP, EP,
brier_before, brier_after,
brier_before > 0 ? 100.0 * (brier_before - brier_after) / brier_before : 0.0,
st.reliability, (long long)st.n_trials, wrote == 0 ? "true" : "false",
st.keystone ? "true" : "false");
st.reliability, (long long)st.n_trials, wrote == 0 ? "true" : "false");
jb_puts(&b, t);
cog_stance_free(&st); engram_geo_free(g);
return el_wrap_str(b.buf);