Refs #2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8.2 KiB
Архитектурные решения (ADR)
Формат записи:
## ADR-NNN: <заголовок>
**Дата:** YYYY-MM-DD · **Статус:** принято | отменено | заменено ADR-MMM
**Контекст:** почему возник вопрос.
**Решение:** что выбрали.
**Альтернативы:** что отвергли и почему.
**Последствия:** что это меняет.
ADR-001: Хостинг кода и задач — SourceCraft
Дата: 2026-10-12 · Статус: принято
Контекст: нужен репозиторий проекта, трекер задач и CI. Решение: код бота, issues и CI — на SourceCraft. Загрузки пользователей — в отдельный репозиторий там же. Альтернативы: GitHub/Gitea — не соответствует постановке преподавателя. Последствия: CI описывается в формате SourceCraft; бот ходит в SourceCraft по своему deploy-ключу.
ADR-002: Telegram — long polling, а не webhook
Дата: 2026-10-12 · Статус: принято
Контекст: бот должен легко разворачиваться «где угодно», включая ноутбук без белого IP.
Решение: long polling (aiogram start_polling).
Альтернативы: webhook — нужен домен, HTTPS и nginx-vhost; выигрыш в задержке для учебной нагрузки несущественен.
Последствия: входящих портов нет; на VPS не нужен vhost. Один экземпляр бота на токен.
ADR-003: Статус CI — polling SourceCraft API
Дата: 2026-10-12 · Статус: принято
Контекст: нужно сообщать пользователю результат CI.
Решение: бот опрашивает /cicd/runs и сопоставляет прогон с коммитом по sha; опрос идёт только пока есть незавершённые загрузки.
Альтернативы: исходящие webhooks — у SourceCraft не задокументированы; «CI сам пишет в Telegram» — секрет бота в чужом CI и нет связи с исходным сообщением.
Последствия: задержка статуса до ~10 с; клиент API обязан переживать обрывы (ретраи).
ADR-004: Образы собираются на хосте, реестр — опционально
Дата: 2026-10-12 · Статус: принято
Контекст: у SourceCraft есть container registry (pkg.sourcecraft.tech), но его создание требует платёжного аккаунта Yandex Cloud и роли администратора организации.
Решение: и бот, и приложения собираются docker build прямо на хосте (как meditron). Переменная REGISTRY в будущем переключит деплой на pull готового образа.
Альтернативы: registry SourceCraft — зависимость от биллинга; GHCR/Docker Hub — ещё один внешний аккаунт.
Последствия: сборка нагружает хост (на время деплоя); деплой не зависит от внешнего реестра.
ADR-005: Приложения пользователей — на том же хосте, в песочнице
Дата: 2026-10-12 · Статус: принято (временно)
Контекст: единственный доступный сервер — VPS vobot, где работают прод и dev Vobot и meditron.
Решение: запускать на нём же, но в жёсткой песочнице (non-root, read-only, cap-drop ALL, лимиты CPU/RAM/PIDs, без сети по умолчанию) и с общим лимитом 10 приложений.
Альтернативы: отдельный VPS — правильнее по изоляции, но лишняя машина для учебного проекта; self-hosted worker SourceCraft — запуск внутри CI не даёт долгоживущих сервисов.
Последствия: риск побега из контейнера ложится на прод-Vobot. Хост приложений вынесен в конфиг (RUNNER_HOST) — переезд на отдельную машину без изменений кода.
ADR-006: Запуск и управление приложениями — через tgit-runner с forced command
Дата: 2026-10-12 · Статус: принято
Контекст: и CI, и бот должны запускать/останавливать контейнеры; полный доступ к Docker равен root на хосте.
Решение: единая точка — скрипт tgit-runner на хосте; SSH-ключи CI и бота в authorized_keys пользователя tgit-deploy ограничены command="tgit-runner"; скрипт валидирует аргументы. Бот читает состояние через read-only docker-socket-proxy.
Альтернативы: смонтировать docker.sock в бота — компрометация бота = root на хосте.
Последствия: вся логика песочницы — в одном месте и тестируется отдельно.
ADR-007: LLM строит структурированный запрос, а не shell-команду
Дата: 2026-10-12 · Статус: принято
Контекст: идея — «LLM формирует grep по логам». Логи содержат текст, подконтрольный пользователю (prompt injection).
Решение: LLM возвращает JSON LogQuery (источник, период, regex, уровень, лимит) по схеме; бот валидирует и выполняет поиск сам, в Python, с таймаутом regex. Пользователю показывается эквивалентный grep.
Альтернативы: выполнять grep от LLM в shell — инъекции команд, ReDoS, доступ к файлам хоста.
Последствия: поиск ограничен возможностями LogQuery, зато безопасен и тестируем.
ADR-008: LLM-провайдер — за протоколом, по умолчанию OpenAI-совместимый API
Дата: 2026-10-12 · Статус: принято
Контекст: провайдер не выбран; нужны тесты без сети.
Решение: протокол LLMProvider + фабрика по LLM_PROVIDER: openai_compat (LLM_BASE_URL, LLM_MODEL, LLM_API_KEY — подходит YandexGPT, OpenAI, локальная модель) и mock.
Альтернативы: привязка к одному вендору — как в Vobot, правило «провайдер за протоколом».
Последствия: выбор конкретной модели — настройка, а не код.
ADR-009: Стек
Дата: 2026-10-12 · Статус: принято
Решение: Python 3.12, uv, aiogram 3, httpx, pydantic-settings, SQLAlchemy 2 async + Alembic, PostgreSQL 16, regex, pytest (+ pytest-asyncio, respx), ruff, mypy. Контейнеризация — Docker Compose.
Альтернативы: python-telegram-bot — синхронное наследие в API; SQLite в проде — проще, но Postgres ближе к реальным сервисам и уже привычен по Vobot/meditron.
Последствия: тесты на SQLite через тот же SQLAlchemy.