auto-sync: 2026-06-03 22:10:01

This commit is contained in:
Stream
2026-06-03 22:10:01 +03:00
parent f0bb792369
commit a61a23d6ac
2 changed files with 84 additions and 0 deletions

View File

@@ -146,3 +146,14 @@ approved=a519a341-dada-4a91-8910-7604f82b79c5 rejected=ba958f3c-5db5-461d-
**Фикс (для Dev):** в handle_comment игнорировать комменты, автором которых является бот/агент (analyst/architect/... или служебный actor). Проверять author/actor вебхука: если это наш сервисный аккаунт или один из агентов — return (no action). Только комменты РЕАЛЬНОГО человека (Слава) должны триггерить approved/rejected/answer-to-questions.
**NB:** PLANE_STATES в Enduro Trails корректен — один In Progress (b873d9eb). «5 In Progress» ранее — статусы ДРУГИХ проектов, ложная тревога.
**Состояние:** #6 сейчас In Progress (откачен), task 26 stage=analysis, артефакты в feature/ET-011-untitled готовы. После фикса бага3 — повторить и убедиться что In Review держится.
---
## РЕШЕНО: чистая статусная модель управления конвейером (03.06, подтверждено Славой)
Слава управляет конвейером ТОЛЬКО статусами. Комменты НИКОГДА не триггерят переходы. Финальная модель:
- **Одобрение** → Слава ставит статус **Approved** → оркестратор двигает на след. стадию (сам ставит статус стадии).
- **Отклонение** → Слава СНАЧАЛА пишет коммент с причиной, ПОТОМ ставит **Rejected** → оркестратор откатывает + читает причину из последнего коммента.
- **Ответ на вопросы analyst** (задача в Needs Input) → Слава пишет коммент(ы), ПОТОМ САМ возвращает статус в **In Progress** → оркестратор перезапускает агента текущей стадии (читает комменты). Триггер = СТАТУС In Progress, не коммент.
- Комментный механизм (:approved:/:rejected:/answer-by-comment) — ВЫПИЛИВАЕТСЯ полностью.
ТЗ: tasks/orchestrator/DEV_TASK_STATUS_ONLY_VERDICT.md
Ключевая правка vs прошлого PR: handle_status_start теперь должен ПЕРЕЗАПУСКАТЬ агента при возврате In Progress из Needs Input (не просто idempotent-skip), отличая от защиты-дублей через running-job/prev-status.

View File

