# SPEC — техническое задание tgit > Статус: **v1, рабочая версия** (2026-10-12). Составлено по постановке преподавателя и согласованным расширениям; > официальное ТЗ ещё не пришло — когда придёт, сверяем и фиксируем расхождения в [`DECISIONS.md`](DECISIONS.md). > При конфликте ADR важнее этого документа. ## 1. Контекст **Что это:** Telegram-бот «git в телеграме». Пользователь отправляет боту `.py`-файл — бот коммитит его в отдельный репозиторий на SourceCraft, там автоматически запускается CI (проверки) и CD (запуск кода на сервере). Бот сообщает результат CI, следит за запущенным кодом, присылает алерты о падениях и перегрузке, отдаёт логи и отвечает на вопросы по логам с помощью LLM. **Зачем:** учебный проект курса Python (НИУ ВШЭ) — демонстрация асинхронного Python, интеграций по API, работы с git, контейнеризации, CI/CD и тестирования. **Исходная постановка** (переписка с преподавателем, 2026-10-12): - бот загружает `.py`-файлы в репозиторий SourceCraft, автоматически запускается CI/CD; - репозиторий для загрузок — отдельный, не репозиторий с домашками; у бота свой ключ; - разворачивание — на VPS или локально; - вайбкодинг разрешён; - согласованы расширения: статус CI, алерты о падении и перегрузке, удобные логи, LLM-чат по логам; - опционально — настроить anytask. ## 2. Термины | Термин | Значение | |---|---| | **Репозиторий бота** | [`rogachevegorushka/tgit`](https://sourcecraft.dev/rogachevegorushka/tgit) — код самого бота | | **Репозиторий загрузок** | `rogachevegorushka/tgit-uploads` — сюда бот коммитит файлы пользователей | | **Проект** | Папка `projects//` в репозитории загрузок; единица запуска и мониторинга | | **Загрузка** | Один отправленный файл → один коммит | | **Прогон** | CI/CD-run SourceCraft, запущенный коммитом загрузки | | **Приложение** | Запущенный на сервере контейнер с кодом проекта | | **Хост** | Сервер, где работают бот и приложения (по умолчанию VPS vobot) | ## 3. Роли | Роль | Может | |---|---| | **Гость** | `/start`, `/help`; видит, что доступа нет, и свой Telegram ID для запроса доступа | | **Пользователь** | Загружать файлы, видеть свои проекты, статусы CI, логи, алерты, спрашивать LLM — **только по своим проектам** | | **Администратор** | Всё + управление доступом (`/allow`, `/deny`), видит все проекты, получает системные алерты бота | Доступ — по белому списку Telegram ID (первичные администраторы задаются в `.env`). ## 4. Функциональные требования Приоритеты: **P0** — без этого нет сдачи; **P1** — согласованные расширения; **P2** — если останется время. ### 4.1 Загрузка кода — P0 | # | Требование | |---|---| | F-1 | Бот принимает документ `.py` от пользователя из белого списка | | F-2 | Валидация до коммита: расширение `.py`, размер ≤ 100 КБ, кодировка UTF-8, синтаксис (`ast.parse`). Ошибка → понятное сообщение с номером строки, коммита нет | | F-3 | Выбор проекта: подпись к файлу `project: `, иначе — инлайн-кнопки «существующий проект / новый». Имя проекта: `^[a-z][a-z0-9-]{1,30}$` | | F-4 | Файл кладётся как `projects//main.py` (или под своим именем, если в проекте уже есть `main.py`); `requirements.txt` принимается так же отдельным документом | | F-5 | Коммит от имени бота с указанием автора: `upload(): by @` и трейлером `Telegram-User: ` | | F-6 | Ответ: ссылка на коммит и сообщение «CI запущен», которое затем обновляется статусом | | F-7 | Повторная загрузка того же содержимого не создаёт пустой коммит — бот сообщает «изменений нет» | ### 4.2 CI в репозитории загрузок — P0 | # | Требование | |---|---| | F-10 | На каждый push в `main` репозитория загрузок запускается CI: `py_compile`, `ruff check`, `pytest` (если в проекте есть `test_*.py`), `bandit` (статический анализ безопасности) | | F-11 | Бот отслеживает прогон своего коммита и обновляет сообщение: ⏳ запущен → ✅ успех / ❌ падение (с указанием упавшего шага) | | F-12 | При падении бот прикладывает лог упавшего шага (текстом, если короткий, иначе файлом) | | F-13 | `/runs [project]` — последние 5 прогонов со статусами и ссылками | ### 4.3 CD и запуск приложений — P1 | # | Требование | |---|---| | F-20 | После успешного CI проект автоматически запускается на хосте в изолированном контейнере (см. §5.3) | | F-21 | Режимы запуска: **service** (долгоживущий процесс, перезапуск при падении, до 3 раз) и **job** (однократный запуск, результат — код выхода и вывод). Режим — в первой строке файла `# tgit: mode=job` или по умолчанию `service` | | F-22 | `/status [project]` — состояние приложения: запущено/остановлено/упало, аптайм, CPU, RAM, число рестартов, ревизия | | F-23 | `/stop `, `/restart ` — управление своим приложением | | F-24 | Лимит: не больше 3 приложений на пользователя и 10 на хост | ### 4.4 Алерты — P1 | # | Требование | |---|---| | F-30 | Падение приложения (выход с ненулевым кодом, OOM-kill, исчерпаны рестарты) → алерт владельцу с хвостом лога (последние 30 строк) | | F-31 | Перегрузка: CPU > 80% лимита или RAM > 90% лимита дольше 1 минуты → алерт | | F-32 | Антиспам: один и тот же алерт по проекту — не чаще раза в 10 минут; повторные копятся счётчиком | | F-33 | Системные алерты администратору: бот не может достучаться до SourceCraft API / Docker / БД дольше 5 минут | | F-34 | `/alerts on|off [project]` — подписка на алерты | ### 4.5 Логи — P1 | # | Требование | |---|---| | F-40 | `/logs [N|5m|1h] [grep]` — хвост логов приложения за период или N строк, с фильтром по подстроке | | F-41 | Ответ длиннее лимита Telegram (4096 символов) — файлом `.log` | | F-42 | Логи CI-прогона — по кнопке под сообщением о прогоне | ### 4.6 LLM-ассистент по логам — P1 | # | Требование | |---|---| | F-50 | `/ask <вопрос>` (или режим диалога после `/ask `) — вопрос на естественном языке: «почему упал ночью?», «сколько было ошибок таймаута за час?» | | F-51 | LLM **не выполняет команды**: она возвращает структурированный запрос к логам (источник, период, регулярное выражение, уровень, лимит), бот валидирует и выполняет его сам, затем LLM формулирует ответ по найденным строкам | | F-52 | В ответе — краткий вывод + использованный запрос (чтобы пользователь видел, «какой grep» был сделан) + до 10 строк-доказательств | | F-53 | Перед отправкой в LLM логи проходят маскирование секретов (токены, ключи, пароли, e-mail) | | F-54 | Лимит: 20 запросов к LLM на пользователя в сутки | ### 4.7 Прочее — P2 | # | Требование | |---|---| | F-60 | `/history [project]` — история загрузок с откатом на ревизию (`/rollback `) | | F-61 | Интеграция с anytask (формат уточнить у преподавателя) | | F-62 | Еженедельная сводка по проектам: загрузки, прогоны, падения | ## 5. Нефункциональные требования ### 5.1 Развёртывание - Всё поднимается одной командой `make up` (Docker Compose) на любой Linux-машине с Docker — ноутбук или VPS. - Бот работает через **long polling** — входящий HTTPS и домен не нужны. - Конфигурация — только через `.env` (пример — `.env.example`); секреты в git не попадают. - Основная площадка — VPS vobot (`/srv/tgit`), деплой — автоматически из CI SourceCraft. ### 5.2 Качество кода - Python 3.12, `asyncio`; типизация (`mypy --strict` для `app/`), `ruff` (lint + format). - Тесты `pytest`, покрытие `app/` ≥ 70%; внешние сервисы (Telegram, SourceCraft, Docker, LLM) в тестах подменяются фейками. - Комментарии — там, где неочевидно «почему»; докстринги у публичных интерфейсов модулей. ### 5.3 Безопасность (запуск чужого кода) - Приложения пользователей — в контейнере: непривилегированный пользователь, `--cap-drop ALL`, `no-new-privileges`, read-only FS + `tmpfs /tmp`, лимиты `--memory 128m --cpus 0.25 --pids-limit 64`, без доступа к Docker-сокету и к сети хоста. Сеть наружу — выключена по умолчанию, включается администратором на проект. - Бот не видит Docker-сокет напрямую — только через прокси с минимальным набором разрешённых вызовов. - SSH-ключ, которым CI запускает приложения на хосте, ограничен одной командой (`command=` в `authorized_keys`). - Секреты (токен бота, токен SourceCraft, ключи) не логируются и не отправляются в LLM. ### 5.4 Надёжность - SourceCraft API нестабилен (обрывы TLS) — все вызовы с ретраями и экспоненциальной задержкой; запись идемпотентна. - Падение бота → перезапуск Docker (`restart: unless-stopped`) + healthcheck. - Пуши в репозиторий загрузок сериализуются (одна очередь), при конфликте — `pull --rebase` и повтор. ### 5.5 Производительность - Ответ на загрузку (коммит + ссылка) — ≤ 10 с. - Обновление статуса CI — ≤ 30 с после смены статуса в SourceCraft. - Бот (без приложений) укладывается в 256 МБ RAM: на хосте свободно ~1,9 ГБ. ## 6. Ключевые сценарии 1. **Успешная загрузка.** Пользователь отправляет `bot.py` с подписью `project: weather` → бот: «✅ Закоммичено `a1b2c3d` → ⏳ CI…» → через минуту сообщение меняется на «✅ CI пройден → 🚀 запущено» → `/status weather` показывает работающий сервис. 2. **Ошибка синтаксиса.** Файл с `def f(:` → бот сразу отвечает «Синтаксическая ошибка, строка 1», коммита нет. 3. **Падение CI.** `ruff` нашёл ошибки → «❌ CI упал на шаге lint» + лог шага файлом. 4. **Падение приложения.** Сервис упал с `KeyError` → алерт с хвостом лога и кнопками «Логи», «Перезапустить», «Спросить ИИ». 5. **Вопрос к логам.** `/ask weather почему падает по ночам?` → LLM строит запрос `{since: 24h, pattern: "Traceback|Error"}` → бот находит строки → ответ: «С 03:00 таймауты API погоды, 14 раз; см. строки…». 6. **Перегрузка.** Скрипт ушёл в бесконечный цикл → через минуту алерт «CPU 100% лимита». ## 7. Критерии приёмки - [ ] Сценарии 1–6 проходят на развёрнутом боте (демо-видео или вживую). - [ ] `make up` с заполненным `.env` поднимает систему на чистой машине. - [ ] Push в `main` репозитория бота → CI зелёный → бот автоматически обновился на VPS. - [ ] `make lint && make test` зелёные, покрытие ≥ 70%. - [ ] README по «Требованиям к оформлению решений»: цели и задачи, что реализовано, развёртывание, входные/выходные данные и API, ограничения. - [ ] Структура каталогов: `app/`, `tests/`, `config/`, `infra/`, `docs/`, `examples/` (демо-скрипты для загрузки). - [ ] Презентация (PDF): принцип работы, архитектура, демо. ## 8. Вне рамок - Веб-интерфейс, поддержка языков кроме Python, многофайловые проекты сложнее «скрипт + requirements + тесты». - Kubernetes, горизонтальное масштабирование, отдельный сервер под приложения (возможное развитие — ADR-005).