107 lines
9.9 KiB
Markdown
107 lines
9.9 KiB
Markdown
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) не определён — <th> прозрачные
|
||
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(--*) + <link> на 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
|