@@ -0,0 +1,73 @@
# DEV TASK — Вердикт ТОЛЬКО статусами (выпил комментного approve + фикс эхо-самоудара)
**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator (8500)
**Ветка:** `fix/status-only-verdict` из свежего main (`git checkout main && git pull && git checkout -b fix/status-only-verdict`).
⚠️ **ГРАБЛЯ push (ORCH-7):** после push `git log origin/main..origin/fix/status-only-verdict` ДОЛЖЕН показать коммиты ДО отчёта «PR готов».
Токен Gitea для PR: `docker exec orchestrator printenv ORCH_GITEA_TOKEN`.
## Контекст и ПРАВИЛЬНАЯ модель (источник правды — решение Славы, подтверждено 03.06)
Слава управляет конвейером **ТОЛЬКО СТАТУСАМИ**. **Комменты НИКОГДА не триггерят переходы.** Точная модель:
- **Одобрение стадии** → Слава ставит статус **Approved** → оркестратор двигает на следующую стадию (сам выставляет статус следующей стадии).
- **Отклонение** → Слава **сначала пишет коммент с причиной**, **потом** ставит статус **Rejected** → оркестратор откатывает + читает причину из последнего коммента.
- **Ответ на вопросы аналитика** (задача в **Needs Input**) → Слава пишет коммент(ы) с ответами, **потом сам возвращает статус в In Progress** → оркестратор перезапускает агента текущей стадии (analyst читает комменты из Plane). Триггер — СТАТУС In Progress, НЕ коммент.
**КОММЕНТНЫЙ МЕХАНИЗМ УПРАВЛЕНИЯ ВЫПИЛИВАЕТСЯ ПОЛНОСТЬЮ.** `:approved:`/`:rejected:` в комментах + «answer-to-questions по комменту» — всё убрать. Они создавали баг 3 (эхо-самоудар): analyst постит свой коммент «жду approved» → handle_comment ловит свой же коммент → откатывает In Review → In Progress.
## БАГ 3 (root) — самоудар + лишний PATCH
**Симптом:** analyst довёл задачу до **In Review** корректно, через 0.1с статус откатился в **In Progress**, уведомления нет.
**Две причины:**
### Причина A — handle_comment управляет конвейером (выпилить весь механизм)
`src/webhooks/plane.py`, `handle_comment` (~397-490): **весь комментный механизм управления выпиливается.**
- Ветка `if ":approved:" in comment_body:` (~427-431) → **ВЫПИЛИТЬ.**
- Ветка `if ":rejected:" in comment_body:` (~420-424) → **ВЫПИЛИТЬ.**
- Ветка `if current_stage == "analysis":` (answer-to-questions по комменту, ~433-490) → **ВЫПИЛИТЬ.** Ответ на вопросы теперь идёт через возврат СТАТУСА в In Progress (см. ниже handle_status_start), не через коммент.
- Итого `handle_comment` либо удаляется целиком (и роутер `comment.created` → no-op/лог), либо сводится к чистому логированию без side-effect. Реши сам — главное: **ни один коммент не меняет статус и не запускает агентов.** Сохрани QG-лог если он информативен, но без действий.
### Причина A2 — возврат в In Progress из Needs Input должен ПЕРЕЗАПУСКАТЬ агента
`handle_status_start` (~161-172) сейчас идемпотентен: `if existing task → not restarting`. Это правильно для защиты от дублей, НО ломает кейс «ответ на вопросы»: Слава вернул задачу из **Needs Input** в **In Progress**, ожидая что analyst перезапустится. **Надо:** если task существует И задача «ждала» (предыдущий статус был needs_input / стадия analysis в ожидании) → **перезапустить агента текущей стадии** (enqueue), чтобы он прочитал новые комменты Славы.
- Как отличить «обычный повторный In Progress» (защита от дублей) от «ответ на вопросы»: проверь предыдущий Plane-статус issue (был needs_input) ИЛИ флаг ожидания в БД. Определи самый надёжный способ на проде (есть ли в БД поле статуса/ожидания; или GET issue даёт прошлый статус через activity). Если нет running job по task И стадия активная (analysis) → перезапуск analyst с пометкой «прочитай свежие комменты Славы и доработай артефакты». Защита от дублей (нет двойного запуска при двойном webhook) — сохрани через проверку running job в очереди.
- ⚠️ Если надёжно определить «был needs_input» сложно — отчитайся с вариантом реализации ДО мержа (это ключевая развилка). Минимально: рестарт если стадия analysis И нет активной/running job для task.
### Причина B — handle_verdict(Approved) сам сбивает статус в In Progress
`handle_verdict` (~170-200), ветка `if approved:` (~185):
```python
set_issue_in_progress(work_item_id) # ← УБРАТЬ
await _try_advance_stage(...)
```
`set_issue_in_progress` здесь лишний — он откатывает статус назад перед advance. `_try_advance_stage` сам выставит статус следующей стадии (architecture/development/...). **Убрать `set_issue_in_progress` из ветки approved.** Проверь, что `_try_advance_stage` действительно сам ставит статус стадии (если НЕ ставит — тогда вместо in_progress выставить статус нужной стадии, НЕ in_progress).
## REJECT: причина из коммента в handle_verdict
Сейчас `handle_verdict(approved=False)` передаёт фиксированную заглушку причины. **Надо:** при Rejected-статусе дотянуть **последний коммент** issue из Plane API (GET comments, взять самый свежий от человека) как `reason` и передать в `_rollback_stage`. Слава пишет причину отдельным комментом ПЕРЕД/вместе со сменой статуса.
- GET comments endpoint: `{PLANE_BASE}/workspaces/{WORKSPACE}/projects/{pid}/issues/{issue_id}/comments/` (тот же PLANE_HEADERS). Взять последний по created_at, стрипнуть HTML.
- Если коммента нет → заглушка «Rejected via status, no reason comment».
## Ограничения
- 🚫 НЕ трогай: nginx/openclaw.json/.env-секреты/deploy-хук/HMAC/verify_plane_signature/очередь(queue_worker/jobs кроме чтения)/webhook URL в БД Plane/per-agent authorship/PLANE_STATES UUID/M-6 derive/uniqueness-guard(из PR #11)/fetch_issue_description(PR #11)/conftest.py.
- Conventional Commits, отдельные коммиты: `fix(webhook): remove comment-based approve, keep status-only verdict`, `fix(webhook): drop redundant in_progress reset on Approved`, `feat(webhook): pull reject reason from latest comment`.
- ⚠️ Старые тесты `test_webhooks.py::test_plane_approved_advances_stage`, `test_plane_rejected_rolls_back`, `test_verdict_status.py` проверяют КОММЕНТНЫЙ `:approved:`/`:rejected:`. Раз комментный механизм выпиливается — **перепиши их под статусную модель** (approve/reject через handle_verdict по СТАТУСУ). Тесты, проверяющие что коммент `:approved:` двигает стадию — перепиши на обратное утверждение: «коммент НЕ триггерит переход».
## Тесты (контейнер)
`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`
Новые/обновлённые:
- `test_inreview_comment_does_not_revert`: задача в In Review, прилетает ЛЮБОЙ коммент → статус НЕ меняется, агенты НЕ запускаются. **Главный тест бага 3.**
- `test_any_comment_no_pipeline_action`: коммент с `:approved:`/`:rejected:`/произвольным текстом → никаких side-effect (статус/очередь не трогаются).
- `test_approved_status_advances_without_inprogress_reset`: Approved-статус → advance, БЕЗ промежуточного PATCH в in_progress.
- `test_rejected_status_pulls_reason_from_comment`: Rejected-статус + последний коммент с причиной (мок GET comments) → reason передан в rollback.
- `test_inprogress_from_needs_input_relaunches_analyst`: задача в analysis/needs_input → возврат статуса в In Progress → analyst перезапущен (enqueue), читает комменты. + защита: двойной In Progress webhook не плодит двойной запуск.
- Перепиши `test_plane_approved_advances_stage`/`test_plane_rejected_rolls_back`/`test_verdict_status` под статусную модель.
## Проверка (ассистент проверит вживую боевым прогоном)
| # | Что | Критерий |
|---|-----|----------|
| 1 | In Review держится | analyst → In Review, статус НЕ откатывается, уведомление с просьбой одобрить приходит |
| 2 | Approved-статус | Слава ставит Approved → задача уходит в Architecture, БЕЗ мелькания In Progress |
| 3 | Rejected-статус+коммент | Слава ставит Rejected + коммент причины → откат, analyst получает причину |
| 4 | коммент не рулит | `:approved:` в комменте больше НЕ двигает задачу |
| 5 | тесты | baseline не сломан + новые passed |
| 6 | git | PR в main, remote содержит коммиты |
## Отчёт
- НЕ деплоить, НЕ мержить (мерж — ассистент после живой проверки + боевого прогона #6).
- В отчёте: что именно выпилил (файл:строки), **как реализовал перезапуск агента при возврате In Progress из Needs Input** (как отличаешь от защиты-дублей — ключевая развилка, опиши подробно), как тянешь reject-reason, какие старые тесты переписал и как, вывод релевантных тестов. Если что-то в коде разошлось с этим ТЗ (напр. _try_advance_stage сам НЕ ставит статус стадии, или нет способа узнать прошлый статус) — **отчитайся ДО мержа, не выдумывай**. Один исполнитель.