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>
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>
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>