9.9 KiB
Root Causes & Plan Ошибка 1: column bot_users_1.patronymic does not exist Причина: _add_column_if_not_exists() — синхронная функция (def), но conn — AsyncConnection. conn.execute() возвращает корутину, которая никогда не awaits:
max_bot/app/main.py:91: conn.execute(sa_text(...)) — нет await Лог при запуске: RuntimeWarning: coroutine 'AsyncConnection.execute' was never awaited Миграции молча не выполняются, колонки не создаются.
План исправления:
main.py:88 — сделать async def _add_column_if_not_exists main.py:91 — добавить await conn.execute(...) main.py:75-85 — добавить await перед каждым вызовом _add_column_if_not_exists После деплоя колонки создадутся при следующем рестарте Ошибка 2: Тёмная тема в тестовом прогоне Причина: bot_test.html — это полноценный HTML-документ (не наследует base.html), цвета жёстко закодированы в <style>:
body { background:#1B2838; color:#E8EDF2; ... } .test-chat { border:1px solid #253248; ... } Если портал в светлой теме, iframe остаётся тёмным.
План исправления:
Добавить в <head> скрипт инициализации темы (как в base.html:7) Заменить хардкод на CSS-переменные (var(--bg-card), var(--text-primary), var(--border), var(--accent)) с запасными цветами Импортировать service-theme.css или определить :root { } блок с reference-переменными Что затронет: Файл Изменение max_bot/app/main.py:88-102 def → async def, await conn.execute() max_bot/app/main.py:75-85 Добавить await перед каждым вызовом _add_column_if_not_exists py_service/app/templates/pages/bot_test.html Добавить тему + CSS variables
Аудит — все баги и проблемы 🔴 P1 — CRITICAL (крашит бота)
Файл Строка Проблема
1 main.py 88-102 _add_column_if_not_exists — def вместо async def, нет await — миграции никогда не выполнялись. Все 11 новых колонок не созданы → column patronymic does not exist 2 main.py 75-85 11 вызовов _add_column_if_not_exists без await — корутина возвращается, SQL не отправляется 🟠 P2 — CSS (внешний вид, тема)
Файл Проблема
3 bot_test.html Полностью standalone HTML — 0 CSS-переменных, все цвета хардкодом. Не наследует тему 4 bot_consent.html 3 хардкод цвета (#155724, #721c24, rgba(0,0,0,0.5)). var(--light-bg) не определён — прозрачные 5 bot_tickets.html 6 хардкод цветов статусов баджей. Тот же var(--light-bg) не определён 6 bot_kb.html 6 хардкод цветов (активные/неактивные, модалка, кнопка удаления). Тот же var(--light-bg) не определён 7 bot_settings.html 1 хардкод rgba(0,150,200,0.1) для фокуса 🟡 P3 — Блокировка event loop
Файл Строка Проблема
8 inquiry.py 96-103 email_sender.send_ticket_escalation() — синхронный subprocess.run() + файловый I/O внутри async-функции. Блокирует event loop 🟢 P4 — SQL injection (низкий риск, но паттерн опасен)
Файл Строка Проблема
9 main.py 91 f-string в sa_text() — {table}, {column}, {col_type} интерполируются напрямую. Сейчас вызывается с константами, но если переиспользовать — дыра План исправления P1 (2 файла):
main.py:88 — def → async def _add_column_if_not_exists main.py:91 — conn.execute( → await conn.execute( main.py:75-85 — добавить await перед каждым _add_column_if_not_exists( P2 (5 файлов): 4. bot_test.html — скрипт темы (как в base.html:7) + заменить хардкод на var(--*) + на CSS 5. bot_consent.html — заменить хардкод на CSS-переменные + var(--light-bg) → var(--bg-card) 6. bot_tickets.html — статус-баджи на CSS-переменные + var(--light-bg) → var(--bg-card) 7. bot_kb.html — хардкод на CSS-переменные + var(--light-bg) → var(--bg-card) 8. bot_settings.html — rgba(0,150,200,0.1) → var(--accent) с opacity
P3 (1 файл): 9. email_sender.py — subprocess.run обернуть в asyncio.to_thread() или loop.run_in_executor() для неблокирующего вызова
P4 (1 файл, профилактика): 10. main.py:91 — убрать f-string, использовать sa_text() с аргументами через bindparams() или встроить валидацию
Повторный аудит — после исправления P1-P4 Все P1-P4 (миграция, CSS, email, SQL-injection) считаю исправленными. Вот что остаётся:
🔴 CRITICAL — краши/NameError
Файл Строка Ошибка
C1 main.py 179, 197 BotConversation не импортирован в main.py. Импорт: from app.models import Base (строка 12), без BotConversation. При контакте или dialog_cleared → NameError: name 'BotConversation' is not defined → HTTP 500 C2 main.py 516 VCF N-field regex сломан: r"N(?:;[^:]*)?:([^;\r\n]+)" — останавливается на ;, захватывает только фамилию. Для N:Doe;John;Michael;; вернёт только "Doe", имя и отчество теряются C3 contact.py 23 Email пишется в user.phone: elif EMAIL_PATTERN.match(text): user.phone = text — должно быть user.email = text C4 inquiry.py 40 user is None → AttributeError: _finalize_ticket(..., user, ...) → user.first_name упадёт, если BotUser не найден C5 inquiry.py 53-54 Преждевременный commit: ticket создаётся, BotTicketMessage и BotTicketStatus — нет. Если второй commit упадёт — висячий тикет без описания и статуса C6 docker-compose.yml 11-15 Секреты в VCS: пароль БД, токен MAX, секрет вебхука — хардкодом. Должны быть ${BOT_DATABASE_URL} c .env 🟠 HIGH — логические, prompt injection
Файл Строка Ошибка
H1 yandex_gpt.py 65-104 Prompt injection: текст пользователя вставляется напрямую в system_prompt без экранирования или instruction boundary H2 contact.py 42-50 Дубликат BotMessage: handle_message() уже сохранил сообщение (greeting.py:94-101), а handle_manual_contact() сохраняет то же самое повторно H3 main.py 476 _extract_phone_from_vcf захватывает только код страны: regex r"+?\d+" → для +7 (861) 203-33-30 возвращает +7 H4 greeting.py 33-59 Race condition на BotUser: два одновременных bot_started для нового пользователя → оба проходят if not user → второй падает с IntegrityError H5 email_sender.py 35 Silent error: except Exception: без as e и без logger.error(). Ошибки sendmail глотаются H6 inquiry.py 88 Блокировка event loop: email_sender.send_ticket_escalation() — синхронный subprocess.run() внутри async-функции 🟡 MEDIUM — состояние, атомарность, TOCTOU
Файл Строка Ошибка
M1 main.py 452-469 TOCTOU на статус тикета: old_status читается, затем пишется — между ними другой запрос может изменить статус. Audit-log врёт M2 inquiry.py 14-31,71-77 Двухсессионный дизайн: если _finalize_ticket упадёт между commits, conversation зависнет в "escalated" без тикета M3 greeting.py 81-105 Stale state: conv.state читается в одной сессии, а используется после закрытия. Callback мог изменить состояние между чтением и маршрутизацией M4 settings_cache.py 19-22 Double-load race: два одновременных get() оба увидят _loaded == False и оба загрузят кэш M5 main.py 538 ORG regex обрезает отдел: [^\r\n;]+ не захватывает ;, теряется Engineering Dept из ORG:Acme Corp;Engineering Dept M6 consent.html, tickets.html, kb.html var(--light-bg) undefined — CSS-переменная не определена нигде, прозрачные M7 bot_test.html весь Standalone, 0 CSS-переменных — всегда тёмная тема, не наследует портал 🟢 LOW — мусор
Файл Строка Ошибка
L1 main.py 482-487 Функция _extract_name_from_vcf никогда не вызывается (dead code) L2 Несколько файлов Неиспользуемые импорты: Optional, json L3 .gitignore .opencode/ не в gitignore 📊 Покрытие тестами — 15% (21 тест) Критические пробелы — 0 тестов на:
FastAPI endpoints (webhook, health, users, tickets, KB CRUD, broadcast, settings) VCF парсинг и handle_contact_share handle_greeting, handle_message, analyze_and_respond Обработка ошибок (MAX API down, DB down, GPT unavailable) Webhook secret validation (401) Concurrent requests