Грабля: created_at в SQLite точен до секунды — 7 быстрых смен дают
равные метки, а secondary-сортировка по UUID-id случайна → «последние 5»
выбираются произвольно (флакущий тест + неверно хранимая история).
id теперь autoincrement (обрезка order_by id desc), UUID остаётся
как public_id. Миграция 010 обновлена (таблица только на CT108,
пересоздаётся миграцией заново — см. деплой).
- backend/password_policy.py: validate_new_password (длина, сложность
буквы+цифры, сверка с текущим и историей), record_password_change
(хранит ПРЕДЫДУШИЙ хэш, обрезка до password_history_count),
password_expired + enforce_expiry (пометка must_change_password)
- users.password_changed_at + таблица password_history (010_b23_policy)
- change-password и админ-сброс теперь под полной политикой (сложность
и переиспользование вместо голой длины); get_current_user проверяет
срок действия каждого пароля при запросе
- backend/purge_logs.py: реальный DELETE просроченных audit_events
(+changes) и auth_events; POST /admin/users/maintenance/purge-logs
(manage_security) + CLI python -m backend.purge_logs для cron
- тесты: +19 (258 passed) — валидация/история/срок, purge/идемпотентность/403