From 337ce5e9f89aff93106b8b01f0bffbd6bd9bae26 Mon Sep 17 00:00:00 2001 From: "Claude Opus 4.6" Date: Mon, 23 Mar 2026 12:32:09 +0000 Subject: [PATCH] Fix statement leaks, error handling, UTC timestamps in Engram --- _build/dev/lib/symbiont/.mix/compile.elixir | Bin 3790 -> 3787 bytes _build/prod/lib/symbiont/.mix/compile.elixir | Bin 3796 -> 3798 bytes lib/symbiont/engram.ex | 84 ++++++++++++------- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/_build/dev/lib/symbiont/.mix/compile.elixir b/_build/dev/lib/symbiont/.mix/compile.elixir index d9a2c6a7681fbaa8d93efebbdee9073d47830143..9973c9e6d3e19e31112d684a0729e0f27049446a 100644 GIT binary patch literal 3787 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 BXb=DZ literal 3790 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 SZA8zA@Z(AN Base.encode16(case: :lower) now - |> NaiveDateTime.to_iso8601() + |> DateTime.to_iso8601() |> String.replace(~r/[T:\-]/, "") |> String.slice(0, 14) |> Kernel.<>("-#{hex}") end defp now_iso do - NaiveDateTime.local_now() |> NaiveDateTime.to_iso8601() + DateTime.utc_now() |> DateTime.to_iso8601() end defp is_stale?(last_heartbeat) do - case NaiveDateTime.from_iso8601(last_heartbeat) do - {:ok, dt} -> - NaiveDateTime.diff(NaiveDateTime.local_now(), dt, :minute) > @stale_threshold_minutes + # Parse timestamp — handles both UTC DateTime (new) and naive (legacy Python) + with {:error, _} <- parse_as_datetime(last_heartbeat), + {:error, _} <- parse_as_naive(last_heartbeat) do + false + else + {:ok, dt} -> DateTime.diff(DateTime.utc_now(), dt, :minute) > @stale_threshold_minutes + end + end - _ -> - false + defp parse_as_datetime(str) do + case DateTime.from_iso8601(str) do + {:ok, dt, _offset} -> {:ok, dt} + _ -> {:error, :invalid} + end + end + + defp parse_as_naive(str) do + case NaiveDateTime.from_iso8601(str) do + {:ok, ndt} -> {:ok, DateTime.from_naive!(ndt, "Etc/UTC")} + _ -> {:error, :invalid} end end @@ -546,49 +560,59 @@ defmodule Symbiont.Engram do defp exec(conn, sql, params \\ []) do {:ok, stmt} = Exqlite.Sqlite3.prepare(conn, sql) - if params != [] do - :ok = Exqlite.Sqlite3.bind(stmt, params) - end + try do + if params != [] do + :ok = Exqlite.Sqlite3.bind(stmt, params) + end - case Exqlite.Sqlite3.step(conn, stmt) do - :done -> :ok - {:row, _} -> :ok - {:error, reason} -> {:error, reason} + case Exqlite.Sqlite3.step(conn, stmt) do + :done -> :ok + {:row, _} -> :ok + {:error, reason} -> raise "SQLite exec error: #{inspect(reason)}" + end + after + Exqlite.Sqlite3.release(conn, stmt) end - after - :ok end defp query_one(conn, sql, params \\ []) do {:ok, stmt} = Exqlite.Sqlite3.prepare(conn, sql) - if params != [] do - :ok = Exqlite.Sqlite3.bind(stmt, params) - end + try do + if params != [] do + :ok = Exqlite.Sqlite3.bind(stmt, params) + end - case Exqlite.Sqlite3.step(conn, stmt) do - {:row, row} -> {:ok, row} - :done -> :empty - {:error, reason} -> {:error, reason} + case Exqlite.Sqlite3.step(conn, stmt) do + {:row, row} -> {:ok, row} + :done -> :empty + {:error, reason} -> raise "SQLite query error: #{inspect(reason)}" + end + after + Exqlite.Sqlite3.release(conn, stmt) end end defp query_all(conn, sql, params \\ []) do {:ok, stmt} = Exqlite.Sqlite3.prepare(conn, sql) - if params != [] do - :ok = Exqlite.Sqlite3.bind(stmt, params) - end + try do + if params != [] do + :ok = Exqlite.Sqlite3.bind(stmt, params) + end - rows = collect_rows(conn, stmt, []) - {:ok, rows} + rows = collect_rows(conn, stmt, []) + {:ok, rows} + after + Exqlite.Sqlite3.release(conn, stmt) + end end defp collect_rows(conn, stmt, acc) do case Exqlite.Sqlite3.step(conn, stmt) do {:row, row} -> collect_rows(conn, stmt, [row | acc]) :done -> Enum.reverse(acc) - {:error, _reason} -> Enum.reverse(acc) + {:error, reason} -> raise "SQLite step error mid-collection: #{inspect(reason)}" end end end