# Глубокий аудит ядра орка под новую статусную модель Дата: 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//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_`. - **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 ОТМЕНЁН СЛАВОЙ (2026-06-07): Needs Input НЕ расширяем на других агентов! Needs Input остаётся ТОЛЬКО у аналитика (как сейчас). D6/D7 НИЖЕ — ВЫЧЕРКНУТЫ, НЕ делаем. ### ~~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. Объём → work items (обновлено после отмены D6/D7) - **WI-1 «Статусная модель + индикация»** (D1-D5): переименования/новые статусы, Phase A/B/C ставят правильные статусы, Monitoring-этап перед Done. Средний размер, хирургично. - **WI-2 «guard переходов»** (D8 только): server-side state-machine ALLOWED_TRANSITIONS + revert недопустимого перехода. **D6/D7 (universal Needs Input) — ОТМЕНЕНЫ Славой.** + Задача гигиены Plane (создать/переименовать статусы через API) — инфра-предусловие, делаю я. ⚠️ НО: Needs Input ОСТАЁТСЯ в жизненном цикле (только analyst). Грабли R1 остаются: переименование In Progress → To Analyse ломает возврат analyst из Needs Input (сейчас возврат = In Progress relaunch). В WI-1 ОБЯЗАТЕЛЬНО перепривязать этот возврат на To Analyse (или оставить In Progress как resume-триггер). ## 7. Открытые решения Славе 1. **Resume из Needs Input:** какой триггер relaunch'ит текущую стадию? (а) возврат в статус текущей стадии; (б) отдельный «Resume»-статус; (в) To Analyse только для analysis, для остальных — статус стадии. Влияет на D2/D7. 2. **Один большой WI или два** (модель + needs-input/guard)? 3. **Guard переходов сейчас или потом** (P2 можно отложить)?