self-review 2026-07-22: persist canonical snapshot on write routes; newest-first tie-break in node listings
El SDK Release / build-and-release (pull_request) Failing after 14m24s
El SDK Release / build-and-release (pull_request) Failing after 14m24s
Durability: the 2026-07-21 fix stopped read routes writing the canonical snapshot but left no save on ANY write path — every mutation lived in RAM until a manual POST /api/save. Observed live: two restarts reverted the store to a 17h-old snapshot, destroying same-day writes. persist_canonical() now runs after node/edge create, knowledge capture, forget, strengthen, and load-merge. ISE telemetry excluded deliberately (48h-pruned, loss-tolerant, ~2/min; snapshotting 28MB per heartbeat is waste). Listing order: scan routes sort by salience with store-order ties, so equal-salience telemetry (all ISEs are 0.3) returned OLDEST first — a limited /api/nodes query silently returned a stale window, and a 41h-old heartbeat series read as a live outage during this review. Ties now break newest-first by created_at.
This commit is contained in:
@@ -85,6 +85,26 @@ fn route_stats(method: String, path: String, body: String) -> String {
|
||||
// save/load with no "path" hit engram_save(""). Rewritten to the
|
||||
// `let x = if cond { a } else { b }` expression form (the pattern the newer
|
||||
// routes route_emit_ise/route_capture_knowledge already use correctly).
|
||||
// persist_canonical — save the canonical snapshot after a durable write.
|
||||
//
|
||||
// WHY (2026-07-22 self-review): the 2026-07-21 fix correctly stopped READ
|
||||
// routes from writing the canonical snapshot.json — but nothing was left
|
||||
// that saved it on WRITE. Every mutation (node create, edge create,
|
||||
// knowledge capture, forget, merge) lived only in RAM until someone POSTed
|
||||
// /api/save manually; a process restart silently discarded everything since
|
||||
// the last manual save. Observed live: two engram restarts during the
|
||||
// 2026-07-22 review reverted the store to a ~17h-old snapshot, destroying
|
||||
// same-day writes. Reads must never write the canonical; writes must always
|
||||
// persist it. ISE telemetry is deliberately excluded (48h-pruned, loss-
|
||||
// tolerant, ~2/min — snapshotting the whole store per heartbeat is waste;
|
||||
// any durable write that follows persists the pruning too).
|
||||
fn persist_canonical() -> Int {
|
||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||
engram_save(dir + "/snapshot.json")
|
||||
return 1
|
||||
}
|
||||
|
||||
fn route_create_node(method: String, path: String, body: String) -> String {
|
||||
let content: String = json_get_string(body, "content")
|
||||
let nt_raw: String = json_get_string(body, "node_type")
|
||||
@@ -92,6 +112,7 @@ fn route_create_node(method: String, path: String, body: String) -> String {
|
||||
let sal_raw: Float = json_get_float(body, "salience")
|
||||
let salience: Float = if sal_raw == 0.0 { 0.5 } else { sal_raw }
|
||||
let id: String = engram_node(content, node_type, salience)
|
||||
let saved: Int = persist_canonical()
|
||||
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
||||
}
|
||||
|
||||
@@ -159,6 +180,7 @@ fn route_create_edge(method: String, path: String, body: String) -> String {
|
||||
let w_raw: Float = json_get_float(body, "weight")
|
||||
let weight: Float = if w_raw == 0.0 { 0.5 } else { w_raw }
|
||||
engram_connect(from_id, to_id, weight, relation)
|
||||
let saved: Int = persist_canonical()
|
||||
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
||||
}
|
||||
|
||||
@@ -173,6 +195,7 @@ fn route_strengthen(method: String, path: String, body: String) -> String {
|
||||
let id: String = json_get_string(body, "node_id")
|
||||
if str_eq(id, "") { return err_json("missing node_id") }
|
||||
engram_strengthen(id)
|
||||
let saved: Int = persist_canonical()
|
||||
ok_json()
|
||||
}
|
||||
|
||||
@@ -180,6 +203,7 @@ fn route_forget(method: String, path: String, body: String) -> String {
|
||||
let id: String = extract_id(path, "/api/nodes/")
|
||||
if str_eq(id, "") { return err_json("missing id") }
|
||||
engram_forget(id)
|
||||
let saved: Int = persist_canonical()
|
||||
ok_json()
|
||||
}
|
||||
|
||||
@@ -244,6 +268,7 @@ fn route_load_merge(method: String, path: String, body: String) -> String {
|
||||
engram_load_merge(p)
|
||||
let added_n: Int = engram_node_count() - before_n
|
||||
let added_e: Int = engram_edge_count() - before_e
|
||||
let saved: Int = persist_canonical()
|
||||
"{\"ok\":true,\"nodes_added\":" + int_to_str(added_n) + ",\"edges_added\":" + int_to_str(added_e) + ",\"node_count\":" + int_to_str(engram_node_count()) + "}"
|
||||
}
|
||||
|
||||
@@ -342,6 +367,7 @@ fn route_capture_knowledge(method: String, path: String, body: String) -> String
|
||||
sal, imp, conf,
|
||||
"Semantic", tags
|
||||
)
|
||||
let saved: Int = persist_canonical()
|
||||
"{\"ok\":true,\"id\":\"" + id + "\"}"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user