feat(bot): клиент SourceCraft API — прогоны, логи кубов, sha коммита, ретраи

- SourceCraftClient (httpx): list_runs, get_run, get_cube_logs (постранично), run_commit_sha;
  ретраи на сетевые ошибки и 5xx с экспоненциальной задержкой, 4xx — SourceCraftError без повторов
- модели: RunStatus/EventType из swagger, «1970-01-01» = не задано, failed_cubes без служебных #-кубов
- у прогона в API нет sha → куб check/meta в tgit-uploads печатает TGIT_COMMIT_SHA (ADR-014)
- CLI: python -m app.sourcecraft.cli runs
- тесты на реальных ответах API (фикстуры), порог покрытия 45 → 80

Refs #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 18:02:52 +03:00
co-authored by Claude Opus 5.5
parent 15f354d06b
commit c016b47a8a
18 changed files with 1433 additions and 4 deletions
+8
View File
@@ -121,3 +121,11 @@
**Решение:** конвейер из пяти шагов (SPEC §4.3.1): детерминированный фильтр → отдельный LLM-вызов «страж» с JSON-вердиктом `safe|attack` (невалидный ответ = `attack`) → отдельный LLM-вызов «планировщик», который получает только промт и список контейнеров и возвращает `LogQuery` → валидация → выполнение в Python. Пользователю показывается итоговый grep и найденные строки. Атака → отказ + алерт админам.
**Альтернативы:** один вызов LLM с инструкцией «откажись, если атака» — страж и исполнитель в одном контексте, injection обходит оба; передавать логи в LLM для поиска — injection из логов и утечка секретов.
**Последствия:** два LLM-вызова на запрос (латентность, токены → квота 30/сутки); даже при обходе стража вред ограничен схемой `LogQuery`.
## ADR-014: Связь «прогон ↔ коммит» — через куб `meta`, печатающий sha
**Дата:** 2026-10-14 · **Статус:** принято (уточняет ADR-003)
**Контекст:** бот должен понять, какой прогон CI относится к его пушу. В API SourceCraft у прогона нет sha коммита (ни в списке, ни в деталях), фильтра по коммиту нет; логи служебных кубов (`#git-clone`) API не отдаёт.
**Решение:** первый куб задачи `check` в `tgit-uploads` — `meta`: `echo "TGIT_COMMIT_SHA=$SOURCECRAFT_COMMIT_SHA"`. Бот читает его лог (`SourceCraftClient.run_commit_sha`) и сопоставляет прогон с пушем.
**Альтернативы:** запускать CI из бота вручную (`POST /cicd/runs` с `head.commit`) — номер прогона известен сразу, но push-триггер пришлось бы убрать, и ручные пуши в репозиторий остались бы без CI; сопоставление по времени — ломается при параллельных пушах.
**Последствия:** +1 короткий куб на прогон; куб `meta` нельзя удалять или переименовывать (он в `.sourcecraft/`, который бот защищает от правок); прогоны старше этого решения sha не имеют.