0.167.45
Fixed
- Транзакционная multi-key запись настроек ( — P3, аудит 2026-08-context). Закрыты два окна целостности из аудита #305: (1) partial-write —
AdvancedHandler.Update(фаза 2) писал ключи цикломSetSetting, сбой середины оставлял частичный апдейт (для anti-loop парыagent_loop_warn/stop_threshold— потенциально невалидную пару в Store); (2) TOCTOU в pair-валидации — два параллельных Update, каждый со своей половиной пары, оба проходили проверку против старого значения другой стороны и сохраняли невалидную пару. Новый методstore.Store.SetSettings(ctx, map[string]string)— multi-key upsert одной транзакцией (SQLite:BeginTx+ prepared upsert; PostgreSQL:pool.Begin, симметрично; DualStore — делегирование; батч-капmaxSettingsBatchSize=256, пустая map — no-op, детерминированный порядок ключей).Updateпишет весь батч одним вызовомSetSettings(сбой → 500, rollback всего батча) и сериализованsync.RWMutexper-handler: критическая секция «pair-валидация → валидация → запись» подLock(второй конкурентный Update валидируется против закоммиченного первого),Get— подRLock(заодно закрыт torn read пары: ранее два независимыхGetSettingмогли увидеть новый warn + старый stop). Отмена клиента в фазе записи — тихий выход без ERROR-лога и 500 (симметрично валидационной ветке). Read-side defense-in-depth (newToolLoopDetectorнормализует пару на каждом Run) сохранён. Тесты (+8): SQLite-транзакционность (upsert всех ключей, перезапись, пустая map no-op, mid-tx сбой через per-instance test-hook → полный rollback, отменённый ctx, oversized-батч отклонён); handler — сбойSetSettings→ 500 без частичных записей, отмена ctx → тихий возврат, конкурентный TOCTOU-тест (25 итераций параллельных half-pair Update: ровно один победитель на раунд, инвариантstop > warnв Store не нарушен ни разу; валидирован на pre-fix коде — падает без мьютекса).