# Ревью и тестирование изменения 030 — 2026-09-27 **Объём:** правки агента (Sonnet 5) по плану `docs/changes/030-review-fixes-025-029/PLAN.md`, которые закрывают замечания 1 и 2 ревью `2026-09-26-changes-025-029-review.md`. Изменены `app/api/v1/prefixes.py`, `app/api/v1/auth.py`, `app/services.py`, `app/rotation.py`. **Тестировались только доработки 1 и 2.** Помимо этого выполнен регрессионный прогон `pytest`. ## Ревью кода ### Замечание 1 — `_lock_prefix` Хелпер работает в таком порядке: 1. `SELECT vrf_id` префикса; 2. `_lock_vrf` для этого VRF и дополнительных (целевого VRF при переносе), одним вызовом, по возрастанию id; 3. повторное чтение префикса под `FOR UPDATE` с `populate_existing`; 4. если `vrf_id` к этому моменту изменился — 409 без повтора. Хелпер применён в `delete_prefix`, `update_prefix` (только при смене VRF), `create_address`, `allocate_subnet`, `allocate_next`. - **Порядок блокировок единый:** везде сначала advisory-блокировка VRF, затем строка префикса. `update_prefix` без смены VRF блокирует только строку (при flush) и не запрашивает блокировку VRF, поэтому цикла ожидания нет. - **Отказ от повтора обоснован.** Повтор оставил бы в транзакции блокировку устаревшего VRF и нарушил бы возрастающий порядок захвата между попытками. Сценарий 1d показал, что ветка 409 реально срабатывает. - **Встречные переносы** (A: 5→3, B: 3→5) блокируют пару VRF одним вызовом в одинаковом порядке, взаимной блокировки нет. Подтверждено сценарием 1c. - **Побочный эффект:** `create_address` теперь берёт `FOR UPDATE` строки префикса, и назначения адресов в одном префиксе выполняются по одному. На нагрузку это не влияет, операции короткие. ### Замечание 2 — константы входа - `LOGIN_WINDOW`, `MAX_PER_LOGIN_IP`, `MAX_PER_IP`, `MAX_PER_LOGIN`, `KNOWN_IP_DAYS` перенесены в `app/services.py`. - `auth.py` и `rotation.py` импортируют их оттуда. Пространства ключей advisory-блокировок остались в `auth.py`. - Импорт `app.rotation` больше не загружает модули `app.api.*`: проверено по `sys.modules`, список пуст. Докстринг `auth.py` форматируется корректно. Новых замечаний нет. ## Тесты | Проверка | Результат | |---|---| | Стенд `ipam_control_006` пересобран, `app` healthy | ✅ | | Регрессия `pytest -q` | ✅ 14 passed | | 2: `app.rotation` не импортирует API-слой | ✅ | | 2: политика входа не изменилась: 5 неудач → 401×4 + 429, на верный пароль тоже 429, `Retry-After` ≈ 600 с | ✅ | | 1a: удаление префикса ∥ вставка промежуточного префикса, ×20 | ✅ все пары ответов 204/201; дерево согласовано: у каждого префикса родитель — самый узкий охватывающий | | 1b: перенос в другой VRF ∥ создание адреса в переносимом префиксе, ×20 | ✅ без 500 и взаимных блокировок; у всех адресов `vrf_id` совпадает с VRF их префикса; все 20 префиксов перенесены | | 1c: встречные переносы двух префиксов между двумя VRF, ×15 | ✅ всё 200/200, взаимных блокировок нет | | 1d: перенос ∥ удаление того же префикса, ×10 | ✅ только ожидаемые исходы: 5× (перенос 200, удаление 409 «одновременно изменяется»), 5× (перенос 404, удаление 204) | | Лог приложения: `deadlock` / `Traceback` | ✅ 0 | Сценарии выполнялись скриптом во временной папке, в репозиторий он не добавлен. Временные данные (`vt30-*`), попытки входа и записи `known_logins` удалены. ## Вывод Замечания 1 и 2 закрыты. Изменения 025–030 готовы к коммиту.