Closes Rule-20 dispatcher gap from audit doc v0.6-phase1-audit.md finding #1.
Pre-this-commit the cold-start anchor, 4 drop counters, per-toggle cooldown
map, and dedup OrderedDict all lived in Dispatcher instance memory and were
lost on every container restart.
v5.sql adds three tables:
- dispatcher_state (singleton id=1): cold_start_anchor + 4 drop counters
- dispatcher_cooldowns ((toggle,category,region) keyed): last_fired_at
- dispatcher_dedup ((source,event_id) keyed): seen_at
Dispatcher refactor:
- __init__ calls _restore_from_db -- counters, cold-start anchor, cooldown
map, and dedup LRU (most-recent 10k by seen_at) all rehydrated from the
three new tables
- write-through on every mutation: _persist_state for counter/anchor,
_persist_cooldown for cooldown UPSERT + 2*cooldown_s prune,
_persist_dedup for dedup INSERT OR REPLACE + 7-day cleanup
- in-memory caches stay authoritative on the fast read path
- cumulative-since-install counters (NOT since-boot); LLM will be able
to answer "we have dropped 47 stale events this week" after commit #5
(env_reporter) lands
- graceful degrade: missing v5 tables / persistence outage falls back to
fresh in-memory state without crashing the constructor
Tests:
- tests/test_dispatcher_persistence.py (17 tests): state restore on init,
counter+cooldown+dedup survival across simulated restart, cooldown rearm
within 2x window, dedup LRU rebuild caps at 10k, 7-day cleanup on insert,
INSERT OR REPLACE on duplicate source+event_id, v5 migration idempotent,
synthetic storm (50 events) -> restart -> replay (5 incl 1 duplicate)
with the duplicate dedup-rejected and counters NOT resetting
- tests/conftest.py (new): autouse MESHAI_DB_PATH redirection to per-test
tmp file, so the dispatcher_* tables on production /data dont get
polluted by tests that construct Dispatcher() without an explicit fixture
- tests/test_notification_toggles.py: _dispatch helper wipes dedup/cooldown/
state tables between calls (per-call independence preserved; pre-v0.6-2
in-memory-only Dispatcher reset naturally per instance)
Test count: 680 -> 697 (+17 new, 0 regressions).
Refs audit doc v0.6-phase1-audit.md finding #1.