Правки по глубокому ревью.
Тесты:
- отказ пула на `submit` (единственная изменённая строка без покрытия): слот
отдаётся синхронно, оба счётчика в нуле. Мутант «старый код, ключевой
счётчик не отдан» краснит и тест, и autouse-сторож.
- отмена ЕЩЁ НЕ НАЧАТОЙ работы возвращает слот ключа — ветка future другая,
чем у отмены начатой, соседний тест её не покрывал.
- пол `max(1, …)`: при очереди в 1 слот доля не округляется в ноль (иначе
молчаливый отказ всем).
- цена размена по NAT закреплена явно: третий одновременный вход с того же
адреса получает 429 при двух занятых слотах из четырёх.
Соседям по NAT стало ХУЖЕ, и теперь это записано в docstring с числами: при
флуде 3 запроса/с свои входят 69% попыток против 94% до правки, порог отказа
падает с ~14 до ~7 запросов/с. Взамен вход с чужих адресов идёт 100% против
37%. Размен сознательный, а не побочный эффект.
Refs #2714
Потолок темпа из #2665 держал слоты общим котлом: флуд занимал все четыре, и
легитимный вход с ВЕРНЫМ паролем получал 429 столько раз, сколько пытался
(замер на стенде с настоящим bcrypt: 20 попыток → 0×200, 20×429).
Теперь ключ (у единственного вызывающего — IP клиента) не берёт больше половины
ёмкости: сколько бы один источник ни слал, вторая половина остаётся тем, кто
приходит впервые. Учёт по ключу живёт в самой `verify_password_bounded` и
отдаётся тем же `_release_verify_slot` — инвариант «одна точка выноса = одна
точка учёта» не делится надвое.
Граница применимости честная и записана в коде: ключом может быть только IP, а
он подделывается за вторым прокси, разделяется за NAT и ротируется ботнетом.
Это поднимает стоимость атаки, но не закрывает её; принципиально закрывают
только доказательство работы или второй фактор.
Замер на стенде после правки: 20 попыток → 5×200, 15×429, и НИ ОДНОГО 429 от
потолка сверок. Оставшиеся 15 — `_LOGIN_LIMITER` (5 попыток на пару имя+IP за
300с), отдельная и намеренная защита, срабатывающая независимо от флуда.
Тесты: вход с другого ключа во время флуда (API-уровень, мерит заявленное — не
латентность и не число попыток атакующего), доля слотов на ключ, возврат слота
при исключении в сверке, autouse-сторож «слотов занято 0» после каждого теста.
Формулировка «вход не заблокирован совсем» в тесте темпа поправлена: она про
попытки атакующего и ничего не говорит про живого пользователя.
Refs #2714, #2665, #2712