From dfe4e83ed1799a775293d3cc8e363aeb8bda8a1c Mon Sep 17 00:00:00 2001 From: Tim Lingo Date: Mon, 8 Jun 2026 16:13:43 -0500 Subject: [PATCH] Fix engram_node_full wrapper field corruption + add node_type/tier validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wrapper signature was stale and didn't match the C primitive __engram_node_full(content, node_type, label, salience, importance, confidence, tier, tags). Because el_val_t is an untyped machine word, the compiler coerced caller args to the wrong declared param types and forwarded them BY POSITION — so tier received an int, importance/confidence received strings, label received a float, etc. (~100 corrupt nodes). - Correct the wrapper to match the C contract 1:1 (no coercion, no reorder). - Add engram_valid_node_type / engram_valid_tier allowlists; engram_node and engram_node_full now reject invalid values with __println + return "" (fail loud, no silent malformed write). See neuron repo: HANDOFF-engram-write-corruption.md for the full write-up + deploy runbook. Co-Authored-By: Claude Opus 4.8 --- lang/runtime/engram.el | 45 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/lang/runtime/engram.el b/lang/runtime/engram.el index 0b90e0d..08930f0 100644 --- a/lang/runtime/engram.el +++ b/lang/runtime/engram.el @@ -6,15 +6,54 @@ // // 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") +} + +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) } -fn engram_node_full(content: String, nt: String, sal: Float, imp: Float, - source: String, lang: String, ts: Int, tags: String) -> String { - return __engram_node_full(content, nt, sal, imp, source, lang, ts, tags) +// 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 ---