Files
el/lang/runtime/engram_cognition.h
T
Neuron 7a1501d097
El SDK CI - dev / build-and-test (pull_request) Failing after 3m59s
Grounding is the edge's weight, and the weight is a vector
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.
2026-08-16 13:18:50 -05:00

469 lines
31 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* engram_cognition.h — THE ONE OPERATION.
*
* The buildable form of the "cognition is one operation" theory (design doc
* engram/spec/cognitive-architecture.design.md; memory bdc8a488 / d582a766).
*
* Cognition is ONE operation — think — a directed traversal-READ of the geometry
* from an anchor, steered by a learned STANCE, whose output is a GRADIENT (a
* direction + spread over the geometry), never a point. The named faculties
* (reason / induce / abduce / analogy / relate / plan / ground) are human LABELS
* on regions of think's steering space: each faculty == { think + a named stance }.
* Collapse-to-a-point happens only at EXPRESSION (a separate faculty), never in think.
*
* NAMING (Will's directive): the surface verbs name the cognitive ACT being
* performed (think / reason / induce / ground / verify), not the internal function
* shape. The single frozen primitive underneath every faculty is engram_think,
* which composes over engram_reason_point_fit + the §5 geo-algebra. Those never
* learn. Only the STANCE learns.
*
* "Stance" is the theory's steering PRIOR, deliberately named distinctly: in this
* codebase the token "prior" already means previous-VERSION (supersession). A
* Stance is a learnable bias/disposition over the geometry — which axes matter,
* which way pays off, plus a calibrated track record — attached to a faculty-label
* and a region, and grounded-for-whom.
*
* PURE + (mostly) READ-ONLY, stdlib + libm only. think() and the warp are pure
* over their inputs. Persistence (Stance <-> StoreNode, edge grounding vectors) is the
* only part that touches the store, and it is additive / supersede / tombstone —
* never mutate-in-place, never delete. It NEVER touches the live daemon: all
* offline against a scratch store, per the design's rails.
*/
#ifndef ENGRAM_COGNITION_H
#define ENGRAM_COGNITION_H
#include <stdint.h>
#include <stddef.h>
#include "engram_geometry.h"
#include "engram_reason.h"
#include "engram_store.h"
/* Max principal axes a stance warps (matches GeoParams.top_axes default budget). */
#define COG_MAX_AXES 32
/* ═══════════════════════════════════════════════════════════════════════════
* §1 GeoGradient — the OUTPUT of think. A direction + spread over the geometry,
* plus the calibrated confidence and the read it was computed against. NOT a point.
* A spiked gradient (spread→0) = "exact" (deduction); a spread gradient = "fuzzy"
* (prediction). The gradient is ALSO the next steering direction (closed-loop flow).
* ═══════════════════════════════════════════════════════════════════════════ */
typedef struct {
int dim;
float* direction; /* unit steering vector in the anchor's frame (owned) */
double spread; /* 0 = spiked/exact ... large = diffuse/fuzzy */
double confidence; /* calibrated, from the stance's track record (reliab.) */
double magnitude; /* THIS read's own membership/fit estimate ∈(0,1].
* The scalar an expression faculty would SAMPLE; kept
* on the gradient but never used AS a decision by think.*/
const char* anchor_id; /* borrowed: the vantage this was read from */
int n_support; /* neighborhood members that shaped the read */
const char* stance_id; /* borrowed: which stance steered this (provenance) */
} GeoGradient;
void engram_gradient_free(GeoGradient* g);
/* ═══════════════════════════════════════════════════════════════════════════
* §2 Stance — the learnable steering prior, as a first-class object. In memory
* here; persisted as a StoreNode (node_type "Stance") via cog_stance_*serialize.
*
* warp: axis_gain[] per-principal-axis multiplier on extents (which axes
* matter — gain>1 WIDENS an axis so it penalizes less);
* bias_dir[] a steering-direction seed in the region's frame;
* scalars faculty constants this stance overrides (ext_floor, etc).
* calibration: the track record — the ONLY thing the loop (§4) updates
* besides warp: n_trials, a Brier accumulator, reliability
* (→ GeoGradient.confidence), and an EMA error.
* keystone: if set, the correspondence-loop MUST NEVER write warp or
* calibration — read-mostly (self / values). §6 metastability.
* ═══════════════════════════════════════════════════════════════════════════ */
typedef struct {
char* id; /* stance node id (owned) */
char* faculty; /* the act this stance serves: "induce"|"relate"|... */
char* anchor_region; /* node/neighborhood id this stance is attached to */
char* for_whom; /* observer id — grounding is relational (NULL=global) */
int keystone; /* 1 = read-mostly, loop never writes it (§6) */
int dim; /* embedding dim of the region */
int n_axes; /* how many axis_gain entries are live (<= COG_MAX_AXES)*/
double axis_gain[COG_MAX_AXES]; /* per-axis extent multipliers (init 1.0) */
float* bias_dir; /* dim floats, steering seed (owned; NULL = none) */
double ext_floor; /* faculty scalar: the extent floor (init 1.0) */
double drop_frac; /* faculty scalar (causal): confound drop (init 0.5) */
double assoc_floor; /* faculty scalar (causal): assoc floor (init 0.2) */
/* calibration / track record */
int64_t n_trials;
double brier_sum; /* Σ (p y)² */
double reliability; /* calibrated ∈[0,1] → GeoGradient.confidence */
double ema_error; /* EMA of per-trial error */
double last_error;
} CogStance;
/* Initialize a neutral stance (all gains 1.0, default scalars, reliability 0.5).
* dim/n_axes taken from the region descriptor. faculty/id/for_whom are copied. */
int cog_stance_init(CogStance* s, const char* id, const char* faculty,
const char* anchor_region, const char* for_whom,
const GeoDescriptor* region);
void cog_stance_free(CogStance* s);
/* A stance set to today's hard-coded constants == behavioral parity with the
* pre-stance operators (axis_gain all 1.0, ext_floor default, drop_frac 0.5,
* assoc_floor 0.2). This is the FROZEN CONTROL used by the validation. */
void cog_stance_set_frozen_defaults(CogStance* s);
/* ── Serialization: Stance <-> StoreNode (compact line schema "STNC1", mirroring
* the reify "GEO1" precedent). Additive; the node's importance field caches the
* reliability readout. Round-trips exactly (reboot-prove). ──────────────────── */
char* cog_stance_to_metadata(const CogStance* s); /* owned string */
int cog_stance_to_node(const CogStance* s, StoreNode* out);/* fills a StoreNode */
int cog_stance_from_node(const StoreNode* n, CogStance* out);/* parse STNC1 */
#define COG_STANCE_NODE_TYPE "Stance"
#define COG_STANCE_META_MAGIC "STNC1"
/* ═══════════════════════════════════════════════════════════════════════════
* §1.2 think — the ONE operation. Frozen procedure over three steps:
* 1. re-origin on the anchor point (the vantage; the manifold is the read
* neighborhood, passed as `region`);
* 2. fit the anchor under the stance's WARP (engram_reason_point_fit with the
* axis extents multiplied by axis_gain and ext_floor substituted);
* 3. emit a GRADIENT: direction = the warped steepest-descent that reduces the
* fit distance (the "which way pays off" seed + bias_dir), spread from the
* fit distance, confidence from the stance's reliability, magnitude = the
* read's membership estimate. NO point-collapse — that is expression.
*
* `region` — the read neighborhood (built by vantage_read / geometry descriptor).
* `anchor` — the point to read FROM (dim floats). NULL = region centroid (self).
* `stance` — the steering prior. NULL = neutral (frozen defaults) => parity.
* Returns 0 and fills `out` (engram_gradient_free), <0 on error.
* ═══════════════════════════════════════════════════════════════════════════ */
int engram_think(const GeoDescriptor* region, const float* anchor,
const CogStance* stance, GeoGradient* out);
/* The warped fit itself (step 2), exposed for the loop + verifier reuse. Identical
* to engram_reason_point_fit when stance==NULL or all gains==1 && ext_floor default. */
int cog_warped_fit(const GeoDescriptor* region, const float* x,
const CogStance* stance, GeoFit* out);
/* EXPRESSION — the ONLY place a gradient collapses to a point. Samples the gradient
* off the anchor along its steering direction, scaled by (1 spread) so a spiked
* (confident) gradient lands a definite point and a diffuse one barely moves.
* This is deliberately a SEPARATE faculty from think (§1.2, M5). */
int engram_express(const GeoGradient* g, const float* anchor, float* out_point);
/* ═══════════════════════════════════════════════════════════════════════════
* §5 HOLD vs GROUND vs ASSERT. Holding is unconditional (the store gates nothing).
* Grounding is an ATTRIBUTE OF a relation — carried on the edge itself, as a
* vector (§7). The honesty floor is checked only at ASSERTION, on both axes.
* ═══════════════════════════════════════════════════════════════════════════ */
/* DELETED 2026-08-16: COG_GROUNDED_BY_RELATION and cog_ground_edge.
*
* A "grounded-by" edge models grounding as a relation BETWEEN two nodes. It is a
* property OF a relation — and it is that relation's weight. Minting a new edge
* to carry a score was the error; #147 corrected which endpoints the edge landed
* on and left the wrong idea standing. There is nothing to ground a claim
* "against" that is not already an edge, and if no edge exists the honest answer
* is that the two are not related — not a freshly minted one scoring 0.98.
* See §7 for what replaced it. */
#define COG_SALIENT_TO_RELATION "salient-to"
/* Write/refresh a salient-to edge: salience is RELATIONAL (grounded-for-whom),
* carried on the edge to the observer — not baked into the node scalar (§2.1). */
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
const char* observer_id, double salience);
/* ═══════════════════════════════════════════════════════════════════════════
* §4 THE REFLEXIVE CORRESPONDENCE-LOOP — the learning engine. think scores its
* OWN gradient against outcome, refines the stance on the error, and (optionally)
* writes the (gradient, outcome, error) back as self-describing geometry. This is
* the dormant verifier turned INWARD.
*
* grade(1) SELF-CONSISTENCY (no external world-labels): the outcome is what the
* geometry itself says — the membership determined by the region's SIGNAL subspace
* (the axes reality actually weights). The stance's cheap warped read is graded
* against that geometric truth; error refines the warp so the read corresponds.
* ═══════════════════════════════════════════════════════════════════════════ */
typedef struct {
double correspondence; /* ∈[0,1]: 1 |p y| for this trial */
double error; /* 1 correspondence */
double brier; /* running mean (p y)² across the stance's trials */
double reliability; /* the stance's current calibrated reliability */
int wrote_keystone; /* 1 iff a keystone update was BLOCKED (safety audit) */
} CogBeatResult;
/* One correspondence beat for ONE trial:
* think(region, anchor, stance) -> gradient (a PREDICTION, ungrounded)
* outcome y := grade-1 self-consistency target (in [0,1])
* error = |magnitude y|; refine stance.warp + calibration on the error
* (bounded step; NEVER writes a keystone stance)
* `learn`==0 grades WITHOUT updating (the frozen-control path). `max_step` bounds
* the per-beat warp change (metastability; §6). Returns 0 / <0. */
int engram_correspondence_beat(const GeoDescriptor* region, const float* anchor,
double outcome_y, CogStance* stance,
int learn, double max_step, CogBeatResult* out);
/* ═══════════════════════════════════════════════════════════════════════════
* §6 METASTABILITY. Keystones (self/values) are read-mostly: the loop reads but
* never writes them. Mark by stance flag or by a keystone-id set the loop consults.
*
* SUPERSEDED BY §7's PROVENANCE CONSTRAINT (2026-08-16). The keystone flag is a
* PERMISSION: it asks who the target is, not where the evidence came from. That
* is censorship, and it costs the ability to ever ground the self (spec
* correspondence-and-censorship.md §0/§2). The constraint that actually protects
* a reference frame is cog_grounding_downstream: a region may not be calibrated
* by evidence downstream of itself. These declarations remain only so existing
* call sites keep compiling; nothing in the grounding path consults them.
* ═══════════════════════════════════════════════════════════════════════════ */
typedef struct { const char** ids; int n; } CogKeystoneSet;
int cog_is_keystone(const CogKeystoneSet* ks, const CogStance* s);
/* ═══════════════════════════════════════════════════════════════════════════
* §7 GROUNDING IS THE EDGE'S WEIGHT, AND THE WEIGHT IS A VECTOR
* (2026-08-16; spec correspondence-and-censorship.md §2–§6 @ 2b7e4ba.)
*
* THE MODEL. Grounding is not a subsystem, a score, or a relation BETWEEN nodes.
* It is an attribute OF a relation. The graph already IS the grounding structure:
* every edge is a grounded relation, and what that relation is worth is carried
* on the edge itself. Three things follow, and each DELETES rather than adds:
*
* 1. `grounded-by` as a relation type does not exist, and cog_ground_edge is
* gone. Minting an edge to hold a score models grounding as a relation
* between nodes when it is a property of a relation. #147 corrected which
* endpoints that edge landed on and left the wrong idea standing.
* 2. There is no observer, and no sampling rate. Change is not a consequence of
* use — it IS use, the way potentiation is the firing rather than something
* that reads the firing and writes a weight. So no supervisor compares a
* value to a threshold and decides to persist.
* 3. Between two recorded versions the trajectory is not unknown. Decay is a
* pure function of the last recorded point and elapsed time, so it is
* ANALYTIC: store the point, read the curve.
*
* WHAT IS *NOT* HERE, DELIBERATELY. An earlier draft of the spec posed "a graph
* predicate for evidence downstream of itself" as the hard problem, and this file
* briefly contained one. It is withdrawn. Non-circularity is TEMPORAL, not
* topological: you cannot recalibrate the ruler while measuring with it, so you
* do it when you are not using the frame to act. Reachability could never have
* worked — measured on the live store, reachability from the self region over
* all relations reaches 89.2% of the graph (10,580 of 11,861 nodes) and 16.0%
* over hebbian/semantic relations alone, so the predicate marks essentially all
* evidence tainted and the constraint degenerates into the total block that
* censorship started as. Nothing replaces it here; the independence is a fact
* about engagement, owned by the dreamer, not a fact about the graph.
*
* ═══════════════════════════════════════════════════════════════════════════
* §7.1 THE VECTOR
*
* The test for a real dimension is whether it can move independently of the
* others. Five can, and each maps onto substrate that already exists:
*
* factual correspondence with evidence. [GRD1]
* relational correspondence with values — min over THIRTEEN
* value regions, carrying the binding value's NAME. [GRD1]
* associative co-activation frequency. This is the edge's `hebb`
* field with its existing dynamics — NOT a new one.
* Independent by construction: every superstition is
* a strong association with no factual grounding.
* polarity SIGNED. Near zero means "no support"; NEGATIVE means
* "this actively contradicts". The edge's `inhibitory`
* bit is exactly this distinction crushed to one bit,
* and is carried forward as the seed value. [GRD1]
* provenance observed / inferred / told / imprinted. Categorical,
* and load-bearing: it governs what the relation is
* entitled to. [GRD1]
*
* Plus a TIMESTAMP, which is what turns the supersession chain into a time
* series of vectors rather than a series of numbers.
*
* DERIVED, THEREFORE NEVER STORED. Confidence (high grounding AND low
* volatility), recency (decay read off the curve), staleness (grounding fallen
* below its floor), volatility (the derivative of a series nothing destroyed).
* Storing confidence separately is how `confidence: 0.5` ends up sitting beside
* a zero direction vector, asserting something nothing computed. Every field in
* CogGrounding below is marked STORED or DERIVED, and the serializer writes
* only the STORED ones.
*
* THE VALUES REFERENCE IS THIRTEEN REGIONS AND THE AGGREGATE IS MIN.
* Measured on the live store: the values root kn-5b606390 `contains` exactly 13
* value nodes; pairwise centroid cosine among their regions is min 0.1525,
* mean 0.5199, median 0.5282, max 0.9278 — they demonstrably do not form one
* region. Against a single union region the individual values sit at cosine
* 0.38..0.89, with constraints-as-freedom at 0.3812 and change-is-the-signal at
* 0.4677, so a union centroid under-represents precisely the values a claim is
* most likely to be measured against. MIN rather than MEAN because a mean lets
* strong agreement with twelve values mask a violation of the thirteenth, which
* is the mechanism of rationalization; min yields a binding constraint with a
* NAME attached rather than a score.
*
* TRAVERSAL CONDUCTS ON FACTUAL; ASSERTION REQUIRES BOTH. If activation
* conducted on relational weight, Neuron could not follow a chain of reasoning
* to a conclusion he then rejects — censorship arriving through the spreading
* rule. The gap between reachable and assertable is where the wide
* factual/relational angles live, and that gap is the interesting part.
* ═══════════════════════════════════════════════════════════════════════════ */
/* ── The one decay model (moved here from el_runtime.c so that node decay and
* edge-grounding decay are a single implementation with a single set of
* constants, rather than a model and a parallel copy of it). Half-life scales
* with how established the thing is: T_eff = T_HALF · (1 + ln(1 + reinforcements)).
* The floor is a preference, not a cliff — max penalty for age alone is 4x.
* `lambda_override` > 0 replaces the default rate; 0 means use the default. */
#define COG_T_HALF_HOURS 168.0
#define COG_DECAY_LAMBDA 0.693147
#define COG_DECAY_FLOOR 0.25
double cog_decay_factor(int64_t age_ms, double reinforcements, double lambda_override);
/* The compact vector block carried in the edge's own metadata. Line schema, same
* precedent as STNC1 / GEO1. Metadata the edge already carried is preserved
* verbatim ahead of the magic line. */
#define COG_GROUNDING_META_MAGIC "GRD1"
/* Provenance class — categorical, and it governs what the relation is entitled
* to. A change of class is inherently significant and needs no threshold,
* because told → observed is a categorical upgrade, not a drift. */
typedef enum {
COG_PROV_UNSET = 0,
COG_PROV_OBSERVED = 1,
COG_PROV_INFERRED = 2,
COG_PROV_TOLD = 3,
COG_PROV_IMPRINTED = 4
} CogProvClass;
const char* cog_prov_name(CogProvClass p);
CogProvClass cog_prov_parse(const char* s);
typedef struct {
int present; /* 1 iff the edge carries a GRD1 block */
/* ── STORED: the vector, as it stood at `ts` ─────────────────────────────── */
double factual; /* correspondence with evidence */
double relational; /* min over the thirteen value regions */
double associative; /* co-activation frequency — mirrors edge->hebb */
double polarity; /* SIGNED support; <0 = actively contradicts */
CogProvClass prov; /* observed / inferred / told / imprinted */
int64_t ts; /* when this version was recorded (ms) */
int64_t seq; /* supersession sequence number */
double reinforcements; /* uses folded into this version */
char binding_value[128]; /* the argmin value — the conflict's NAME */
/* the two gradients as frame-independent signed projections, plus the angle
* between them in full R^dim. These are part of the JOINT STATE a decision
* saw, not a convenience: near +1 evidence and values push the same way; at
* or below 0 the relation is factually supported and relationally wrong. */
double fac_proj, rel_proj, cos_angle;
int agreement; /* sign(cos_angle): +1 / 0 / 1 */
double floor_at_record, rel_floor_at_record;
char prev_edge[192]; /* the version this superseded ("" if first) */
/* ── DERIVED at read time. NEVER serialized. ─────────────────────────────── */
int64_t age_ms; /* recency: now ts */
double decay; /* cog_decay_factor over that age */
double factual_now; /* factual · decay */
double relational_now;
double associative_now;
int stale; /* grounding fallen below its floor */
} CogGrounding;
/* Read an edge's vector as of `now_ms`. Pure — never writes. An edge with no
* GRD1 block still has an associative strength (its accrued hebb) and a polarity
* (its signed authored weight); `present` says whether the grounding dimensions
* have ever been established, and an unestablished dimension is reported as such
* rather than defaulted to a passing value. */
int cog_grounding_parse(const StoreEdge* e, int64_t now_ms, CogGrounding* out);
/* Serialize the STORED half of the vector, preserving pre-existing non-GRD1
* metadata. Returns an owned string. Derived fields are not written. */
char* cog_grounding_metadata(const char* base_meta, const CogGrounding* g);
/* ── §7.2 CONSOLIDATION-GATED SUPERSESSION ──────────────────────────────────
*
* Supersession is not recording — it is CONSOLIDATION, gated by salience, which
* is why you remember the argument and not the commute. Significance is
* evaluated PER-DIMENSION but the record is the WHOLE VECTOR: any dimension
* moving enough to matter triggers a supersession, and the new version captures
* every dimension as it stood at that instant. Versioning axes independently
* would make the joint state unreconstructable, and the joint state is the point
* — it is what makes "stayed true, became wrong" visible as an event (factual
* holding steady across versions while relational degrades).
*
* There is deliberately no epsilon in this enum or in the function that computes
* it. Every test is a floor crossing or a sign change, both exact. Two of them
* are INHERENTLY significant because they are discrete state changes rather than
* drift, and those bypass the salience gate entirely. */
typedef enum {
COG_SIG_NONE = 0, /* nothing decision-relevant moved — DO NOT RECORD */
COG_SIG_FIRST_RECORD = 1, /* no prior version exists */
COG_SIG_POLARITY_FLIP = 2, /* INHERENT: support ↔ contradiction, or ignorance
* ↔ either. A discrete change of state. */
COG_SIG_PROVENANCE_CHANGE = 3, /* INHERENT: told → observed is a categorical
* upgrade in what the relation is entitled to. */
COG_SIG_FACTUAL_FLOOR = 4, /* crossed the assert floor, factual axis */
COG_SIG_RELATIONAL_FLOOR = 5, /* crossed the assert floor, relational axis */
COG_SIG_AGREEMENT_FLIP = 6, /* factual/relational agreement changed sign */
COG_SIG_DIRECTION_REVERSAL = 7 /* a gradient reversed direction */
} CogSignificance;
CogSignificance cog_grounding_significant(const CogGrounding* prev,
const CogGrounding* now,
double floor, double rel_floor);
const char* cog_significance_name(CogSignificance s);
/* 1 iff this reason is a discrete state change that consolidates regardless of
* salience (polarity flip, provenance change, first record). */
int cog_significance_inherent(CogSignificance s);
/* ── §7.3 RECORDING: supersession of the EDGE, never an overwrite ────────────
* Writes version seq+1 as a NEW edge record with the same endpoints and relation
* and id "<root>#<seq+1>", carrying a GRD1 `p` pointer to its predecessor. The
* predecessor is never touched. The chain IS the trajectory: 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.
* Returns the version written (>=1), or <0 on error. */
int cog_grounding_record(EngramPagedStore* s, const StoreEdge* base,
const CogGrounding* g, char* out_id, size_t out_id_cap);
/* Walk forward from a base edge id to its newest recorded version. Point reads
* only; consolidation is gated, so the chain is short. Returns the highest
* version found (0 = the base record is the only one). */
int cog_grounding_head(EngramPagedStore* s, const char* base_id,
StoreEdge* out, int max_versions);
/* VOLATILITY — derived, never stored: the mean absolute per-version change of a
* dimension across the recorded chain. Feeds the equally-derived `confidence`
* (high grounding AND low volatility), which is likewise never stored. */
typedef struct {
int n_versions;
double factual_volatility;
double relational_volatility;
double factual_drift; /* signed: newest oldest */
double relational_drift;
int stayed_true_became_wrong; /* factual steady while relational degraded */
} CogTrajectory;
int cog_grounding_trajectory(EngramPagedStore* s, const char* base_id,
int64_t now_ms, CogTrajectory* out);
/* ── §7.4 ASSERTION GATES ON BOTH FLOORS ────────────────────────────────────
* A well-evidenced claim must not earn the right to be asserted regardless of
* whether it means the right thing. `may_assert` requires the decayed factual
* grounding to clear `floor` AND the decayed relational grounding to clear
* `rel_floor`. A relation whose relational axis has never been established does
* not pass by default — it is reported unestablished and refused, because
* defaulting it to passing is exactly the exemption §0 forbids. Traversal is
* untouched: activation still conducts on the factual/associative side, so a
* relation can remain thinkable while ceasing to be assertable. */
typedef struct {
int may_assert;
int found; /* any relation at all on this claim */
int relational_established;
int still_held; /* DERIVED: node present and not tombstoned */
double factual; /* best decayed factual grounding */
double relational; /* the SAME edge's relational axis, not a max */
double polarity;
double cos_angle;
int agreement;
CogProvClass prov;
char best_edge[192];
char binding_value[128];
int n_edges;
} CogAssertion;
int cog_assert_two_axis(EngramPagedStore* s, const char* claim_id,
double floor, double rel_floor, int64_t now_ms,
CogAssertion* out);
#endif /* ENGRAM_COGNITION_H */