docs: ТЗ v1, архитектура, ADR-002..009, открытые вопросы

Refs #2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 18:32:54 +03:00
co-authored by Claude Opus 5.5
parent 74aabba5dc
commit 018e3d26c4
4 changed files with 428 additions and 39 deletions
+63
View File
@@ -21,3 +21,66 @@
**Решение:** код бота, 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.