From 018e3d26c486c68ae25d1f184190b407f645986a Mon Sep 17 00:00:00 2001 From: Lacky1234union Date: Mon, 12 Oct 2026 18:32:54 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=A2=D0=97=20v1,=20=D0=B0=D1=80=D1=85?= =?UTF-8?q?=D0=B8=D1=82=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=B0,=20ADR-002..00?= =?UTF-8?q?9,=20=D0=BE=D1=82=D0=BA=D1=80=D1=8B=D1=82=D1=8B=D0=B5=20=D0=B2?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=BE=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #2 Co-Authored-By: Claude Opus 5.5 --- docs/ARCHITECTURE.md | 197 ++++++++++++++++++++++++++++++++++++++-- docs/DECISIONS.md | 63 +++++++++++++ docs/QUESTIONS_IDEAS.md | 26 ++++-- docs/SPEC.md | 181 +++++++++++++++++++++++++++++++----- 4 files changed, 428 insertions(+), 39 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 43eb73b..88666c2 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,15 +1,196 @@ -# Архитектура +# Архитектура tgit -> Заполняется после согласования ТЗ. +> Требования — в [`SPEC.md`](SPEC.md), решения и их обоснования — в [`DECISIONS.md`](DECISIONS.md). -## Компоненты +## 1. Общая схема -_TODO: бот, git-интеграция с SourceCraft, наблюдатель за CI, сбор логов/метрик, LLM-ассистент, хранилище._ +``` + Telegram ◄──long polling──┐ + │ + ┌────────────┴─────────────────────────── хост (VPS / ноутбук) ─────────────┐ + │ tgit-bot (один процесс, asyncio) │ + │ ├─ bot/ aiogram: команды, загрузка файлов, кнопки │ + │ ├─ uploads/ валидация → GitPublisher (очередь, git + SSH) ───────────┼──► SourceCraft + │ ├─ ci/ CIWatcher: polling прогонов по sha, логи кубов ◄────────┼─── REST API + │ ├─ runtime/ Monitor: события и метрики контейнеров, логи │ + │ ├─ alerts/ правила, антиспам, доставка │ + │ └─ llm/ LogAssistant: вопрос → LogQuery → поиск → ответ ─────────┼──► LLM API + │ │ │ │ + │ ┌────┴─────┐ ┌────────┴────────┐ │ + │ │ postgres │ │ docker-proxy │ (только чтение: ps/logs/ │ + │ └──────────┘ │ socket-proxy │ events/stats) │ + │ └────────┬────────┘ │ + │ │ /var/run/docker.sock │ + │ tgit-runner (скрипт на хосте) ─┤ │ + │ ▲ ssh, forced command ▼ │ + │ │ tgit-app- контейнеры (песочница) │ + └─────┼─────────────────────────────────────────────────────────────────────┘ + │ + SourceCraft CI (tgit-uploads): check → deploy ──┘ +``` -## Потоки данных +**Два репозитория:** -_TODO: загрузка файла → коммит → CI → уведомление._ +| Репозиторий | Что внутри | CI/CD | +|---|---|---| +| `tgit` | код бота | PR/push `dev`: lint + types + tests; push `main`: + деплой бота на хост | +| `tgit-uploads` | `projects//…` от пользователей | push `main`: проверки проекта; успех → `ssh tgit-runner deploy ` | -## Деплой +## 2. Компоненты -_TODO._ +### 2.1 `app/bot` — интерфейс Telegram +aiogram 3, роутеры по фичам (`upload`, `projects`, `logs`, `ask`, `admin`). Middleware: проверка доступа по белому +списку, логирование апдейтов без содержимого файлов. Длинные ответы → файл. + +### 2.2 `app/uploads` — загрузка и коммит +- `validate(file) -> ValidatedUpload` — размер, кодировка, `ast.parse`, режим `# tgit: mode=…`. +- `GitPublisher` — держит локальный клон `tgit-uploads` в volume; **единственная очередь** (`asyncio.Queue`, один воркер), + чтобы пуши не конфликтовали. Шаги: `fetch` → `reset --hard origin/main` → запись файла → `commit` → `push`; + при отказе push — повтор с `pull --rebase` (до 3 раз). git вызывается через `asyncio.create_subprocess_exec` + с `GIT_SSH_COMMAND="ssh -i -o IdentitiesOnly=yes"`. +- Пустой diff → «изменений нет», коммита нет. + +### 2.3 `app/sourcecraft` — клиент API +`httpx.AsyncClient`, Bearer-токен, ретраи на сетевые ошибки и 5xx (экспоненциально, до 5 попыток), +типизированные ответы (pydantic). Методы: `list_runs`, `get_run`, `get_cube_logs`, `trigger_workflow`. +Подробности API — [`dev/SOURCECRAFT_API.md`](dev/SOURCECRAFT_API.md). + +### 2.4 `app/ci` — наблюдатель за CI +Фоновая задача. Опрашивает `/cicd/runs` **только пока есть незавершённые загрузки** (интервал 10 с, бэкофф до 60 с +при ошибках). Сопоставляет прогон с загрузкой по sha коммита, на смене статуса редактирует сообщение пользователя. +При падении скачивает лог упавшего куба. Таймаут прогона — 30 мин → статус `timeout`. +Webhooks у SourceCraft нет — отсюда polling (ADR-003). + +### 2.5 `app/runtime` — мониторинг приложений +Через `docker-socket-proxy` (только `GET` на containers/logs/events/stats): +- поток `events` с фильтром `label=tgit.project` → `die` (код выхода), `oom`, `restart`; +- раз в 15 с `stats` по контейнерам приложений → CPU% / RAM% от лимита, скользящее окно 1 мин; +- `logs` с `since`/`tail` — для `/logs` и LLM. + +Управление (`stop`/`restart`) бот **не** делает через сокет — он вызывает `tgit-runner` (ADR-006). + +### 2.6 `app/alerts` — алерты +Правила (пороги — в `config/alerts.yaml`) → событие → антиспам (ключ `project+kind`, окно 10 мин, счётчик) → +доставка владельцу и подписчикам. Системные алерты (недоступен API/Docker/БД) — администраторам. + +### 2.7 `app/llm` — ассистент по логам +``` +вопрос ──► LLM #1 (JSON-схема LogQuery) ──► валидация ──► поиск по логам ──► маскирование ──► LLM #2 ──► ответ +``` +- `LogQuery`: `source` (`app` | `ci`), `since` (≤ 7 д), `pattern` (regex ≤ 200 символов), `level`, `limit` (≤ 500). +- Поиск — Python `regex` с таймаутом (защита от ReDoS), **никакого shell** (ADR-007). +- Маскирование: токены, `KEY=…`, `password=…`, JWT, e-mail, IP → `***`. +- Провайдер — за протоколом `LLMProvider` + фабрика по конфигу: OpenAI-совместимый API (YandexGPT, OpenAI, локальная + модель) и `mock` для тестов (ADR-008). + +### 2.8 `app/db` — хранилище +SQLAlchemy 2 (async) + Alembic. PostgreSQL в Compose; в тестах — SQLite. + +| Таблица | Поля (основные) | +|---|---| +| `users` | `tg_id` PK, `username`, `role` (`user`/`admin`), `is_allowed`, `llm_quota_used`, `created_at` | +| `projects` | `id`, `name` UNIQUE, `owner_id` → users, `mode` (`service`/`job`), `network_enabled`, `alerts_enabled` | +| `uploads` | `id`, `project_id`, `user_id`, `filename`, `sha`, `status` (`committed`/`ci_running`/`ci_ok`/`ci_failed`/`deployed`/`timeout`), `chat_id`, `message_id`, `created_at` | +| `ci_runs` | `id`, `upload_id`, `run_slug`, `status`, `failed_cube`, `url`, `updated_at` | +| `alerts` | `id`, `project_id`, `kind`, `payload` JSON, `count`, `first_at`, `last_at`, `delivered_at` | +| `llm_requests` | `id`, `user_id`, `project_id`, `question`, `log_query` JSON, `tokens`, `created_at` | + +### 2.9 Конфигурация +`app/config.py` — `pydantic-settings`, один класс на подсистему (`TelegramSettings`, `SourceCraftSettings`, +`DockerSettings`, `LLMSettings`, …), всё из `.env`. Пороги и тексты — `config/*.yaml`. + +## 3. Потоки данных + +### 3.1 Загрузка → CI → CD +``` +user ──.py──► bot ── validate ──✗──► «ошибка, строка N» + │✓ + ▼ + GitPublisher ──push──► tgit-uploads@main + │ │ (SourceCraft CI) + «✅ a1b2c3d, ⏳ CI» ◄┘ ├─ check: py_compile · ruff · pytest · bandit + └─ deploy: ssh tgit-deploy@host → tgit-runner deploy + │ + CIWatcher ──poll runs(sha)──► статус ──► edit message ▼ + docker build + run tgit-app- + Monitor ◄── events/stats ── docker-proxy ◄──────────────────┘ + └──► alerts ──► user +``` + +### 3.2 Вопрос к логам +`/ask` → квота → LLM #1 → `LogQuery` → валидация → `runtime.logs()` / `sourcecraft.get_cube_logs()` → фильтр regex → +маскирование → LLM #2 (вопрос + найденные строки) → ответ + запрос + доказательства → запись в `llm_requests`. + +## 4. Песочница приложений (`tgit-runner`) + +`infra/runner/tgit-runner` — bash-скрипт на хосте; единственная команда, доступная ключу CI (`command="…"` в +`authorized_keys` пользователя `tgit-deploy`). Подкоманды: `deploy `, `stop `, `restart `. + +`deploy`: забирает архив проекта по sha из `tgit-uploads` → собирает образ `tgit-app-:` из шаблона +(`python:3.12-slim` + `requirements.txt`) → запускает: + +``` +docker run -d --name tgit-app- --label tgit.project= --label tgit.sha= + --user 10001:10001 --read-only --tmpfs /tmp:size=16m + --cap-drop ALL --security-opt no-new-privileges + --memory 128m --memory-swap 128m --cpus 0.25 --pids-limit 64 + --network none # или tgit-apps-egress, если сеть разрешена + --restart on-failure:3 # service; для job — без рестарта + --log-opt max-size=5m --log-opt max-file=2 +``` + +## 5. CI/CD + +### 5.1 Репозиторий бота (`tgit/.sourcecraft/ci.yaml`) +| Триггер | Workflow | Шаги | +|---|---|---| +| PR в `dev`/`main`, push в `dev` | `check` | `uv sync --frozen` → `ruff check` + `ruff format --check` → `mypy app` → `pytest --cov=app --cov-fail-under=70` | +| push в `main` | `check` + `deploy` | → `infra/deploy/deploy.sh` по SSH (секреты `DEPLOY_SSH_KEY`, `DEPLOY_HOST`) → smoke: healthcheck контейнера | + +### 5.2 Репозиторий загрузок (`tgit-uploads/.sourcecraft/ci.yaml`) +Шаблон хранится в `tgit/infra/uploads-template/` и ставится в `tgit-uploads` один раз. +Определение изменённого проекта — `git diff --name-only HEAD~1 -- projects/` внутри куба. + +## 6. Деплой + +| Где | Как | +|---|---| +| Любая машина | `git clone` → `cp .env.example .env` → заполнить → `make up` | +| VPS vobot (основной) | push в `main` → CI → `infra/deploy/deploy.sh` → `/srv/tgit` (пользователь `tgit-deploy`, Compose-проект `tgit`) | + +`deploy.sh` (по образцу meditron): `git archive` → `scp` → распаковка в `app.new` → перенос `.env` → атомарная замена +`app` → `docker compose -p tgit up -d --build` → ожидание healthcheck → запись `REVISION`. Все SSH-шаги с ретраями +(хост режет часть соединений). Хост задаётся `DEPLOY_HOST` — тот же скрипт работает для любого сервера. + +Бюджет ресурсов на VPS (свободно ~1,9 ГБ RAM): бот ≤ 256 МБ, postgres ≤ 256 МБ, proxy ≤ 32 МБ, +приложения ≤ 10 × 128 МБ. + +## 7. Структура репозитория + +``` +app/ код бота: bot, uploads, sourcecraft, ci, runtime, alerts, llm, db, config.py, main.py +config/ alerts.yaml, texts.yaml, llm-промпты +infra/ + deploy/ deploy.sh, docker-compose.server.yml + runner/ tgit-runner, шаблон Dockerfile приложения, authorized_keys.example + uploads-template/ .sourcecraft/ci.yaml и README для tgit-uploads +examples/ демо-скрипты для загрузки (ok, syntax error, lint fail, crash, cpu hog) +tests/ pytest: unit + интеграционные с фейками +docs/ SPEC, ARCHITECTURE, DECISIONS, dev/, презентация +Dockerfile · docker-compose.yml · Makefile · pyproject.toml · .env.example +``` + +## 8. Что важно учесть + +| Риск | Что делаем | +|---|---| +| Чужой код на одном сервере с прод-Vobot | Песочница §4, лимиты, сеть выключена; общий лимит 10 приложений; в перспективе — отдельный хост (ADR-005) | +| Docker-сокет = root на хосте | Бот — только через read-only proxy; запуск — через `tgit-runner` с forced command | +| Пользователь `tgit-deploy` в группе `docker` ≈ root | Ключ ограничен одной командой; `tgit-runner` валидирует аргументы (`^[a-z][a-z0-9-]{1,30}$`, sha hex) | +| Prompt injection через логи | LLM только возвращает `LogQuery`, который валидируется; ответ LLM — просто текст пользователю | +| ReDoS в regex от LLM | Модуль `regex` с таймаутом, лимит длины паттерна и объёма логов | +| Утечка секретов в LLM | Маскирование перед отправкой; секреты бота не попадают в логи | +| Нестабильный SourceCraft API | Ретраи, идемпотентность, проверка перед повторным POST | +| Гонки пушей | Одна очередь GitPublisher, `pull --rebase` при отказе | +| Лимиты Telegram (4096 символов, ~30 msg/s) | Длинное — файлом; редактирование одного сообщения вместо потока | +| Диск хоста занят на 75% | Ротация логов контейнеров, `docker image prune` старых ревизий приложений в `tgit-runner` | diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 32a4dfd..4deadaf 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -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. diff --git a/docs/QUESTIONS_IDEAS.md b/docs/QUESTIONS_IDEAS.md index 5619278..8846550 100644 --- a/docs/QUESTIONS_IDEAS.md +++ b/docs/QUESTIONS_IDEAS.md @@ -2,16 +2,22 @@ ## Вопросы -- [ ] Что именно делает CD: куда «доставляется» загруженный `.py` (запуск как сервис? проверка тестами?) -- [ ] Есть ли на SourceCraft container registry, или пушим образы в другое место? -- [ ] Где деплоим: VPS или локально? Нужна ли БД (пользователи, история загрузок)? -- [ ] Как бот узнаёт статус CI: polling SourceCraft API или webhook? -- [ ] Кто может загружать файлы: любой пользователь или whitelist? -- [ ] Какой LLM-провайдер для чата по логам? -- [ ] Нужна ли интеграция с anytask и в каком виде? +- [ ] **Официальное ТЗ** — сверить с [`SPEC.md`](SPEC.md), расхождения → ADR. +- [ ] **LLM:** какую модель берём (YandexGPT / OpenAI / локальная) и кто платит за токены? Архитектура не зависит (ADR-008). +- [ ] **anytask:** что именно интегрировать — отправку решений, оценки, уведомления? (SPEC F-61) +- [ ] **Чужой код на сервере Vobot:** ок ли временно (ADR-005), или сразу берём отдельный VPS под приложения? +- [ ] **Доступ:** кто в белом списке на защите — только мы или вся группа/преподаватель? +- [ ] **Сеть для приложений:** нужен ли интернет скриптам пользователей (например, свои боты)? По умолчанию выключен. + +## Решено + +- ~~Container registry на SourceCraft~~ — есть, но требует биллинга Yandex Cloud → ADR-004. +- ~~Как бот узнаёт статус CI~~ — polling API → ADR-003. +- ~~Где деплоим~~ — Compose где угодно, основной хост — VPS vobot → SPEC §5.1. ## Идеи -- Команда `/logs` с фильтрами (сервис, уровень, период). -- LLM формирует `grep`/запрос к логам, выполняет его в песочнице, суммирует результат. -- Алерты: падение пайплайна, рестарт контейнера, CPU/RAM выше порога. +- Еженедельная сводка по проектам (SPEC F-62). +- Откат на ревизию (SPEC F-60). +- Бейдж статуса CI в README загрузок. +- Экспорт метрик в Prometheus (на хосте уже есть мониторинг Vobot). diff --git a/docs/SPEC.md b/docs/SPEC.md index e09bc92..f57d11f 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1,32 +1,171 @@ -# ТЗ — черновик +# SPEC — техническое задание tgit -> Статус: **черновик**. Ждём официальное ТЗ от преподавателя (обещано 2026-10-12), затем сверяем и детализируем. +> Статус: **v1, рабочая версия** (2026-10-12). Составлено по постановке преподавателя и согласованным расширениям; +> официальное ТЗ ещё не пришло — когда придёт, сверяем и фиксируем расхождения в [`DECISIONS.md`](DECISIONS.md). +> При конфликте ADR важнее этого документа. -## Исходная постановка (от преподавателя) +## 1. Контекст -- Telegram-бот, который загружает `.py`-файлы в репозиторий на SourceCraft. -- После загрузки автоматически запускается CI/CD. -- Репозиторий для загрузок — отдельный (не репозиторий с домашками). -- У бота свой SSH/deploy-ключ. -- Развёртывание: VPS или локально на ноутбуке. -- Опционально: настроить anytask. -- Вайбкодинг разрешён. +**Что это:** Telegram-бот «git в телеграме». Пользователь отправляет боту `.py`-файл — бот коммитит его в отдельный +репозиторий на SourceCraft, там автоматически запускается CI (проверки) и CD (запуск кода на сервере). +Бот сообщает результат CI, следит за запущенным кодом, присылает алерты о падениях и перегрузке, отдаёт логи +и отвечает на вопросы по логам с помощью LLM. -## Согласованные расширения +**Зачем:** учебный проект курса Python (НИУ ВШЭ) — демонстрация асинхронного Python, интеграций по API, +работы с git, контейнеризации, CI/CD и тестирования. -- Уведомления об успешности/падении CI. -- Алерты о падении и перегрузке сервиса. -- Удобное получение логов. -- Чат с LLM, который строит `grep`-запросы по логам и отвечает на вопросы. +**Исходная постановка** (переписка с преподавателем, 2026-10-12): +- бот загружает `.py`-файлы в репозиторий SourceCraft, автоматически запускается CI/CD; +- репозиторий для загрузок — отдельный, не репозиторий с домашками; у бота свой ключ; +- разворачивание — на VPS или локально; +- вайбкодинг разрешён; +- согласованы расширения: статус CI, алерты о падении и перегрузке, удобные логи, LLM-чат по логам; +- опционально — настроить anytask. -## Функциональные требования +## 2. Термины -_TODO — после получения ТЗ._ +| Термин | Значение | +|---|---| +| **Репозиторий бота** | [`rogachevegorushka/tgit`](https://sourcecraft.dev/rogachevegorushka/tgit) — код самого бота | +| **Репозиторий загрузок** | `rogachevegorushka/tgit-uploads` — сюда бот коммитит файлы пользователей | +| **Проект** | Папка `projects//` в репозитории загрузок; единица запуска и мониторинга | +| **Загрузка** | Один отправленный файл → один коммит | +| **Прогон** | CI/CD-run SourceCraft, запущенный коммитом загрузки | +| **Приложение** | Запущенный на сервере контейнер с кодом проекта | +| **Хост** | Сервер, где работают бот и приложения (по умолчанию VPS vobot) | -## Нефункциональные требования +## 3. Роли -_TODO._ +| Роль | Может | +|---|---| +| **Гость** | `/start`, `/help`; видит, что доступа нет, и свой Telegram ID для запроса доступа | +| **Пользователь** | Загружать файлы, видеть свои проекты, статусы CI, логи, алерты, спрашивать LLM — **только по своим проектам** | +| **Администратор** | Всё + управление доступом (`/allow`, `/deny`), видит все проекты, получает системные алерты бота | -## Критерии приёмки +Доступ — по белому списку Telegram ID (первичные администраторы задаются в `.env`). -_TODO._ +## 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).