From e2dbe6a2d535ecdb6c7bc1eae12e7c6f33cab4fe Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Fri, 1 May 2026 13:19:23 -0700 Subject: [PATCH] fix(server): COALESCE diagnostic columns so v1.0 heartbeats don't clear v1.1 data store_heartbeat_diagnostics() unconditionally SET each diagnostic column to its parameter, so a v1.0.0 heartbeat (which omits the five v1.1.0 fields and leaves them as None after Pydantic parsing) erased previously stored diagnostics for that device. Wrap each parameter in COALESCE(?, column_name) so omitted fields preserve the existing value. Found via adversarial review (gpt-5.5 reviewer, run 2026-05-01-191359). Co-Authored-By: Claude Opus 4.7 (1M context) --- server/heartbeat_diagnostics_stub.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/server/heartbeat_diagnostics_stub.py b/server/heartbeat_diagnostics_stub.py index 30e7943..6c78932 100644 --- a/server/heartbeat_diagnostics_stub.py +++ b/server/heartbeat_diagnostics_stub.py @@ -70,13 +70,15 @@ def store_heartbeat_diagnostics( else None ) cursor = db.cursor() + # COALESCE preserves existing column values when the v1.0.0 payload omits + # diagnostic fields (Pydantic resolves them to None). cursor.execute( """UPDATE heartbeats - SET reset_reason = ?, - heap_free = ?, - heap_min_free = ?, - last_disconnect_code = ?, - recent_events = ? + SET reset_reason = COALESCE(?, reset_reason), + heap_free = COALESCE(?, heap_free), + heap_min_free = COALESCE(?, heap_min_free), + last_disconnect_code = COALESCE(?, last_disconnect_code), + recent_events = COALESCE(?, recent_events) WHERE device_id = ?""", ( hb.reset_reason,