auto-sync: 2026-06-04 02:40:01
This commit is contained in:
95
tasks/orchestrator/DEV_TASK_DEPLOY_VERDICT.md
Normal file
95
tasks/orchestrator/DEV_TASK_DEPLOY_VERDICT.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 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`**, и фича оказалась нерабочей на проде.
|
||||
|
||||
**Корень — ДВА места:**
|
||||
1. `src/stages.py:19` — у стадии deploy `"qg": None`. **Нет проверки выхода вообще** (у всех других стадий QG есть). deploy → done безусловно.
|
||||
2. `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:`:
|
||||
```python
|
||||
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). Один исполнитель.
|
||||
Reference in New Issue
Block a user