Самокритика v0.5 — что починено и что осталось

Дата: 2026-05-14 Перед этим: critique-v0.4.md — 20 пунктов критики.

✅ Починено в v0.5 (12 пунктов)

Безопасность (4)

  • #6 Cookie Secure флаг. Подтверждено: set-cookie: ...; HttpOnly; Max-Age=43200; Path=/; SameSite=lax; Secure.
  • #7 Rate-limit на /login. In-memory bucket per-IP, 5 попыток / 60 сек. Считает только failed попытки, при успехе bucket очищается. Подтверждено: 5 раз 401, 6-й → 429.
  • #8 CSRF tokens для login/logout. Signed token через itsdangerous, hidden input в формах, verify_csrf_token на POST. Подтверждено: невалидный токен → 403, отсутствие → 422.
  • #9 Cashier/viewer больше не видят KPI флагов. Карточка рендерится только если can_see(managers). Аналогично карточка «Расхождения» прячется без can_see(reconciliation).

Audit (1)

  • #13 Авто-аудит login/logout. Записываются: login_success, login_failed, login_blocked (rate-limit hit), logout. С IP-адресом в note. Подтверждено в БД (видны записи за последние минуты).

Технические (2)

  • #10 Lifespan handler вместо @app.on_event("startup"). FastAPI 0.95+ совместимо.
  • #11 N+1 устранён. joinedload(Operation.branch) в operations / reconciliation / dashboard; joinedload(User.branch) и joinedload(TgFlag.manager) в managers.

UI/UX (5)

  • #3 Нерабочие кнопки → disabled + tooltip. «Добавить операцию», «связать вручную», «одиночная» — cursor-not-allowed, title="Появится на E1". Никто больше не кликнет ожидая результата.
  • #15 Селектор периода. На дашборде ?period=today|7d|30d, по умолчанию 7d. KPI и график подстраиваются.
  • #16 Mobile responsive. Sidebar схлопывается под 768px, появляется кнопка «☰», KPI грид становится 1/2/4 колонки по breakpoint'ам.
  • #17 Wallets — статус «не синхронизировано». Если last_synced_at старше 10 минут или null — badge «⊘ не синхронизировано», цифра выцветает. Сверху страницы — баннер «это seed, реальные данные на E2».
  • #18 Inline <style> в handbook → app.css. Стили для .prose вынесены в общий css.

⏸ Сознательно отложено (8 пунктов)

# Что Когда Почему отложено
1 Балансы наличных уходят в минус E1 Нужна модель cash_shift / cash_count (открытие смены + пересчёт) и UI ввода. Большой кусок, нужны бизнес-ответы.
2 Фейковая маржа 2% E1 Жду от пользователя ответ про источник рыночного курса (Binance API / свой реестр).
4 Визуальная связь пар в сверке E1 Полировка, не блокер. Сейчас связь видна по op #N.
5 Revocation сессий после logout E5 Сейчас signed cookie без серверной таблицы. Перейдём при появлении production-нагрузки.
12 Alembic вместо create_all Перед первым изменением схемы Сейчас overkill: схема меняется в одной голове. Перед E1 нужно поставить.
14 Пагинация в operations Когда >1000 записей Сейчас 200 (лимит) из 143 — не блокер.
19 «Последние операции» больше строк Косметика Есть ссылка «все →».
20 Cashier: «моя смена» vs «моя касса» E1 После модели смен.

Новая мини-критика того, что только что написал

Чтобы не было ощущения что v0.5 — идеал:

  • CSRF token не привязан к сессии, а только подписан временем. Если злоумышленник получит свой токен — может использовать против любого пользователя в течение 2ч. Для прода — связать с user_id или session_id.
  • Rate-limit в памяти процесса, при рестарте сервиса счётчики сбрасываются. На один-инстанс OK, для масштабирования — Redis.
  • Только login/logout идут в аудит, бизнес-действия (матчинг, создание операции) пока не пишутся — потому что их пока нет в UI. На E1 нужно сразу заложить вызов record_audit в каждом write-endpoint.
  • Audit log без фильтров в UI. В таблице есть поля для фильтра, но они не подключены к query.
  • Secure cookie на dev/local не работает — если кто-то запустит на http://localhost:8790, cookie не выставится. Не критично т.к. рабочий доступ только через Caddy → HTTPS.

Что не проверял отдельно

  • Mobile responsive — глазами. По curl-у не видно. Тебе нужно открыть с телефона.
  • joinedload действительно ли убирает N+1 (можно проверить через SQLAlchemy echo, не делал).
  • Поведение под нагрузкой rate-limit (10 потоков параллельно).