// runtime/engram.el — El wrapper for the engram graph store // // Thin wrappers over the __engram_* seed primitives defined in el_seed.c. // Each function delegates directly to the corresponding seed — no logic here. // The seed layer owns all storage, indexing, and graph traversal. // // Dependencies: runtime/string.el, runtime/json.el // --- Validation (defense in depth) --- // el_val_t is an untyped machine word, so a wrong TYPE can't be caught here — but a // wrong VALUE can (a tier in the node_type slot, an empty/garbage string, an int, a // path, a model name, a cgi id). Reject loudly instead of silently writing junk. fn engram_valid_node_type(t: String) -> Bool { return str_eq(t, "Memory") || str_eq(t, "Knowledge") || str_eq(t, "Belief") || str_eq(t, "Project") || str_eq(t, "Tag") || str_eq(t, "BacklogItem") || str_eq(t, "Artifact") || str_eq(t, "Conversation") || str_eq(t, "ExecutionContext") || str_eq(t, "InternalStateEvent") || str_eq(t, "Self") || str_eq(t, "Entity") || str_eq(t, "Process") || str_eq(t, "ConfigEntry") || str_eq(t, "Concept") || str_eq(t, "Imprint") || str_eq(t, "SessionSummary") } fn engram_valid_tier(t: String) -> Bool { return str_eq(t, "Semantic") || str_eq(t, "Episodic") || str_eq(t, "Working") || str_eq(t, "Procedural") || str_eq(t, "Canonical") || str_eq(t, "Note") || str_eq(t, "Lesson") } // --- Node creation --- fn engram_node(content: String, node_type: String, salience: Float) -> String { if !engram_valid_node_type(node_type) { __println("[engram] REJECTED node write — invalid node_type '" + node_type + "'") return "" } return __engram_node(content, node_type, salience) } // Signature MUST match the C primitive __engram_node_full exactly (el_seed.h): // (content, node_type, label, salience, importance, confidence, tier, tags) // The previous wrapper declared a stale 8-arg schema with wrong names AND types // (sal:Float at the label slot, ts:Int at the tier slot). Because el_val_t is an // untyped machine word, the EL compiler coerced caller args to those wrong param // types and then forwarded them BY POSITION into the C function — so tier received // an int, importance/confidence received strings, label received a float, etc. // That is the field-corruption bug. Match the contract 1:1 — no coercion, no reorder. fn engram_node_full(content: String, node_type: String, label: String, salience: Float, importance: Float, confidence: Float, tier: String, tags: String) -> String { if !engram_valid_node_type(node_type) { __println("[engram] REJECTED node write — invalid node_type '" + node_type + "' (label=" + label + ")") return "" } if !engram_valid_tier(tier) { __println("[engram] REJECTED node write — invalid tier '" + tier + "' (node_type=" + node_type + ", label=" + label + ")") return "" } return __engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags) } // --- Node retrieval --- fn engram_get_node(id: String) -> String { return __engram_get_node(id) } fn engram_node_count() -> Int { return __engram_node_count() } // --- Node lifecycle --- fn engram_strengthen(id: String) -> Bool { return __engram_strengthen(id) } fn engram_forget(id: String) -> Bool { return __engram_forget(id) } // --- Search and scan --- fn engram_search(query: String, limit: Int) -> String { return __engram_search(query, limit) } fn engram_scan_nodes(limit: Int, offset: Int) -> String { return __engram_scan_nodes(limit, offset) } fn engram_scan_nodes_json(limit: Int, offset: Int) -> String { return __engram_scan_nodes_json(limit, offset) } // --- Graph edges --- fn engram_connect(from: String, to: String, rel: String, weight: Float) -> Bool { return __engram_connect(from, to, rel, weight) } fn engram_edge_between(a: String, b: String) -> String { return __engram_edge_between(a, b) } // --- Graph traversal --- fn engram_neighbors(id: String) -> String { return __engram_neighbors(id) } fn engram_neighbors_filtered(id: String, rel: String, min_w: Float) -> String { return __engram_neighbors_filtered(id, rel, min_w) } fn engram_activate(query: String, depth: Int) -> String { return __engram_activate(query, depth) } fn engram_activate_json(query: String, limit: Int) -> String { return __engram_activate_json(query, limit) } // --- Generation --- fn generate(form: String) -> String { return __generate(form) }