12 KiB
Глубокий аудит ядра орка под новую статусную модель
Дата: 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.
❌ ЧЕГО НЕТ / зазоры
- 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). - Возврат завязан на
In Progress(handle_status_start). Если переименуем In Progress → To Analyse, сломается возврат из Needs Input (webhook слушает in_progress UUID). ⚠️ ГРАБЛИ: либо возврат тоже на To Analyse, либо оставить отдельный «Resume»-триггер. - Нет матрицы разрешённых переходов (вопрос Славы про «только доступные значения»).
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.pyhandle_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.pyGuard 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. Открытые решения Славе
- Resume из Needs Input: какой триггер relaunch'ит текущую стадию? (а) возврат в статус текущей стадии; (б) отдельный «Resume»-статус; (в) To Analyse только для analysis, для остальных — статус стадии. Влияет на D2/D7.
- Один большой WI или два (модель + needs-input/guard)?
- Guard переходов сейчас или потом (P2 можно отложить)?