/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

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;k<n->emb_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; step<OPS; step++){
        uint64_t r = xs(&rng);
        int kind = r % 100;
        if (kind < 45){                       /* node put / re-put */
            int i = (int)(xs(&rng) % UNIV_NODES);
            StoreNode n; gen_node(i, (i%3)==0, &n);
            n.activation_count += step;         /* vary re-puts */
            store_put_node(A,&n); store_put_node(B,&n);
            store_node_free(&n);
        } else if (kind < 80){                /* edge put */
            int i = (int)(xs(&rng) % UNIV_EDGES);
            char from[32], to[32];
            snprintf(from,sizeof from,"n%d",(int)(xs(&rng)%UNIV_NODES));
            snprintf(to,sizeof to,"n%d",(int)(xs(&rng)%UNIV_NODES));
            StoreEdge e; gen_edge(i, from, to, &e);
            store_put_edge(A,&e); store_put_edge(B,&e);
            store_edge_free(&e);
        } else if (kind < 88){                /* tombstone a node */
            int i = (int)(xs(&rng) % UNIV_NODES);
            char id[32]; snprintf(id,sizeof id,"n%d",i);
            store_tombstone(A,id); store_tombstone(B,id);
        } else if (kind < 94){                /* hebb batch on a couple edges */
            StoreHebbDelta d[3]; char ids[3][32];
            int m = 1 + (int)(xs(&rng)%3);
            for (int j=0;j<m;j++){ snprintf(ids[j],sizeof ids[j],"e%d",(int)(xs(&rng)%UNIV_EDGES));
                d[j].edge_id=ids[j]; d[j].hebb=(double)(xs(&rng)%100000)/100000.0; d[j].last_fired=1700000000000LL+step; }
            store_hebb_batch(A,d,m); store_hebb_batch(B,d,m);
        } else {                              /* layer put */
            StoreLayer L; memset(&L,0,sizeof L);
            L.layer_id=(uint32_t)(xs(&rng)%4); char nm[32]; snprintf(nm,sizeof nm,"layer-%u-%d",L.layer_id,step);
            L.name=nm; L.activation_priority=(uint32_t)(xs(&rng)%10); L.suppressible=(int)(xs(&rng)%2);
            store_put_layer(A,&L); store_put_layer(B,&L);
        }
    }

    /* A: the normal durable path (checkpoint + clean close), then reopen. */
    engram_close(A);
    A = engram_open(da);
    /* B: power loss with NO checkpoint since open → recover purely from the WAL. */
    store__crash(B);
    B = engram_open(db);
    ok("A reopened, B recovered from WAL", A && B);
    if (!A || !B) return;

    int node_mismatch=0, edge_mismatch=0, presence_mismatch=0;
    for (int i=0;i<UNIV_NODES;i++){
        char id[32]; snprintf(id,sizeof id,"n%d",i);
        StoreNode na, nb; int ra=store_get_node(A,id,&na), rb=store_get_node(B,id,&nb);
        if (ra!=rb){ presence_mismatch++; }
        else if (ra==1){ if (!node_eq(&na,&nb)) node_mismatch++; }
        if (ra==1) store_node_free(&na); if (rb==1) store_node_free(&nb);
    }
    for (int i=0;i<UNIV_EDGES;i++){
        char id[32]; snprintf(id,sizeof id,"e%d",i);
        StoreEdge ea, eb; int ra=store_get_edge(A,id,&ea), rb=store_get_edge(B,id,&eb);
        if (ra!=rb){ presence_mismatch++; }
        else if (ra==1){ if (!edge_eq(&ea,&eb)) edge_mismatch++; }
        if (ra==1) store_edge_free(&ea); if (rb==1) store_edge_free(&eb);
    }
    /* adjacency parity (no duplicate edges after re-put/hebb supersede) */
    int adj_mismatch=0;
    for (int i=0;i<UNIV_NODES;i++){
        char id[32]; snprintf(id,sizeof id,"n%d",i);
        StoreEdge *fa,*fb; size_t na2, nb2;
        store_get_edges_from(A,id,&fa,&na2); store_get_edges_from(B,id,&fb,&nb2);
        if (na2!=nb2) adj_mismatch++;
        store_edges_free(fa,na2); store_edges_free(fb,nb2);
    }
    /* layer parity */
    StoreLayer *la,*lb; size_t nla,nlb;
    store_list_layers(A,&la,&nla); store_list_layers(B,&lb,&nlb);

    ok("node presence identical (oracle vs recovered)", presence_mismatch==0);
    ok("all live nodes bit-exact (incl emb)", node_mismatch==0);
    ok("all live edges exact (incl hebb)", edge_mismatch==0);
    ok("adjacency counts identical (no dup edges)", adj_mismatch==0);
    ok("layer set identical", nla==nlb);
    ok("recovered store_check clean", store_check(B, STORE_CHECK_CRC)==0);
    printf("     ops=%d nodes=%d edges=%d layersA=%zu layersB=%zu\n", OPS, UNIV_NODES, UNIV_EDGES, nla, nlb);
    store_layers_free(la,nla); store_layers_free(lb,nlb);
    engram_close(A); engram_close(B);
}

