Самокритика 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.
Securecookie на dev/local не работает — если кто-то запустит на http://localhost:8790, cookie не выставится. Не критично т.к. рабочий доступ только через Caddy → HTTPS.
Что не проверял отдельно
- Mobile responsive — глазами. По curl-у не видно. Тебе нужно открыть с телефона.
joinedloadдействительно ли убирает N+1 (можно проверить через SQLAlchemy echo, не делал).- Поведение под нагрузкой rate-limit (10 потоков параллельно).