Commit Graph

54 Commits

Author SHA1 Message Date
adlee-was-taken
4b0d547322 fix(web): arm/disarm actually transition the FSM via MQTT (issue #2)
Was: /system/api/arm verified the PIN against [security] pin_hash and
returned {ok: true} without ever calling the FSM. State never changed.
Now: the endpoint publishes a SYSTEM_ARM_REQUEST message to the local
MQTT broker. The event processor (see previous commit) picks it up,
ArmStateFSM verifies the PIN via alerts.pin.verify_pin and performs
the transition. Response is 202 Accepted; clients poll /system/status
for the new state.

Design: PIN travels over localhost-only MQTT, which matches the
existing trust boundary for the internal bus.
2026-04-05 12:58:09 -04:00
adlee-was-taken
e6069a68fc refactor(events): drop forward-ref quote and test triggered_by default
Code review follow-up on f4d66dd:
- _handle_arm_request signature used "ArmStateFSM" as a string forward
  reference even though the type is imported at module top.
  _handle_event uses the bare form; match it for consistency.
- Add a test asserting that omitting triggered_by in an arm-request
  payload defaults to "unknown". That value feeds the audit log, so
  it deserves explicit regression coverage.

No behavior change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 12:58:09 -04:00
adlee-was-taken
82ff7fb276 feat(events): processor handles SYSTEM_ARM_REQUEST over MQTT
Adds _handle_arm_request and a dedicated bus.subscribe on
Topics.SYSTEM_ARM_REQUEST. Payload {mode, pin, triggered_by} is
dispatched to ArmStateFSM.transition, which verifies the PIN via
alerts.pin.verify_pin and performs the state change.

This is the missing link for web /system/api/arm to actually move
the system into an armed state. Part of issue #2.
2026-04-05 12:58:09 -04:00
adlee-was-taken
e568f20871 feat(config): deprecation warning for [system] arm_pin_hash
If a config still has the legacy [system] arm_pin_hash set but no
[security] pin_hash, load_config logs a WARNING telling the operator
to re-run 'vigilar config set-pin'. The legacy field is still parsed
(so old configs don't fail validation) but ignored at runtime.

Part of issue #2 PIN hashing unification.
2026-04-05 12:57:20 -04:00
adlee-was-taken
c2976876ed fix(cli): set-pin emits PBKDF2 under [security] pin_hash (issue #2)
Was: HMAC-SHA256(random, pin) written to [system] arm_pin_hash —
no verifier in the codebase accepted this output.
Now: PBKDF2-SHA256 via alerts.pin.hash_pin written to [security]
pin_hash, matching what the web and FSM paths verify against.

Also fixes show_cmd to redact the new location.
2026-04-05 12:57:20 -04:00
adlee-was-taken
efa3ce4b1b fix(events): ArmStateFSM uses PBKDF2 via alerts.pin (issue #2)
Was: unsalted SHA-256 read from [system] arm_pin_hash.
Now: PBKDF2-SHA256 600k iterations read from [security] pin_hash,
matching the web arm/disarm path and the alerts/pin module.

Also drops the redundant pin re-hash on the arm_state_log audit row
(a fresh PBKDF2 salt made the column valueless for traceability).

Part of issue #2 PIN hashing unification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 12:57:20 -04:00
adlee-was-taken
09b59e3bb5 feat: wire MQTT → SSE bridge so the event timeline updates live
Closes #1.

The Flask event-timeline was dead: `broadcast_sse_event` existed in
`vigilar/web/blueprints/events.py` but had zero call sites. Clients
subscribed to `/events/stream`, received the initial "connected"
message, and then only keepalives — a page refresh was required to
see new events. (Web Push via VAPID was independent and already worked.)

The root cause was a process-boundary gap: the events subsystem runs
in its own OS process and emits to MQTT, while the Flask app runs in a
separate process with no MQTT client of its own.

This change adds a thin bridge:

- EventProcessor._handle_event now publishes a classified summary
  (id, ts, type, severity, source_id, payload) to a new topic
  `Topics.EVENTS_PUBLISHED = "vigilar/events/published"` right after
  `insert_event()`. Classification logic stays in one place.

- A new module `vigilar/web/sse_bridge.py` provides `forward_event`
  (MQTT handler) and `start_sse_bridge(cfg)` (creates a MessageBus,
  subscribes forward_event to EVENTS_PUBLISHED, connects, returns the
  bus).

- `vigilar/main.py:_run_web` starts the bridge after `create_app(cfg)`
  and disconnects it on shutdown. Bridge failure is logged but does
  not kill the web process — the UI still works without live updates.

- `create_app` is deliberately NOT changed. Keeping the bridge out of
  the app factory means no existing test triggers a real MQTT
  connection, and the bridge stays a production-only concern wired by
  the supervisor.

Tests (all added with TDD, RED verified before GREEN):

- tests/unit/test_events.py::TestEventsPublishedBroadcast — asserts
  `_handle_event` publishes the classified payload for a motion event
  and does NOT publish for unclassified topics (heartbeats).
- tests/unit/test_sse_bridge.py — asserts `forward_event` reaches SSE
  subscribers, and `start_sse_bridge` wires the handler to
  `Topics.EVENTS_PUBLISHED` on a connected bus (fake bus, no real
  MQTT in tests).

Also refreshes the docs that previously flagged the dead SSE as a
known limitation (operator guide, web architecture doc).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:55:27 +00:00
Aaron D. Lee
bdfadbb829 feat(Q6): timelapse generator, schedules, and web routes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 19:08:29 -04:00
Aaron D. Lee
622af22642 feat(Q1): highlight reel event scoring and FFmpeg clip assembly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 19:07:12 -04:00
Aaron D. Lee
b4dbb41624 feat(Q4): kiosk ambient mode with camera rotation, alert takeover, dimming
Add GET /kiosk/ambient route and standalone fullscreen template with
rotating camera snapshots (crossfade), top bar clock/date, pet status
bottom bar, SSE-driven alert takeover with HLS playback, configurable
screen dimming, and 6-hour auto-refresh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 19:06:57 -04:00
Aaron D. Lee
d69bf6d6af feat(Q1,Q4): add HighlightsConfig, KioskConfig, HIGHLIGHT/TIMELAPSE triggers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 19:06:08 -04:00
Aaron D. Lee
23d5bf062a feat(S3): visitors blueprint with profiles, visits, labeling, privacy controls
Add /visitors/ blueprint with REST API endpoints for listing profiles and
visits, labeling (with consent gate), household linking, ignore/unignore,
and cascading forget. Register blueprint in app.py. Add dashboard and profile
detail templates (Bootstrap 5 dark, tab navigation, fetch-based JS). All six
API tests pass (339 total).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 19:01:33 -04:00
Aaron D. Lee
a5ddc53cf0 feat(S3): FaceRecognizer with in-memory embedding matching
Add FaceRecognizer class that loads face encodings from the database,
supports runtime add_encoding(), and matches new encodings by L2 distance.
face_recognition import is deferred so the class works without dlib installed.
FaceResult dataclass carries profile_id, name, confidence, crop, and bbox.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:59:07 -04:00
Aaron D. Lee
5a438fdb32 feat(S3): face profile, embedding, and visit CRUD queries
Add create/get/update/delete_cascade for face_profiles, insert/get for
face_embeddings, and insert/get/get_active for visits. All seven query
functions covered by unit tests (327 passing).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:58:19 -04:00
Aaron D. Lee
a44187d0f1 feat(S5): pet rule CRUD routes with validation and templates 2026-04-03 18:53:50 -04:00
Aaron D. Lee
931b453ba9 feat(S5): pet rule engine with condition evaluation and cooldown 2026-04-03 18:52:38 -04:00
Aaron D. Lee
fac51a7c8a feat(S5): pet rule CRUD query functions 2026-04-03 18:51:47 -04:00
Aaron D. Lee
cdc13e05f6 feat(Q5): package event queries and tracker state machine
Add insert/get/update queries for package_events table, and
notification content for PACKAGE_DELIVERED and PACKAGE_REMINDER events.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:47:40 -04:00
Aaron D. Lee
31757f410a feat(Q5): package delivery state machine with sunset-aware reminders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:46:59 -04:00
Aaron D. Lee
8bf7900324 feat(Q3): wildlife journal blueprint with API routes and template
Add wildlife_bp with sightings, stats, frequency, and CSV export
endpoints; Bootstrap 5 dark journal template with live-updating
summary cards, species bars, time-of-day frequency chart, and
paginated/filterable sightings table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:46:32 -04:00
Aaron D. Lee
8c2a8ea1c5 feat(Q3): wildlife journal query functions
Add get_wildlife_sightings_paginated, get_wildlife_stats, and
get_wildlife_frequency to queries.py with full test coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:46:25 -04:00
Aaron D. Lee
66a53f0cd8 feat(Q2): heatmap generation with bbox accumulation and colormap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:46:00 -04:00
Aaron D. Lee
7ccd818a93 feat(Q3): Open-Meteo weather fetcher with hourly caching 2026-04-03 18:42:52 -04:00
Aaron D. Lee
e75a9a9d71 feat(Q5): NOAA sunset calculator (stdlib only) 2026-04-03 18:42:26 -04:00
Aaron D. Lee
a5dd15d0a1 feat(Q5): add package event types and package_events table 2026-04-03 18:41:31 -04:00
Aaron D. Lee
38ff219364 feat(Q3): add temperature, conditions, bbox columns to wildlife_sightings 2026-04-03 18:40:55 -04:00
Aaron D. Lee
4097ee9dd3 feat(Q3): add LocationConfig for latitude/longitude 2026-04-03 18:40:36 -04:00
Aaron D. Lee
c3c743ec74 feat(F1): configure syslog audit logging for vigilar.alerts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 18:36:57 -04:00
Aaron D. Lee
f33b82cc83 feat(F1): integrate Web Push notifications into event processor
Import send_alert into processor.py, store engine as self._engine after
init_db(), extend _execute_action() to accept event_type/severity/source_id
and call send_alert for alert_all and push_and_record actions, and pass
those params from _handle_event().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:01:11 -04:00
Aaron D. Lee
2c79e0c044 feat(F1): notification content mapping and Web Push sender
Add vigilar/alerts/sender.py with _CONTENT_MAP for human-readable push
notification titles/bodies, build_notification(), and send_alert() which
retrieves VAPID key, iterates push subscriptions, calls pywebpush, and
logs results to alert_log with auto-cleanup of expired (410) endpoints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:01:07 -04:00
Aaron D. Lee
602945e99d feat(F2): recording list, download (decrypt), and delete API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 17:42:35 -04:00
Aaron D. Lee
1b7f77b298 feat(F2): integrate AES-256-CTR encryption into AdaptiveRecorder
After FFmpeg finishes writing, _stop_ffmpeg() now reads VIGILAR_ENCRYPTION_KEY
and encrypts the MP4 to .vge format via encrypt_file(), updating the returned
RecordingSegment to reflect the encrypted file path and size.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 17:41:39 -04:00
Aaron D. Lee
e630c206b2 feat(F4): PIN verification on arm/disarm + reset-pin endpoint
Adds PIN checking to arm/disarm endpoints using verify_pin() against
cfg.security.pin_hash, and a new POST /system/api/reset-pin endpoint
that verifies the recovery passphrase before updating the PIN hash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 17:40:01 -04:00
Aaron D. Lee
f8d28cf78e feat(F2): AES-256-CTR encryption module for recordings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 17:39:40 -04:00
Aaron D. Lee
0544f7218a feat(F4): add SecurityConfig model to VigilarConfig 2026-04-03 17:38:10 -04:00
Aaron D. Lee
3f2a59c11e feat(F4): add PIN hashing utilities with PBKDF2-SHA256 2026-04-03 17:37:42 -04:00
Aaron D. Lee
c77f732ac7 Differentiate PET_ESCAPE and UNKNOWN_ANIMAL events by zone and identity
Replace the flat pet/detected handler with context-aware classification:
unknown animals (no pet_id) → UNKNOWN_ANIMAL/WARNING, known pets in
exterior/transition zones → PET_ESCAPE/ALERT, known pets indoors →
PET_DETECTED/INFO. Adds four new unit tests covering all three paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:45:15 -04:00
Aaron D. Lee
94c5184f46 Add pets web blueprint with API endpoints
Implements all /pets/* routes (register, status, sightings, wildlife,
unlabeled crops, label, upload, update, delete, train, training-status,
highlights), registers the blueprint in app.py, adds a placeholder
dashboard template, and covers the API with 11 passing unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:27:46 -04:00
Aaron D. Lee
2b3a4ba853 Add pet and wildlife counts to daily digest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:24:33 -04:00
Aaron D. Lee
45007dcac2 Add crop manager for staging and training image lifecycle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:22:26 -04:00
Aaron D. Lee
d0acf7703c Handle pet and wildlife events in event processor 2026-04-03 13:20:46 -04:00
Aaron D. Lee
53daf58c78 Add camera_location filtering to alert profile matching
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:20:37 -04:00
Aaron D. Lee
e48ba305ea Add pet ID model trainer with MobileNetV3-Small transfer learning
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:19:12 -04:00
Aaron D. Lee
c7f9304f2a Add pet ID classifier with species-filtered identification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:18:42 -04:00
Aaron D. Lee
13b7c2a219 Add wildlife threat classification with size heuristics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:17:21 -04:00
Aaron D. Lee
131eed73b1 Add YOLOv8 unified detector with class classification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:15:31 -04:00
Aaron D. Lee
d83710b839 Add pet and wildlife database query functions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:13:56 -04:00
Aaron D. Lee
ea092bca10 Add pets, pet_sightings, wildlife_sightings, pet_training_images tables
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:11:01 -04:00
Aaron D. Lee
6e3ef1dcdc Add pet detection, wildlife, and activity config models
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:08:40 -04:00
Aaron D. Lee
aae857ec53 Add pet/wildlife enums, event types, and MQTT topics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 13:07:09 -04:00