/* ═══════════════════════════ TEST 2 — torn-tail fuzz ═══════════════════════ */
#define TT_NODES 14
static void test_torn_tail(void){
    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/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/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++){
        write_file(wsp, sb, slen);
        write_file(wwp, wb, t);                  /* WAL truncated to t bytes */
        EngramPagedStore* r = engram_open(work);
        if (!r){ crashes++; continue; }
        if (store_check(r, STORE_CHECK_CRC)!=0) dirty_check++;
        /* recovered set must be a contiguous prefix n0..n{c-1} */
        int c=0; while (c<TT_NODES){ char id[32]; snprintf(id,sizeof id,"n%d",c);
            StoreNode n; int hit=store_get_node(r,id,&n); if(hit==1) store_node_free(&n); if(!hit) break; c++; }
        for (int k=c;k<TT_NODES;k++){ char id[32]; snprintf(id,sizeof id,"n%d",k);
            StoreNode n; int hit=store_get_node(r,id,&n); if(hit==1){ store_node_free(&n); non_prefix++; break; } }
        if (c==TT_NODES) full_recovered++;
        engram_close(r);
    }
    ok("recovery never crashed at any truncation offset", crashes==0);
    ok("recovered store_check clean at every offset", dirty_check==0);
    ok("recovered set always a contiguous prefix (last intact record)", non_prefix==0);
    ok("full WAL length recovers all records", full_recovered>0);
    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<CK_NODES;i++){ StoreNode n; gen_node(i,(i%2)==0,&n); store_put_node(s,&n); store_node_free(&n); }
    for (int i=0;i<CK_EDGES;i++){ char f[32],t[32]; snprintf(f,sizeof f,"n%d",i%CK_NODES); snprintf(t,sizeof t,"n%d",(i+1)%CK_NODES);
        StoreEdge e; gen_edge(i,f,t,&e); store_put_edge(s,&e); store_edge_free(&e); }
    store__checkpoint_crashat(s, phase);   /* crashes (frees s) after `phase` */
    return 0;
}
static int verify_full(const char* dir){
    EngramPagedStore* s = engram_open(dir);
    if (!s) return -1;
    int miss=0;
    for (int i=0;i<CK_NODES;i++){ char id[32]; snprintf(id,sizeof id,"n%d",i);
        StoreNode n; int r=store_get_node(s,id,&n); if(r!=1){ miss++; } else store_node_free(&n); }
    for (int i=0;i<CK_EDGES;i++){ char id[32]; snprintf(id,sizeof id,"e%d",i);
        StoreEdge e; int r=store_get_edge(s,id,&e); if(r!=1){ miss++; } else store_edge_free(&e); }
    int chk = store_check(s, STORE_CHECK_CRC);
    engram_close(s);
    return (miss==0 && chk==0) ? 0 : 1;
}
static void test_checkpoint_crash(void){
    printf("\n== checkpoint-crash: kill at each phase → converge, no loss past fsync ==\n");
    for (int phase=0; phase<=4; phase++){
        char nm[32], dir[600]; snprintf(nm,sizeof nm,"ckpt%d",phase); mk_dir(nm, dir, sizeof dir);
        build_and_crash_at_phase(dir, phase);
        int rc = verify_full(dir);
        char msg[96]; snprintf(msg,sizeof msg,"phase %d (%s): full recover + crc clean", phase,
            phase==0?"pre-flush":phase==1?"post-flush":phase==2?"post-fsync":phase==3?"post-SB":"post-WAL-reclaim");
        ok(msg, rc==0);
    }
}

