push ─► pushes(ci=pending) ─► wake()
PipelineWatcher ─ Gitea API runs?head_sha=… (10 с, бэкофф до 60 с)
├ running ─► «CI: ⏳ идёт проверка» в сообщении о push
├ success ─► «✅ CI пройден» ─► tgit-runner ─► «🚀 CD: сервисы … развёрнуты» / ⛔ + алерт всем
├ failed ─► «❌ CI упал на шаге check / lint» + хвост лога (длинный — ещё и файлом), CD не запускается
└ 30 мин без вердикта ─► ⌛, CD не запускается
Что сделано
CI загрузок — Gitea Actions (ADR-016): infra/uploads-template/.gitea/workflows/check.yml — compile · ruff · bandit · pytest по шагам. У прогона Gitea есть head_sha, куб meta не нужен. .gitea/ защищён от правок из бота.
app/pipeline/: протокол CIProvider и gitea.py (ретраи на сеть и 5xx, упавший шаг, лог джоба). SourceCraft подключится второй реализацией, наблюдатель не изменится.
PipelineWatcher: состояние — в pushes.ci_status/cd_status и pipeline_runs, сообщение о push каждый раз рендерится из БД. Опрос — только пока есть незавершённые push, иначе спит до нового push. После рестарта продолжает: ждёт CI дальше, прерванный деплой повторяет.
Деплой только после зелёного CI. Прогоны могут закончиться не по порядку — старый push не разворачивает сервис, который развернёт более новый (иначе откатил бы его).
CI_PROVIDER=none (по умолчанию) — прежнее поведение: деплой сразу после push, без строки CI.
Миграция 0002 (данные): старые push → skipped, чтобы первый тик не разослал авторам «таймаут CI».
Живой Gitea: workflow установлен в HSE/tgit-uploads (540eaa4) → прогон 5225✅ за 28 с, все 7 шагов, тест hello прошёл; GiteaCI.find(sha) на живом API — running → success.
357 тестов (23 новых): клиент Gitea на respx (статусы, упавший шаг, отмена, ретраи, 404); наблюдатель — зелёный путь, красный с логом, длинный лог файлом, таймаут, падение деплоя, порядок версий, рестарт посреди деплоя, без CI, цикл переживает ошибки API и просыпается на push. Покрытие 92 %, ruff, mypy strict.
Чтобы включить на dev
В /srv/tgit/dev/.env: CI_PROVIDER=gitea, CI_TOKEN=<токен Gitea с чтением HSE/tgit-uploads>. Без них бот работает как раньше.
Closes #11 · C-24, C-30…C-34 · ADR-016
## Как это работает
```
push ─► pushes(ci=pending) ─► wake()
PipelineWatcher ─ Gitea API runs?head_sha=… (10 с, бэкофф до 60 с)
├ running ─► «CI: ⏳ идёт проверка» в сообщении о push
├ success ─► «✅ CI пройден» ─► tgit-runner ─► «🚀 CD: сервисы … развёрнуты» / ⛔ + алерт всем
├ failed ─► «❌ CI упал на шаге check / lint» + хвост лога (длинный — ещё и файлом), CD не запускается
└ 30 мин без вердикта ─► ⌛, CD не запускается
```
## Что сделано
- **CI загрузок — Gitea Actions** (ADR-016): `infra/uploads-template/.gitea/workflows/check.yml` — compile · ruff · bandit · pytest по шагам. У прогона Gitea есть `head_sha`, куб `meta` не нужен. `.gitea/` защищён от правок из бота.
- **`app/pipeline/`**: протокол `CIProvider` и `gitea.py` (ретраи на сеть и 5xx, упавший шаг, лог джоба). SourceCraft подключится второй реализацией, наблюдатель не изменится.
- **`PipelineWatcher`**: состояние — в `pushes.ci_status/cd_status` и `pipeline_runs`, сообщение о push каждый раз рендерится из БД. Опрос — только пока есть незавершённые push, иначе спит до нового push. После рестарта продолжает: ждёт CI дальше, прерванный деплой повторяет.
- **Деплой только после зелёного CI.** Прогоны могут закончиться не по порядку — старый push не разворачивает сервис, который развернёт более новый (иначе откатил бы его).
- `CI_PROVIDER=none` (по умолчанию) — прежнее поведение: деплой сразу после push, без строки CI.
- Миграция 0002 (данные): старые push → `skipped`, чтобы первый тик не разослал авторам «таймаут CI».
- ADR-016, ARCHITECTURE §2.5/§5.2, README, DEPLOY, `.env.example`.
## Как проверено
- **Живой Gitea:** workflow установлен в `HSE/tgit-uploads` (`540eaa4`) → прогон [5225](https://gitea.24studbank.ru/HSE/tgit-uploads/actions/runs/5225) ✅ за 28 с, все 7 шагов, тест `hello` прошёл; `GiteaCI.find(sha)` на живом API — `running` → `success`.
- 357 тестов (23 новых): клиент Gitea на respx (статусы, упавший шаг, отмена, ретраи, 404); наблюдатель — зелёный путь, красный с логом, длинный лог файлом, таймаут, падение деплоя, порядок версий, рестарт посреди деплоя, без CI, цикл переживает ошибки API и просыпается на push. Покрытие 92 %, ruff, mypy strict.
## Чтобы включить на dev
В `/srv/tgit/dev/.env`: `CI_PROVIDER=gitea`, `CI_TOKEN=<токен Gitea с чтением HSE/tgit-uploads>`. Без них бот работает как раньше.
## Чек-лист
- [x] rebase на origin/dev
- [x] lint + тесты зелёные
- [x] секретов в диффе нет
- [x] лог в issue дописан
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- app/pipeline: протокол CIProvider, клиент Gitea Actions (прогон по head_sha, упавший
шаг, лог джоба, ретраи), PipelineWatcher — опрос только при незавершённых push,
сообщение о push обновляется CI ⏳ → ✅/❌ → CD 🚀/⛔, автору — отдельные уведомления
- деплой сервисов — только после зелёного CI; старый push не откатывает сервис,
который развернёт более новый; после рестарта бот продолжает с того же места
- CI_PROVIDER=none — прежнее поведение (деплой сразу после push)
- шаблон tgit-uploads: .gitea/workflows/check.yml; .gitea/ защищён от правок из бота
- миграция 0002: старые push помечены skipped, чтобы не слать им «таймаут CI»
- ADR-016, ARCHITECTURE §2.5/§5.2, README, DEPLOY, .env.example
Refs #11
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
У Settings нет префикса env, поэтому переменная CI=true с раннера Gitea
попадала в поле ci и роняла создание настроек во всех тестах.
Refs #11
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #11 · C-24, C-30…C-34 · ADR-016
Как это работает
Что сделано
infra/uploads-template/.gitea/workflows/check.yml— compile · ruff · bandit · pytest по шагам. У прогона Gitea естьhead_sha, кубmetaне нужен..gitea/защищён от правок из бота.app/pipeline/: протоколCIProviderиgitea.py(ретраи на сеть и 5xx, упавший шаг, лог джоба). SourceCraft подключится второй реализацией, наблюдатель не изменится.PipelineWatcher: состояние — вpushes.ci_status/cd_statusиpipeline_runs, сообщение о push каждый раз рендерится из БД. Опрос — только пока есть незавершённые push, иначе спит до нового push. После рестарта продолжает: ждёт CI дальше, прерванный деплой повторяет.CI_PROVIDER=none(по умолчанию) — прежнее поведение: деплой сразу после push, без строки CI.skipped, чтобы первый тик не разослал авторам «таймаут CI»..env.example.Как проверено
HSE/tgit-uploads(540eaa4) → прогон 5225 ✅ за 28 с, все 7 шагов, тестhelloпрошёл;GiteaCI.find(sha)на живом API —running→success.Чтобы включить на dev
В
/srv/tgit/dev/.env:CI_PROVIDER=gitea,CI_TOKEN=<токен Gitea с чтением HSE/tgit-uploads>. Без них бот работает как раньше.Чек-лист
🤖 Generated with Claude Code