Replace httptest.Server with raw net.Listen to avoid Close() blocking
on active connections after a timeout, which caused the test to hang
indefinitely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
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>
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>
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>
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>
- 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>
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>
Add comprehensive activity timeline view for the Spaxel dashboard:
- Timeline sidebar with scrollable chronological event list (newest first)
- Event types with distinct icons/colors: zone_entry/exit (green/orange),
portal_crossing (blue), anomaly/security_alert (red), learning_milestone (purple), system (grey)
- Each event shows: timestamp, description, person name, zone name
- Click event → jump to that moment in replay mode
- Filter bar: filter by person, zone, event type, time range (today/7d/30d)
- Search box with debounced text filter (300ms)
- Inline feedback (thumbs up/down) on presence detection events
- POST /api/feedback endpoint for feedback submission
- GET /api/events endpoint with pagination and filtering
- Live updates: 'event' messages from WebSocket feed
- New events prepend without layout shift using DOM insertion
- Loading states, empty state, and "load more" pagination
Acceptance criteria met:
- 200 events render within 200ms
- New events prepend without layout shift
- Clicking event seeks replay to that moment
- Feedback shows toast confirmation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Added SettingsHandler with GET/PATCH /api/settings for configurable settings
- Added ZonesHandler with full CRUD for zones (GET/POST/PUT/DELETE /api/zones)
- Added PortalsHandler with full CRUD for portals (GET/POST/PUT/DELETE /api/portals)
- Added TriggersHandler with full CRUD for automation triggers including test endpoint
- Added NotificationsHandler for notification channel config and test notifications
- Added ReplayHandler for CSI replay sessions (start/stop/seek/tune endpoints)
- All endpoints follow OpenAPI-style patterns with appropriate godoc comments
- Settings persist to SQLite across restarts
- Zone/portal updates reflected in live 3D view via WebSocket
Endpoints implemented:
GET/PATCH /api/settings
GET/POST/PUT/DELETE /api/zones
GET/POST/PUT/DELETE /api/portals
GET/POST/PUT/DELETE /api/triggers
POST /api/triggers/{id}/test
GET/PATCH /api/notifications/config
POST /api/notifications/test
GET/POST /api/replay/sessions
POST /api/replay/start
POST /api/replay/stop
POST /api/replay/seek
POST /api/replay/tune
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>