# 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 (счётчик). Маленькая правка — не раздувай. Один исполнитель.