- Add missing CountAnomaliesSince method to mockDetectorProvider
in security_test.go to satisfy the DetectorProvider interface
- Fix variable shadowing bug in anomaly.go QueryAnomalyEvents
where incomplete rename from 'events' to 'result' caused
append(events, &e) to reference the package instead of the slice
All security mode endpoints verified:
- GET /api/anomalies?since=24h — lists recent anomaly events
- POST /api/security/arm + /api/security/disarm — arm/disarm
- GET /api/security/status — {armed, learning_until, anomaly_count_24h}
- Anomaly events push to dashboard WS as 'alert' messages
- Arm/disarm state persists across restarts via learning_state table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the full ordered shutdown sequence so the mothership drains
cleanly without data loss on SIGTERM (Docker stop, Kubernetes termination).
Shutdown sequence (30s hard deadline):
1. Set shutting_down=true; ingestion server returns HTTP 503 to new WebSocket upgrade requests
2. Broadcast {type:'shutdown', reconnect_in_ms:30000} to all dashboard WebSocket clients
3. Cancel fusion loop context (stops fusion goroutine)
4. Drain signal processing pipeline: wait for in-flight CSI frames (max 2s)
5. Flush in-memory baselines to SQLite in a single transaction
6. Sync CSI recording buffer to disk (close writer, fsync)
7. Close all node WebSocket connections with normal close frame (1000)
8. Write {type:'system', description:'Mothership stopped'} event to events table
9. PRAGMA wal_checkpoint(FULL) to collapse WAL into main DB file
10. sqlite3.Close()
Each step gets its own log line: '[SHUTDOWN] Step N/10 — ...'
Steps that fail log ERROR but do not abort remaining steps.
Exit code 0 if all steps completed within deadline; exit code 1 if deadline exceeded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add full CRUD endpoints for triggers with OpenAPI-style godoc comments:
- GET/POST /api/triggers (list all, create new)
- PUT/DELETE /api/triggers/{id} (update, delete)
- POST /api/triggers/{id}/test (fire trigger once for testing)
Both TriggersHandler (simple) and VolumeTriggersHandler (3D geometry)
implement all endpoints with table-driven tests covering validation,
persistence, and round-trip lifecycle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add complete health check implementation for Docker HEALTHCHECK and
Traefik health routing with:
Response fields:
- status: "ok" or "degraded"
- uptime_s: seconds since mothership boot
- version: mothership version string
- nodes_online: count of connected nodes
- db: "ok" or "failing" (SELECT 1 with 100ms timeout)
- load_level: 0-3 from load shedding state
- reason: human-readable explanation (only when degraded)
HTTP status codes:
- 200 for healthy (status="ok")
- 503 for degraded (status="degraded")
Degraded conditions:
- Database unreachable
- Load level 3 sustained for >60 seconds
- No nodes connected after 5 minutes uptime
Docker HEALTHCHECK updated to verify status="ok" response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Events (zone entries/exits, portal crossings, presence transitions)
were already broadcast immediately via BroadcastEvent, but the
buffered copies included in the 10 Hz delta tick were silently
dropped by handleIncrementalUpdate. Now delta events are processed
through the same handleEventMessage path, with dedup to avoid
double-processing when both immediate and delta copies arrive.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement versioned NVS key migration on ESP32-S3 firmware so
OTA-updated firmware gracefully handles NVS written by older versions.
- Add nvs_migration.c/h with migration framework
- On boot, read schema_ver from NVS; initialize to 1 if missing
- Run migrations sequentially if schema_ver < COMPILED_NVS_VERSION
- Each migration commits after each write for durability
- Log all migration steps to UART for debugging
- Example migration v1→v2: rename 'ms_ip' to 'mothership_ip',
add 'ntp_server' with default 'pool.ntp.org'
- Migration failure leaves NVS in consistent state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update learning progress display to show "X of Y days complete" format
- Add last anomaly location info to security dialog stats
- Add CSS styling for anomaly event type in timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "no-op update returns current" test case was missing wantEnable: true,
causing a false negative since the seeded trigger has Enabled: true.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add GetArmedAt() method to persist armed timestamp across restarts
- Add blob appear/disappear callbacks to tracker for security events
- Add security handler for arm/disarm API endpoints
- Update /api/security endpoint to return armed_at timestamp
- Add tracker tests for blob lifecycle callbacks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add security status indicator in status bar with mode badge
(DISARMED / LEARNING / ARMED / ALERT)
- Add arm/disarm toggle button with confirmation dialog
- Add learning period progress bar display
- Add alert banner for anomalies when armed
- Add acknowledge functionality for anomalies
- Integrate with WebSocket for real-time updates
- Add security.css with responsive styles
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 5 new message types (event, alert, ble_scan, trigger_state,
system_health) were already implemented in hub.go with broadcast methods,
called from main.go/ingestion/volume_triggers/events, and handled in
app.js. Also includes security mode persistence from anomaly DB and
OpenAPI docs for triggers endpoints.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET /api/ble/devices: list all BLE devices with filtering
- PUT /api/ble/devices/{mac}: update device label and assign to person
- Added comprehensive OpenAPI-style godoc comments
- Supports filtering by registered/discovered/archived status
- Includes device history and aliases endpoints
- Add GET /api/ble/devices to list known devices with filtering
- Add PUT /api/ble/devices/{mac} to set label and assign to person
- Add comprehensive OpenAPI-style godoc comments for all BLE endpoints
- Add table-driven tests for BLE handler endpoints
Endpoints support:
- Filtering by registration status (registered/discovered)
- Time window filtering (hours parameter)
- Device labels and person assignment
- Sighting history per device
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add OpenAPI-style godoc comments and comprehensive table-driven tests
for replay endpoints:
- GET /api/replay/sessions - list recording sessions and replay store info
- POST /api/replay/start - start replay at timestamp (speed 1/2/5)
- POST /api/replay/stop - stop replay, return to live
- POST /api/replay/seek - seek within session
- POST /api/replay/tune - update pipeline parameters mid-replay
Improvements:
- Fix writeJSON calls to use proper 3-argument signature
- Add detailed request/response type documentation
- Add mockRecordingStore for isolated unit testing
- Add 12 table-driven test cases covering all endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GET /api/notifications/config to get all delivery channel settings
- POST /api/notifications/config to set channel configurations
- POST /api/notifications/test to send test notifications
- Support for ntfy, pushover, gotify, webhook, and mqtt channel types
- Config validation for each channel type
- Table-driven tests for all endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement GET and POST/PATCH /api/settings endpoints with:
- GET /api/settings: Returns all configurable settings as JSON with
default values merged in for any settings not explicitly set
- POST/PATCH /api/settings: Partial update with merge semantics,
only updating the keys provided in the request body
- SQLite persistence: Settings stored in settings table with
JSON-encoded values, loaded on startup
- Comprehensive validation: All known settings validated with
proper range checks (fusion_rate_hz, grid_cell_m, delta_rms_threshold,
tau_s, fresnel_decay, n_subcarriers, breathing_sensitivity,
motion_threshold, dwell_seconds, vacant_seconds, max_tracked_blobs,
replay_retention_hours, replay_max_mb, security_mode,
events_archive_days)
- OpenAPI-style godoc comments: Documentation for all endpoints
using swagger annotations
- Table-driven tests: Comprehensive test coverage for all
functionality including GET, POST, PATCH, validation,
persistence, and error cases
- Helper utilities: Extracted writeJSON, writeJSONError,
writeJSONData to utils.go for reuse
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Broadcasts { type: 'trigger_state', trigger: { id, name, last_fired, enabled } }
on trigger fire, enable, and disable events. Handled in app.js onmessage
and forwarded to window.Automations.updateTriggerState().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The BLE handler was set but never invoked when BLE messages arrived
from nodes. Replace the TODO with actual bleHandler call so BLE scan
data flows to the registry, enabling the existing 5s ble_scan
WebSocket broadcast to dashboard clients.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BroadcastEventFromDB was using inconsistent field names (timestamp_ms,
type, person) that didn't match the canonical event format (ts, kind,
person_name) expected by handleEventMessage in app.js. This caused
DB-sourced events to render with "undefined" kind/person and "Invalid
Date" timestamps.
Also adds table-driven tests for BroadcastEventFromDB covering zone
entry/exit, portal crossing, anomaly, and minimal events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>