CI / ruff · mypy · pytest (pull_request) Failing after 3m40s
- 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>
27 lines
808 B
Python
27 lines
808 B
Python
"""What the watcher needs from a CI: the run of a commit and the log of what failed. Vendors live next to it."""
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Protocol
|
|
|
|
from app.db.models import PipelineStatus
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CIRun:
|
|
id: str
|
|
status: PipelineStatus # pending · running · success · failed · skipped
|
|
url: str
|
|
failed_step: str | None = None # "job / step" of the first failure
|
|
|
|
|
|
class CIProvider(Protocol):
|
|
async def find(self, sha: str) -> CIRun | None:
|
|
"""The newest run of the uploads workflow for this commit; None — not started (yet)."""
|
|
...
|
|
|
|
async def failure_log(self, run: CIRun) -> str:
|
|
"""Log of the failed job, plain text without runner timestamps."""
|
|
...
|
|
|
|
async def aclose(self) -> None: ...
|