Новый файл backend/tests/test_store_concurrency.py:
* N потоков × M upsert с чтениями → 0 ошибок, rules == audit_rows == N*M;
* чтение во время открытой транзакции другого потока не видит незафиксированных
строк (ждёт коммита);
* чтение во время падающего apply не видит состояния, которое будет откачено
(AC «не частично применённая конфигурация»);
* BaseException (KeyboardInterrupt/SystemExit/CancelledError) из Applier →
revert вызван, failed в audit_log, БД восстановлена;
* два конкурентных set_geoip_schedule → связная цепочка old→new в журнале.
На коде до правки все 7 тестов падают; после — проходят.
Refs #20, Refs #54, Refs #55
Закрывает замечания ревью QA по PR #50:
* #54: все чтения Store идут через Database.read() под тем же RLock, что
транзакции; контракт «одно соединение, все операции под локом»
задокументирован в db.py и README модуля. Устраняет TypeError/IndexError
на общем соединении, грязные чтения и выдачу состояния, которое затем
откатывается падающим apply.
* #55 D-1: apply ловит BaseException — отмена/CancelledError/
KeyboardInterrupt вызывают Applier.revert(previous) и пишут failed в
audit_log (без «висящей» записи staged); падение revert попадает в detail.
* #55 D-2: set_geoip_schedule читает old_value внутри той же транзакции.
Refs #20, Refs #54, Refs #55