/* test_m35_hebb_persist.c — M3.5 PRE-FLIP GATE. * * Proves that in-place field mutations made during spreading activation — edge * `hebb` (+ last_fired), node `activation_count`, node working-memory weight — * PERSIST to the paged store and survive a restart from neuron.egm with * snapshot.json deleted. This is the "hebb-survives-restart" fix that gates the * live cutover. * * Same style as test_m3_parity.c: a REAL el-level harness linking the actual * el_runtime.c native engram builtins + engram_store.c, driving engram_node_full * / engram_connect / engram_activate_json / engram_save / engram_store_boot / * engram_store_checkpoint / engram_store_close directly from C. No EL interpreter. * * Modes (argv[1]), data dir (argv[2]): * pos_seed — ENGRAM_STORE=1: fresh store, seed a graph tuned so activation * co-activates a connected pair (edge hebb 0 -> ETA) and reinforces * nodes (activation_count 0 -> >=1, WM weight -> >0). Export the * post-activation resident graph to pre_reboot.json, then CHECKPOINT * (the M3.5 field-persist), then close. * pos_reboot— ENGRAM_STORE=1, snapshot.json deleted by runner: boot from * neuron.egm (WAL replay), export reboot.json, close. The values in * reboot.json are what actually survived the round-trip. * neg_seed — identical to pos_seed but WITHOUT the checkpoint field-persist * (negative control): activation mutations never reach the store. * neg_reboot— boot from neuron.egm, export neg_reboot.json, close. * offcheck — ENGRAM_STORE unset: seed+activate+checkpoint must NOT touch the * store (no neuron.egm, checkpoint returns 0). * * The pass/fail assertions live in run_m35_hebb_persist.sh (python over the JSON * exports): reboot.json must carry the learned hebb / activation_count and the * JSON-identical halved WM weight; neg_reboot.json must have LOST them. */ #include "el_runtime.h" #include #include #include extern int engram_store_enabled(void); extern el_val_t engram_store_boot(el_val_t data_dir); extern el_val_t engram_store_checkpoint(void); extern el_val_t engram_store_close(void); static el_val_t S(const char* s){ return EL_STR(s); } static el_val_t F(double d){ return el_from_float(d); } /* Two nodes with DISTINCT content (so the redundancy-suppression pass cannot * dedup one of them away) that both match the query strongly, wired by one * "associate" edge. A handful of weakly-related distractors make it a real * graph. On activation both A and B promote to working memory and co-activate, * so their edge's hebb rises from 0 to ENGRAM_HEBB_ETA. */ static void build_seed(void){ el_val_t a = engram_node_full(S("hebbian potentiation strengthens co-active memory links"), S("Concept"), S("hebb-a"), F(0.9), F(0.85), F(1.0), S("Semantic"), S("hebbian,memory,activation")); el_val_t b = engram_node_full(S("co-active memory links accrue hebbian associative weight"), S("Concept"), S("hebb-b"), F(0.9), F(0.85), F(1.0), S("Semantic"), S("hebbian,memory,weight")); el_val_t c = engram_node_full(S("unrelated culinary recipe for sourdough bread"), S("Fact"), S("distractor-1"), F(0.4), F(0.4), F(1.0), S("Semantic"), S("food")); el_val_t d = engram_node_full(S("the weather forecast predicts rain tomorrow afternoon"), S("Fact"), S("distractor-2"), F(0.4), F(0.4), F(1.0), S("Semantic"), S("weather")); engram_connect(a, b, F(0.8), S("associate")); /* the edge under test */ engram_connect(a, c, F(0.3), S("associate")); engram_connect(b, d, F(0.3), S("associate")); } static const char* QUERY = "hebbian potentiation co-active memory links associative weight"; static void export_graph(const char* dir, const char* name){ char p[1024]; snprintf(p, sizeof p, "%s/%s", dir, name); if (!engram_save(S(p))){ fprintf(stderr, "save %s failed\n", name); exit(2); } } int main(int argc, char** argv){ if (argc < 3){ fprintf(stderr, "usage: %s \n", argv[0]); return 2; } const char* mode = argv[1]; const char* dir = argv[2]; if (!strcmp(mode, "pos_seed") || !strcmp(mode, "neg_seed")){ int persist = !strcmp(mode, "pos_seed"); if (!engram_store_enabled()){ fprintf(stderr, "%s requires ENGRAM_STORE=1\n", mode); return 2; } if (!engram_store_boot(S(dir))){ fprintf(stderr, "store boot failed\n"); return 2; } build_seed(); el_val_t act = engram_activate_json(S(QUERY), (el_val_t)3); (void)act; /* Capture the post-activation resident state BEFORE persisting/closing. */ export_graph(dir, persist ? "pre_reboot.json" : "neg_pre.json"); printf("[%s] nodes=%lld edges=%lld\n", mode, (long long)(int64_t)engram_node_count(), (long long)(int64_t)engram_edge_count()); if (persist){ if (!engram_store_checkpoint()){ fprintf(stderr, "checkpoint failed\n"); return 2; } } /* neg mode: NO field-persist checkpoint. engram_store_close still flushes * pages, but no store_put_* ran post-creation, so the store keeps the * pristine creation-time field values (hebb=0, activation_count=0). */ engram_store_close(); return 0; } if (!strcmp(mode, "pos_reboot") || !strcmp(mode, "neg_reboot")){ if (!engram_store_enabled()){ fprintf(stderr, "%s requires ENGRAM_STORE=1\n", mode); return 2; } /* snapshot.json deleted by the runner — boot MUST come from neuron.egm. */ if (!engram_store_boot(S(dir))){ fprintf(stderr, "reboot boot failed\n"); return 2; } export_graph(dir, !strcmp(mode, "pos_reboot") ? "reboot.json" : "neg_reboot.json"); printf("[%s] nodes=%lld edges=%lld\n", mode, (long long)(int64_t)engram_node_count(), (long long)(int64_t)engram_edge_count()); engram_store_close(); return 0; } if (!strcmp(mode, "offcheck")){ int en = engram_store_enabled(); el_val_t boot = engram_store_boot(S(dir)); /* no-op with flag off */ build_seed(); engram_activate_json(S(QUERY), (el_val_t)3); el_val_t ck = engram_store_checkpoint(); /* must be a no-op */ printf("[offcheck] enabled=%d boot=%lld checkpoint=%lld\n", en, (long long)(int64_t)boot, (long long)(int64_t)ck); return (en == 0 && (int64_t)boot == 0 && (int64_t)ck == 0) ? 0 : 1; } fprintf(stderr, "unknown mode %s\n", mode); return 2; }