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,