on_departure() holds known_lock when calling _check_churn(). When churn
threshold is hit, the nested `with known_lock:` inside _check_churn tried
to reacquire an already-held non-reentrant lock → permanent deadlock.
This killed the production process on the OPi Zero 3 after ~15 hours of
runtime on 2026-04-12 at ~02:12 UTC. infrastructure_ips.add() is safe
without the inner lock since the caller already holds known_lock.
Also: reset _churn_tracker between tests to prevent cross-test state
accumulation that triggered the churn threshold in test isolation.
Fix#605: Remove MATRIX_HOMESERVER, MATRIX_ACCESS_TOKEN, MATRIX_ROOM_ID from module-level globals.
Credentials are now read at call time in send_alert() via _read_env_value() to minimize
credential lifetime in process memory.
Fix#606: Update deploy.sh to use Infisical creds CLI instead of .secrets file.
Fetches NET_ALERTER_PASSWORD from matrix folder in Infisical vault, then generates
access token at deploy time.
All 16 net_alerter tests pass when run individually or in groups.
Two P1 bugs fixed:
1. Timer cancel deadlock: timer.cancel() was called while holding locks, which would deadlock if the callback was running/paused waiting for those same locks. Fixed by extracting timers outside of all locks before calling cancel().
2. MAC validation: _normalize_mac() now validates format with regex and returns empty string for invalid MACs. Callers (load_personal_macs_from_env, is_personal_device) skip empty results and log warnings.
All 16 tests pass.
- P1-1: Fix lock ordering deadlock by establishing canonical order (known_lock → departure_lock → occupancy_lock). Refactor on_arrival() to acquire locks in correct order. Fix send_departure_alert() callback to hold occupancy_lock while updating state.
- P1-2: Add _normalize_mac() helper to strip colons/hyphens, apply to load_personal_macs_from_env() and is_personal_device() for consistent MAC comparison.
- P1-3: Remove occupancy_lock acquisition from _update_occupancy_state_unlocked() - callers must hold lock. Update all callers to acquire occupancy_lock before calling.
All individual tests pass. Fixes allow on_arrival() and timer callbacks to execute without deadlock.
- Add MOBILE_DEVICE_OUIS set with 55+ OUI prefixes for personal devices
- Add is_personal_device() function to detect personal devices by OUI or manual config
- Add load_personal_macs_from_env() to parse NET_ALERTER_PERSONAL_MACS
- Add location_occupancy state tracking (VACANT/OCCUPIED/UNKNOWN)
- Add update_occupancy_state() function with thread-safe alerts on transitions
- Add _update_occupancy_state_unlocked() helper to prevent deadlock
- Fix deadlock in on_arrival() by using unlocked version when holding known_lock
- Update test suite with 5 new occupancy tests
- Fix test assertions to exclude occupancy alerts for device arrival tests
Tests now call cleanup_flap_state() at the start of each test to properly
reset the interface flap detection state (_flap_window, _interface_recovering,
_recovery_timer) between tests, preventing state carryover failures.
- Fix 1: Interface flap detection suppresses departure storms when wlan0 briefly loses association and triggers simultaneous RTM_DELNEIGH events for all cached neighbors
- Tracks 3-second window of departure MACs
- Locks recovery state for 60 seconds if 3+ departures occur in rapid succession
- Prevents false positive alerts during interface recovery
- Fix 2: Additional infrastructure IPs via NET_ALERTER_INFRA_IPS env var
- Allows operator to suppress alerts for devices like access points that cycle regularly
- Comma-separated list of IP addresses to be seeded as infrastructure at startup
- Example: NET_ALERTER_INFRA_IPS=192.168.1.214,192.168.1.220
Addresses DevTrack #467 (net_alerter false positive alerts)
sensor.service running at 192.168.1.10. ARP + DHCP sensors active.
Probe sniffer degraded (no wlan1). BLE degraded (BlueZ too old for or_patterns).
Untracked audit files committed for reference.
- P1 #551: Add STATUS_PORT env var (default 9191) for configurable HTTP status server port
- P1 #552: Fix SQLite connection leak — wrap all sqlite3.connect() with try/finally in lookup_person(), seed_persons_from_db(), log_signal(), log_presence_change()
- P1 #553: Fix race condition in update_person_state() — compute certainty snapshot under devices_lock, determine state transition under persons_lock, send alert outside locks to prevent stale snapshot
- P2 #559 (included): Add WAL mode + pragmas to init_db() for concurrent write safety
- Database path: Align to /opt/sensor/presence.db in both daemon and install.sh
- Increase connection timeout from 1s to 10s across all functions
- install.sh: Document PRESENCE_STATUS_PORT and PRESENCE_MATRIX_WEBHOOK env vars
Implements DevTrack #530: Person Presence Intelligence daemon for BigBrother drop implant.
Core components:
- presence_daemon.py: 4 sensor threads (probe, ARP, DHCP, BLE) with exponential decay
- presence_schema.sql: SQLite schema for persons, devices, signals, occupancy log
- install.sh: Deployment script (copies to /opt/sensor/, creates systemd service)
- AUDIT_impl.md: Implementation notes, limitations, audit checklist
Features:
- Exponential decay model (lambda=0.08/min) for signal certainty
- Presence anchor (45-min hold time prevents false VACANT)
- Person state machine: UNKNOWN → PRESENT ↔ ABSENT
- Multi-signal fusion: max + 0.08×min for device aggregate
- Thread-safe in-memory state with SQLite persistence
- Matrix webhook alerting (if configured)
- HTTP status endpoint on 127.0.0.1:9191
- Graceful BLE degrade if bleak unavailable
- No tool fingerprints in output or service names
- Reject dash/colon MAC-format device names (e.g. 45-48-39-7F-73-C7)
from open mode — were slipping past is_generic_name()
- Add exponential backoff (1s, 2s) on Matrix 429/5xx, max 3 attempts —
startup burst was rate-limiting and dropping alerts
- Commit active scanner flag (scanning_mode=active) that was in working
tree but not in prior commit 8686eb5 despite the commit message
- Add 3 tests: MAC format filter, 429 retry success, 429 give-up
Fixes DevTrack #518
Passive mode fails on Debian bookworm bleak 0.20.2 without or_patterns filter.
Active mode is functionally equivalent for building-level presence detection
and actually yields more complete advertisement data including device names.
When on_departure popped the device from known_devices, rapid RTM_NEWNEIGH
events during ARP cache rebuild each triggered on_arrival as if the device
was brand new, flooding the Matrix room with 5-10+ duplicate ARRIVED alerts.
Fix: keep device in known_devices with departing=True flag. on_arrival
silently cancels pending departure timers and returns without alerting when
it sees this flag. Timer callback checks flag before sending the alert and
only pops the device when departure is confirmed after 15 minutes.
- Bug: device departs → timer starts, but last_departed_time not set yet
If device re-arrives before timer fires, timer never cancelled
Result: false DEPARTED alert sent after re-arrival debounce expires
- Fix: Cancel any pending departure timer immediately on re-arrival,
before checking last_departed_time
- Test: Added regression test that verifies timer is cancelled when
device re-arrives before 15-min debounce expires, preventing false alerts
- All 10 tests passing
- lookup_oui() now tries SQLite first (/opt/net_alerter/oui.db)
- Falls back to inline OUI_DICT if DB missing or query fails
- No network calls, local SQLite reads only
- Debug-level logging for failures (no output noise)
- Infrastructure IPs (gateway, self, broadcast) silently ignored in
on_arrival() and on_departure(); seeded at startup from 'ip route'
- 15-minute departure debounce: alert only fires if device is still
absent after 900s, eliminating ARP cache flush false positives
- 5-minute re-arrival suppression: ARRIVED alert skipped when device
returns within 5 min of a recorded departure (covers interface flap)
- DHCP renewal dedup: known device re-announcing within 30 min skips
ARRIVED alert
- Tests added for all four suppression paths (9 total, all passing)
- Replace broken OUI file lookup with inline dict of 200+ common vendors (works on Armbian)
- Fix hostname/IP duplication when reverse DNS fails (show IP once, not twice)
- Add format_arrival() and format_departure() helpers for consistent alert formatting
- Update on_arrival/on_departure to use new formatting functions
- Add comprehensive tests for hostname dedup and OUI lookup
Replace all sensor.* logger namespaces with __name__ (generic module
identifiers instead of discoverable 'sensor.*' prefixes).
Change hardcoded 'bb' API user to 'admin' in config and code defaults.
Change hardcoded relay_user 'bb' to 'operator' — prevents network
profiling from exposing tool identity via SSH config.
Fixes#457, #458, #459