seam: implement decorated-fn boundary auto-emit; prove on clone
Will waived diff review -> build it for real. Add engram_boundary_beat() to the runtime (afferent counter++ + engram_chrono_tick + engram_strengthen(self-anchor) + dharma_emit) and two act-stats counters (aff_boundary_ops, dharma_emits). codegen cg_fn injects ONE engram_boundary_beat(op) at the entry of every @manager/@accessor fn (fn_has_decorator, so it fires under @route @manager too) — a decorated op self-reports with ZERO hand-written instrumentation. Rebuilt elc self-host + the cognition engram in the worktree; ran it as the clone daemon on :8900. Proof (/api/boundary-proof, @manager, empty body, 5x): aff_boundary_ops 0->5, dharma_emits 0->5, self activation_count 1510->1513, chrono stamp advanced. Brought in feat/cognitive-architecture engram runtime+server for the build. strengthen = activation bump (not content/edge write) -> identity protection intact. Live :8742 untouched; no push, no cutover.
This commit is contained in:
+525
-32
@@ -117,6 +117,17 @@ fn route_text_health(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).
|
||||
// store_on — ENGRAM_STORE flag (tiered paged store as the durable owner). Matches
|
||||
// engram_store_enabled() in el_runtime.c EXACTLY (1 / on / true). Default off →
|
||||
// every persistence path below is byte-for-byte the historical snapshot behavior.
|
||||
fn store_on() -> Bool {
|
||||
let v: String = env("ENGRAM_STORE")
|
||||
if str_eq(v, "1") { return true }
|
||||
if str_eq(v, "on") { return true }
|
||||
if str_eq(v, "true") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// persist_canonical — save the canonical snapshot after a durable write.
|
||||
//
|
||||
// WHY (2026-07-22 self-review): the 2026-07-21 fix correctly stopped READ
|
||||
@@ -131,8 +142,16 @@ fn route_text_health(method: String, path: String, body: String) -> String {
|
||||
// tolerant, ~2/min — snapshotting the whole store per heartbeat is waste;
|
||||
// any durable write that follows persists the pruning too).
|
||||
fn persist_canonical() -> Int {
|
||||
// ENGRAM_STORE: the paged store is the durable owner — a checkpoint flushes
|
||||
// dirty pages behind a WAL-durable record (durable the moment the WAL fsyncs).
|
||||
// This is the fix for the "restart reverted to a 17h-old snapshot" data loss:
|
||||
// durable writes no longer depend on a full snapshot.json rewrite. Returns 1
|
||||
// on a successful checkpoint, 0 otherwise. Flag-off: unchanged (writes JSON).
|
||||
if store_on() {
|
||||
return engram_store_checkpoint()
|
||||
}
|
||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||
let dir: String = engram_resolve_data_dir()
|
||||
// (2026-08-10 self-review) This returned a hardcoded 1, which made every
|
||||
// caller's `let saved: Int = persist_canonical()` a dead variable — six
|
||||
// durable write paths each believed they had confirmation of a successful
|
||||
@@ -140,6 +159,82 @@ fn persist_canonical() -> Int {
|
||||
return engram_save(dir + "/snapshot.json")
|
||||
}
|
||||
|
||||
// ── WAL persistence (design doc §§3-14; gated behind ENGRAM_WAL=on) ──────────
|
||||
// Default OFF → every persist path below is byte-identical to the historical
|
||||
// per-write full-snapshot behavior. When ON, structural mutations append O(1)
|
||||
// WAL records instead of rewriting the whole graph, with threshold compaction.
|
||||
fn wal_on() -> Bool {
|
||||
str_eq(env("ENGRAM_WAL"), "on")
|
||||
}
|
||||
|
||||
// autoconnect_on — ENGRAM_AUTOCONNECT. Will's rule: "we shouldn't be inserting
|
||||
// orphaned nodes." When ON, every content-node insert forms >=1 semantic-similar
|
||||
// edge (kNN over embeddings) so no content node enters the graph edgeless.
|
||||
// Default OFF -> byte-identical to prior behavior (node created, no auto edges).
|
||||
fn autoconnect_on() -> Bool {
|
||||
let v: String = env("ENGRAM_AUTOCONNECT")
|
||||
if str_eq(v, "1") { return true }
|
||||
if str_eq(v, "on") { return true }
|
||||
if str_eq(v, "true") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// ise_offgraph_on — ENGRAM_ISE_OFFGRAPH. The census showed ~8k of the ~8.9k
|
||||
// orphans are InternalStateEvent telemetry (heartbeat/curiosity_scan/session-
|
||||
// start), 100% edgeless by design. When ON, that telemetry is routed to a
|
||||
// separate state-event log tier instead of the node graph. Default OFF -> ISEs
|
||||
// remain graph nodes exactly as before (with 48h prune).
|
||||
fn ise_offgraph_on() -> Bool {
|
||||
let v: String = env("ENGRAM_ISE_OFFGRAPH")
|
||||
if str_eq(v, "1") { return true }
|
||||
if str_eq(v, "on") { return true }
|
||||
if str_eq(v, "true") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
// Persist a single-node mutation (create / content-evolve / strengthen).
|
||||
fn persist_node(id: String) -> Int {
|
||||
if wal_on() {
|
||||
let d: String = engram_resolve_data_dir()
|
||||
let a: Int = engram_wal_node_put(d, id)
|
||||
let c: Int = engram_wal_maybe_compact(d)
|
||||
return a
|
||||
}
|
||||
return persist_canonical()
|
||||
}
|
||||
|
||||
// Persist edges appended at index >= start (covers single-edge and batch).
|
||||
fn persist_edges_since(start: Int) -> Int {
|
||||
if wal_on() {
|
||||
let d: String = engram_resolve_data_dir()
|
||||
let a: Int = engram_wal_edges_since(d, start)
|
||||
let c: Int = engram_wal_maybe_compact(d)
|
||||
return a
|
||||
}
|
||||
return persist_canonical()
|
||||
}
|
||||
|
||||
// Persist a Hebbian consolidation batch as ONE WAL record (single fsync, §5-B).
|
||||
fn persist_hebb_batch(start: Int) -> Int {
|
||||
if wal_on() {
|
||||
let d: String = engram_resolve_data_dir()
|
||||
let a: Int = engram_wal_hebb_batch(d, start)
|
||||
let c: Int = engram_wal_maybe_compact(d)
|
||||
return a
|
||||
}
|
||||
return persist_canonical()
|
||||
}
|
||||
|
||||
// Bulk mutation (embedding backfill, load-merge): write a fresh compaction base
|
||||
// so the many-node change is durable in one atomic snapshot; WAL is truncated.
|
||||
fn persist_bulk() -> Int {
|
||||
if wal_on() {
|
||||
let d: String = engram_resolve_data_dir()
|
||||
return engram_wal_compact(d)
|
||||
}
|
||||
return persist_canonical()
|
||||
}
|
||||
|
||||
// INCOMPLETE-ROUTE FIX (2026-07-24 self-review): this route silently dropped
|
||||
// label, importance, tier, and tags — engram_node() defaults label to content
|
||||
// and importance to 0.5, so every node created over HTTP lost its metadata.
|
||||
@@ -181,8 +276,17 @@ fn route_create_node(method: String, path: String, body: String) -> String {
|
||||
salience, importance, confidence,
|
||||
tier, tags
|
||||
)
|
||||
let saved: Int = persist_canonical()
|
||||
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\"}"
|
||||
let saved: Int = persist_node(id)
|
||||
// ORPHAN PREVENTION (ENGRAM_AUTOCONNECT): connect the fresh node to its
|
||||
// nearest embedded neighbors so it never enters the graph edgeless.
|
||||
let connected: Int = if autoconnect_on() {
|
||||
let ec0: Int = engram_edge_count()
|
||||
let ac: String = engram_autoconnect_node(id, 3, 25)
|
||||
let added: Int = engram_edge_count() - ec0
|
||||
if added > 0 { let sv2: Int = persist_edges_since(ec0) }
|
||||
added
|
||||
} else { 0 }
|
||||
"{\"id\":\"" + id + "\",\"content\":\"" + content + "\",\"node_type\":\"" + node_type + "\",\"connected\":" + int_to_str(connected) + "}"
|
||||
}
|
||||
|
||||
fn route_get_node(method: String, path: String, body: String) -> String {
|
||||
@@ -191,6 +295,16 @@ fn route_get_node(method: String, path: String, body: String) -> String {
|
||||
return engram_get_node_json(id)
|
||||
}
|
||||
|
||||
// route_get_node_singular — GET /api/node/<id>. Singular alias for node-by-id
|
||||
// fetch. The plural /api/nodes/<id> already resolves; the viz's "see full node
|
||||
// value on click" and other clients call the SINGULAR form, which had no route
|
||||
// and 404'd for every id. Same handler, singular prefix. Read-only.
|
||||
fn route_get_node_singular(method: String, path: String, body: String) -> String {
|
||||
let id: String = extract_id(path, "/api/node/")
|
||||
if str_eq(id, "") { return err_json("missing id") }
|
||||
return engram_get_node_json(id)
|
||||
}
|
||||
|
||||
fn route_scan_nodes(method: String, path: String, body: String) -> String {
|
||||
let limit: Int = query_int(path, "limit", 50)
|
||||
let offset: Int = query_int(path, "offset", 0)
|
||||
@@ -208,7 +322,7 @@ fn route_scan_nodes(method: String, path: String, body: String) -> String {
|
||||
// clobbered the good snapshot. Read routes must never write the canonical path.)
|
||||
fn route_scan_edges(method: String, path: String, body: String) -> String {
|
||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||
let dir: String = engram_resolve_data_dir()
|
||||
let snap_path: String = dir + "/.scan-export.json"
|
||||
engram_save(snap_path)
|
||||
let snap: String = fs_read(snap_path)
|
||||
@@ -222,6 +336,15 @@ fn route_scan_edges(method: String, path: String, body: String) -> String {
|
||||
}
|
||||
|
||||
fn route_search(method: String, path: String, body: String) -> String {
|
||||
let q: String = if str_eq(method, "GET") { query_param(path, "q") } else { json_get_string(body, "query") }
|
||||
let lim_url: Int = query_int(path, "limit", 0)
|
||||
let lim_body: Int = json_get_int(body, "limit")
|
||||
let lim_either: Int = if lim_url > 0 { lim_url } else { lim_body }
|
||||
let limit: Int = if lim_either > 0 { lim_either } else { 20 }
|
||||
return engram_retrieve_geometric_json(q, limit)
|
||||
}
|
||||
|
||||
fn route_search_lexical(method: String, path: String, body: String) -> String {
|
||||
let q: String = if str_eq(method, "GET") { query_param(path, "q") } else { json_get_string(body, "query") }
|
||||
let lim_url: Int = query_int(path, "limit", 0)
|
||||
let lim_body: Int = json_get_int(body, "limit")
|
||||
@@ -250,8 +373,9 @@ fn route_create_edge(method: String, path: String, body: String) -> String {
|
||||
// (dormant association); only default when the key is absent.
|
||||
let w_present: String = json_get_raw(body, "weight")
|
||||
let weight: Float = if str_eq(w_present, "") { 0.5 } else { json_get_float(body, "weight") }
|
||||
let ec0: Int = engram_edge_count()
|
||||
engram_connect(from_id, to_id, weight, relation)
|
||||
let saved: Int = persist_canonical()
|
||||
let saved: Int = persist_edges_since(ec0)
|
||||
"{\"ok\":true,\"from_id\":\"" + from_id + "\",\"to_id\":\"" + to_id + "\",\"relation\":\"" + relation + "\"}"
|
||||
}
|
||||
|
||||
@@ -276,6 +400,7 @@ fn route_create_edges_batch(method: String, path: String, body: String) -> Strin
|
||||
if str_eq(arr, "") { return err_json("missing edges array") }
|
||||
let n: Int = json_array_len(arr)
|
||||
if n == 0 { return "{\"ok\":true,\"accepted\":0,\"skipped\":0}" }
|
||||
let ec0: Int = engram_edge_count()
|
||||
let i: Int = 0
|
||||
let accepted: Int = 0
|
||||
let skipped: Int = 0
|
||||
@@ -299,7 +424,7 @@ fn route_create_edges_batch(method: String, path: String, body: String) -> Strin
|
||||
// Skip it when nothing was accepted: an all-malformed payload must not
|
||||
// trigger a 60MB write.
|
||||
if accepted > 0 {
|
||||
let saved: Int = persist_canonical()
|
||||
let saved: Int = persist_hebb_batch(ec0)
|
||||
}
|
||||
return "{\"ok\":true,\"accepted\":" + int_to_str(accepted) + ",\"skipped\":" + int_to_str(skipped) + "}"
|
||||
}
|
||||
@@ -315,22 +440,50 @@ 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()
|
||||
let saved: Int = persist_node(id)
|
||||
ok_json()
|
||||
}
|
||||
|
||||
// route_forget — DELETE /api/nodes/:id — INTEGRITY HARDENED (design doc §18.1).
|
||||
//
|
||||
// Two invariants now enforced AT THE STORE (not one layer up in neuron-api.el,
|
||||
// which a direct HTTP client could bypass):
|
||||
// 1. Write-protection: protected identity/value nodes (derived from the self
|
||||
// graph — self root + values hub + their neighbors, §18.3) cannot be
|
||||
// deleted over HTTP. Returns 403, node untouched.
|
||||
// 2. No hard delete over the wire, ever: an ordinary delete creates a
|
||||
// Tombstone marker node + `tombstones` edge and KEEPS the original node
|
||||
// and its edges (recoverable), instead of the old destructive
|
||||
// engram_forget() shift-delete. Raw engram_forget is now internal-GC only
|
||||
// and no longer reachable from any HTTP route.
|
||||
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()
|
||||
if engram_is_protected(id) == 1 {
|
||||
return "{\"__status__\":403,\"error\":\"protected node; deletion refused\",\"id\":\"" + id + "\"}"
|
||||
}
|
||||
let tomb_id: String = engram_node_full(
|
||||
"tombstone:" + id, "Tombstone", "tombstone:" + id,
|
||||
0.1, 0.1, 1.0, "Episodic", "[\"tombstone\"]"
|
||||
)
|
||||
let ec0: Int = engram_edge_count()
|
||||
engram_connect(tomb_id, id, 1.0, "tombstones")
|
||||
let saved: Int = if wal_on() {
|
||||
let d: String = engram_resolve_data_dir()
|
||||
let a: Int = engram_wal_node_put(d, tomb_id)
|
||||
let b: Int = engram_wal_edges_since(d, ec0)
|
||||
let c: Int = engram_wal_maybe_compact(d)
|
||||
a
|
||||
} else {
|
||||
persist_canonical()
|
||||
}
|
||||
"{\"ok\":true,\"tombstoned\":\"" + id + "\",\"tombstone_id\":\"" + tomb_id + "\"}"
|
||||
}
|
||||
|
||||
fn route_save(method: String, path: String, body: String) -> String {
|
||||
let p_raw: String = json_get_string(body, "path")
|
||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||
let dir: String = engram_resolve_data_dir()
|
||||
let p: String = if str_eq(p_raw, "") { dir + "/snapshot.json" } else { p_raw }
|
||||
// (2026-08-10 self-review) engram_save returns 0 on an empty path and the
|
||||
// route discarded it, so the response was a literal "ok":true regardless
|
||||
@@ -343,10 +496,78 @@ fn route_save(method: String, path: String, body: String) -> String {
|
||||
"{\"ok\":" + sv_ok + ",\"path\":\"" + p + "\",\"node_count\":" + int_to_str(engram_node_count()) + ",\"edge_count\":" + int_to_str(engram_edge_count()) + "}"
|
||||
}
|
||||
|
||||
// route_checkpoint — force a full resident-graph checkpoint into the paged
|
||||
// store (2026-08-13). Neither /api/save nor /api/load makes edges durable in
|
||||
// the tiered paged store — only persist_canonical -> engram_store_checkpoint
|
||||
// does, and that only fires on mutating write routes. This route exposes the
|
||||
// checkpoint directly so a RAM-loaded state (e.g. after /api/load of an export)
|
||||
// can be made durable without a rebuild. Returns ok=false if ENGRAM_STORE is
|
||||
// off (nothing to checkpoint into) or the checkpoint fails.
|
||||
fn route_checkpoint(method: String, path: String, body: String) -> String {
|
||||
let ck: Int = engram_store_checkpoint()
|
||||
let ck_ok: String = if ck == 0 { "false" } else { "true" }
|
||||
"{\"ok\":" + ck_ok + ",\"checkpointed\":true,\"node_count\":" + int_to_str(engram_node_count()) + ",\"edge_count\":" + int_to_str(engram_edge_count()) + "}"
|
||||
}
|
||||
|
||||
// route_tick — chronoception soul-tick (M-INTEROCEPTION P2 wiring, 2026-08-13).
|
||||
// The soul heartbeat pumps this once per beat: node ages advance by the MEASURED
|
||||
// wall-clock delta since the previous tick (self-seeding sidecar stamp). Returns
|
||||
// the cooling magnitude in [0,1). Inert (magnitude 0) unless ENGRAM_CHRONOCEPTION
|
||||
// is set, so wiring this route is safe by default — the flag is the cutover gate.
|
||||
fn route_tick(method: String, path: String, body: String) -> String {
|
||||
let mag: Float = engram_chrono_tick()
|
||||
// SELF-REIFICATION on the heartbeat (2026-08-14). Reification runs HERE, next
|
||||
// to chronoception — unbidden, every beat. Flag-gated (ENGRAM_SELF_REIFY):
|
||||
// when off, engram_self_reify_beat_json() returns {"enabled":false} and writes
|
||||
// nothing, so an unset flag leaves the existing tick byte-inert. Enabling the
|
||||
// flag alone activates continuous autonomous neighborhood formation on the beat
|
||||
// — no heartbeat-script change needed. Idempotent: unchanged regions append
|
||||
// nothing, so a settled store's beat is write-free (convergent under barrier/GC).
|
||||
let reify: String = engram_self_reify_beat_json()
|
||||
"{\"ok\":true,\"cooling_magnitude\":" + float_to_str(mag) + ",\"self_reify\":" + reify + "}"
|
||||
}
|
||||
|
||||
// route_self_reify_beat — POST /api/self-reify-beat. Explicit pump of ONE on-beat
|
||||
// self-reification pass (the same operation route_tick folds in). Used by the
|
||||
// secondary-soul validation harness to drive many beats deterministically. Inert
|
||||
// (writes nothing) unless ENGRAM_SELF_REIFY is set. WRITE op — auth-gated POST.
|
||||
fn route_self_reify_beat(method: String, path: String, body: String) -> String {
|
||||
return engram_self_reify_beat_json()
|
||||
}
|
||||
|
||||
// route_rename — POST /api/rename {"neighborhood_id":"...","name":"..."}. The
|
||||
// DEGENERATE, ASYNCHRONOUS, NON-BLOCKING override: Will (or a self-facet) renames
|
||||
// a reified neighborhood at any time; it SUPERSEDES the autonomous record into the
|
||||
// residue chain (cause="explicit-override", prior name retained). Never halts or
|
||||
// gates the beat — it just writes a superseding record the next beat sees. WRITE.
|
||||
fn route_rename(method: String, path: String, body: String) -> String {
|
||||
let id: String = json_get_string(body, "neighborhood_id")
|
||||
if str_eq(id, "") { return err_json("missing neighborhood_id") }
|
||||
let name: String = json_get_string(body, "name")
|
||||
if str_eq(name, "") { return err_json("missing name") }
|
||||
return engram_neighborhood_rename_json(id, name)
|
||||
}
|
||||
|
||||
// route_self_anchor — capture the durable SelfAnchor drift baseline (the current
|
||||
// self-geometry). Returns ok=false if ENGRAM_DRIFT_SENSOR is off or geometry is
|
||||
// unavailable. Pump once to establish the baseline before reading drift.
|
||||
fn route_self_anchor(method: String, path: String, body: String) -> String {
|
||||
let a: Int = engram_self_anchor_capture()
|
||||
let a_ok: String = if a == 0 { "false" } else { "true" }
|
||||
"{\"ok\":" + a_ok + ",\"anchored\":" + a_ok + "}"
|
||||
}
|
||||
|
||||
// route_drift — live self-drift reading: displacement of CURRENT self-geometry
|
||||
// vs the persisted SelfAnchor (GROWTH vs CORRUPTION split). {"error":...} when
|
||||
// disabled / no anchor yet. The heartbeat can fold this into its ISEs.
|
||||
fn route_drift(method: String, path: String, body: String) -> String {
|
||||
return engram_self_drift_json()
|
||||
}
|
||||
|
||||
fn route_load(method: String, path: String, body: String) -> String {
|
||||
let p_raw: String = json_get_string(body, "path")
|
||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||
let dir: String = engram_resolve_data_dir()
|
||||
let p: String = if str_eq(p_raw, "") { dir + "/snapshot.json" } else { p_raw }
|
||||
// (2026-08-10 self-review) This was a stub response over the single most
|
||||
// destructive operation in the server. engram_load returns 0 on an empty
|
||||
@@ -398,7 +619,7 @@ fn route_embed_backfill(method: String, path: String, body: String) -> String {
|
||||
let result: String = engram_embed_backfill(n)
|
||||
let done: Float = json_get_float(result, "embedded")
|
||||
if done > 0.0 {
|
||||
let saved: Int = persist_canonical()
|
||||
let saved: Int = persist_bulk()
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -417,7 +638,7 @@ fn route_embed_backfill(method: String, path: String, body: String) -> String {
|
||||
// (2026-06-27 self-review: added this route to fix silent 10-min sync failures)
|
||||
fn route_sync(method: String, path: String, body: String) -> String {
|
||||
let dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||
let dir: String = if str_eq(dir_raw, "") { "/tmp/engram" } else { dir_raw }
|
||||
let dir: String = engram_resolve_data_dir()
|
||||
// 2026-07-21 self-review: export to a scratch path, never the canonical
|
||||
// snapshot.json — read routes must not be able to clobber the good snapshot.
|
||||
let snap_path: String = dir + "/.sync-export.json"
|
||||
@@ -451,7 +672,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()
|
||||
let saved: Int = persist_bulk()
|
||||
"{\"ok\":true,\"nodes_added\":" + int_to_str(added_n) + ",\"edges_added\":" + int_to_str(added_e) + ",\"node_count\":" + int_to_str(engram_node_count()) + "}"
|
||||
}
|
||||
|
||||
@@ -485,6 +706,15 @@ fn route_load_merge(method: String, path: String, body: String) -> String {
|
||||
fn route_emit_ise(method: String, path: String, body: String) -> String {
|
||||
let content: String = json_get_string(body, "content")
|
||||
if str_eq(content, "") { return err_json("missing content") }
|
||||
// TELEMETRY OFF-GRAPH (ENGRAM_ISE_OFFGRAPH): route the state event to a
|
||||
// separate append-only log tier instead of inserting an edgeless graph node.
|
||||
// dream-recall (engram_dreams_json) reads the log tail when this is on, so
|
||||
// the one in-graph consumer is preserved. Default OFF -> existing behavior.
|
||||
if ise_offgraph_on() {
|
||||
let ok: Int = engram_ise_log_append(content)
|
||||
let ok_s: String = if ok == 0 { "false" } else { "true" }
|
||||
return "{\"ok\":" + ok_s + ",\"offgraph\":true}"
|
||||
}
|
||||
let sal: Float = 0.3
|
||||
let imp: Float = 0.3
|
||||
let conf: Float = 0.8
|
||||
@@ -550,8 +780,17 @@ 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 + "\"}"
|
||||
let saved: Int = persist_node(id)
|
||||
// ORPHAN PREVENTION (ENGRAM_AUTOCONNECT): same kNN auto-connect as
|
||||
// route_create_node — captured Knowledge should not land edgeless either.
|
||||
let connected: Int = if autoconnect_on() {
|
||||
let ec0: Int = engram_edge_count()
|
||||
let ac: String = engram_autoconnect_node(id, 3, 25)
|
||||
let added: Int = engram_edge_count() - ec0
|
||||
if added > 0 { let sv2: Int = persist_edges_since(ec0) }
|
||||
added
|
||||
} else { 0 }
|
||||
"{\"ok\":true,\"id\":\"" + id + "\",\"connected\":" + int_to_str(connected) + "}"
|
||||
}
|
||||
|
||||
// route_similarity — GET /api/similarity?a=<id>&b=<id>
|
||||
@@ -573,6 +812,139 @@ fn route_similarity(method: String, path: String, body: String) -> String {
|
||||
"{\"a\":\"" + a + "\",\"b\":\"" + b + "\",\"cosine\":" + float_to_str(sim) + "}"
|
||||
}
|
||||
|
||||
// ── M10 reified-neighborhood viz surface (read-only) ────────────────────────────
|
||||
//
|
||||
// The soul maintains reified neighborhoods (centroid, covariance-ellipsoid
|
||||
// extents, k-core skeleton, membership) as a resident index loaded at boot. These
|
||||
// routes surface that ALREADY-maintained structure so the viz shows the mind's
|
||||
// real reified regions instead of recomputing them client-side. They compute
|
||||
// nothing on request. NOTE: the offline reify WRITER (engram_geo_reify_store) is
|
||||
// currently unwired, so on the live store the resident index is empty and the
|
||||
// list returns [] until reification runs — see the cutover report.
|
||||
fn route_neighborhoods(method: String, path: String, body: String) -> String {
|
||||
engram_geo_reify_list_json()
|
||||
}
|
||||
|
||||
fn route_neighborhood(method: String, path: String, body: String) -> String {
|
||||
let id: String = extract_id(path, "/api/neighborhoods/")
|
||||
if str_eq(id, "") { return err_json("missing id") }
|
||||
return engram_geo_reify_get_json(id)
|
||||
}
|
||||
|
||||
// route_reify — POST /api/reify. WIRES the M10 reification writer: computes the
|
||||
// store's neighborhoods and PERSISTS each as a first-class Neighborhood node
|
||||
// (geometry in metadata) with relation="member" edges to its members, then
|
||||
// rebuilds the resident index so /api/neighborhoods reflects them at once. The
|
||||
// records live in neuron.egm, so they survive a cold reboot. WRITE op — the
|
||||
// central auth gate covers POST. Returns {"reified":N,"resident":M}.
|
||||
fn route_reify(method: String, path: String, body: String) -> String {
|
||||
return engram_geo_reify_run_json()
|
||||
}
|
||||
|
||||
// ── Geometry OPERATORS (read-only). The viz runs these on activated node/region
|
||||
// id-sets; the math already lives in the binary, these routes just expose it.
|
||||
// Faculty name -> underlying geometry op:
|
||||
// /api/gauge-distance -> engram_geo_distance (centroid distance/cosine + Wasserstein-2)
|
||||
// /api/recognize -> engram_geo_overlap (shared members, jaccard, overlap_score)
|
||||
// /api/discern -> engram_geo_subtract (?mode=setdiff | orthogonal residual)
|
||||
// /api/synthesize -> engram_geo_combine (merged region descriptor)
|
||||
// Inputs: ?a=<csv ids>&b=<csv ids> (id sets = activated neighborhoods' members / nodes).
|
||||
// Compute-only: no store writes, so auth-exempt like the other GET read routes.
|
||||
fn route_gauge_distance(method: String, path: String, body: String) -> String {
|
||||
let a: String = query_param(path, "a")
|
||||
let b: String = query_param(path, "b")
|
||||
if str_eq(a, "") { return err_json("missing a") }
|
||||
if str_eq(b, "") { return err_json("missing b") }
|
||||
return engram_geo_distance_json(a, b)
|
||||
}
|
||||
fn route_recognize(method: String, path: String, body: String) -> String {
|
||||
let a: String = query_param(path, "a")
|
||||
let b: String = query_param(path, "b")
|
||||
if str_eq(a, "") { return err_json("missing a") }
|
||||
if str_eq(b, "") { return err_json("missing b") }
|
||||
return engram_geo_overlap_json(a, b)
|
||||
}
|
||||
fn route_discern(method: String, path: String, body: String) -> String {
|
||||
let a: String = query_param(path, "a")
|
||||
let b: String = query_param(path, "b")
|
||||
if str_eq(a, "") { return err_json("missing a") }
|
||||
if str_eq(b, "") { return err_json("missing b") }
|
||||
let mode: String = query_param(path, "mode")
|
||||
return engram_geo_subtract_json(a, b, mode)
|
||||
}
|
||||
fn route_synthesize(method: String, path: String, body: String) -> String {
|
||||
let a: String = query_param(path, "a")
|
||||
let b: String = query_param(path, "b")
|
||||
if str_eq(a, "") { return err_json("missing a") }
|
||||
if str_eq(b, "") { return err_json("missing b") }
|
||||
return engram_geo_combine_json(a, b)
|
||||
}
|
||||
|
||||
// route_nearest — GET /api/nearest/<id>?k=3 — read-only kNN semantic neighbors of
|
||||
// a node (cosine). Drives the orphan-backfill dry-run and manual inspection.
|
||||
fn route_nearest(method: String, path: String, body: String) -> String {
|
||||
let id: String = extract_id(path, "/api/nearest/")
|
||||
if str_eq(id, "") { return err_json("missing id") }
|
||||
let k: Int = query_int(path, "k", 3)
|
||||
return engram_nearest_json(id, k)
|
||||
}
|
||||
|
||||
// ── COGNITION: THE ONE OPERATION (think) surfaced as act-named verbs. Every
|
||||
// faculty routes to engram_think_json with a faculty label — one primitive
|
||||
// underneath. ground/assert/attend are the hold/ground/assert split; the
|
||||
// correspondence-beat is the reflexive learning loop, keystone-protected.
|
||||
fn route_think(method: String, path: String, body: String) -> String {
|
||||
let seeds: String = query_param(path, "seeds")
|
||||
if str_eq(seeds, "") { return err_json("missing seeds") }
|
||||
let faculty: String = query_param(path, "faculty")
|
||||
let f: String = if str_eq(faculty, "") { "reason" } else { faculty }
|
||||
return engram_think_json(seeds, f)
|
||||
}
|
||||
fn route_faculty(path: String, faculty: String) -> String {
|
||||
let seeds: String = query_param(path, "seeds")
|
||||
if str_eq(seeds, "") { return err_json("missing seeds") }
|
||||
return engram_think_json(seeds, faculty)
|
||||
}
|
||||
// PROOF of the decorator-seam auto-emit. The body does exactly ONE thing —
|
||||
// return a string — with ZERO hand-written telemetry. The @manager decorator
|
||||
// makes codegen inject engram_boundary_beat() at entry, so every call fires
|
||||
// interoception (chrono tick) + telemetry (afferent counter) + strengthen
|
||||
// (self-activity) + a dharma bus event. Observe via /api/act-stats before/after.
|
||||
@manager
|
||||
fn route_boundary_proof(method: String, path: String, body: String) -> String {
|
||||
return "{\"op\":\"boundary_proof\",\"body_instrumentation\":\"none\",\"seam\":\"@manager -> engram_boundary_beat auto-injected\"}"
|
||||
}
|
||||
fn route_ground(method: String, path: String, body: String) -> String {
|
||||
let claim: String = json_get_string(body, "claim")
|
||||
let evidence: String = json_get_string(body, "evidence")
|
||||
let for_whom: String = json_get_string(body, "for_whom")
|
||||
if str_eq(claim, "") { return err_json("missing claim") }
|
||||
if str_eq(evidence, "") { return err_json("missing evidence") }
|
||||
return engram_ground_json(claim, evidence, for_whom)
|
||||
}
|
||||
fn route_assert(method: String, path: String, body: String) -> String {
|
||||
let claim: String = query_param(path, "claim")
|
||||
if str_eq(claim, "") { return err_json("missing claim") }
|
||||
let for_whom: String = query_param(path, "for_whom")
|
||||
let floor: String = query_param(path, "floor")
|
||||
return engram_assert_json(claim, for_whom, floor)
|
||||
}
|
||||
fn route_attend(method: String, path: String, body: String) -> String {
|
||||
let node: String = json_get_string(body, "node")
|
||||
let observer: String = json_get_string(body, "observer")
|
||||
let salience: String = json_get_string(body, "salience")
|
||||
if str_eq(node, "") { return err_json("missing node") }
|
||||
return engram_attend_json(node, observer, salience)
|
||||
}
|
||||
fn route_correspondence_beat(method: String, path: String, body: String) -> String {
|
||||
let seeds: String = json_get_string(body, "seeds")
|
||||
if str_eq(seeds, "") { return err_json("missing seeds") }
|
||||
let faculty: String = json_get_string(body, "faculty")
|
||||
let f: String = if str_eq(faculty, "") { "induce" } else { faculty }
|
||||
let keystone: String = json_get_string(body, "keystone")
|
||||
return engram_correspondence_beat_json(seeds, f, keystone)
|
||||
}
|
||||
|
||||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn check_auth_ok(method: String, body: String) -> Bool {
|
||||
@@ -639,6 +1011,11 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/nodes/") {
|
||||
return route_get_node(method, path, body)
|
||||
}
|
||||
// Singular alias: /api/node/<id>. Distinct prefix from /api/nodes/ ("node/"
|
||||
// vs "nodes/"), so no collision with the plural route above.
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/node/") {
|
||||
return route_get_node_singular(method, path, body)
|
||||
}
|
||||
if str_eq(method, "DELETE") && str_starts_with(clean, "/api/nodes/") {
|
||||
return route_forget(method, path, body)
|
||||
}
|
||||
@@ -654,10 +1031,85 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/edges/batch") || str_eq(clean, "/edges/batch")) {
|
||||
return route_create_edges_batch(method, path, body)
|
||||
}
|
||||
// M10 reified neighborhoods (read-only viz surface). Checked before the
|
||||
// /api/neighbors/ prefix; the two do not collide ("neighborhoods" vs
|
||||
// "neighbors/") but keeping them adjacent documents the intent.
|
||||
if str_eq(method, "GET") && (str_eq(clean, "/api/neighborhoods") || str_eq(clean, "/neighborhoods")) {
|
||||
return route_neighborhoods(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/neighborhoods/") {
|
||||
return route_neighborhood(method, path, body)
|
||||
}
|
||||
// WRITE: run reification, persisting Neighborhood nodes + member edges.
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/reify") || str_eq(clean, "/reify")) {
|
||||
return route_reify(method, path, body)
|
||||
}
|
||||
// WRITE: on-beat self-reification (flag-gated). Explicit pump for validation.
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/self-reify-beat") || str_eq(clean, "/self-reify-beat")) {
|
||||
return route_self_reify_beat(method, path, body)
|
||||
}
|
||||
// WRITE: async explicit override — rename a reified neighborhood (→ residue).
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/rename") || str_eq(clean, "/rename")) {
|
||||
return route_rename(method, path, body)
|
||||
}
|
||||
// READ-ONLY geometry operators over id-sets (?a=csv&b=csv[&mode=]).
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/gauge-distance") {
|
||||
return route_gauge_distance(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/recognize") {
|
||||
return route_recognize(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/discern") {
|
||||
return route_discern(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/synthesize") {
|
||||
return route_synthesize(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/nearest/") {
|
||||
return route_nearest(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/neighbors/") {
|
||||
return route_neighbors(method, path, body)
|
||||
}
|
||||
|
||||
// ── COGNITION: the ONE operation + grounding, surfaced live (2026-08-14).
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/boundary-proof") {
|
||||
return route_boundary_proof(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/think") {
|
||||
return route_think(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/reason") {
|
||||
return route_faculty(path, "reason")
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/induce") {
|
||||
return route_faculty(path, "induce")
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/abduce") {
|
||||
return route_faculty(path, "abduce")
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/relate") {
|
||||
return route_faculty(path, "relate")
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/analogize") {
|
||||
return route_faculty(path, "analogy")
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/plan") {
|
||||
return route_faculty(path, "plan")
|
||||
}
|
||||
if str_eq(method, "POST") && str_starts_with(clean, "/api/ground") {
|
||||
return route_ground(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/assert") {
|
||||
return route_assert(method, path, body)
|
||||
}
|
||||
if str_eq(method, "POST") && str_starts_with(clean, "/api/attend") {
|
||||
return route_attend(method, path, body)
|
||||
}
|
||||
if str_eq(method, "POST") && str_starts_with(clean, "/api/correspondence-beat") {
|
||||
return route_correspondence_beat(method, path, body)
|
||||
}
|
||||
|
||||
// Activation + Search
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/activate") || str_eq(clean, "/activate")) {
|
||||
return route_activate(method, path, body)
|
||||
@@ -665,6 +1117,12 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/activate") {
|
||||
return route_activate(method, path, body)
|
||||
}
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/search-lexical") || str_eq(clean, "/search-lexical")) {
|
||||
return route_search_lexical(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/search-lexical") {
|
||||
return route_search_lexical(method, path, body)
|
||||
}
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/search") || str_eq(clean, "/search")) {
|
||||
return route_search(method, path, body)
|
||||
}
|
||||
@@ -681,6 +1139,20 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/save") || str_eq(clean, "/save")) {
|
||||
return route_save(method, path, body)
|
||||
}
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/checkpoint") || str_eq(clean, "/checkpoint")) {
|
||||
return route_checkpoint(method, path, body)
|
||||
}
|
||||
|
||||
// M-INTEROCEPTION: chronoception soul-tick + self-drift (flag-gated)
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/tick") || str_eq(clean, "/tick")) {
|
||||
return route_tick(method, path, body)
|
||||
}
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/self_anchor") || str_eq(clean, "/self_anchor")) {
|
||||
return route_self_anchor(method, path, body)
|
||||
}
|
||||
if (str_eq(method, "POST") || str_eq(method, "GET")) && (str_eq(clean, "/api/drift") || str_eq(clean, "/drift")) {
|
||||
return route_drift(method, path, body)
|
||||
}
|
||||
if str_eq(method, "POST") && (str_eq(clean, "/api/load") || str_eq(clean, "/load")) {
|
||||
return route_load(method, path, body)
|
||||
}
|
||||
@@ -713,23 +1185,44 @@ let bind_str: String = if str_eq(bind_raw, "") { ":8742" } else { bind_raw }
|
||||
let port: Int = parse_port(bind_str)
|
||||
|
||||
// On startup, try to load any existing snapshot (best effort).
|
||||
let data_dir_raw: String = env("ENGRAM_DATA_DIR")
|
||||
let data_dir: String = if str_eq(data_dir_raw, "") { "/tmp/engram" } else { data_dir_raw }
|
||||
// §18.2: resolve the data dir safely — unset ENGRAM_DATA_DIR → $HOME/.neuron/engram,
|
||||
// never /tmp; fail loud if HOME is unresolvable (engram_resolve_data_dir exits).
|
||||
let data_dir: String = engram_resolve_data_dir()
|
||||
let snapshot_path: String = data_dir + "/snapshot.json"
|
||||
engram_load(snapshot_path)
|
||||
// ENGRAM_STORE (tiered paged store — engram-tiered-storage-engine.md). When set,
|
||||
// the durable owner is the paged store (neuron.egm + neuron.wal): engram_store_boot
|
||||
// imports snapshot.json ONCE into a fresh neuron.egm, else replays the WAL and loads
|
||||
// the store resident — snapshot.json is never read again as the ongoing store. This
|
||||
// closes the "restart reverted to a 17h-old snapshot" data-loss window. Flag-off
|
||||
// (default): byte-for-byte the historical snapshot + optional-WAL boot below.
|
||||
if store_on() {
|
||||
engram_store_boot(data_dir)
|
||||
println("[engram] ENGRAM_STORE enabled — tiered paged store is the durable owner")
|
||||
} else {
|
||||
engram_load(snapshot_path)
|
||||
|
||||
// 2026-07-21 self-review boot guard: if the snapshot file has content but the
|
||||
// load produced 0 nodes, something is wrong (corrupt file / parse failure).
|
||||
// Preserve the evidence and warn loudly — and since read routes no longer write
|
||||
// the canonical path, a bad boot can no longer clobber the good snapshot.
|
||||
let boot_snap: String = fs_read(snapshot_path)
|
||||
if !str_eq(boot_snap, "") {
|
||||
if engram_node_count() == 0 {
|
||||
println("[engram] WARNING: snapshot.json is non-empty but load produced 0 nodes — preserving copy at snapshot.failed-load.json")
|
||||
fs_write(data_dir + "/snapshot.failed-load.json", boot_snap)
|
||||
} else {
|
||||
// Good load: keep a boot-time backup of the snapshot as loaded.
|
||||
fs_write(data_dir + "/snapshot.boot-backup.json", boot_snap)
|
||||
// WAL replay (design doc §6). Gated: default OFF is byte-identical to legacy
|
||||
// snapshot-only boot. When ON, the snapshot above is the compaction BASE and
|
||||
// the WAL carries every mutation since; replay reconstructs state to the last
|
||||
// CRC-valid record, then opens the WAL for appending.
|
||||
if wal_on() {
|
||||
let replayed: Int = engram_wal_boot(data_dir)
|
||||
println("[engram] WAL enabled — replayed " + int_to_str(replayed) + " records")
|
||||
}
|
||||
|
||||
// 2026-07-21 self-review boot guard: if the snapshot file has content but the
|
||||
// load produced 0 nodes, something is wrong (corrupt file / parse failure).
|
||||
// Preserve the evidence and warn loudly — and since read routes no longer write
|
||||
// the canonical path, a bad boot can no longer clobber the good snapshot.
|
||||
let boot_snap: String = fs_read(snapshot_path)
|
||||
if !str_eq(boot_snap, "") {
|
||||
if engram_node_count() == 0 {
|
||||
println("[engram] WARNING: snapshot.json is non-empty but load produced 0 nodes — preserving copy at snapshot.failed-load.json")
|
||||
fs_write(data_dir + "/snapshot.failed-load.json", boot_snap)
|
||||
} else {
|
||||
// Good load: keep a boot-time backup of the snapshot as loaded.
|
||||
fs_write(data_dir + "/snapshot.boot-backup.json", boot_snap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user