From fc002354d4fdb4845c42efbf5ae176dc42149caf Mon Sep 17 00:00:00 2001 From: m3taversal Date: Fri, 8 May 2026 13:12:25 -0400 Subject: [PATCH] fix(substantive_fixer): json_valid guard in front of json_each MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ganymede review of 5db6a02 (msg 2 of 3): json_each(invalid_json) throws 'malformed JSON' and propagates up through EXISTS, failing the SELECT. The fix-cycle call site at teleo-pipeline.py:104 isn't try/except wrapped (the reaper at line 109-116 is, the substantive cycle isn't), so a single corrupt eval_issues row would trip the fix-stage breaker after 5 occurrences. Fix is one line — AND json_valid(eval_issues) before the EXISTS clause. json_valid(NULL) returns NULL (false in WHERE), json_valid(invalid) returns 0, json_valid(valid) returns 1. SQLite 3.9+, predates VPS 3.45.1. WARN-on-corrupt-JSON path kept per Ganymede's Q3 — json_valid and json.loads use technically distinct parsers, cost is ~3 rows × parse-empty-string per cycle, journal entry names the failure mode if SQLite ever surfaces a row that passes both SQL guards but fails json.loads. Comment updated to reflect new guard ordering. --- lib/substantive_fixer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/substantive_fixer.py b/lib/substantive_fixer.py index 6280e55..c7537b0 100644 --- a/lib/substantive_fixer.py +++ b/lib/substantive_fixer.py @@ -540,6 +540,7 @@ async def substantive_fix_cycle(conn, max_workers=None) -> tuple[int, int]: AND (domain_verdict = 'request_changes' OR leo_verdict = 'request_changes') AND COALESCE(fix_attempts, 0) < ? AND (last_attempt IS NULL OR last_attempt < datetime('now', '-3 minutes')) + AND json_valid(eval_issues) AND EXISTS ( SELECT 1 FROM json_each(eval_issues) WHERE value IN ({placeholders}) @@ -552,10 +553,11 @@ async def substantive_fix_cycle(conn, max_workers=None) -> tuple[int, int]: if not rows: return 0, 0 - # Defense-in-depth: corrupt eval_issues JSON shouldn't reach here (json_each - # would error in the SELECT and SQLite would skip the row), but the WARN log - # stays so we catch any edge case where a row's JSON parses for json_each - # but not for json.loads (different parsers, technically). + # Defense-in-depth: json_valid(eval_issues) in the SELECT already filters + # corrupt JSON before json_each runs, so this WARN should be unreachable. + # Kept anyway: json_valid and json.loads use technically distinct parsers, + # and the journal entry names the failure mode if SQLite ever surfaces a + # row that passes json_valid + json_each but fails json.loads. substantive_rows = [] for row in rows: try: