Files
wiki/tasks/orchestrator/reports/dev-2026-06-03-deploy-verdict-gate.md
2026-06-04 02:50:01 +03:00

7.9 KiB
Raw Blame History

Dev Report: БАГ 8 — deploy verdict gate

Дата: 2026-06-03 Статус: DONE

Задача

Завести QG check_deploy_status по образцу check_reviewer_verdict, гейтить deploy→done на машинном вердикте deployer'а (deploy_status: из 14-deploy-log.md), а не на exit_code LLM-процесса (всегда 0). PR в main, НЕ мержить/деплоить.

Сделано

  • Ветка fix/deploy-verdict-gate из свежего main (a0621b9, PR#18)
  • Правка 1: check_deploy_status + регистрация в QG_CHECKS
  • Правка 2: stages.py deploy qg None → check_deploy_status
  • Правка 3: вердикт-проверка в advance deploy→done
  • Тесты: +7 (test_qg) +3 (test_stage_engine), все зелёные
  • Commit + push, remote проверен (ORCH-7), PR #19
  • Правка 4: НЕ трогал (enduro-trails) — см. ниже

Изменённые файлы

  • src/qg/checks.py — новая функция check_deploy_status + запись в QG_CHECKS
  • src/stages.py — deploy "qg": None"qg": "check_deploy_status"
  • src/stage_engine.py — новая deployer-ветка в _handle_qg_failure_rollbacks
  • tests/test_qg.py — class TestCheckDeployStatus (7 тестов)
  • tests/test_stage_engine.py — class TestDeployVerdict (3 теста)

Правка 1 — точный код check_deploy_status (src/qg/checks.py)

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: "check_deploy_status": check_deploy_status, (после check_tests_local).

Правка 2 — stages.py

"deploy": {"next": "done", "agent": None, "qg": None}"deploy": {"next": "done", "agent": None, "qg": "check_deploy_status"}

Правка 3 — ГДЕ нашёл advance deploy→done и как вписал вердикт

Auto-advance унифицирован в src/stage_engine.py::advance_stage (ORCH-4/M-3 merge). launcher.py:_try_advance_stage (стр.648) — тонкий wrapper, который грузит task по (repo,branch) и вызывает advance_stage(current_stage=<db stage>, finished_agent=agent). Когда deployer завершается, current_stage="deploy", finished_agent="deployer".

В advance_stage QG уже выполняется ГЕНЕРИЧЕСКИ:

  • qg_name = get_qg_for_stage("deploy") теперь = "check_deploy_status";
  • _run_qg → ветка «everything else» → check_deploy_status(repo, work_item_id, branch) (сигнатура совпала);
  • SUCCESS (True) → блок Advance: update_task_stage(done)+notify (агент не запускается, agent=None);
  • FAILED (False) → вызывается _handle_qg_failure_rollbacks(...).

В _handle_qg_failure_rollbacks НЕ было ветки для deployer → ничего не происходило. Добавил новую ветку (после architect-ветки, в конце функции), триггер по ВЕРДИКТУ не exit_code:

    if agent == "deployer" and qg_name == "check_deploy_status":
        update_task_stage(task_id, "development")
        notify_stage_change(task_id, current_stage, "development")
        plane_notify_stage(work_item_id, current_stage, "development")
        result.rolled_back_to = "development"
        set_issue_blocked(work_item_id)
        notify_qg_failure(task_id, "deploy", "check_deploy_status", reason)
        plane_add_comment(work_item_id, "❌ Deploy FAILED (...). Rolled back ...", author="deployer")
        send_telegram("🚨 {wid}: Deploy FAILED (...). Rolled back to development. Needs fix.")
        result.alerted = True
        logger.error(...)

launcher.py:475 блок (exit_code-based deployer-failure) НЕ удалён — оставлен как был; он просто не срабатывает (exit_code=0), а реальный гейт теперь по вердикту. Advance других стадий не тронут.

Новые тесты

test_qg.py / TestCheckDeployStatus (через tmp 14-deploy-log.md + fixture с monkeypatch repos_dir):

  • SUCCESS frontmatter → True; FAILED → False; нет файла → False; нет поля → False; prose-only без frontmatter → False; get_qg_for_stage("deploy")=="check_deploy_status"; зарегистрирована в QG_CHECKS. test_stage_engine.py / TestDeployVerdict (pure-logic, БЕЗ TestClient POST):
  • FAILED вердикт → НЕ done, откат в development, alerted, set_issue_blocked, send_telegram;
  • нет лога → откат в development; SUCCESS → done, агент не запущен, jobs пусто.

Результат — полный вывод pytest (контейнер)

docker run --rm -v ... --entrypoint python3 orchestrator-orchestrator -m pytest tests/ -q10 failed, 227 passed

  • passed: 217 (baseline PR#18) + 10 новых = 227 ✓ (не уронил)
  • 10 failed — ровно baseline off-limits (9 HMAC/401 в test_webhooks + 1 webhook-POST test_plane_webhook_event_logged). Их не трогал. Целевой прогон новых: pytest TestCheckDeployStatus TestDeployVerdict -v → 10 passed.

Статус правки 4 (deployer-промпт enduro-trails)

НЕ трогал (по умолчанию — только orchestrator). Требует отдельной правки в репо admin/enduro-trails: .openclaw/agents/deployer.md обязать deployer'а писать YAML-frontmatter в начало 14-deploy-log.md:

---
deploy_status: SUCCESS   # или FAILED
version: vX.Y.Z
---

Без этого frontmatter check_deploy_status вернёт False (нет поля) → задача откатится в development. То есть гейт fail-safe, но для прохождения SUCCESS промпт enduro-trails надо обновить.

Push / PR (ORCH-7 проверен)

  • Коммит: e4a9c48 fix(deploy): gate deploy->done on deployer verdict, not LLM exit code
  • git log origin/main..origin/fix/deploy-verdict-gate → e4a9c48 (remote содержит коммит ✓)
  • PR #19: admin/orchestrator#19 (fix/deploy-verdict-gate → main)
  • НЕ мержил, НЕ деплоил — мерж + боевой прогон делает ассистент.