9a0266cbf9
Flag-on checkpoint now full-walks the resident graph: store_put_node (WM weight, activation_count, last_activated, wm_anchor) + store_put_edge (hebb, last_fired) for every node/edge, then engram_checkpoint. Uses store_put_edge (idempotent upsert) not store_hebb_batch, because activation FORMS new hebbian-associate edges that bypass the create hook and delta-only hebb_batch can't create them. Store-on boot now applies the same WM-halving + floor + cap transforms as engram_load. This is the hebb-survives-restart fix. Gate: reboot from neuron.egm with snapshot.json deleted -> edge hebb + activation_count survive unchanged, WM weight survives with identical boot transform; negative control proves persist is load-bearing (hebb->0 without it). M1 33/33 + M2 36/36 + M3 parity PASS, ASan/UBSan clean, flag-off untouched. Engine unchanged (boundary held).
159 lines
7.1 KiB
Bash
Executable File
159 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# M3.5 PRE-FLIP GATE. Pure C harness (NOT elb/elc): links the real el_runtime.c
|
|
# native engram builtins + engram_store.c and proves activation-time field
|
|
# mutations (edge hebb, node activation_count, WM weight) persist through a
|
|
# checkpoint and survive a reboot from neuron.egm with snapshot.json DELETED.
|
|
# Writes ONLY under a throwaway /tmp dir with a throwaway HOME.
|
|
set -u
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
RT="$HERE/../../lang/runtime/el_runtime.c"
|
|
ST="$HERE/../../lang/runtime/engram_store.c"
|
|
INC="$HERE/../../lang/runtime"
|
|
WORK="$(mktemp -d /tmp/engram-m35-XXXXXX)"
|
|
BIN="$WORK/m35"
|
|
export HOME="$WORK/home"; mkdir -p "$HOME" # never touch real ~/.neuron
|
|
export ENGRAM_WAL_SYNC=always
|
|
unset ENGRAM_STORE
|
|
fail=0
|
|
|
|
echo "== compiling harness (gcc: el_runtime.c + engram_store.c + test_m35_hebb_persist.c) =="
|
|
gcc -O1 -std=c11 -I "$INC" "$HERE/test_m35_hebb_persist.c" "$RT" "$ST" -lcurl -o "$BIN" 2>"$WORK/cc.log"
|
|
if [ $? -ne 0 ]; then echo "COMPILE FAILED:"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; fi
|
|
|
|
echo
|
|
echo "== 0) flag-OFF: seed+activate+checkpoint must NOT touch the store =="
|
|
DOFF="$WORK/off"; mkdir -p "$DOFF"
|
|
( unset ENGRAM_STORE; "$BIN" offcheck "$DOFF" )
|
|
[ $? -ne 0 ] && { echo "FAIL: offcheck"; fail=1; }
|
|
[ -e "$DOFF/neuron.egm" ] && { echo "FAIL: neuron.egm created while flag OFF"; fail=1; } \
|
|
|| echo " ok: no neuron.egm created with flag OFF"
|
|
|
|
echo
|
|
echo "== 1) POSITIVE: ENGRAM_STORE=1 seed -> activate -> checkpoint(field-persist) -> close =="
|
|
DPOS="$WORK/pos"; mkdir -p "$DPOS"
|
|
ENGRAM_STORE=1 "$BIN" pos_seed "$DPOS" || { echo "FAIL: pos_seed"; fail=1; }
|
|
[ -e "$DPOS/neuron.egm" ] && echo " ok: neuron.egm created" || { echo "FAIL: neuron.egm missing"; fail=1; }
|
|
|
|
echo
|
|
echo "== 2) reboot from neuron.egm with snapshot.json DELETED (must never read JSON) =="
|
|
rm -f "$DPOS/snapshot.json"
|
|
ENGRAM_STORE=1 "$BIN" pos_reboot "$DPOS" || { echo "FAIL: pos_reboot"; fail=1; }
|
|
|
|
echo
|
|
echo "== 3) NEGATIVE CONTROL: seed -> activate -> close WITHOUT the field-persist checkpoint =="
|
|
DNEG="$WORK/neg"; mkdir -p "$DNEG"
|
|
ENGRAM_STORE=1 "$BIN" neg_seed "$DNEG" || { echo "FAIL: neg_seed"; fail=1; }
|
|
rm -f "$DNEG/snapshot.json"
|
|
ENGRAM_STORE=1 "$BIN" neg_reboot "$DNEG" || { echo "FAIL: neg_reboot"; fail=1; }
|
|
|
|
echo
|
|
echo "== 4) assertions (python over the JSON exports) =="
|
|
python3 - "$DPOS" "$DNEG" <<'PY'
|
|
import json, sys, os
|
|
WM_FLOOR = 0.05
|
|
HEBB_MIN = 1e-6
|
|
|
|
def load(d, name):
|
|
with open(os.path.join(d, name)) as f: return json.load(f)
|
|
|
|
def node_by_label(g, label):
|
|
for n in g["nodes"]:
|
|
if n.get("label") == label: return n
|
|
return None
|
|
|
|
def edge_between(g, a_id, b_id):
|
|
for e in g["edges"]:
|
|
if e.get("from_id") == a_id and e.get("to_id") == b_id:
|
|
return e
|
|
return None
|
|
|
|
rc = 0
|
|
def check(cond, msg):
|
|
global rc
|
|
if cond: print(f" PASS: {msg}")
|
|
else: print(f" FAIL: {msg}"); rc = 1
|
|
|
|
dpos, dneg = sys.argv[1], sys.argv[2]
|
|
pre = load(dpos, "pre_reboot.json")
|
|
rebt = load(dpos, "reboot.json")
|
|
|
|
pa, pb = node_by_label(pre, "hebb-a"), node_by_label(pre, "hebb-b")
|
|
ra = node_by_label(rebt, "hebb-a")
|
|
assert pa and pb and ra, "target nodes missing"
|
|
pe = edge_between(pre, pa["id"], pb["id"])
|
|
re = edge_between(rebt, pa["id"], pb["id"])
|
|
assert pe and re, "target edge missing"
|
|
|
|
pre_hebb = pe.get("hebb", 0.0)
|
|
rebt_hebb = re.get("hebb", 0.0)
|
|
pre_ac = pa.get("activation_count", 0)
|
|
rebt_ac = ra.get("activation_count", 0)
|
|
pre_wm = pa.get("working_memory_weight", 0.0)
|
|
rebt_wm = ra.get("working_memory_weight", 0.0)
|
|
|
|
print(f" edge hebb-a->hebb-b : pre={pre_hebb!r} reboot={rebt_hebb!r}")
|
|
print(f" node hebb-a act_cnt : pre={pre_ac!r} reboot={rebt_ac!r}")
|
|
print(f" node hebb-a wm : pre={pre_wm!r} reboot={rebt_wm!r} (halved+floored expected)")
|
|
|
|
# --- learning actually happened this run (else the test proves nothing) ---
|
|
check(pre_hebb > HEBB_MIN, f"activation raised edge hebb above 0 (pre={pre_hebb})")
|
|
check(pre_ac >= 1, f"activation reinforced node activation_count (pre={pre_ac})")
|
|
check(pre_wm > 0.0, f"activation promoted node to working memory (pre_wm={pre_wm})")
|
|
|
|
# --- the load-bearing survival assertions after a real delete-JSON reboot ---
|
|
check(abs(rebt_hebb - pre_hebb) < 1e-12,
|
|
f"edge hebb SURVIVED reboot unchanged ({rebt_hebb} == {pre_hebb})")
|
|
check(rebt_ac == pre_ac,
|
|
f"node activation_count SURVIVED reboot unchanged ({rebt_ac} == {pre_ac})")
|
|
|
|
# --- WM weight: must equal the JSON path's boot transform exactly (halve+floor) ---
|
|
expected_wm = pre_wm * 0.5
|
|
if expected_wm < WM_FLOOR: expected_wm = 0.0
|
|
check(abs(rebt_wm - expected_wm) < 1e-9,
|
|
f"node WM weight SURVIVED with the SAME boot transform as JSON path "
|
|
f"(reboot={rebt_wm} == halve+floor(pre)={expected_wm})")
|
|
check(expected_wm > 0.0,
|
|
f"WM survival is observable (halved weight stays above floor: {expected_wm} > {WM_FLOOR})")
|
|
|
|
# --- NEGATIVE CONTROL: without the field-persist step the learning is LOST ---
|
|
npre = load(dneg, "neg_pre.json")
|
|
nrebt = load(dneg, "neg_reboot.json")
|
|
na_pre = node_by_label(npre, "hebb-a")
|
|
na_rebt = node_by_label(nrebt, "hebb-a")
|
|
ne_pre = edge_between(npre, na_pre["id"], node_by_label(npre, "hebb-b")["id"])
|
|
ne_rebt = edge_between(nrebt, na_rebt["id"], node_by_label(nrebt, "hebb-b")["id"])
|
|
print(f" [neg] edge hebb : pre={ne_pre.get('hebb',0.0)!r} reboot={ne_rebt.get('hebb',0.0)!r}")
|
|
print(f" [neg] node act_cnt : pre={na_pre.get('activation_count',0)!r} reboot={na_rebt.get('activation_count',0)!r}")
|
|
check(ne_pre.get("hebb", 0.0) > HEBB_MIN,
|
|
f"[neg] activation DID raise hebb in RAM (pre={ne_pre.get('hebb',0.0)})")
|
|
check(ne_rebt.get("hebb", 0.0) == 0.0,
|
|
"[neg] WITHOUT checkpoint field-persist, edge hebb is LOST on reboot (==0) — fix is load-bearing")
|
|
check(na_rebt.get("activation_count", 0) == 0,
|
|
"[neg] WITHOUT checkpoint field-persist, activation_count is LOST on reboot (==0)")
|
|
|
|
sys.exit(rc)
|
|
PY
|
|
[ $? -ne 0 ] && fail=1
|
|
|
|
echo
|
|
echo "== 5) ASan+UBSan build, exercise the full persist+reboot flow (leaks off — harness intentionally leaks el_strdup) =="
|
|
SANBIN="$WORK/m35.san"
|
|
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
|
|
-I "$INC" "$HERE/test_m35_hebb_persist.c" "$RT" "$ST" -lcurl -o "$SANBIN" 2>"$WORK/san_cc.log"
|
|
if [ $? -ne 0 ]; then echo " SAN COMPILE FAILED:"; tail -20 "$WORK/san_cc.log"; fail=1; else
|
|
export ASAN_OPTIONS=detect_leaks=0
|
|
DSAN="$WORK/san"; mkdir -p "$DSAN"
|
|
ENGRAM_STORE=1 "$SANBIN" pos_seed "$DSAN" >/dev/null 2>"$WORK/san_run.log" && \
|
|
{ rm -f "$DSAN/snapshot.json"; ENGRAM_STORE=1 "$SANBIN" pos_reboot "$DSAN" >/dev/null 2>>"$WORK/san_run.log"; }
|
|
if grep -qiE 'runtime error|AddressSanitizer|UndefinedBehavior|ERROR: ' "$WORK/san_run.log"; then
|
|
echo " FAIL: sanitizer findings:"; grep -iE 'runtime error|Sanitizer|ERROR' "$WORK/san_run.log" | head; fail=1
|
|
else
|
|
echo " ok: ASan+UBSan clean across pos_seed/checkpoint/reboot (field-persist, boot laundering)"
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
if [ "$fail" -eq 0 ]; then echo "================ M3.5 HEBB-PERSIST GATE: PASS ================"; else echo "================ M3.5 HEBB-PERSIST GATE: FAIL ================"; fi
|
|
rm -rf "$WORK"
|
|
exit $fail
|