- Add hourly zone history snapshot recording that runs every hour
- Returns [{timestamp, count, people:[]}] in hourly buckets
- Supports period query parameter: 24h (default), 7d, 30d
- Zone history is persisted in zone_history SQLite table
- Initial snapshot recorded on startup for the current hour
Add TestGetZoneHistoryWithData which verifies:
- Response format with timestamp, count, and people array
- Period query parameter (24h, 7d, 30d)
- Correct number of hourly buckets returned
- People array parsing from JSON
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement hourly occupancy history endpoint that returns zone_history
records from the database. The endpoint returns hourly snapshots with
count and people list, supporting period query parameter (24h, 7d, 30d).
Changes:
- Modified GetZoneHistory to query zone_history table instead of
computing from crossing_events
- Added encoding/json import to parse people JSON field
- Split database schema creation into manager_migrate.go for editability
- Split zone history snapshot logic into manager_history.go
- Updated tests to insert zone_history data instead of crossing_events
The zone_history table stores pre-aggregated hourly snapshots written
by RecordZoneHistorySnapshot, which should be called periodically.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add crossing_events table (migration 16) for portal crossing log with
cursor pagination support. The endpoint returns [{timestamp, direction,
person, blob_id, from_zone, to_zone}] with ?limit and ?before cursor
pagination parameters.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add ID field to CrossingEvent struct for database row ID
- Update GetPortalCrossings to query and return the database id column
- Fix getPortalCrossings handler to populate all response fields:
- ID: database row ID
- PortalID: portal ID from the crossing event
- BlobID: blob identifier
- Direction: a_to_b or b_to_a
- FromZone: source zone name
- ToZone: destination zone name
- Timestamp: crossing timestamp
- Person: BLE identity if available
- Update tests to verify all fields are properly set
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Added "idle" to validRoles map for consistency with disable/enable flow
- Added comprehensive tests for disableNode and enableNode handlers
- Tests cover: disable from tx/rx/tx_rx, already idle, node not found
- Tests cover: enable with saved role, no saved role (defaults to rx),
already enabled, node not found
- Added round-trip test for full disable/enable cycle
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements baseline read/capture endpoints for the dashboard. GET /api/baseline
returns [{link_id, snapshot_time_ms, confidence, n_sub}] for all links.
POST /api/baseline/capture starts a 60s quiet-room capture with optional
links filter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements two REST API endpoints as defined in the plan's REST API spec:
1. GET /api/status - Returns system status including:
- version: Application version string
- nodes: Number of online nodes
- blobs: Number of currently tracked blobs
- uptime_s: Uptime in seconds
- detection_quality: System-wide detection quality (0-100)
2. GET /api/occupancy - Returns zone occupancy data:
- zones: Map of zone names to {count, people[]}
- count: Number of people in the zone
- people: List of person names (BLE-identified)
These are simple read-only endpoints for dashboard and Home Assistant
integration for quick system checks and occupancy queries without WebSocket.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The GET /api/notifications/preview endpoint was already implemented
in internal/api/notifications.go but was never registered in main.go.
This commit wires up the NotificationsHandler to enable the test
thumbnail endpoint for UI development and QA.
The endpoint accepts query parameters:
- type: notification type (fall, anomaly, zone_enter, sleep)
- person: person name (optional, defaults to "Alice")
It calls the appropriate Generate*Thumbnail function from the
render package and returns PNG bytes with Content-Type: image/png.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add SubscribeToSecurityMode handler that calls anomalyDetector.SetSecurityMode()
with SecurityModeArmed for 'arm' and SecurityModeDisarmed for 'disarm'
- Add SubscribeToRebaseline handler that publishes event bus events for baseline capture
- Both subscriptions allow Home Assistant automations to control security and rebaseline
Implements Component 6 from the plan - automatic OTA updates with canary
deployment strategy and configurable quiet window scheduling.
Features:
- Canary strategy: updates one node first, monitors detection quality
for 10 minutes (configurable), then rolls out fleet-wide if quality
degradation is below threshold (default 5%)
- Quiet window: configurable time range (default 02:00-05:00) when
auto-updates are allowed. Supports overnight windows (e.g., 22:00-06:00)
- Zone vacancy check: only updates when all zones have been vacant
for >10 minutes
- Auto-update mode toggle: enable/disable via settings
- REST API endpoints for status, config, trigger, cancel, and history
- Dashboard integration for real-time progress updates
Settings keys:
- auto_update_enabled: bool (default false)
- quiet_window_start: HH:MM format (default "02:00")
- quiet_window_end: HH:MM format (default "05:00")
- canary_duration_min: 5-60 minutes (default 10)
- auto_update_quality_threshold: 0.01-0.5 (default 0.05)
Implementation:
- internal/ota/autoupdate.go: AutoUpdateManager with canary selection,
monitoring, and fleet rollout
- internal/ota/autoapi.go: REST API handlers
- internal/autoupdate/adapters.go: integration with quality provider,
node provider, event notifier, zone vacancy checker
- internal/api/settings.go: auto-update settings with validation
- dashboard/js/ota.js: auto-update state tracking
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Export NodeConnectedGetter interface type in adapters
- Add GetConnectedMACs() method to fleet.Manager
- Fix syntax error in main.go (missing closing brace)
- Correct dashboard broadcaster setup to use autoupdate adapter
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The /api/doctor endpoint complements /healthz (runtime state) with
configuration correctness checks. Returns 200 with a JSON report
containing all check results.
Checks implemented:
- data_dir_writable: verifies /data is writable with >100 MB free
- db_integrity: runs PRAGMA integrity_check
- firmware_dir: checks for *.bin files in /firmware
- mdns_binding: verifies mDNS service is registered (or SPAXEL_MDNS_ENABLED=false)
- mqtt_reachable: TCP connectivity test if SPAXEL_MQTT_BROKER is set
- ntp_reachable: UDP connectivity test if SPAXEL_NTP_SERVER is set
- install_secret: verifies install_secret row exists in auth table
- pin_configured: verifies pin_bcrypt is non-null in auth table
- node_token_consistency: verifies all nodes have valid MAC addresses
Response format includes overall status (ok/warn/error), individual check
results with name/status/message, and checked_at timestamp.
Requires session cookie authentication via authHandler.RequireAuth.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implemented all 6 acceptance scenarios as verifiable integration tests:
- AS-1: First-time setup in under 5 minutes
- AS-2: Person detected while walking
- AS-3: Fall alert fires correctly
- AS-4: BLE identity resolves to person name
- AS-5: OTA update succeeds / rollback on bad firmware
- AS-6: Replay shows recorded history
Each scenario includes multiple test cases covering pass/fail criteria.
Tests use spaxel-sim as the test harness for simulating CSI data without
hardware. The integration test entry point runs all scenarios sequentially
for CI/CD verification.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Added comprehensive integration tests in test/acceptance/ covering all 6 acceptance scenarios from plan.md
- AS-1: First-time setup in under 5 minutes - verifies PIN setup and node auto-discovery
- AS-2: Person detected while walking - verifies blob detection during walker simulation
- AS-3: Fall alert fires correctly - verifies fall detection with webhook integration
- AS-4: BLE identity resolves to person name - verifies BLE device registration and identity matching
- AS-5: OTA update succeeds / rollback on bad firmware - verifies OTA workflow and rollback
- AS-6: Replay shows recorded history - verifies replay session creation, seeking, and playback
Tests use spaxel-sim CLI as the test harness and verify:
- API endpoint responses (/api/auth/setup, /api/nodes, /api/blobs, /api/events, /api/ble/devices, /api/replay/*)
- Detection accuracy thresholds (>60% blob presence during walking)
- Alert generation and webhook delivery
- Firmware version updates and rollback behavior
- Replay session lifecycle management
All tests skip by default unless ACCEPTANCE_TEST=1 or SPAXEL_INTEGRATION_TEST=1 is set.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Configure minimum linter set: errcheck, staticcheck, govet, ineffassign, unused
- Disable noisy style linters: gocyclo, funlen, wsl
- Add per-path exclusions for appropriate error handling patterns
- Configure errcheck exclusions for standard library and common patterns
- Configure staticcheck to enable only S/SA series (correctness)
- All tests pass with 0 lint issues
Per plan §Quality Gates / Definition of Done (item 3)
- Add PersonID field to BlobPos struct (internal/volume/shape.go)
- Remove dead code in CalculateGDOPImprovement (internal/diagnostics/reposition.go)
- Add //nolint:errcheck annotations for deferred Close() calls
These fixes allow golangci-lint to pass with zero issues, enabling
the CI quality gate per plan §Quality Gates / Definition of Done.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add BenchmarkFusionLoop and TestTimingBudgetProduction that enforce the fusion loop timing budget as a CI quality gate per plan §Quality Gates / Definition of Done (item 9).
The benchmark runs the full fusion pipeline (phase sanitization → feature extraction → Fresnel accumulation → peak extraction → UKF update) against synthetic CSI data from spaxel-sim output.
Timing constraints:
- Median fusion iteration < 15ms (production target)
- Median fusion iteration < 30ms (CI threshold - 2x allowance for slower CI hardware)
- P99 < 40ms (hard limit)
Typical results on reference hardware:
- Median: ~3-5ms (well under 15ms production target)
- P99: ~14-20ms (well under 40ms hard limit)
Also includes:
- GitHub Actions workflow (.github/workflows/benchmark-ci.yml) for CI
- Documentation (docs/ci-benchmark-integration.md) for Argo Workflows integration
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- FuzzParseBinaryFrame: validates ParseFrame never panics on any input
Seed corpus: valid frame, truncated header, n_sub mismatch, channel=0, n_sub>128
Property: never panic; drop/parse/error all OK
- FuzzParseJSONFrame: validates ParseJSONMessage never panics on any input
Seed corpus: hello, health, ble, motion_hint, ota_status, unknown type
Property: never panic; unknown types return typed error
- Phase sanitization property test: validates output never contains NaN or Inf
For all valid int8 I/Q pairs: all-zero, max int8 (127), min int8 (-128),
alternating signs, typical CSI values, extreme RSSI values
All fuzz tests run for 60 seconds with no panics found.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Added stderr buffer to TestHarness to capture mothership output
when health check fails. This helps diagnose issues like port
conflicts during e2e test runs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add dispatchEvent and classList mocks for more complete DOM element simulation
- Fix help_articles.json path expectation (relative, not absolute)
- Ensure window.HelpOverlay is properly loaded from module
Phase 9 implementation verification - all guided troubleshooting features are in place:
- Proactive quality prompts with 5-minute threshold (proactive.js)
- Repeated-setting change detection with guided calibration flow
- Post-feedback explanations via DiagnosticEngine.GetDiagnosticFor
- Feature discovery notifications with quiet hours support (notifier.go)
- Contextual help system with 73 articles (help.js + help_articles.json)
All acceptance criteria met. Tests passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add HasAnyCompletedSession() method to sleep storage to check if any
sleep sessions have been completed. This is used by the feature discovery
notification system to determine when to fire the "first sleep session
complete" notification.
A completed session has both sleep_onset and wake_time set.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>