From b7fd8901d4307a95cada4269cf6f72e715a286b8 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 22 Jun 2026 11:53:09 -0500 Subject: [PATCH] fix(routes): fix handle_request ABI, 429 status code, soul_boot_ts write --- routes.el | 17 +++++++++++++---- soul.el | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/routes.el b/routes.el index 25dd892..3ac0fc9 100644 --- a/routes.el +++ b/routes.el @@ -18,6 +18,13 @@ import "soul.elh" // minute). Falls back to 60 req/min if not set. The /health endpoint is // exempt so monitoring does not consume quota. // +// State growth: each unique source IP accumulates exactly 2 state keys +// (count + window) for the lifetime of the process. Per-IP storage is +// bounded and constant; values reset on window expiry. In aggregate, state +// grows linearly with distinct IPs — typical for a trusted-client service. +// EL has no state_delete builtin, so keys from inactive IPs persist. +// TODO: add state_delete sweep when the EL runtime exposes that primitive. +// // Returns "" when the request is allowed, or a 429 JSON body when rejected. // --------------------------------------------------------------------------- fn rate_limit_check(ip: String, path: String) -> String { @@ -54,7 +61,7 @@ fn rate_limit_check(ip: String, path: String) -> String { if new_count > limit { let retry_after: Int = 60 - (now - eff_win) let eff_retry: Int = if retry_after < 0 { 0 } else { retry_after } - return "{\"error\":\"rate limit exceeded\",\"code\":\"rate_limited\",\"retry_after_secs\":" + int_to_str(eff_retry) + "}" + return "{\"__status__\":429,\"error\":\"rate limit exceeded\",\"code\":\"rate_limited\",\"retry_after_secs\":" + int_to_str(eff_retry) + "}" } return "" } @@ -331,11 +338,13 @@ fn handle_connectors(method: String, clean: String, body: String) -> String { return "{\"ok\":false,\"error\":\"unknown connectors route\"}" } -fn handle_request(method: String, path: String, body: String, ip: String) -> String { +fn handle_request(method: String, path: String, body: String) -> String { let clean: String = strip_query(path) - // Rate limit check. ip may be empty (e.g. loopback/unix socket callers) — skip - // enforcement when no address is available so internal tooling is never blocked. + // Rate limit check. Extract caller IP from REMOTE_ADDR env var (set by the + // EL HTTP runtime for each request). Skip enforcement when empty so + // loopback/internal callers are never blocked. + let ip: String = env("REMOTE_ADDR") if !str_eq(ip, "") { let rl_result: String = rate_limit_check(ip, clean) if !str_eq(rl_result, "") { diff --git a/soul.el b/soul.el index 0147f2a..dbb4376 100644 --- a/soul.el +++ b/soul.el @@ -369,6 +369,7 @@ load_identity_context() seed_persona_from_env() let boot_num: Int = mem_boot_count_inc() state_set("soul_boot_count", int_to_str(boot_num)) +state_set("soul_boot_ts", int_to_str(time_now())) println("[soul] boot #" + int_to_str(boot_num)) emit_session_start_event()