diff --git a/tasks/orchestrator/DEV_TASK_CI_FAIL_RETRY.md b/tasks/orchestrator/DEV_TASK_CI_FAIL_RETRY.md new file mode 100644 index 0000000..3b75d8d --- /dev/null +++ b/tasks/orchestrator/DEV_TASK_CI_FAIL_RETRY.md @@ -0,0 +1,93 @@ +# DEV TASK — БАГ 7: красный CI на development должен авто-возвращать задачу developer'у (retry, как review) + +**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator (8500) +**Ветка:** `fix/ci-fail-retry-developer` из свежего main (`git checkout main && git pull && git checkout -b fix/ci-fail-retry-developer`). main = `7922f6b` (PR #17). + +⚠️ **ГРАБЛЯ push (ORCH-7):** после push `git log origin/main..origin/fix/ci-fail-retry-developer` ДОЛЖЕН показать коммит ДО отчёта «PR готов». Токен Gitea: `docker exec orchestrator printenv ORCH_GITEA_TOKEN`. + +## Контекст / дыра +После бага 6 (PR #17) CI стал авторитетным гейтом стадии development. Но при **красном CI** наш код (gitea.py `handle_ci_status`, ветка `elif state == "failure" and current_stage == "development":`) делает ТОЛЬКО `notify_qg_failure` — уведомляет и встаёт. **Никто не перезапускает developer'а** → конвейер виснет, требует ручного вмешательства. + +Для review такая ветка УЖЕ есть (request_changes → relaunch developer, max 3x). Нужно сделать **симметрично** для CI-failure. + +## Образец (УЖЕ в коде — копируй его логику) — `handle_pr`, ветка REQUEST_CHANGES, ~стр.289-314: +```python + elif review_state == "REQUEST_CHANGES" and current_stage == "review": + conn = get_db() + retry_count = conn.execute( + "SELECT COUNT(*) as cnt FROM agent_runs WHERE task_id = ? AND agent = 'developer'", + (task_id,), + ).fetchone()["cnt"] + conn.close() + if retry_count < MAX_DEV_RETRIES: + update_task_stage(task_id, "development") + notify_stage_change(task_id, current_stage, "development") + try: + task_desc = (... attempt {retry_count + 1}/{MAX_DEV_RETRIES} ...) + job_id = enqueue_job("developer", repo_name, task_desc, task_id=task_id) + logger.info(...) + except Exception as e: + notify_error(task_id, f"Failed to relaunch developer: {e}") + else: + notify_error(task_id, f"Max developer retries ({MAX_DEV_RETRIES}) reached, escalating") + logger.error(...) +``` +`MAX_DEV_RETRIES = 3` (gitea.py:33). Импорты `enqueue_job`, `update_task_stage`, `notify_stage_change`, `notify_error`, `notify_qg_failure`, `get_db` УЖЕ есть в файле — не дублируй. + +## Фикс — переписать CI-failure ветку в `handle_ci_status` (~стр.219-222) +БЫЛО: +```python + elif state == "failure" and current_stage == "development": + # CI is now the authoritative gate for development -> review. + # A failing CI means the QG did not pass; notify (do not silently advance). + notify_qg_failure(task_id, current_stage, "check_ci_green", f"Gitea CI failed on branch '{branch}'") +``` +СТАЛО (уведомить О ПРОВАЛЕ + авто-retry developer'а с тем же лимитом): +```python + elif state == "failure" and current_stage == "development": + # CI is the authoritative gate for development -> review. + # On red CI: notify, then bounce the task back to the developer (capped retries), + # symmetric to the review REQUEST_CHANGES path. + notify_qg_failure(task_id, current_stage, "check_ci_green", f"Gitea CI failed on branch '{branch}'") + conn = get_db() + retry_count = conn.execute( + "SELECT COUNT(*) as cnt FROM agent_runs WHERE task_id = ? AND agent = 'developer'", + (task_id,), + ).fetchone()["cnt"] + conn.close() + if retry_count < MAX_DEV_RETRIES: + # task already on 'development' — no stage change needed, just relaunch developer + try: + task_desc = ( + f"Work item: {work_item_id}\nRepo: {repo_name}\nBranch: {branch}\n" + f"Stage: development\nNote: CI failed, fix and re-push (attempt {retry_count + 1}/{MAX_DEV_RETRIES})" + ) + job_id = enqueue_job("developer", repo_name, task_desc, task_id=task_id) + logger.info(f"Task {task_id}: CI failed, enqueued developer (attempt {retry_count + 1}, job_id={job_id})") + except Exception as e: + notify_error(task_id, f"Failed to relaunch developer after CI failure: {e}") + else: + notify_error(task_id, f"Max developer retries ({MAX_DEV_RETRIES}) reached after CI failure, escalating") + logger.error(f"Task {task_id}: max retries reached after CI failure, needs manual intervention") +``` +**Ключевые отличия от review-ветки (учти!):** +- Задача УЖЕ в `development` → НЕ зови `update_task_stage`/`notify_stage_change` (в review был переход review→development, здесь перехода нет). +- branch берётся из переменной `branch` (не `head_branch`). +- Сохрани `notify_qg_failure` ПЕРЕД retry (чтобы Слава видел и провал, и факт ретрая). + +## Ограничения +- 🚫 НЕ трогай: review-retry ветку (образец оставь как есть), check_ci_green success-ветку, HMAC/project-filter, stages.py, qg/checks.py, stage_engine, nginx, openclaw.json, .env, deploy, очередь (enqueue_job не меняй), PLANE_STATES, conftest.py, status-only, gitea_public_url. +- 🚫 retry_count считается по ВСЕМ agent_runs developer'а задачи — это ОБЩИЙ лимит на developer (review-retry + CI-retry вместе ≤ MAX_DEV_RETRIES). Так и надо — общий потолок на доработки developer'а. НЕ заводи отдельный счётчик. +- Conventional Commits, один коммит: `fix(ci): bounce task back to developer on red CI (capped retries)`. + +## Тесты (контейнер) +`IMG=$(docker inspect orchestrator --format '{{.Config.Image}}'); docker run --rm -v /home/slin/repos/orchestrator:/code -w /code --entrypoint python3 $IMG -m pytest tests/ -q` +- Добавь/обнови в tests/test_webhooks.py (рядом с `test_gitea_ci_failure_on_development_notifies_qg_failure`): + - CI failure на development при retry_count < 3 → enqueue_job("developer",...) ВЫЗВАН + notify_qg_failure вызван, stage остаётся "development". + - CI failure при retry_count >= 3 → enqueue_job НЕ вызван, notify_error («Max developer retries... after CI failure») вызван. + - (мокай get_db/agent_runs count и enqueue_job/notify_*). Эти тесты — pure-logic, НЕ через webhook-POST (чтобы не упереться в 401 HMAC-барьер baseline-9). +- Baseline после PR #17: **215 passed + 10 failed** (9 baseline HMAC/401 + 1 webhook-POST тест на том же 401 — off-limits, не «чинить»). Итог passed ≥215 (+ твои новые pure-logic тесты). НЕ урони passed; новые webhook-тесты делай pure-logic (вызов handle_ci_status напрямую с моками), НЕ через TestClient POST. + +## Отчёт +- НЕ деплоить, НЕ мержить (мерж + боевой прогон делает ассистент: задеплою и дёрну CI-failure на ET-011 → должно вернуть developer'а, он починит 10× E402 в src/api/main.py и перезапушит → зелёный CI → development→review автономно). +- В отчёте: точный старый/новый код CI-failure ветки, новые тесты с пояснением, полный вывод pytest (счётчик). Маленькая правка — не раздувай. Один исполнитель.