Статус: v2 (2026-10-13). v1 — постановка преподавателя и согласованные расширения; v2 — доступ по QR-приглашению,
меню из четырёх разделов, интерактивный коммит, LLM-агент с фильтром атак, общие алерты.
Официальное ТЗ ещё не пришло — когда придёт, сверяем и фиксируем расхождения в DECISIONS.md.
При конфликте ADR важнее этого документа.
1. Контекст
Что это: Telegram-бот «git в телеграме» для небольшой команды. Попасть в бота можно только по QR-приглашению.
Внутри — меню из четырёх разделов:
Коммит — пройтись по дереву репозитория, создать или изменить файлы, написать сообщение коммита и нажать «Push».
Дальше в SourceCraft автоматически идут CI (проверки) и CD (развёртывание сервисов), бот присылает их результат.
Статус — состояние и health развёрнутых контейнеров, запуск проверок.
Логи — логи выбранного сервиса: хвост, свой grep или вопрос на естественном языке к LLM-агенту, который
отсекает попытки атаки и сам строит grep.
Алерты — падения, краши, перегрузка и прочее приходят всем авторизованным; список алертов.
Зачем: учебный проект курса Python (НИУ ВШЭ) — асинхронный Python, интеграции по API, git, контейнеры, CI/CD,
безопасность и тестирование.
Исходная постановка (переписка с преподавателем, 2026-10-12):
бот загружает .py-файлы в репозиторий SourceCraft, автоматически запускается CI/CD;
репозиторий для загрузок — отдельный, не репозиторий с домашками; у бота свой ключ;
разворачивание — на VPS или локально; вайбкодинг разрешён;
согласованы расширения: статус CI, алерты о падении и перегрузке, удобные логи, LLM-чат по логам;
SourceCraft rogachevegorushka/tgit-uploads — общий репозиторий команды, в который бот коммитит изменения
Сервис
Папка services/<name>/ в репозитории загрузок (точка входа main.py, опционально requirements.txt, тесты); CD разворачивает её в контейнер
Черновик
Набор несохранённых изменений пользователя (создать / заменить / удалить файл) до нажатия «Push»
Пуш
Один коммит из черновика в main репозитория загрузок
Прогон
CI/CD-run SourceCraft, запущенный пушем
Приглашение
Одноразовая ссылка t.me/<bot>?start=<token> в виде QR-кода
Хост
Сервер, где работают бот и сервисы (по умолчанию VPS vobot)
3. Роли и доступ
Роль
Может
Неавторизованный
Ничего. На любое сообщение — одна фраза «доступ только по приглашению» (не чаще раза в сутки на пользователя), дальше бот молчит
Участник
Все четыре раздела меню. Модель — команда: все участники работают с одним репозиторием и видят все сервисы
Администратор
Всё + выпуск приглашений (QR), список участников, отзыв доступа
3.1 Приглашение по QR — P0
#
Требование
A-1
Администратор в меню «Участники» нажимает «Пригласить» → бот присылает QR-код (PNG) и ту же ссылку текстом
A-2
Ссылка — deep link https://t.me/<bot>?start=<token>; token — 128 бит случайности (base64url, ≤ 64 символов — лимит Telegram)
A-3
Приглашение одноразовое, живёт 24 ч (настраивается); в БД хранится только хэш токена
A-4
Переход по QR → /start <token> → проверка → пользователь становится участником, видит главное меню. Повторное использование, истёкший или неизвестный токен → отказ
A-5
Без приглашения ни одна функция недоступна: проверка — в middleware на каждом апдейте (сообщения и нажатия кнопок)
A-6
Защита от перебора: не больше 5 неудачных /start <token> в час с одного Telegram ID, дальше — игнор
A-7
Администратор видит участников (имя, кто пригласил, когда, последняя активность) и может отозвать доступ — с этого момента апдейты пользователя игнорируются, его черновик удаляется
A-8
Первые администраторы задаются в .env (ADMIN_IDS); остальные — только через приглашение
4. Функциональные требования
Приоритеты: P0 — без этого нет сдачи; P1 — нужно для полного балла; P2 — если останется время.
4.0 Главное меню — P0
#
Требование
M-1
После авторизации — главное меню (постоянная клавиатура): 📝 Коммит · 🩺 Статус · 📜 Логи · 🚨 Алерты; у администратора дополнительно 👥 Участники
M-2
Внутри разделов — инлайн-кнопки; сообщение раздела редактируется на месте, а не плодит новые
M-3
В любом шаге есть «⬅️ Назад» и «✖️ Отмена»; /menu возвращает в главное меню из любого состояния
M-4
Состояние диалога (FSM) хранится в БД/Redis-совместимом хранилище и переживает перезапуск бота
4.1 Раздел «Коммит» — P0
Навигация по репозиторию
#
Требование
C-1
Раздел открывается браузером дерева main репозитория загрузок: кнопки 📁 папок и 📄 файлов текущей папки, «⬆️ Вверх», путь в заголовке сообщения
C-2
Больше 20 элементов — постраничная навигация «◀️ / ▶️»
C-3
Нажатие на 📄 файл → карточка файла: размер, последний коммит; кнопки «👁 Показать», «✏️ Заменить содержимое», «🗑 Удалить»
C-4
В папке — кнопки «➕ Новый файл» и «📁 Новая папка» (папка существует, пока в ней нет файлов, только в черновике)
Изменения
#
Требование
C-10
«➕ Новый файл» → бот просит имя файла → проверка (допустимые символы [A-Za-z0-9._-], не существует, длина ≤ 100) → просит содержимое
C-11
Содержимое принимается текстом сообщения (обрамление ``` снимается) или документом (если не помещается в 4096 символов)
C-12
Валидация: ≤ 100 КБ, UTF-8, без NUL-байтов; для .py — синтаксис через ast.parse, при ошибке — номер строки и выбор «исправить» / «всё равно добавить»
C-13
«✏️ Заменить содержимое» — то же для существующего файла; бот показывает краткий diff (+N/−M строк)
C-14
Каждое изменение попадает в черновик; бот показывает его: список A path, M path, D path с кнопками «убрать из черновика»
C-15
Черновик переживает перезапуск бота и живёт 24 ч; у каждого участника свой черновик
C-16
Запрещено изменять .sourcecraft/ и корневые служебные файлы репозитория загрузок (защита CI от правок из бота)
Коммит и пуш
#
Требование
C-20
Кнопка «💬 Сообщение коммита» → ввод текста (1–200 символов, первая строка ≤ 72)
C-21
Кнопка «🚀 Push» активна, когда черновик не пуст и есть сообщение; перед пушем — сводка и подтверждение
C-22
Пуш = один коммит в main; автор — бот, в сообщении трейлер Telegram-User: @<username> (<id>)
C-23
Если с начала черновика в main изменились те же файлы — предупреждение о конфликте: «перезаписать» / «отменить»
C-24
После пуша — сообщение с sha и ссылкой на коммит, которое дальше обновляется статусом: CI ⏳ → ✅/❌ → CD ⏳ → ✅/❌
C-25
Лимит: не больше 20 пушей на участника в час (антиспам)
Уведомления CI/CD
#
Требование
C-30
Успех CI → уведомление автору пуша: «✅ CI пройден» + ссылка на прогон
C-31
Успех CD → уведомление автору: «🚀 CD: сервисы a, b развёрнуты, ревизия sha»
C-32
Падение CI или CD → уведомление автору с упавшим шагом и логом шага (текстом или файлом)
C-33
Падение CD дополнительно публикуется как алерт всем участникам (§4.4) — сервис мог остаться в старой версии
C-34
Статус CI/CD отслеживается опросом SourceCraft API (ADR-003); задержка уведомления ≤ 30 с
4.2 Раздел «Статус» — P0
#
Требование
S-1
Список всех контейнеров системы: сервисы из репозитория загрузок и служебные (бот, БД, proxy). Для каждого: 🟢 healthy / 🟡 starting / 🔴 unhealthy или упал / ⚪ остановлен
S-2
Карточка контейнера: состояние, health, аптайм, число рестартов, CPU и RAM относительно лимита, ревизия (sha), время последнего деплоя
S-3
Кнопка «🩺 Запустить проверки» — для каждого контейнера выполняется набор проверок и выводится отчёт: запущен; Docker health = healthy; рестарты не растут за последние 5 мин; CPU/RAM ниже порогов; нет ошибок в логе за 5 мин; HTTP-проба, если сервис объявил health_url
Выбор контейнера/сервиса из списка (тот же, что в «Статусе»)
L-2
Хвост: 50 / 200 строк или за 5 мин / 1 ч / 24 ч
L-3
Свой grep: пользователь вводит выражение в синтаксисе grep: шаблон, флаги -i, -v, -E, -c, -C N. Выполняется ботом в Python, не в shell; шаблон ≤ 200 символов, regex с таймаутом
L-4
Вопрос агенту: пользователь пишет промт («почему сервис падал ночью?», «сколько таймаутов за час?») — дальше конвейер §4.3.1
L-5
Результат длиннее 4096 символов — файлом .log; не больше 5 МБ
L-6
Каждый поиск записывается в журнал: кто, что искал, сколько строк получил
Шаг 1 — фильтр без LLM: промт ≤ 500 символов; отклоняются известные признаки атаки: просьбы выполнить команды, shell-метасимволы с намерением исполнения (;, &&, $(…), `), пути к системным файлам (/etc/, ~/.ssh, .env), фразы prompt injection («игнорируй инструкции», «system prompt», «ты теперь…»)
L-11
Шаг 2 — LLM-страж: отдельный вызов с узкой задачей — классифицировать промт как safe или attack (попытка сломать или взломать: исполнение команд, доступ к секретам или чужим данным, обход ограничений, DoS через тяжёлый regex). Ответ — строго JSON по схеме, иначе считается attack
L-12
attack → вежливый отказ пользователю без деталей и алерт администраторам (кто, текст промта, причина)
L-13
Шаг 3 — генерация grep: LLM возвращает только LogQuery (контейнер из списка доступных, период ≤ 7 д, шаблон, флаги, лимит ≤ 500 строк). Никакого свободного текста, команд или путей
L-14
Шаг 4 — валидация: pydantic-схема, белый список контейнеров, шаблон компилируется, длина ≤ 200, проверка на катастрофический бэктрекинг
L-15
Шаг 5 — выполнение и ответ: бот выполняет запрос сам и отвечает пользователю: сформированный grep (в виде команды, чтобы было видно, что искалось) + найденные строки; P2 — краткий вывод LLM по найденному
L-16
Логи — недоверенный ввод: в LLM на шаге 3 логи не передаются (только промт и список контейнеров) — prompt injection из логов невозможен; в P2-выводе логи проходят маскирование секретов
L-17
Квота: 30 вопросов агенту на участника в сутки
4.4 Раздел «Алерты» — P1
#
Требование
AL-1
Алерты получают все авторизованные участники
AL-2
Типы: 💥краш (выход с ненулевым кодом), 🧠OOM, 🔁цикл рестартов (≥ 3 за 10 мин), 🩺unhealthy (health-check проваливается), 🔥перегрузка (CPU > 80 % или RAM > 90 % лимита дольше 1 мин), ⛔отказ (контейнер не запускается после деплоя), 📦CD упал, 💾мало места на хосте (< 10 %), 🔌внешняя зависимость недоступна (SourceCraft API / Docker / БД дольше 5 мин), 🛡 попытка атаки через агента (только администраторам)
AL-3
Сообщение алерта: тип, сервис, время, хвост лога (до 30 строк) и кнопки «📜 Логи», «🩺 Статус»
AL-4
Восстановление: когда условие проходит (контейнер снова healthy, нагрузка в норме) — сообщение «✅ восстановлено» в ответ на исходный алерт
AL-5
Антиспам: повтор того же алерта (сервис + тип) не чаще раза в 10 минут — вместо нового сообщения растёт счётчик в исходном
AL-6
Кнопка «📋 Список алертов»: активные сверху, затем история за 7 дней, по 10 на страницу; карточка алерта — время начала/конца, число повторов, хвост лога
AL-7
P2: личная «🔕 Тишина на 1 час»
4.5 Прочее — P2
#
Требование
X-1
История коммитов по файлу или папке и откат файла на ревизию
X-2
Интеграция с anytask (формат уточнить у преподавателя)
X-3
Еженедельная сводка: пуши, прогоны, алерты
5. Нефункциональные требования
5.1 Развёртывание
Всё поднимается одной командой make up (Docker Compose) на любой Linux-машине с Docker — ноутбук или VPS.
Бот работает через long polling — входящий HTTPS и домен не нужны (ADR-002).
Конфигурация — только через .env (пример — .env.example); секреты в git не попадают.
Основная площадка — VPS vobot (/srv/tgit), деплой — автоматически из CI SourceCraft.
Комментарии — там, где неочевидно «почему»; докстринги у публичных интерфейсов модулей.
5.3 Безопасность
Доступ — только по одноразовым приглашениям; проверка на каждом апдейте; отзыв доступа мгновенный.
Антиспам — лимиты на пуши, размер файлов, запросы к агенту, попытки приглашений.
Сервисы — в контейнере: непривилегированный пользователь, --cap-drop ALL, no-new-privileges, read-only FS +
tmpfs /tmp, лимиты --memory 128m --cpus 0.25 --pids-limit 64, без доступа к Docker-сокету; сеть наружу —
выключена по умолчанию, включается администратором для сервиса.
Docker — бот видит его только через прокси с минимальным набором разрешённых вызовов; запуск и перезапуск — через tgit-runner с SSH-ключом, ограниченным одной командой.
LLM — фильтр + страж + структурированный вывод; grep выполняет бот, а не shell; логи в LLM для генерации запроса не попадают.
Секреты (токены бота, SourceCraft, LLM, ключи) не логируются и не отправляются в LLM.
5.4 Надёжность
SourceCraft API нестабилен — все вызовы с ретраями и экспоненциальной задержкой; запись идемпотентна.
Пуши сериализуются (одна очередь), при конфликте — pull --rebase и повтор.
5.5 Производительность
Пуш (коммит + ссылка) — ≤ 10 с; навигация по дереву — ≤ 2 с на нажатие.
Уведомление о CI/CD и алерт — ≤ 30 с после события.
Бот (без сервисов) укладывается в 256 МБ RAM: на хосте свободно ~1,9 ГБ.
6. Ключевые сценарии
Вход по QR. Администратор → «👥 Участники» → «Пригласить» → QR. Новый участник сканирует → бот открывается, /start → «Добро пожаловать» + главное меню. Второй человек сканирует тот же QR → «приглашение уже использовано».
Случайный пользователь. Нашёл бота поиском, пишет /start и что угодно → одна фраза «доступ по приглашению», дальше тишина.