9.2 KiB
DEV TASK — БАГ 8: deploy не имеет QG-гейта → провалившийся деплой уходит в done
Проект: orchestrator | Сервер: slin@82.22.50.71 | Репо: /home/slin/repos/orchestrator | Контейнер: orchestrator (8500)
Ветка: fix/deploy-verdict-gate из свежего main (git checkout main && git pull && git checkout -b fix/deploy-verdict-gate). main = a0621b9 (PR #18, перед стартом сделай git pull — мог уехать).
⚠️ ГРАБЛЯ push (ORCH-7): после push git log origin/main..origin/fix/deploy-verdict-gate ДОЛЖЕН показать коммит ДО отчёта «PR готов». Токен Gitea: docker exec orchestrator printenv ORCH_GITEA_TOKEN.
Контекст / дыра (баг 8, подтверждён боевым прогоном ET-011)
deployer (run 71) РЕАЛЬНО провалил деплой (deploy-hook упал на permission denied, контейнер не пересобрался), честно написал Status: FAILED в 14-deploy-log.md и в текстовом отчёте «Exiting with non-zero». НО задача всё равно уехала в done, и фича оказалась нерабочей на проде.
Корень — ДВА места:
src/stages.py:19— у стадии deploy"qg": None. Нет проверки выхода вообще (у всех других стадий QG есть). deploy → done безусловно.exit_codeвagent_runs= код процесса LLM-агента (Claude CLI), который ВСЕГДА 0 при успешной LLM-сессии — не отражает реальный результат деплоя. Существующая защитаlauncher.py:475 if exit_code != 0 and agent == "deployer"поэтому НЕ срабатывает.
Решение (по образцу check_reviewer_verdict, S-5): завести QG check_deploy_status, который читает машинно-читаемое поле из 14-deploy-log.md (как reviewer читает verdict: из 12-review.md). Привязать к стадии deploy. deployer-промпт обязать писать это поле во frontmatter.
Правка 1 — новый QG check_deploy_status в src/qg/checks.py
Добавь функцию ПО ОБРАЗЦУ check_reviewer_verdict (checks.py:210). Образец читает frontmatter verdict: из 12-review.md. Сделай аналог для 14-deploy-log.md, поле deploy_status::
def check_deploy_status(repo: str, work_item_id: str, branch: str | None = None) -> tuple[bool, str]:
"""
БАГ 8 fix: gate the deploy -> done transition on the deployer's machine-readable
verdict in 14-deploy-log.md frontmatter, NOT on the LLM process exit code
(which is always 0 on a successful agent session even when the deploy failed).
Mirrors check_reviewer_verdict (S-5): reads ONLY `deploy_status:` from YAML
frontmatter. Returns:
(True, ...) -> deploy_status: SUCCESS
(False, ...) -> deploy_status: FAILED, missing field, or no frontmatter
"""
import yaml
repo_path = _repo_path(repo, branch)
log_path = os.path.join(repo_path, f"docs/work-items/{work_item_id}/14-deploy-log.md")
if not os.path.isfile(log_path):
return False, "Deploy log not found (14-deploy-log.md)"
try:
with open(log_path, "r") as f:
content = f.read()
status = None
if content.startswith("---"):
parts = content.split("---", 2)
if len(parts) >= 3:
try:
fm = yaml.safe_load(parts[1]) or {}
except yaml.YAMLError as e:
return False, f"Invalid YAML frontmatter in deploy log: {e}"
status = str(fm.get("deploy_status", "")).upper().strip()
if status == "SUCCESS":
return True, "Deploy status: SUCCESS"
if status == "FAILED":
return False, "Deploy status: FAILED"
return False, f"No machine-readable deploy_status in frontmatter (got: {status!r})"
except OSError as e:
return False, f"Error reading deploy log: {e}"
И зарегистрируй в QG_CHECKS (рядом с остальными, ~стр.286): "check_deploy_status": check_deploy_status,.
Правка 2 — привязать QG к стадии deploy в src/stages.py:19
БЫЛО: "deploy": {"next": "done", "agent": None, "qg": None},
СТАЛО: "deploy": {"next": "done", "agent": None, "qg": "check_deploy_status"},
(agent остаётся None — deployer запускается на ВХОДЕ стадии deploy из testing, как сейчас; check_deploy_status гейтит ВЫХОД deploy→done.)
Правка 3 — где вызывается advance deploy→done: убедиться, что QG check_deploy_status проверяется
Найди в src/stage_engine.py / src/agents/launcher.py, где после deployer'а делается переход deploy → done (auto-advance). Сейчас он, видимо, безусловный (qg=None). Нужно: ПЕРЕД update_task_stage(task_id, "done") вызвать check_deploy_status(repo, work_item_id, branch):
- если True → done (как сейчас) + notify;
- если False → НЕ done. Вместо этого:
update_task_stage(task_id, "development")+set_issue_blocked(work_item_id)+notify_qg_failure(task_id, "deploy", "check_deploy_status", reason)+ Telegram «Deploy FAILED, rolled back to development». (Это дублирует существующий launcher.py:475-495 блок deployer-failure — можно переиспользовать ту же логику, главное чтобы она реально вызывалась на основе ВЕРДИКТА, а не exit_code.) - ⚠️ Точное место advance найди сам (grep
deploy.*done,update_task_stage.*done,next_stage == "done",_try_advance). НЕ ломай advance для других стадий.
Правка 4 — deployer-промпт .openclaw/agents/deployer.md (в репо enduro-trails, НЕ в orchestrator!)
⚠️ Этот файл — в целевом репо admin/enduro-trails, ветка отдельная. СНАЧАЛА уточни у ассистента нужно ли его трогать в этой задаче или отдельным PR в enduro-trails. Если ассистент скажет править здесь — добавь в инструкцию deployer'а требование писать YAML-frontmatter в начало 14-deploy-log.md:
---
deploy_status: SUCCESS # или FAILED
version: v0.0.3
---
По умолчанию (если ассистент не уточнил) — НЕ трогай enduro-trails, только orchestrator. Отметь в отчёте, что deployer-промпт enduro-trails требует отдельной правки (добавить frontmatter deploy_status).
Ограничения
- 🚫 НЕ трогай: другие QG-функции (check_ci_green, check_reviewer_verdict, check_tests_passed, check_tests_local, check_architecture_done, check_analysis_*), review/CI-retry ветки, HMAC/project-filter, nginx, openclaw.json, .env, очередь, PLANE_STATES, conftest.py, status-only, gitea_public_url, стадии кроме deploy.
- 🚫 НЕ удаляй существующий launcher.py:475 блок (можешь переиспользовать его логику для вердикт-пути).
- Conventional Commits, один коммит:
fix(deploy): gate deploy->done on deployer verdict, not LLM exit code.
Тесты (контейнер)
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_qg.py + tests/test_stage_engine.py (pure-logic, БЕЗ TestClient POST — иначе 401 HMAC baseline):
check_deploy_status: SUCCESS frontmatter → (True,...); FAILED → (False,...); нет файла → (False,...); нет поля → (False,...). (создай tmp 14-deploy-log.md с фронтматтером, мокни _repo_path/путь).get_qg_for_stage("deploy") == "check_deploy_status".- advance: deploy с deploy_status=FAILED → задача НЕ в done (откат в development), deploy_status=SUCCESS → done.
- Baseline после PR #18: 217 passed + 10 failed (10 off-limits: 9 HMAC/401 + 1 webhook-POST). Итог passed ≥217 + новые. НЕ урони passed.
Отчёт
- НЕ деплоить, НЕ мержить (мерж + боевой прогон делает ассистент).
- В отчёте: точный код check_deploy_status, правка stages.py, ГДЕ именно нашёл advance deploy→done и как туда вписал вердикт-проверку, новые тесты, полный вывод pytest. Отметь статус правки 4 (deployer-промпт enduro-trails). Один исполнитель.