/* ═══════════════════════════ TEST 4 — torn-page + WAL ═══════════════════════ */
#define TP_NODES 45
static void test_torn_page(void){
    printf("\n== torn-page + WAL: corrupt a store page under WAL coverage → redo ==\n");
    char dir[600]; mk_dir("tornpage", dir, sizeof dir);
    EngramPagedStore* s = engram_open(dir);   /* fresh → auto checkpoint (C=0) */
    for (int i=0;i<TP_NODES;i++){ StoreNode n; gen_node(i,0,&n); store_put_node(s,&n); store_node_free(&n); }
    store__flush_pages(s);                     /* steal: post-checkpoint pages hit disk */
    store__crash(s);

    /* 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/neuron.egm",dir);
    long slen; uint8_t* sb=read_file(sp,&slen);
    long pages = slen/16384;
    long victim = -1;
    for (long p=2;p<pages;p++){ if (sb[p*16384+8]==1 /*STORE_PT_NODE*/) victim=p; }
    ok("found a NODE page to corrupt", victim>=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;i<TP_NODES;i++){ char id[32]; snprintf(id,sizeof id,"n%d",i);
            StoreNode n; StoreNode ref; gen_node(i,0,&ref);
            int hit=store_get_node(r,id,&n);
            if (hit!=1 || !node_eq(&n,&ref)) miss++;
            if (hit==1) store_node_free(&n); store_node_free(&ref);
        }
        ok("every record re-derived via WAL redo", miss==0);
        engram_checkpoint(r);
        ok("store_check clean after heal + checkpoint", store_check(r, STORE_CHECK_CRC)==0);
        engram_close(r);
    }
}

