From 40eb48e92f63d62d59a152ca3987909d6065dec8 Mon Sep 17 00:00:00 2001 From: bigmerge Date: Sat, 15 Aug 2026 19:34:04 -0500 Subject: [PATCH] engram: fix silently-wrong query params, and make el_seed.o + el_runtime.o link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three real bugs, all found by actually running the thing rather than reading it. 1. query_param never URL-decoded. A GET of /api/search?q=neural%20network searched for the literal string "neural%20network" and returned []. Every multi-word search against the live engram has been silently returning empty results — not an error, an empty result, which is why it went unnoticed. Affects every GET route that reads query params, not just search. 2. query_param matched key names unanchored. str_index_of(qs, "q=") matches inside "faq=", so "?faq=X&q=Y" returned X for key "q". Verified live before the fix. Now searches for "&key=" against "&"+querystring so a match can only land on a real parameter boundary. 3. el_request_start/el_request_end were defined in BOTH el_seed.c and el_runtime.c, so linking the two objects together — which is exactly what the product build does — failed with duplicate symbols. el_seed.c's own comment already says these moved there ("formerly defined in el_runtime.c. Now self-contained in el_seed.c"); the el_runtime.c copies were left behind during that move. Removed them, kept declarations since http_worker calls them. Also added the three missing prototypes (engram_op_assert_json, engram_node_full_in, engram_connect_in) that el_seed.c wraps but never declared, which made it fail to compile standalone under C99+. Verified: engram builds and links clean from canonical source; before/after comparison on a copy of the real store shows "neural network" returning a real match where the live build returns [], and "?faq=WRONG&q=MetaColloc" now resolving to MetaColloc. Live engram on :8742 was never touched. --- engram/src/server.el | 22 +++++++++++++++++----- lang/runtime/el_runtime.c | 25 ++++++++----------------- lang/runtime/el_seed.c | 11 +++++++++++ 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/engram/src/server.el b/engram/src/server.el index 01afb1b..40f7bac 100644 --- a/engram/src/server.el +++ b/engram/src/server.el @@ -41,17 +41,29 @@ fn strip_query(path: String) -> String { str_slice(path, 0, q) } +// query_param — extract one query-string value, URL-DECODED. +// +// The decode step was missing (found 2026-08-15): a claim sent as +// "test%20claim" arrived at engram_assert_json still percent-encoded and was +// stored/compared that way, so any value containing a space, &, =, or non-ASCII +// character silently became a different string than the caller sent. Affects +// every GET route that reads params this way, not just /api/assert. fn query_param(path: String, key: String) -> String { let q: Int = str_index_of(path, "?") if q < 0 { return "" } let qs: String = str_slice(path, q + 1, str_len(path)) - let needle: String = key + "=" - let pos: Int = str_index_of(qs, needle) + // Anchor the match to a real key boundary: prefixing "&" and searching for + // "&key=" means "q" can never match inside "faq=". (Found 2026-08-15: + // "?faq=X&q=Y" returned X for key "q" — a silently wrong value, not an + // error.) The leading "&" makes the first parameter match the same way. + let hay: String = "&" + qs + let needle: String = "&" + key + "=" + let pos: Int = str_index_of(hay, needle) if pos < 0 { return "" } - let after: String = str_slice(qs, pos + str_len(needle), str_len(qs)) + let after: String = str_slice(hay, pos + str_len(needle), str_len(hay)) let amp: Int = str_index_of(after, "&") - if amp < 0 { return after } - str_slice(after, 0, amp) + let raw: String = if amp < 0 { after } else { str_slice(after, 0, amp) } + return __url_decode(raw) } fn query_int(path: String, key: String, default_val: Int) -> Int { diff --git a/lang/runtime/el_runtime.c b/lang/runtime/el_runtime.c index 4140699..173f98e 100644 --- a/lang/runtime/el_runtime.c +++ b/lang/runtime/el_runtime.c @@ -105,23 +105,14 @@ static void el_arena_track(char* p) { _tl_arena.ptrs[_tl_arena.count++] = p; } -/* Called by http_worker before dispatching the El handler. */ -void el_request_start(void) { - _tl_arena.count = 0; - _tl_arena_active = 1; - _tl_fs_read_len = 0; /* never let a previous request's file length */ - _tl_fs_read_buf = NULL; /* leak into this response's byte accounting */ -} - -/* Called by http_worker after the El handler returns and the response is sent. - * Frees every intermediate string allocated during the request. */ -void el_request_end(void) { - _tl_arena_active = 0; - for (size_t i = 0; i < _tl_arena.count; i++) { - free(_tl_arena.ptrs[i]); - } - _tl_arena.count = 0; -} +/* el_request_start / el_request_end moved to el_seed.c (see its comment at the + * definition: "formerly defined in el_runtime.c. Now self-contained in + * el_seed.c, delegating to the seed arena."). The copies here were left behind + * during that move and made el_seed.o + el_runtime.o fail to link together with + * duplicate symbols — which is exactly the link the real product build does. + * Declared (not defined) here: el_runtime.c's http_worker still calls them. */ +void el_request_start(void); +void el_request_end(void); /* ── Scoped arena for CLI use ─────────────────────────────────────────────── * * CLI programs never call el_request_start/end, so all strdup allocations are diff --git a/lang/runtime/el_seed.c b/lang/runtime/el_seed.c index a178449..07696c6 100644 --- a/lang/runtime/el_seed.c +++ b/lang/runtime/el_seed.c @@ -1383,6 +1383,17 @@ el_val_t __engram_activate_json(el_val_t query, el_val_t depth) { return engram_activate_json(query, depth); } +/* Forward decls for el_runtime.c symbols this file wraps. el_seed.c does not + * include el_runtime.h (documented in lang/AGENTS.md), so each wrapped symbol + * needs a prototype here or clang treats it as an implicit declaration (error + * under C99+) and the ABI mis-truncates the el_val_t return. */ +el_val_t engram_op_assert_json(el_val_t node_id, el_val_t depth); +el_val_t engram_node_full_in(el_val_t purview, el_val_t content, el_val_t node_type, el_val_t label, + el_val_t salience, el_val_t importance, el_val_t confidence, + el_val_t tier, el_val_t tags); +void engram_connect_in(el_val_t purview, el_val_t from_id, el_val_t to_id, + el_val_t weight, el_val_t relation); + el_val_t __engram_stats_json(void) { return engram_stats_json(); } el_val_t __engram_op_assert_json(el_val_t node_id, el_val_t depth) { return engram_op_assert_json(node_id, depth); } el_val_t __engram_node_full_in(el_val_t purview, el_val_t content, el_val_t node_type, el_val_t label,