Add TestHub_BroadcastEvent covering all event kinds (zone_entry,
zone_exit, portal_crossing, presence_transition). Also wire
presence_transition broadcasts in the ingestion server when
motion state changes on a link.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Connect bleRegistry to the dashboard hub so ble_scan messages are
broadcast every 5s to all connected dashboard clients. Add rssi,
last_seen, and blob_id fields to GetCurrentDevices output to match
the frontend's expected message format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a 5-second ticker in the dashboard hub that broadcasts BLE device
list updates as typed 'ble_scan' messages when devices are present.
The BroadcastBLEScan method and frontend handler already existed but
were never wired up to a periodic timer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verify the alert message format {type:'alert', alert:{id,ts,severity,description,acknowledged}}
for anomaly detections and security mode triggers. The BroadcastAlert function and
app.js handler already existed; this adds coverage for critical anomaly, security
mode armed/disarmed, and acknowledged alert scenarios.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire up the SQLite Online Backup API streaming endpoint that was
implemented in ad1e17d into the main router.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use modernc.org/sqlite's Online Backup API (NewBackup/Step/Commit)
with in-memory destination and Serialize for consistent hot backups
without temp files. Streams zip directly to HTTP response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- FFT-based breathing rate estimator: 512-sample window at 20Hz, zero-padded to 1024,
dominant peak detection in 0.1-0.5 Hz band (6-30 BPM), 60-second EMA smoothing
- Per-night statistics: breathing_rate_avg, breathing_regularity (CV), anomaly count
- Anomaly detection: 30-day personal average (EMA α=0.05), flags when >25% above baseline
- Morning briefing integration: elevated breathing rate warnings with BPM comparison
- SQLite: breathing_anomaly BOOL and breathing_samples_json columns on sleep_records
- API: GET /api/sleep includes breathing anomaly and personal average fields
- Table-driven tests for FFT accuracy, EMA convergence, anomaly thresholds, regularity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update partition layout to 4MB factory/ota_0/ota_1 partitions for larger
firmware images. Add a 60-second validation timer that starts after the
hello message is sent on boot from an OTA partition. The partition is only
marked valid (via esp_ota_mark_app_valid_cancel_rollback) after receiving
a role message from the mothership. If the timer expires without role
receipt, the partition stays unconfirmed and the bootloader rolls back to
the previous firmware on next reset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- BreathingRateEstimator: FFT peak detection on 512-sample (25.6s) phase windows
at 20Hz, zero-padded to 1024 points, 0.1-0.5 Hz band (6-30 BPM), 60s EMA smoothing
- BreathingAnomalyTracker: per-person 30-day rolling EMA (α=0.05), flags elevated
breathing at >25% above personal average
- ComputeBreathingRegularity: coefficient of variation (std/mean) with regular/normal/irregular labels
- Migration 008: add breathing_anomaly BOOL and breathing_samples_json TEXT to sleep_records
- Integration: anomaly check in GenerateMorningReports, anomaly data in report/metrics/handler JSON
- Table-driven tests: synthetic 15/12/20 BPM signals, circular buffer, noise, EMA convergence,
anomaly threshold boundary cases, JSON round-trip, regularity CV computation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Phase 5→6 merge left garbled duplicate portal API code and
missing closing braces. Fixed the zones PUT handler closure, added
missing zones DELETE handler, cleaned up duplicate portal code, and
replaced non-existent GetZoneName() calls with GetZone().Name lookups.
The phase6 build tag and Phase 5 main.go.bak were already removed
in prior work — this commit fixes the remaining compilation errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Route test requests through chi router instead of calling handlers directly
- Fix weekday test to use correct day mapping
- Use context cancellation for timeout test instead of long sleep
- Move mockServer.Close() after assertions to prevent goroutine leak
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use time.Now().In(m.tz) instead of time.Now() in reconcileOccupancy
to correctly compute midnight in the configured timezone
- Fix ReconcileTick to only mark reconciled on exact blob count match
(diff==0), keeping diff==1 as uncertain per spec
- Fix timestamp units consistency (UnixNano → UnixMilli) in crossing
event recording and retrieval
- Fix reconciliation query to use from_zone/to_zone columns
- Reset m.reconciled=false in tests that create uncertain occupancy
after manager construction
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- HTTP client with 5s timeout, fire-and-forget webhook delivery
- Payload schema: {trigger_id, trigger_name, condition, blob_id, person, position, zone, dwell_s, timestamp_ms}
- 4xx response: disable trigger, set error_message, push WS alert to dashboard
- 5xx/timeout: log warning, increment error_count, do NOT disable
- error_count resets on first 2xx response
- POST /api/triggers/{id}/test endpoint with synthetic payload
- POST /api/triggers/{id}/enable clears error state and re-enables
- GET /api/triggers/{id}/webhook-log for last N firings
- Audit log via webhook_log table (migration_007)
Dashboard:
- Error badge (ERR) on trigger cards when error_message is set
- Disabled badge when trigger disabled due to 4xx
- Warning badge for transient error_count > 0
- Test Webhook button with real-time response display
- Webhook Log button showing last N firings
- Re-enable button to clear error state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Hub sends full snapshot (type=snapshot) on new client connect before adding
to broadcast list, preventing race with delta messages
- tickDelta at 10 Hz computes byte-level diffs of cached JSON fields, broadcasts
only changed fields with no type field
- Dashboard sets awaitingSnapshot on WS open, drops incremental updates until
snapshot received, rebuilds full state from snapshot
- Fix zone snapshot building (nil→ok check, SizeX fields)
- Remove unused broadcastBLEScan function
- Add drainSnapshot helper and fix TestHub_LinkEvents to account for snapshot
- Add TestHub_SnapshotOnConnect, TestHub_SnapshotBeforeDelta, TestHub_DeltaOmitsTypeField
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Formalizes the webhook audit infrastructure (webhook_log table, trigger_state
persistence, error_message/error_count columns) that was previously created
via ALTER TABLE in Store.init(). All webhook action firing, fault tolerance,
test endpoint, enable/disable, and dashboard UI were already implemented
in prior commits.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change testAlertHandler.mu from sync.Mutex to sync.RWMutex to match
RLock/RUnlock calls in alertCount/webhookCount/escalationCount
- Add proper mutex locking in SendAlert/SendWebhook/SendEscalation
- Fix TestAnomaly_UnknownBLEDevice: use security mode so score (0.8)
exceeds security threshold (0.4) instead of normal threshold (0.6)
- Fix TestAnomaly_UnusualDwell: use security mode for threshold check
- Fix TestAnomaly_SecurityModeThreshold: remove normal mode call that
creates cooldown and prevents security mode detection
- Fix TestAnomaly_AlertChainNormalMode/SecurityMode: add waitForGoroutines()
before checking alerts sent via goroutines
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SetCleanOnConnect → SetCleanSession
- OnDisconnect field + OnConnectionLost → SetConnectionLostHandler
- Remove redundant type assertions in type switch cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unused "math" import, unused startZ/endZ variables, and fix
Position struct json tags to pass go vet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>