85eee42106
Register the six engram_geo_*_json operators in the compiler builtin_arity table (bare heavy-runtime names + __ seed names, mirroring engram_activate_json) and add the engram.el module wrappers, so a compiled El (CGI) program can call them by name. The heavy-runtime C functions already existed (el_runtime.c:12287+, declared el_runtime.h:627-632); this completes the EL call surface. The shipped elc already emits a direct C call for these builtins (unknown ident-calls pass through), so no self-host compiler fold — the memory-heavy, drift-prone step — was required. Demonstrated end-to-end: a compiled geo_ops_demo.el booted a copy of the store (13,036 nodes) and produced real subtract/distance JSON on two real neighborhoods; test_geo_ops.c stays 20/20, ASan/UBSan clean. Also brace the centroid_unit normalization if/else in engram_geometry.c to clear the misleading-indentation warning (behavior-neutral).
158 lines
5.7 KiB
EmacsLisp
158 lines
5.7 KiB
EmacsLisp
// 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)
|
|
}
|
|
|
|
// --- Geometry operators (§5) ---
|
|
// Relational-neighborhood algebra over centered engram descriptors. Each takes
|
|
// comma-separated seed-id set(s); a centered neighborhood is grown from those
|
|
// seeds (ad-hoc NOCACHE path) and the operator's JSON result is returned.
|
|
// Delegates to the native __engram_geo_*_json seeds (el_seed.c); in the heavy
|
|
// engram runtime the same bare names resolve directly to el_runtime.c symbols.
|
|
|
|
fn engram_geo_descriptor_json(seeds: String) -> String {
|
|
return __engram_geo_descriptor_json(seeds)
|
|
}
|
|
|
|
fn engram_geo_overlap_json(a_seeds: String, b_seeds: String) -> String {
|
|
return __engram_geo_overlap_json(a_seeds, b_seeds)
|
|
}
|
|
|
|
fn engram_geo_subtract_json(a_seeds: String, b_seeds: String, mode: String) -> String {
|
|
return __engram_geo_subtract_json(a_seeds, b_seeds, mode)
|
|
}
|
|
|
|
fn engram_geo_combine_json(a_seeds: String, b_seeds: String) -> String {
|
|
return __engram_geo_combine_json(a_seeds, b_seeds)
|
|
}
|
|
|
|
fn engram_geo_distance_json(a_seeds: String, b_seeds: String) -> String {
|
|
return __engram_geo_distance_json(a_seeds, b_seeds)
|
|
}
|
|
|
|
fn engram_geo_analogy_json(a_seeds: String, b_seeds: String) -> String {
|
|
return __engram_geo_analogy_json(a_seeds, b_seeds)
|
|
}
|
|
|
|
// --- Generation ---
|
|
|
|
fn generate(form: String) -> String {
|
|
return __generate(form)
|
|
}
|