4638366b77
Core.AlarmHistorian-002 — drain loop now honors exponential backoff: StartDrainLoop arms a self-rescheduling one-shot Timer. RescheduleDrain sets the next due-time to max(tickInterval, CurrentBackoff) while the sink is BackingOff, so a historian outage genuinely slows the cadence down the 1s->2s->5s->15s->60s ladder instead of hammering at the fixed tick. Class doc-comment updated. Core.AlarmHistorian-004 — SQLite busy handling: the connection string is built via SqliteConnectionStringBuilder with DefaultTimeout=5, and a new OpenConnection helper applies PRAGMA busy_timeout=5000 and PRAGMA journal_mode=WAL on every open. A concurrent enqueue-vs-drain file-lock collision now waits the lock out instead of failing fast with SQLITE_BUSY. All connection open sites switched to the helper. Core.AlarmHistorian-006 — drain-loop faults are no longer unobserved: the timer callback (DrainTimerCallback) awaits DrainOnceAsync inside a try/catch that logs via _logger.Error, records the message into _lastError, and sets _drainState=BackingOff so a stalled drain is visible on GetStatus; a finally always re-arms the timer. Regression tests added to SqliteStoreAndForwardSinkTests: StartDrainLoop_honors_backoff_and_slows_cadence_under_retry, StartDrainLoop_keeps_steady_cadence_when_writer_is_healthy, StartDrainLoop_records_drain_fault_and_keeps_running, Concurrent_enqueue_and_drain_do_not_throw_sqlite_busy. findings.md: 002/004/006 marked Resolved; open count 10 -> 7. Build: clean (0 warnings). Tests: 20/20 passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>