engram tiered storage M3: wire store behind ENGRAM_STORE (default off) + .egm rename

Caller-side shim in el_runtime.c maps EngramNode/Edge <-> StoreNode/Edge; engine
keeps zero soul deps (libengram boundary, design §10). Flag off = today's JSON
path byte-for-byte (proven: no neuron.egm created, graph identical). Flag on =
engram_open (import snapshot.json once into neuron.egm, else WAL-replay) +
resident load; node/edge create + forget dual-write via guarded hooks. Files
renamed engram.store->neuron.egm, engram.wal->neuron.wal.

Gate: M3 parity PASS (graph on==off byte-exact modulo ordering; snapshot round-trip;
reboot-from-egm with snapshot.json deleted; activation set+sequence identical;
ASan/UBSan clean). M1 33/33 + M2 36/36 green post-rename.

Known gap (pre-flip): in-place hebb/WM/activation_count updates during activation
are not yet persisted to the store (create/connect/forget are). Must close before
live flip so learned edges survive restart.
This commit is contained in:
2026-08-11 23:21:21 -05:00
parent 8affb1d6e0
commit a72145b44e
6 changed files with 588 additions and 12 deletions
+7 -7
View File
@@ -6,7 +6,7 @@
*
* Covers §7/M2 gates:
* 1 replay parity — random op stream: normal-durable path == crash-recover path
* 2 torn-tail fuzz — truncate engram.wal at EVERY byte offset → never crash,
* 2 torn-tail fuzz — truncate neuron.wal at EVERY byte offset → never crash,
* recover to the last intact record (contiguous prefix)
* 3 checkpoint-crash — kill at each checkpoint phase → converge, no loss past fsync
* 4 torn-page + WAL — corrupt a store page under WAL coverage → redo re-derives
@@ -215,19 +215,19 @@ static void test_replay_parity(void){
/* ═══════════════════════════ TEST 2 — torn-tail fuzz ═══════════════════════ */
#define TT_NODES 14
static void test_torn_tail(void){
printf("\n== torn-tail fuzz: truncate engram.wal at every byte offset ==\n");
printf("\n== torn-tail fuzz: truncate neuron.wal at every byte offset ==\n");
char base[600]; mk_dir("tornbase", base, sizeof base);
EngramPagedStore* s = engram_open(base);
for (int i=0;i<TT_NODES;i++){ StoreNode n; gen_node(i,0,&n); store_put_node(s,&n); store_node_free(&n); }
store__crash(s); /* leave store(at ckpt) + full WAL on disk */
char sp[700], wp[700]; snprintf(sp,sizeof sp,"%s/engram.store",base); snprintf(wp,sizeof wp,"%s/engram.wal",base);
char sp[700], wp[700]; snprintf(sp,sizeof sp,"%s/neuron.egm",base); snprintf(wp,sizeof wp,"%s/neuron.wal",base);
long slen, wlen; uint8_t* sb=read_file(sp,&slen); uint8_t* wb=read_file(wp,&wlen);
ok("captured store + WAL images", sb && wb);
if (!sb || !wb) return;
char work[600]; mk_dir("tornwork", work, sizeof work);
char wsp[700], wwp[700]; snprintf(wsp,sizeof wsp,"%s/engram.store",work); snprintf(wwp,sizeof wwp,"%s/engram.wal",work);
char wsp[700], wwp[700]; snprintf(wsp,sizeof wsp,"%s/neuron.egm",work); snprintf(wwp,sizeof wwp,"%s/neuron.wal",work);
int crashes=0, dirty_check=0, non_prefix=0, full_recovered=0;
for (long t=0; t<=wlen; t++){
@@ -300,7 +300,7 @@ static void test_torn_page(void){
/* corrupt the highest-id NODE data page on disk (its records are post-checkpoint,
* so the WAL still covers them). */
char sp[700]; snprintf(sp,sizeof sp,"%s/engram.store",dir);
char sp[700]; snprintf(sp,sizeof sp,"%s/neuron.egm",dir);
long slen; uint8_t* sb=read_file(sp,&slen);
long pages = slen/16384;
long victim = -1;
@@ -379,8 +379,8 @@ static void test_legacy_import(void){
EngramPagedStore* s = engram_open(dir); /* store absent + snapshot present → import */
ok("engram_open imported the snapshot", s!=NULL);
char sp[700]; snprintf(sp,sizeof sp,"%s/engram.store",dir); struct stat st;
ok("engram.store created by import", stat(sp,&st)==0);
char sp[700]; snprintf(sp,sizeof sp,"%s/neuron.egm",dir); struct stat st;
ok("neuron.egm created by import", stat(sp,&st)==0);
if (!s) return;
int nmiss=0, embmiss=0;