/* test_wal_store.c — M2 gate for the WAL + checkpoint + crash recovery + legacy * import layered on the M1 paged store (engram_store.{c,h}). * * Pure C. Build: gcc -O2 test_wal_store.c ../../lang/runtime/engram_store.c -o t * Writes ONLY under a throwaway /tmp dir. Never touches ~/.neuron or live ports. * * Covers §7/M2 gates: * 1 replay parity — random op stream: normal-durable path == crash-recover path * 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 * 5 legacy import — synth snapshot.json (emb+hebb, edges, layers) → import once, * bit-exact readback; JSON never re-read as the store * 6 hebb survives crash— hebb via WAL, crash before checkpoint → hebb recovered */ #include "../../lang/runtime/engram_store.h" #include #include #include #include #include #include #include static int g_pass = 0, g_fail = 0; static void ok(const char* name, int cond){ printf(" [%s] %s\n", cond ? "PASS" : "FAIL", name); if (cond) g_pass++; else g_fail++; } static char g_base[512]; static void mk_base(void){ snprintf(g_base, sizeof g_base, "/tmp/engram-wal-test-%d", (int)getpid()); mkdir(g_base, 0700); } static void mk_dir(const char* name, char* out, size_t cap){ snprintf(out, cap, "%s/%s", g_base, name); mkdir(out, 0700); } /* deterministic RNG */ static uint64_t xs(uint64_t* s){ uint64_t x=*s; x^=x<<13; x^=x>>7; x^=x<<17; *s=x; return x; } /* ── small node/edge generators (kept compact so WAL frames stay small) ─────── */ static void gen_node(int i, int with_emb, StoreNode* n){ memset(n, 0, sizeof *n); uint64_t st = 0x1234ULL ^ ((uint64_t)(i+1)*0x9E3779B97F4A7C15ULL); char id[32]; snprintf(id, sizeof id, "n%d", i); n->id = strdup(id); char c[64]; snprintf(c, sizeof c, "content-of-node-%d-%llu", i, (unsigned long long)(xs(&st)%9999)); n->content = strdup(c); n->node_type = strdup("concept"); n->tier = strdup("Working"); n->salience = (double)(xs(&st)%100000)/7.0; n->importance = (double)(xs(&st)%100000)/11.0; n->confidence = (double)(xs(&st)%100000)/13.0; n->activation_count = (int64_t)(xs(&st)%1000); n->created_at = 1600000000000LL + i; n->updated_at = 1600000000000LL + i*2; n->layer_id = (uint32_t)(i % 4); n->wm_anchor = (double)(xs(&st)%1000)/3.0; if (with_emb){ n->emb_dim = 32; n->emb = (float*)malloc(sizeof(float)*n->emb_dim); for (int k=0;kemb_dim;k++){ uint32_t u=(uint32_t)xs(&st); memcpy(&n->emb[k],&u,4); } } } static void gen_edge(int i, const char* from, const char* to, StoreEdge* e){ memset(e, 0, sizeof *e); uint64_t st = 0xABCDULL ^ ((uint64_t)(i+1)*0xD1B54A32D192ED03ULL); char id[32]; snprintf(id, sizeof id, "e%d", i); e->id = strdup(id); e->from_id = strdup(from); e->to_id = strdup(to); e->relation = strdup("relates_to"); e->weight = (double)(xs(&st)%100000)/17.0; e->hebb = (double)(xs(&st)%100000)/100000.0; e->confidence = (double)(xs(&st)%100000)/19.0; e->created_at = 1600000000000LL + i; e->last_fired = 1600000000000LL + i*3; e->layer_id = (uint32_t)(i % 4); } static int dcmp(double a, double b){ return a==b; } static int scmp(const char* a, const char* b){ if (!a && !b) return 1; if (!a || !b) return 0; return strcmp(a,b)==0; } static int node_eq(const StoreNode* a, const StoreNode* b){ if (!scmp(a->id,b->id) || !scmp(a->content,b->content) || !scmp(a->node_type,b->node_type) || !scmp(a->tier,b->tier)) return 0; if (!dcmp(a->salience,b->salience) || !dcmp(a->importance,b->importance) || !dcmp(a->confidence,b->confidence) || a->activation_count!=b->activation_count || a->created_at!=b->created_at || a->updated_at!=b->updated_at || a->layer_id!=b->layer_id || !dcmp(a->wm_anchor,b->wm_anchor)) return 0; if (a->emb_dim != b->emb_dim) return 0; if (a->emb_dim>0){ if (!a->emb || !b->emb) return 0; if (memcmp(a->emb, b->emb, sizeof(float)*a->emb_dim)!=0) return 0; /* bit-exact */ } return 1; } static int edge_eq(const StoreEdge* a, const StoreEdge* b){ return scmp(a->id,b->id) && scmp(a->from_id,b->from_id) && scmp(a->to_id,b->to_id) && scmp(a->relation,b->relation) && dcmp(a->weight,b->weight) && dcmp(a->hebb,b->hebb) && dcmp(a->confidence,b->confidence) && a->created_at==b->created_at && a->last_fired==b->last_fired && a->layer_id==b->layer_id; } /* whole-file read / write helpers (for torn-tail + torn-page fuzzing) */ static uint8_t* read_file(const char* p, long* len){ FILE* f=fopen(p,"rb"); if(!f) return NULL; fseek(f,0,SEEK_END); long n=ftell(f); fseek(f,0,SEEK_SET); uint8_t* b=malloc(n?n:1); if(fread(b,1,n,f)!=(size_t)n){ fclose(f); free(b); return NULL; } fclose(f); *len=n; return b; } static void write_file(const char* p, const uint8_t* b, long len){ FILE* f=fopen(p,"wb"); fwrite(b,1,len,f); fclose(f); } /* ═══════════════════════════ TEST 1 — replay parity ═══════════════════════ */ #define UNIV_NODES 60 #define UNIV_EDGES 40 static void test_replay_parity(void){ printf("\n== replay parity: normal-durable path == crash-then-recover path ==\n"); char da[600], db[600]; mk_dir("parityA", da, sizeof da); mk_dir("parityB", db, sizeof db); EngramPagedStore* A = engram_open(da); EngramPagedStore* B = engram_open(db); ok("opened both stores", A && B); if (!A || !B) return; uint64_t rng = 0xF00DFACEULL; int OPS = 800; for (int step=0; step0); printf(" WAL bytes fuzzed=%ld full-recover offsets=%d\n", wlen, full_recovered); free(sb); free(wb); } /* ═══════════════════════════ TEST 3 — checkpoint-crash ═══════════════════════ */ #define CK_NODES 30 #define CK_EDGES 20 static int build_and_crash_at_phase(const char* dir, int phase){ EngramPagedStore* s = engram_open(dir); if (!s) return -1; for (int i=0;i=0); if (victim>=0){ for (int k=0;k<64;k++) sb[victim*16384 + 200 + k] ^= 0xA5; /* trash record area → bad crc */ write_file(sp, sb, slen); } free(sb); EngramPagedStore* r = engram_open(dir); /* heal torn page + replay WAL */ ok("reopened after page corruption", r!=NULL); if (r){ int miss=0; for (int i=0;iemb_dim;k++) n->emb[k] = (float)((double)(xs(&es)%2000001)/1000000.0 - 1.0); } fprintf(f, "%s{\"id\":\"%s\",\"content\":\"%s\",\"node_type\":\"%s\",\"tier\":\"%s\"," "\"salience\":%.17g,\"importance\":%.17g,\"confidence\":%.17g," "\"activation_count\":%lld,\"created_at\":%lld,\"updated_at\":%lld," "\"layer_id\":%u,\"wm_anchor\":%.17g,\"emb\":\"", i?",":"", n->id, n->content, n->node_type, n->tier, n->salience, n->importance, n->confidence, (long long)n->activation_count, (long long)n->created_at, (long long)n->updated_at, n->layer_id, n->wm_anchor); for (int k=0;kemb_dim;k++) fprintf(f, "%s%.9g", k?",":"", (double)n->emb[k]); /* exact float32 repr */ fprintf(f, "\"}"); } fprintf(f, "],\"edges\":["); for (int i=0;iid, e->from_id, e->to_id, e->relation, e->weight, e->hebb, e->confidence, (long long)e->created_at, (long long)e->last_fired, e->layer_id); } fprintf(f, "],\"layers\":["); fprintf(f, "{\"layer_id\":0,\"name\":\"SAFETY\",\"activation_priority\":9,\"suppressible\":0,\"transparent\":0,\"injectable\":0},"); fprintf(f, "{\"layer_id\":1,\"name\":\"CORE_IDENTITY\",\"activation_priority\":8,\"suppressible\":0,\"transparent\":1,\"injectable\":1}"); fprintf(f, "]}"); fclose(f); 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/neuron.egm",dir); struct stat st; ok("neuron.egm created by import", stat(sp,&st)==0); if (!s) return; int nmiss=0, embmiss=0; for (int i=0;i0 && memcmp(got.emb,onodes[i].emb,sizeof(float)*got.emb_dim)!=0)) embmiss++; store_node_free(&got); } } int emiss=0, hebbmiss=0; for (int i=0;i