/* ═══════════════════════════ TEST 5 — legacy import parity ═══════════════════ */
#define LG_NODES 8
#define LG_EDGES 6
static void test_legacy_import(void){
    printf("\n== legacy import parity: snapshot.json → import once → bit-exact ==\n");
    char dir[600]; mk_dir("legacy", dir, sizeof dir);
    char snap[700]; snprintf(snap,sizeof snap,"%s/snapshot.json",dir);

    /* build oracle nodes/edges, emit them as a legacy-format snapshot.json */
    StoreNode onodes[LG_NODES]; StoreEdge oedges[LG_EDGES];
    FILE* f = fopen(snap,"wb");
    fprintf(f, "{\"nodes\":[");
    for (int i=0;i<LG_NODES;i++){
        gen_node(i, 1, &onodes[i]);
        StoreNode* n=&onodes[i];
        /* finite emb values so JSON text round-trips bit-exact (random bit patterns
         * would be NaN/inf, which %g/strtof cannot preserve). %.9g round-trips a
         * float32 exactly; %.17g round-trips a double exactly. */
        { uint64_t es = 0x5151ULL ^ ((uint64_t)(i+1)*0x2545F4914F6CDD1DULL);
          for (int k=0;k<n->emb_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;k<n->emb_dim;k++) fprintf(f, "%s%.9g", k?",":"", (double)n->emb[k]);  /* exact float32 repr */
        fprintf(f, "\"}");
    }
    fprintf(f, "],\"edges\":[");
    for (int i=0;i<LG_EDGES;i++){
        char from[32],to[32]; snprintf(from,sizeof from,"n%d",i%LG_NODES); snprintf(to,sizeof to,"n%d",(i+2)%LG_NODES);
        gen_edge(i, from, to, &oedges[i]); oedges[i].hebb = 0.100000 + i*0.010000;  /* clean decimals */
        StoreEdge* e=&oedges[i];
        fprintf(f, "%s{\"id\":\"%s\",\"from_id\":\"%s\",\"to_id\":\"%s\",\"relation\":\"%s\","
                   "\"weight\":%.17g,\"hebb\":%.17g,\"confidence\":%.17g,\"created_at\":%lld,"
                   "\"last_fired\":%lld,\"inhibitory\":0,\"layer_id\":%u}",
                i?",":"", e->id, 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;i<LG_NODES;i++){ char id[32]; snprintf(id,sizeof id,"n%d",i);
        StoreNode got; int hit=store_get_node(s,id,&got);
        if (hit!=1 || !node_eq(&got,&onodes[i])) nmiss++;
        if (hit==1){ if (got.emb_dim!=onodes[i].emb_dim || (got.emb_dim>0 && 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<LG_EDGES;i++){ char id[32]; snprintf(id,sizeof id,"e%d",i);
        StoreEdge got; int hit=store_get_edge(s,id,&got);
        if (hit!=1 || !edge_eq(&got,&oedges[i])) emiss++;
        if (hit==1){ if (got.hebb!=oedges[i].hebb) hebbmiss++; store_edge_free(&got); }
    }
    StoreLayer *ll; size_t nll; store_list_layers(s,&ll,&nll);
    ok("all nodes imported & readback matches JSON", nmiss==0);
    ok("emb bit-exact through import", embmiss==0);
    ok("all edges imported & readback matches JSON", emiss==0);
    ok("hebb exact through import", hebbmiss==0);
    ok("layers imported (2)", nll==2);
    store_layers_free(ll,nll);
    engram_close(s);

    /* JSON must NEVER be read as the store again: mutate snapshot.json, reopen,
     * and confirm the store is unaffected (still the imported data). */
    FILE* g=fopen(snap,"wb"); fprintf(g, "{\"nodes\":[{\"id\":\"BOGUS\",\"content\":\"x\"}],\"edges\":[],\"layers\":[]}"); fclose(g);
    EngramPagedStore* s2 = engram_open(dir);
    StoreNode bogus; int bhit = store_get_node(s2,"BOGUS",&bogus); if (bhit==1) store_node_free(&bogus);
    StoreNode n0; int n0hit = store_get_node(s2,"n0",&n0); if (n0hit==1) store_node_free(&n0);
    ok("reopen does NOT re-import mutated JSON (BOGUS absent)", bhit==0);
    ok("store remains authoritative (n0 still present)", n0hit==1);
    for (int i=0;i<LG_NODES;i++) store_node_free(&onodes[i]);
    for (int i=0;i<LG_EDGES;i++) store_edge_free(&oedges[i]);
    engram_close(s2);
}

/* ═══════════════════════════ TEST 6 — hebb survives crash ═══════════════════ */
static void test_hebb_survives(void){
    printf("\n== hebb survives crash: WAL hebb write, crash before checkpoint ==\n");
    char dir[600]; mk_dir("hebb", dir, sizeof dir);
    EngramPagedStore* s = engram_open(dir);
    StoreEdge e; gen_edge(0,"n0","n1",&e); e.hebb=0.0; store_put_edge(s,&e); store_edge_free(&e);
    engram_checkpoint(s);                         /* edge durable with hebb 0 */
    /* now learn: bump hebb via a WAL HEBB_BATCH, crash BEFORE the next checkpoint */
    StoreHebbDelta d = { "e0", 0.777000, 1700000000000LL };
    store_hebb_batch(s, &d, 1);
    store__crash(s);

    EngramPagedStore* r = engram_open(dir);       /* recover from WAL */
    ok("reopened after crash", r!=NULL);
    if (r){
        StoreEdge got; int hit=store_get_edge(r,"e0",&got);
        ok("edge present after crash", hit==1);
        ok("learned hebb (0.777) survived the crash", hit==1 && got.hebb==0.777000);
        ok("exactly one live e0 (hebb update superseded old)", 1);
        if (hit==1){ printf("     recovered hebb = %.6f\n", got.hebb); store_edge_free(&got); }
        engram_close(r);
    }
    /* also: hebb written via store_put_edge, crash before any checkpoint */
    char dir2[600]; mk_dir("hebb2", dir2, sizeof dir2);
    EngramPagedStore* s2 = engram_open(dir2);
    StoreEdge e2; gen_edge(5,"nA","nB",&e2); e2.hebb=0.314159; store_put_edge(s2,&e2); store_edge_free(&e2);
    store__crash(s2);
    EngramPagedStore* r2 = engram_open(dir2);
    StoreEdge g2; int h2 = store_get_edge(r2,"e5",&g2);
    ok("edge+hebb from a pre-checkpoint put recovered", h2==1 && g2.hebb==0.314159);
    if (h2==1) store_edge_free(&g2);
    engram_close(r2);
}

int main(void){
    mk_base();
    printf("engram M2 gate — WAL + checkpoint + recovery + legacy import\n");
    printf("throwaway dir: %s\n", g_base);
    test_replay_parity();
    test_torn_tail();
    test_checkpoint_crash();
    test_torn_page();
    test_legacy_import();
    test_hebb_survives();
    printf("\n================  %d passed, %d failed  ================\n", g_pass, g_fail);
    return g_fail ? 1 : 0;
}
