auto-sync: 2026-06-07 23:50:01

This commit is contained in:
Stream
2026-06-07 23:50:01 +03:00
parent 108c93b99e
commit eb53ce5bc2

View File

@@ -0,0 +1,133 @@
# Глубокий аудит ядра орка под новую статусную модель
Дата: 2026-06-07 · Автор: Стрим · Уровень: ЯДРО (stage_engine/webhooks/plane_sync/stages)
## 0. Метод
Прочитан реальный код прода: `src/stages.py`, `src/stage_engine.py`, `src/webhooks/plane.py`,
`src/plane_sync.py`, `src/reconciler.py`. Ниже — что есть, какие зазоры с моделью, и что
доработать. Каждый пункт — с привязкой к коду.
## 1. КРИТИЧЕСКОЕ различие: «стадия конвейера» ≠ «Plane-статус»
Это два РАЗНЫХ слоя, их легко спутать и сломать.
### Слой A — STAGE_TRANSITIONS (`stages.py`) — машина СТАДИЙ (internal)
```
created → analysis → architecture → development → review → testing →
deploy-staging → deploy → done
```
Каждая стадия: `{next, agent, qg}`. Это движок: какой агент запускается, какой QG-гейт
проходится. **Здесь НЕТ Awaiting Deploy / Deploying / Monitoring** — это внутренние стадии,
а наши новые статусы — отображение на доске Plane.
### Слой B — Plane-статусы (`plane_sync.py`) — ИНДИКАЦИЯ на доске
`_STAGE_TO_STATE_KEY` маппит стадию → Plane-статус. Сейчас:
- analysis → in_progress, deploy → in_progress (ПЕРЕГРУЗ), остальные → одноимённые.
### ⚠️ Вывод
Новая модель меняет в основном **слой B (Plane-индикацию)** + точки, где Phase A/B/C ставят
статусы вручную. **STAGE_TRANSITIONS трогать минимально** (риск регресса всего конвейера).
Новые статусы — это переименования/добавления в маппинге + ручных set_issue_*.
## 2. Маппинг новой модели на стадии (что куда)
| Стадия (слой A) | Сейчас Plane | НАДО (новая модель) | Как ставится |
|---|---|---|---|
| (человек жмёт старт) | In Progress | **To Analyse** (вход-триггер, переименование In Progress) | человек |
| analysis | In Progress | **Analysis** (новый выходной) | орк при старте analyst |
| analysis → approve-pending | In Review | In Review (без изменений) | `_handle_analysis_approved_flow` |
| analysis → вопросы | Needs Input | Needs Input (уже есть!) | `set_issue_needs_input` (01-questions.md) |
| architecture | Architecture | Architecture | stage_to_state |
| development | Development | Development | stage_to_state |
| review | Review | **Code-Review** (переименование) | stage_to_state |
| testing | Testing | Testing | stage_to_state |
| deploy-staging → Phase A | In Review | **Awaiting Deploy** | `_handle_self_deploy_phase_a` (стр 1012) |
| deploy → Phase B старт | (In Progress) | **Deploying** | `_handle_self_deploy_phase_b` |
| deploy → Phase C health-OK | Done | **Monitoring after Deploy** | deploy-finalizer |
| post-deploy окно closed HEALTHY | (Done) | **Done** | post-deploy-monitor (ORCH-21) |
| любой фейл деплоя/окна | Blocked | **Blocked** | set_issue_blocked |
## 3. Needs Input — ГЛАВНЫЙ запрос Славы. Что есть, чего нет.
### ✅ ЧТО УЖЕ РАБОТАЕТ (нашла в коде!)
`_handle_analysis_approved_flow` (stage_engine.py ~502-549):
- analyst пишет файл `docs/work-items/<WI>/01-questions.md` → орк видит его
`set_issue_needs_input(work_item_id)` + коммент «❓ Analyst нуждается в уточнении: ...».
- **Возврат:** человек отвечает в Plane → переводит issue обратно в **In Progress**
`handle_status_start` видит существующий task + idle-агента → **relaunch analyst** (он
читает свежие комменты). Механизм статус-driven.
### ❌ ЧЕГО НЕТ / зазоры
1. **Needs Input ТОЛЬКО у analyst — ПОДТВЕРЖДЕНО ГРЕПОМ (Слава поправил).**
`set_issue_needs_input` вызывается РОВНО в ОДНОМ месте во всём src/: `stage_engine.py:538`
внутри `_handle_analysis_approved_flow`, у которой ЖЁСТКИЙ гард:
`if not (agent == "analyst" and work_item_id): return`. Для любого НЕ-аналитика — просто return.
`01-questions.md` захардкожен под analysis (префикс 01-), читается только там.
=> architect/developer/reviewer/tester **ФИЗИЧЕСКИ НЕ МОГУТ** уйти в Needs Input. Механизма НЕТ.
«И Т.Д.» = ПОСТРОИТЬ С НУЛЯ universal-механизм для всех стадий — ПОЛНОЦЕННАЯ фича (D6).
2. **Возврат завязан на `In Progress`** (`handle_status_start`). Если переименуем In Progress
→ To Analyse, **сломается возврат из Needs Input** (webhook слушает in_progress UUID).
⚠️ ГРАБЛИ: либо возврат тоже на To Analyse, либо оставить отдельный «Resume»-триггер.
3. **Нет матрицы разрешённых переходов** (вопрос Славы про «только доступные значения»).
Plane не умеет (404 на transitions/). Нужен guard в `handle_issue_updated`.
## 4. Доработки ядра (полный список с приоритетом)
### P0 — без этого модель не работает
- **D1.** `plane_sync.py`: добавить ключи/маппинг `to_analyse, analysis, code_review,
awaiting_deploy, deploying, monitoring` в `_DEFAULT_STATES` + `_PLANE_NAME_TO_KEY` +
обновить `_STAGE_TO_STATE_KEY` (analysis→analysis, review→code_review, deploy→deploying).
Хелперы `set_issue_<status>`.
- **D2.** `webhooks/plane.py` `handle_status_start`: триггер старта конвейера и возврата из
Needs Input привязать к **To Analyse** (новый вход), НЕ к In Progress. Учесть fork
«старт vs возврат» (как сейчас по active-job).
- **D3.** Phase A (`_handle_self_deploy_phase_a`): `set_issue_in_review` → `set_issue_awaiting_deploy`.
- **D4.** Phase B: при старте detached-деплоя ставить **Deploying**.
- **D5.** Phase C / post-deploy: health-OK → **Monitoring after Deploy** (не Done); окно
HEALTHY closed → **Done**; UNHEALTHY → **Blocked**.
### P1 — то, что Слава просит «и т.д.» (расширение Needs Input)
- **D6 (ПОЛНОЦЕННАЯ ФИЧА, НЕ детект).** Universal «агент просит уточнение» для ВСЕХ стадий:
(1) **договор сигнала**: агент пишет verdict-маркер `status: NEEDS_INPUT` в своём
артефакте ИЛИ `NN-questions.md` — единый контракт для всех;
(2) **детект для каждого агента** в advance_stage: сейчас ветка жёстко analyst-only
(`if not (agent==analyst)`), надо вынести в общий pre-QG чек: NEEDS_INPUT →
set_issue_needs_input + коммент + Telegram, НЕ запускать QG/advance;
(3) **resume**: возврат relaunch'ит ТЕКУЩУЮ стадию. `handle_status_start` уже умеет
relaunch по current_stage (переиспользуемо), НО триггерится только через In Progress —
перепривязать на resume-триггер (D7).
**Детект-сторона + контракт сигнала — ПОЛНОСТЬЮ НОВЫЕ.** + правка промптов всех агентов
(научить писать NEEDS_INPUT когда застряли без ввода).
- **D7.** Чёткий «Resume» semantics: из Needs Input/Blocked человек возвращает задачу — какой
статус-триггер relaunch'ит текущую стадию? Предложение: отдельный вход **To Analyse**
только для старта; для resume — возврат в статус ТЕКУЩЕЙ стадии ИЛИ спец-триггер. РЕШИТЬ.
### P2 — guard переходов (вопрос Славы про «только доступные»)
- **D8.** `ALLOWED_TRANSITIONS` матрица (из нашей схемы) в коде. В `handle_issue_updated`:
входящий человеческий переход проверяется по матрице {current_stage/status → allowed[]}.
Недопустимый → revert статуса назад + коммент «Недопустимый переход. Разрешено: [...]».
Это server-side state-machine вместо UI-ограничения (Plane его не умеет).
## 5. Грабли/риски (НЕ сломать прод)
- **R1.** Переименование In Progress ломает 3 вещи: старт конвейера, возврат из Needs Input,
и `_STAGE_TO_STATE_KEY[deploy]=in_progress`. Все три перепривязать атомарно.
- **R2.** `reconciler.py` Guard 2 пропускает blocked/needs_input — при новых статусах
проверить, что reconciler не «оживляет» Awaiting Deploy/Monitoring (это активные ожидания,
не зависшие). Возможно добавить их в skip-список.
- **R3.** fail-closed везде: нет статуса в проекте (enduro/fallback) → не падать, вести себя
как раньше (как сделали в ORCH-59 AC-7).
- **R4.** STAGE_TRANSITIONS НЕ менять (инвариант AC-9 из ORCH-59). Новые статусы — только
индикация + ручные set_issue_*, не новые стадии конвейера.
- **R5.** Идемпотентность: повторный webhook одного статуса → no-op (как Phase B marker).
## 6. Объём → 2 work item (предложение)
- **WI-1 «Статусная модель + индикация»** (D1-D5): переименования/новые статусы, Phase A/B/C
ставят правильные статусы, Monitoring-этап перед Done. Средний размер, хирургично.
- **WI-2 «Needs Input для всех стадий + guard переходов»** (D6-D8): расширение clarification-
механизма + server-side state-machine. Содержательный, можно отдельным PR после WI-1.
+ Задача гигиены Plane (создать/переименовать статусы через API) — инфра-предусловие, делаю я.
## 7. Открытые решения Славе
1. **Resume из Needs Input:** какой триггер relaunch'ит текущую стадию? (а) возврат в статус
текущей стадии; (б) отдельный «Resume»-статус; (в) To Analyse только для analysis, для
остальных — статус стадии. Влияет на D2/D7.
2. **Один большой WI или два** (модель + needs-input/guard)?
3. **Guard переходов сейчас или потом** (P2 можно отложить)?