A misbehaving or clock-broken device could submit period_end <=
period_start, polluting the camera_records table with zero-length or
inverted windows that corrupt downstream hourly analytics. Add a
Pydantic model_validator so the request is rejected at the API
boundary instead of silently persisting bad ranges.
Found via adversarial review (run 2026-05-01-191359, both reviewers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
Two FATAL while(true) hangs in main.cpp (config load fail, camera init
fail) previously relied on the hardware watchdog to reboot the device,
leaving the cause invisible beyond a generic TWDT reset reason. Now
each path logs EVT_REBOOT with REBOOT_FATAL_CONFIG or REBOOT_FATAL_CAMERA
before hanging, so the next heartbeat's recent_events surfaces which
branch hung. Server-side decoder updated for the two new enum values.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The real server lives in a separate repo; this repo carries reference
stubs for each endpoint (see camera_endpoint.py precedent). Adds the
Pydantic extension, persistence helper, migration 005, and tests that
the real server can copy when adding diagnostic-field support.
Matches the firmware v1.1.0 heartbeat payload shape. Old-shape
payloads (firmware v1.0.0) continue to parse cleanly with the new
fields defaulting to None.
Self-contained server stub and pytest tests for the /api/v1/camera/events/batch
endpoint, mirroring the BLE batch pattern with idempotent INSERT on
(device_id, period_start).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>