fix(releases/v1.0.0): println stdout flush for launchd; Knowledge node activation threshold

This commit is contained in:
2026-06-29 12:38:36 -05:00
parent 192241c7c1
commit 3da9181deb
+69 -45
View File
@@ -140,6 +140,12 @@ void println(el_val_t s) {
const char* str = EL_CSTR(s);
if (str) puts(str);
else puts("");
/* Flush stdout immediately so logs are visible even when stdout is redirected
* to a file (launchd plists redirect stdout; stdio defaults to full-buffering
* for non-tty streams, which silently holds log messages indefinitely).
* (2026-06-29 self-review: soul.out.log showed May 26 mtime despite daemon
* running since June 29 07:14AM startup println output stuck in stdio buffer) */
fflush(stdout);
}
void print(el_val_t s) {
@@ -5562,6 +5568,13 @@ static double engram_type_threshold(const char* node_type, const char* tier) {
if (strcmp(tier, "Lesson") == 0) return 0.25;
}
if (node_type) {
/* Knowledge nodes with Procedural/Semantic tier (post-activation-count migration)
* should keep a lower threshold than the generic 0.40 default.
* Canonical/Lesson Knowledge: handled by tier checks above (0.15/0.25).
* Procedural Knowledge (activation_count >= 50): 0.20 proven repeated use
* justifies lower threshold, consistent with ACT-R base-level learning rationale.
* (2026-06-29 self-review: documented in knowledge graph since 2026-06-04) */
if (strcmp(node_type, "Knowledge") == 0) return 0.20;
if (strcmp(node_type, "Belief") == 0) return 0.30;
if (strcmp(node_type, "Entity") == 0) return 0.30;
}
@@ -5830,11 +5843,15 @@ static void engram_grow_edges(void) {
g->edge_capacity = nc;
}
/* Build a fresh UUID string. Reuses uuid_new but takes the underlying char*. */
/* Build a fresh UUID string. Reuses uuid_new but takes the underlying char*.
* Must use el_strdup_persist because the returned pointer is stored in EngramNode/EngramEdge
* fields that outlive the current HTTP request's per-request string arena. If el_strdup
* were used the arena teardown at el_request_end() would free the id/content/etc. strings,
* leaving dangling pointers in the EngramStore. (2026-06-27 fix: arena corruption bug) */
static char* engram_new_id(void) {
el_val_t v = uuid_new();
const char* s = EL_CSTR(v);
return el_strdup(s ? s : "");
return el_strdup_persist(s ? s : "");
}
/* Convert a node into an ElMap of its fields. */
@@ -5877,11 +5894,14 @@ static double engram_decode_score(el_val_t v) {
return (double)n;
}
/* engram_first_n_chars — extract label substring from content.
* Must use persistent allocation since the result is stored in EngramNode.label.
* (2026-06-27 fix: arena corruption bug was using el_strbuf/el_strdup) */
static char* engram_first_n_chars(const char* s, size_t n) {
if (!s) return el_strdup("");
if (!s) return el_strdup_persist("");
size_t l = strlen(s);
if (l > n) l = n;
char* out = el_strbuf(l);
char* out = el_strbuf_persist(l);
memcpy(out, s, l);
out[l] = '\0';
return out;
@@ -5895,12 +5915,13 @@ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
n->id = engram_new_id();
const char* c = EL_CSTR(content);
const char* nt = EL_CSTR(node_type);
n->content = el_strdup(c ? c : "");
n->node_type = el_strdup(nt && *nt ? nt : "Memory");
/* Use el_strdup_persist: these strings outlive the HTTP request arena. */
n->content = el_strdup_persist(c ? c : "");
n->node_type = el_strdup_persist(nt && *nt ? nt : "Memory");
n->label = engram_first_n_chars(c, 60);
n->tier = el_strdup("Working");
n->tags = el_strdup("");
n->metadata = el_strdup("{}");
n->tier = el_strdup_persist("Working");
n->tags = el_strdup_persist("");
n->metadata = el_strdup_persist("{}");
n->salience = engram_decode_score(salience);
if (n->salience <= 0.0 || n->salience > 1.0) n->salience = 0.5;
n->importance = 0.5;
@@ -5913,7 +5934,7 @@ el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience) {
n->updated_at = now;
n->layer_id = ENGRAM_LAYER_DEFAULT;
g->node_count++;
return el_wrap_str(el_strdup(n->id));
return el_wrap_str(el_strdup_persist(n->id));
}
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
@@ -5929,12 +5950,13 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
const char* lb = EL_CSTR(label);
const char* ti = EL_CSTR(tier);
const char* tg = EL_CSTR(tags);
n->content = el_strdup(c ? c : "");
n->node_type = el_strdup(nt && *nt ? nt : "Memory");
n->label = el_strdup(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : ""));
n->tier = el_strdup(ti && *ti ? ti : "Working");
n->tags = el_strdup(tg ? tg : "");
n->metadata = el_strdup("{}");
/* Use el_strdup_persist: these strings outlive the HTTP request arena. */
n->content = el_strdup_persist(c ? c : "");
n->node_type = el_strdup_persist(nt && *nt ? nt : "Memory");
n->label = el_strdup_persist(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : ""));
n->tier = el_strdup_persist(ti && *ti ? ti : "Working");
n->tags = el_strdup_persist(tg ? tg : "");
n->metadata = el_strdup_persist("{}");
n->salience = engram_decode_score(salience);
n->importance = engram_decode_score(importance);
n->confidence = engram_decode_score(confidence);
@@ -5949,7 +5971,7 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
n->updated_at = now;
n->layer_id = ENGRAM_LAYER_DEFAULT;
g->node_count++;
return el_wrap_str(el_strdup(n->id));
return el_wrap_str(el_strdup_persist(n->id));
}
/* engram_node_layered — like engram_node_full but with explicit layer
@@ -5982,20 +6004,21 @@ el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t labe
const char* lb = EL_CSTR(label);
const char* tg = EL_CSTR(tags);
const char* st = EL_CSTR(status);
n->content = el_strdup(c ? c : "");
n->node_type = el_strdup(nt && *nt ? nt : "Memory");
n->label = el_strdup(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : ""));
n->tier = el_strdup("Working");
n->tags = el_strdup(tg ? tg : "");
/* Use el_strdup_persist: these strings outlive the HTTP request arena. */
n->content = el_strdup_persist(c ? c : "");
n->node_type = el_strdup_persist(nt && *nt ? nt : "Memory");
n->label = el_strdup_persist(lb && *lb ? lb : (c ? engram_first_n_chars(c, 60) : ""));
n->tier = el_strdup_persist("Working");
n->tags = el_strdup_persist(tg ? tg : "");
if (st && *st) {
/* Minimal metadata payload: {"status":"..."}. Keep it cheap so
* callers using `status` don't pay JSON parse cost on every read. */
size_t sl = strlen(st) + 16;
char* meta = el_strbuf(sl);
char* meta = el_strbuf_persist(sl);
snprintf(meta, sl, "{\"status\":\"%s\"}", st);
n->metadata = meta;
} else {
n->metadata = el_strdup("{}");
n->metadata = el_strdup_persist("{}");
}
n->salience = engram_decode_score(salience);
n->importance = engram_decode_score(certainty);
@@ -6016,7 +6039,7 @@ el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t labe
if (!engram_find_layer((uint32_t)lid)) lid = (int64_t)ENGRAM_LAYER_DEFAULT;
n->layer_id = (uint32_t)lid;
g->node_count++;
return el_wrap_str(el_strdup(n->id));
return el_wrap_str(el_strdup_persist(n->id));
}
/* ── Layer registry public API ──────────────────────────────────────────────
@@ -6268,10 +6291,11 @@ void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t
EngramEdge* e = &g->edges[g->edge_count];
memset(e, 0, sizeof(*e));
e->id = engram_new_id();
e->from_id = el_strdup(f);
e->to_id = el_strdup(t);
e->relation = el_strdup(r && *r ? r : "associate");
e->metadata = el_strdup("{}");
/* Use el_strdup_persist: edge strings outlive the HTTP request arena. */
e->from_id = el_strdup_persist(f);
e->to_id = el_strdup_persist(t);
e->relation = el_strdup_persist(r && *r ? r : "associate");
e->metadata = el_strdup_persist("{}");
e->weight = engram_decode_score(weight);
if (e->weight <= 0.0 || e->weight > 1.0) e->weight = 0.5;
e->confidence = 1.0;
@@ -7080,7 +7104,7 @@ el_val_t engram_load(el_val_t path) {
nn->tier = eg_get_str_field(obj, "tier");
nn->tags = eg_get_str_field(obj, "tags");
nn->metadata = eg_get_str_field(obj, "metadata");
if (!nn->metadata || !*nn->metadata) { free(nn->metadata); nn->metadata = el_strdup("{}"); }
if (!nn->metadata || !*nn->metadata) { free(nn->metadata); nn->metadata = el_strdup_persist("{}"); }
nn->salience = eg_get_num_field(obj, "salience");
nn->importance = eg_get_num_field(obj, "importance");
nn->confidence = eg_get_num_field(obj, "confidence");
@@ -7131,7 +7155,7 @@ el_val_t engram_load(el_val_t path) {
ee->to_id = eg_get_str_field(obj, "to_id");
ee->relation = eg_get_str_field(obj, "relation");
ee->metadata = eg_get_str_field(obj, "metadata");
if (!ee->metadata || !*ee->metadata) { free(ee->metadata); ee->metadata = el_strdup("{}"); }
if (!ee->metadata || !*ee->metadata) { free(ee->metadata); ee->metadata = el_strdup_persist("{}"); }
ee->weight = eg_get_num_field(obj, "weight");
ee->confidence = eg_get_num_field(obj, "confidence");
ee->created_at = eg_get_int_field(obj, "created_at");
@@ -8007,13 +8031,13 @@ static int64_t dharma_find_or_create_relation_edge(const char* peer_base, int cr
engram_grow_nodes();
EngramNode* n = &g->nodes[g->node_count];
memset(n, 0, sizeof(*n));
n->id = el_strdup(self_id);
n->content = el_strdup(_el_cgi_dharma_id ? _el_cgi_dharma_id : "(self)");
n->node_type = el_strdup("DharmaSelf");
n->label = el_strdup("dharma:self");
n->tier = el_strdup("Working");
n->tags = el_strdup("dharma");
n->metadata = el_strdup("{}");
n->id = el_strdup_persist(self_id);
n->content = el_strdup_persist(_el_cgi_dharma_id ? _el_cgi_dharma_id : "(self)");
n->node_type = el_strdup_persist("DharmaSelf");
n->label = el_strdup_persist("dharma:self");
n->tier = el_strdup_persist("Working");
n->tags = el_strdup_persist("dharma");
n->metadata = el_strdup_persist("{}");
n->salience = 1.0; n->importance = 1.0; n->confidence = 1.0;
int64_t now = engram_now_ms();
n->created_at = now; n->updated_at = now; n->last_activated = now;
@@ -8024,13 +8048,13 @@ static int64_t dharma_find_or_create_relation_edge(const char* peer_base, int cr
engram_grow_nodes();
EngramNode* n = &g->nodes[g->node_count];
memset(n, 0, sizeof(*n));
n->id = el_strdup(peer_node);
n->content = el_strdup(peer_base);
n->node_type = el_strdup("DharmaPeer");
n->label = el_strdup(peer_node);
n->tier = el_strdup("Working");
n->tags = el_strdup("dharma");
n->metadata = el_strdup("{}");
n->id = el_strdup_persist(peer_node);
n->content = el_strdup_persist(peer_base);
n->node_type = el_strdup_persist("DharmaPeer");
n->label = el_strdup_persist(peer_node);
n->tier = el_strdup_persist("Working");
n->tags = el_strdup_persist("dharma");
n->metadata = el_strdup_persist("{}");
n->salience = 0.5; n->importance = 0.5; n->confidence = 1.0;
int64_t now = engram_now_ms();
n->created_at = now; n->updated_at = now; n->last_activated = now;