From 091cc1fc0ef8abf7215e915557454d14cb9eb67f Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Tue, 21 Jul 2026 11:50:59 -0500 Subject: [PATCH] Fix issue #150: fresh-install genesis boot SIGSEGV in mem_save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fresh-install (SOUL_CGI_ID=ntn-genesis) boot crashed with "Segmentation fault: 11" right after the http server came up — a real customer's very first boot. Backtrace: strcmp(0x1) <- str_eq (el_runtime.c:219) <- mem_save <- awareness_run Root cause: the el runtime's engram_save returns an Int (1 = ok, 0 = failure), but mem_save did `str_eq(engram_save(path), "")`, treating the return as a String. str_eq runs EL_CSTR on it, which is a raw cast: EL_CSTR(1) = (char*)0x1. On a SUCCESSFUL save (return 1) strcmp then dereferences 0x1 and segfaults. Genesis is the first path that both seeds the brain AND saves it successfully on the very first awareness pass, so it crashes there; non-genesis boots (contract gate, refusal test) don't hit a successful early mem_save, which is why they passed. handle_api_consolidate had the identical latent bug. Fix: read engram_save's Int result and compare `== 0` instead of str_eq'ing it — in mem_save (memory.el) and handle_api_consolidate (neuron-api.el). Regression: pre-existing, NOT introduced by the immutability/floor rebuild. The pre-immutability build (1442ce2) genesis-crashes identically in the same unchanged mem_save; #159 never actually fixed #150 for a release-runtime build. Regenerated dist/soul.c + per-module dist/{memory,neuron-api}.c (3GB RSS watchdog, built against release el_runtime v1.0.0-20260501). Verified: genesis boot survives (/health 200, no segfault), verify-soul-contract.sh GATE PASS (PRESENCE + IMMUTABILITY), and the bounded-persona floor is still compiled in (BOUNDED PERSONA / SOUL_PERSONA_NAME strings present). --- dist/memory.c | 4 ++-- dist/neuron-api.c | 4 ++-- dist/soul.c | 8 ++++---- memory.el | 8 ++++++-- neuron-api.el | 6 ++++-- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/dist/memory.c b/dist/memory.c index fd77cef..a464f29 100644 --- a/dist/memory.c +++ b/dist/memory.c @@ -120,8 +120,8 @@ el_val_t mem_consolidate(void) { } el_val_t mem_save(el_val_t path) { - el_val_t save_result = engram_save(path); - if (str_eq(save_result, EL_STR(""))) { + el_val_t saved = engram_save(path); + if (saved == 0) { println(el_str_concat(el_str_concat(EL_STR("[memory] mem_save: engram_save failed for "), path), EL_STR(" \xe2\x80\x94 snapshot may be incomplete"))); } return 0; diff --git a/dist/neuron-api.c b/dist/neuron-api.c index 1a97d4a..c03371e 100644 --- a/dist/neuron-api.c +++ b/dist/neuron-api.c @@ -744,8 +744,8 @@ el_val_t handle_api_consolidate(el_val_t body) { el_val_t summary = json_get(body, EL_STR("summary")); el_val_t snap = state_get(EL_STR("soul_snapshot_path")); if (!str_eq(snap, EL_STR(""))) { - el_val_t save_result = engram_save(snap); - if (str_eq(save_result, EL_STR(""))) { + el_val_t saved = engram_save(snap); + if (saved == 0) { println(el_str_concat(el_str_concat(EL_STR("[api] consolidate: engram_save failed for "), snap), EL_STR(" \xe2\x80\x94 snapshot may be out of sync"))); } } diff --git a/dist/soul.c b/dist/soul.c index c7fb6eb..0265c00 100644 --- a/dist/soul.c +++ b/dist/soul.c @@ -25383,8 +25383,8 @@ el_val_t mem_consolidate(void) { } el_val_t mem_save(el_val_t path) { - el_val_t save_result = engram_save(path); - if (str_eq(save_result, EL_STR(""))) { + el_val_t saved = engram_save(path); + if (saved == 0) { println(el_str_concat(el_str_concat(EL_STR("[memory] mem_save: engram_save failed for "), path), EL_STR(" \xe2\x80\x94 snapshot may be incomplete"))); } return 0; @@ -29336,8 +29336,8 @@ el_val_t handle_api_consolidate(el_val_t body) { el_val_t summary = json_get(body, EL_STR("summary")); el_val_t snap = state_get(EL_STR("soul_snapshot_path")); if (!str_eq(snap, EL_STR(""))) { - el_val_t save_result = engram_save(snap); - if (str_eq(save_result, EL_STR(""))) { + el_val_t saved = engram_save(snap); + if (saved == 0) { println(el_str_concat(el_str_concat(EL_STR("[api] consolidate: engram_save failed for "), snap), EL_STR(" \xe2\x80\x94 snapshot may be out of sync"))); } } diff --git a/memory.el b/memory.el index 46d9f28..542ee6c 100644 --- a/memory.el +++ b/memory.el @@ -133,8 +133,12 @@ fn mem_consolidate() -> String { } fn mem_save(path: String) -> Void { - let save_result: String = engram_save(path) - if str_eq(save_result, "") { + // engram_save returns an Int (1 = ok, 0 = failure), NOT a String. Calling + // str_eq on it casts EL_CSTR(1) -> (char*)0x1 and SIGSEGVs on a SUCCESSFUL + // save — which is exactly what a fresh-install genesis boot does first + // (seeds the brain, saves, crashes). This is issue #150. Check the Int. + let saved: Int = engram_save(path) + if saved == 0 { println("[memory] mem_save: engram_save failed for " + path + " — snapshot may be incomplete") } } diff --git a/neuron-api.el b/neuron-api.el index 12ce4c5..e1196f1 100644 --- a/neuron-api.el +++ b/neuron-api.el @@ -725,8 +725,10 @@ fn handle_api_consolidate(body: String) -> String { let summary: String = json_get(body, "summary") let snap: String = state_get("soul_snapshot_path") if !str_eq(snap, "") { - let save_result: String = engram_save(snap) - if str_eq(save_result, "") { + // engram_save returns an Int (1 = ok, 0 = failure); str_eq on it derefs + // EL_CSTR(1)=0x1 and SIGSEGVs on success (issue #150). Check the Int. + let saved: Int = engram_save(snap) + if saved == 0 { println("[api] consolidate: engram_save failed for " + snap + " — snapshot may be out of sync") } }