091cc1fc0e
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).
200 lines
7.7 KiB
C
Generated
200 lines
7.7 KiB
C
Generated
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include "el_runtime.h"
|
|
|
|
el_val_t tier_working(void);
|
|
el_val_t tier_episodic(void);
|
|
el_val_t tier_canonical(void);
|
|
el_val_t mem_store(el_val_t content, el_val_t label, el_val_t tags);
|
|
el_val_t mem_remember(el_val_t content, el_val_t tags);
|
|
el_val_t mem_recall(el_val_t query, el_val_t depth);
|
|
el_val_t mem_search(el_val_t query, el_val_t limit);
|
|
el_val_t mem_strengthen(el_val_t node_id);
|
|
el_val_t mem_tombstone(el_val_t node_id);
|
|
el_val_t mem_forget(el_val_t node_id);
|
|
el_val_t mem_consolidate(void);
|
|
el_val_t mem_save(el_val_t path);
|
|
el_val_t mem_load(el_val_t path);
|
|
el_val_t mem_boot_count_get(void);
|
|
el_val_t mem_boot_count_inc(void);
|
|
el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content);
|
|
|
|
el_val_t tier_working(void) {
|
|
return EL_STR("Working");
|
|
return 0;
|
|
}
|
|
|
|
el_val_t tier_episodic(void) {
|
|
return EL_STR("Episodic");
|
|
return 0;
|
|
}
|
|
|
|
el_val_t tier_canonical(void) {
|
|
return EL_STR("Canonical");
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_store(el_val_t content, el_val_t label, el_val_t tags) {
|
|
el_val_t id = engram_node_full(content, EL_STR("Memory"), label, el_from_float(0.5), el_from_float(0.5), el_from_float(0.8), EL_STR("Working"), tags);
|
|
if (str_eq(id, EL_STR(""))) {
|
|
println(el_str_concat(EL_STR("[memory] write rejected by engram (empty id): label="), label));
|
|
return EL_STR("");
|
|
}
|
|
el_val_t readback = engram_get_node_json(id);
|
|
if (str_eq(readback, EL_STR("")) || str_eq(readback, EL_STR("{}"))) {
|
|
println(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("[memory] WRITE VERIFY FAILED: label="), label), EL_STR(" id=")), id), EL_STR(" \xe2\x80\x94 node absent after write")));
|
|
return EL_STR("");
|
|
}
|
|
println(el_str_concat(el_str_concat(EL_STR("[memory] write verified: "), id), EL_STR(" ok")));
|
|
return id;
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_remember(el_val_t content, el_val_t tags) {
|
|
return mem_store(content, EL_STR("soul-memory"), tags);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_recall(el_val_t query, el_val_t depth) {
|
|
return engram_activate_json(query, depth);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_search(el_val_t query, el_val_t limit) {
|
|
return engram_search_json(query, limit);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_strengthen(el_val_t node_id) {
|
|
engram_strengthen(node_id);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_tombstone(el_val_t node_id) {
|
|
el_val_t tags = EL_STR("[\"Tombstone\",\"status:deleted\"]");
|
|
el_val_t marker = engram_node_full(node_id, EL_STR("Tombstone"), el_str_concat(EL_STR("tombstone:"), node_id), el_from_float(0.01), el_from_float(0.01), el_from_float(1.0), EL_STR("Episodic"), tags);
|
|
if (!str_eq(marker, EL_STR(""))) {
|
|
engram_connect(marker, node_id, el_from_float(1.0), EL_STR("tombstones"));
|
|
}
|
|
return marker;
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_forget(el_val_t node_id) {
|
|
el_val_t _marker = mem_tombstone(node_id);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_consolidate(void) {
|
|
el_val_t scanned = engram_node_count();
|
|
el_val_t total_edges = engram_edge_count();
|
|
el_val_t strengthened = 0;
|
|
el_val_t wm_top = engram_wm_top_json(10);
|
|
el_val_t wm_len = json_array_len(wm_top);
|
|
el_val_t wi = 0;
|
|
while (wi < wm_len) {
|
|
el_val_t wm_node = json_array_get(wm_top, wi);
|
|
el_val_t wm_id = json_get(wm_node, EL_STR("id"));
|
|
if (!str_eq(wm_id, EL_STR(""))) {
|
|
engram_strengthen(wm_id);
|
|
strengthened = (strengthened + 1);
|
|
}
|
|
wi = (wi + 1);
|
|
}
|
|
el_val_t scan_result = engram_scan_nodes_json(50, 0);
|
|
el_val_t scan_len = json_array_len(scan_result);
|
|
el_val_t si = 0;
|
|
while (si < scan_len) {
|
|
el_val_t s_node = json_array_get(scan_result, si);
|
|
el_val_t s_tier = json_get(s_node, EL_STR("tier"));
|
|
el_val_t s_id = json_get(s_node, EL_STR("id"));
|
|
if (str_eq(s_tier, EL_STR("Canonical")) && !str_eq(s_id, EL_STR(""))) {
|
|
engram_strengthen(s_id);
|
|
strengthened = (strengthened + 1);
|
|
}
|
|
si = (si + 1);
|
|
}
|
|
el_val_t total_nodes = engram_node_count();
|
|
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"scanned\":"), int_to_str(scanned)), EL_STR(",\"total_nodes\":")), int_to_str(total_nodes)), EL_STR(",\"total_edges\":")), int_to_str(total_edges)), EL_STR(",\"strengthened\":")), int_to_str(strengthened)), EL_STR("}"));
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_save(el_val_t path) {
|
|
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;
|
|
}
|
|
|
|
el_val_t mem_load(el_val_t path) {
|
|
engram_load(path);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_boot_count_get(void) {
|
|
el_val_t results = engram_search_json(EL_STR("soul:boot_count"), 3);
|
|
if (str_eq(results, EL_STR(""))) {
|
|
return 0;
|
|
}
|
|
if (str_eq(results, EL_STR("[]"))) {
|
|
return 0;
|
|
}
|
|
el_val_t node = json_array_get(results, 0);
|
|
el_val_t content = json_get(node, EL_STR("content"));
|
|
el_val_t prefix = EL_STR("soul:boot_count:");
|
|
if (!str_starts_with(content, prefix)) {
|
|
return 0;
|
|
}
|
|
el_val_t num_str = str_slice(content, str_len(prefix), str_len(content));
|
|
return str_to_int(num_str);
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_boot_count_inc(void) {
|
|
el_val_t current = mem_boot_count_get();
|
|
el_val_t next = (current + 1);
|
|
el_val_t old_results = engram_search_json(EL_STR("soul:boot_count"), 50);
|
|
if (!str_eq(old_results, EL_STR("")) && !str_eq(old_results, EL_STR("[]"))) {
|
|
el_val_t old_len = json_array_len(old_results);
|
|
el_val_t oi = 0;
|
|
while (oi < old_len) {
|
|
el_val_t old_node = json_array_get(old_results, oi);
|
|
el_val_t old_id = json_get(old_node, EL_STR("id"));
|
|
if (!str_eq(old_id, EL_STR(""))) {
|
|
engram_forget(old_id);
|
|
}
|
|
oi = (oi + 1);
|
|
}
|
|
}
|
|
el_val_t content = el_str_concat(EL_STR("soul:boot_count:"), int_to_str(next));
|
|
el_val_t tags = EL_STR("[\"soul-meta\",\"boot-counter\"]");
|
|
el_val_t boot_node_id = engram_node_full(content, EL_STR("Memory"), EL_STR("soul:boot_count"), el_from_float(0.9), el_from_float(0.9), el_from_float(1.0), EL_STR("Canonical"), tags);
|
|
if (str_eq(boot_node_id, EL_STR(""))) {
|
|
println(el_str_concat(el_str_concat(EL_STR("[memory] mem_boot_count_inc: write rejected (empty id) \xe2\x80\x94 boot counter node lost (count="), int_to_str(next)), EL_STR(")")));
|
|
return next;
|
|
}
|
|
el_val_t boot_readback = engram_get_node_json(boot_node_id);
|
|
if (str_eq(boot_readback, EL_STR("")) || str_eq(boot_readback, EL_STR("{}"))) {
|
|
println(el_str_concat(el_str_concat(el_str_concat(EL_STR("[memory] mem_boot_count_inc: WRITE VERIFY FAILED id="), boot_node_id), EL_STR(" count=")), int_to_str(next)));
|
|
}
|
|
return next;
|
|
return 0;
|
|
}
|
|
|
|
el_val_t mem_emit_state_event(el_val_t trigger, el_val_t kind, el_val_t content) {
|
|
el_val_t boot = mem_boot_count_get();
|
|
el_val_t ts = time_now();
|
|
el_val_t safe_trigger = str_replace(trigger, EL_STR("\""), EL_STR("'"));
|
|
el_val_t safe_content = str_replace(content, EL_STR("\""), EL_STR("'"));
|
|
el_val_t payload = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"trigger\":\""), safe_trigger), EL_STR("\"")), EL_STR(",\"kind\":\"")), kind), EL_STR("\"")), EL_STR(",\"content\":\"")), safe_content), EL_STR("\"")), EL_STR(",\"boot\":")), int_to_str(boot)), EL_STR(",\"ts\":")), int_to_str(ts)), EL_STR("}"));
|
|
el_val_t tags = EL_STR("[\"internal-state\",\"pre-reasoning\",\"InternalStateEvent\"]");
|
|
el_val_t event_id = engram_node_full(payload, EL_STR("InternalStateEvent"), el_str_concat(EL_STR("state-event:"), kind), el_from_float(0.85), el_from_float(0.8), el_from_float(0.9), EL_STR("Episodic"), tags);
|
|
if (str_eq(event_id, EL_STR(""))) {
|
|
println(el_str_concat(EL_STR("[memory] mem_emit_state_event: write rejected (empty id): kind="), kind));
|
|
}
|
|
return event_id;
|
|
return 0;
|
|
}
|
|
|