Files
orchestrator/docs/work-items/ORCH-020/02-trz.md
claude-bot babc475ec3
All checks were successful
CI / test (push) Successful in 1m15s
analyst(ET): auto-commit from analyst run_id=797
2026-06-17 20:59:30 +03:00

17 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used
work_item stage author_agent status created_at model_used
ORCH-020 analysis analyst ready-for-review 2026-06-17 claude-opus-4-8

02 — ТЗ (TRZ): ORCH-020 — Оценка задачи, запускаемая Plane-статусом «Оценка»

Work Item: ORCH-020 · Repo: orchestrator · Стадия: analysis

ТЗ описывает конкретные изменения к реализации, выведенные из BRD и фактического кода. Архитектурное обоснование/решения (выбор механизма оценки эвристика vs LLM vs гибрид, точные сигнатуры врезок, индексы, формулы маппинга, сглаживание массовой нагрузки, Plane-группа статуса «Оценка») — задача архитектора (06-adr).

1. Сводка изменения

Вводится новый операторский Plane-статус «Оценка» — триггер механизма оценки (по образцу action-статусов STOP/ORCH-090 и Confirm Deploy/ORCH-059). Перевод issue в «Оценка» (в т.ч. массово через Plane multi-select) запускает новый leaf-модуль оценки (src/estimator.py, never-raise), который прогнозирует стоимость / время / токены / сложность (story points {1,2,3,5,8}) на основе истории завершённых задач (агрегаты src/usage.py). Прогноз: (a) пишется в Plane-поле estimate_point, (b) публикуется Plane-комментом, (c) добавляется пунктом «Оценка» (время/токены/стоимость) в общую Telegram-карточку, (d) сохраняется в новой аддитивной таблице task_estimates (леджер прогноз↔факт, ключ work_item_id). По завершении оценки оркестратор возвращает issue в статус Backlog. По завершении самой задачи (переход в done) факт пишется в Plane-поле point. Пере-оценка — повтор перевода в «Оценка» (идемпотентно).

Инвариант (NFR-1/NFR-3): оценка — наблюдатель/продюсер, не Quality Gate и не переход стадии. STAGE_TRANSITIONS / QG_CHECKS / check_* / machine-verdict-ключи / схемы существующих таблиц — байт-в-байт; горячий путь resolve_agent_model/resolve_agent_effort/_spawn — не трогается. Статус «Оценка» не добавляет ребра в машину стадий.

2. Задействованные модули / пути

Путь Действие
src/plane_sync.py изменить — (1) _PLANE_NAME_TO_KEY += {"Оценка": "estimate"}; ключ estimate НЕ добавлять в _DEFAULT_STATES (fail-closed, как stop/confirm_deploy); (2) новые write-хелперы set_issue_estimate_point(work_item, value), set_issue_point(work_item, value), set_issue_backlog(work_item) (все через guard _guard_allows_write, ORCH-117); (3) read-хелпер текущих полей estimate_point/point. fail-safe при отсутствии estimate-конфига
src/webhooks/plane.py изменить — в handle_issue_updated добавить fail-closed ветку estimate_state = proj_states.get("estimate")handle_estimate(data, project_id) (распознаётся как отдельный жест, не алиасит stop/to_analyse/confirm_deploy/approved/rejected). Новый handle_estimate: резолв issue (pipeline-задачи может не быть), guard estimator.applies(repo), guard «нет активного job» (BR-T6), запуск оценки, затем set_issue_backlog
src/estimator.py создать — leaf: `estimate(work_item_id, issue
src/db.py изменить — аддитивная таблица task_estimates (CREATE TABLE IF NOT EXISTS в init_db()) + хелперы record_estimate/set_actual/get_estimate/estimates_snapshot; существующие таблицы/колонки не трогать
src/usage.py переиспользовать (read-only) — task_usage_summary/agent_cost_totals/тайминги для факта; при необходимости тонкий read-only агрегат «история похожих задач»
src/notifications.py изменить — пункт «Оценка» (время · токены · стоимость) в рендере общей карточки; never-raise, пустой прогноз → пункт опускается
src/main.py изменить — (опц.) POST /estimate?work_item=<id> / POST /estimate/backlog как программное удобство; read-only блок estimator в GET /queue
src/config.py изменить — флаги (см. §7)
tests/test_orch020_estimator.py создать — покрытие (см. 04-test-plan.yaml)

3. Функциональные требования

FR-T1 — Статус «Оценка» как триггер (BR-T1, BR-T5)

_PLANE_NAME_TO_KEY["Оценка"] = "estimate"; ключ estimate отсутствует в _DEFAULT_STATES. В handle_issue_updated — отдельная ветка: estimate_state = proj_states.get("estimate"); if estimate_state and new_state == estimate_state: await handle_estimate(...). Доска без статуса → estimate_state is None → ветка инертна (fail-closed, зеркало stop/confirm_deploy). Ветка не должна аннулировать/перехватывать STOP/to_analyse/confirm_deploy/approved/rejected (UUID «Оценка» отличен от всех; порядок ветки выбирает архитектор, инвариант — взаимоисключение жестов).

FR-T2 — Обработчик handle_estimate (BR-T1, BR-T6)

handle_estimate(data, project_id): резолвит plane_id/work_item_id; repo определяется по проекту. Guard-цепочка (все — no-op-with-log при невыполнении, never-raise):

  1. estimator.applies(repo) — kill-switch + скоуп (False → no-op);
  2. анти-disruption (BR-T6): если у issue есть pipeline-задача с активным job (has_active_job_for_task) → no-op + лог (не выдёргивать in-flight работу). Issue без задачи (бэклог) или с терминальной/idle-задачей → оценка допустима. Далее: estimator.estimate(...) → запись прогноза (FR-T3) → set_issue_backlog(work_item) (BR-T2). Контракт never-raise: любая ошибка логируется, вебхук-флоу не падает.

FR-T3 — Прогноз задачи (BR-1, BR-2, BR-3)

estimator.estimate(work_item_id, description|issue, repo) возвращает {forecast_tokens, forecast_seconds, forecast_cost_usd, story_points}, story_points ∈ {1,2,3,5,8}. База — история похожих завершённых задач (средние токены/время/стоимость из usage.py-агрегатов); пустая история → bootstrap-дефолт. Маппинг величин → bucket — чистая функция (пороги — 06-adr). never-raise: сбой → безопасный дефолт + warning.

FR-T4 — Семантика story points (BR-3)

Шкала фиксированная: 1 docs/label/config · 2 небольшой фикс · 3 средняя · 5 сложная (код+тесты) · 8 эпик/разбивать. Значения вне набора не выдаются.

FR-T5 — Авто-возврат в Backlog + анти-loop (BR-T2, BR-T6)

После оценки handle_estimate зовёт set_issue_backlog(work_item) → issue возвращается в Backlog. Это не создаёт цикла: Backlog-UUID не совпадает ни с одной триггер-веткой handle_issue_updated (stop/to_analyse/confirm_deploy/approved/rejected/estimate) → входящий webhook «state → Backlog» = no-op-эхо. Возврат best-effort: сбой записи статуса не роняет флоу (прогноз уже записан).

FR-T6 — Массовость и пере-оценка (BR-T3, BR-T4)

Массовый перевод N задач в «Оценка» = N независимых issue.updated-вебхуков → N вызовов handle_estimate (никакого спец-batch-кода). Пере-оценка = повторный перевод: estimate идемпотентно перезаписывает прогноз в task_estimates (UPSERT по work_item_id) и estimate_point; дублей строк нет.

FR-T7 — Запись прогноза и факта в Plane (BR-7, BR-8, NFR-6, NFR-7)

  • Прогноз story points → set_issue_estimate_point → поле issue estimate_point.
  • По завершении задачи (переход в done, врезка в существующий done-путь): из usage.py считается факт (токены/время/стоимость) → маппится в story-point bucket → set_issue_point → поле point; estimate_point не перезаписывается.
  • Все записи через plane_sync под guard ORCH-117; отсутствие estimate-конфига/поля → best-effort пропуск + лог (не падать).

FR-T8 — Отображение (BR-9)

  • Plane-коммент с прогнозом (стоимость/время/токены/story points) — plane_sync.add_comment.
  • Telegram-карточка — пункт «Оценка»: время · токены · стоимость (notifications). Обе поверхности — best-effort, не блокируют конвейер.

FR-T9 — Леджер прогноз↔факт (BR-10)

task_estimates хранит прогноз (на момент оценки) и факт (на момент done) + дельту, ключ work_item_id (т.к. на момент оценки task_id может быть NULL — issue на бэклоге). Фундамент калибровки (ORCH-8); авто-уточнение модели в объём не входит.

FR-T10 — leaf-инварианты (NFR-2, NFR-3)

applies(repo) = estimator_enabled ∧ скоуп estimator_repos (пусто → self-hosting only), проверяется локально и ПЕРВЫМ (без сети). Выключено → весь модуль инертен (нулевая регрессия: статус «Оценка» не обрабатывается, ничего не пишется). read-only блок estimator в GET /queue (флаг/скоуп/счётчики прогнозов/записей/возвратов-в-Backlog).

4. Изменения API

Метод/путь Назначение
Plane-статус «Оценка» (не HTTP-эндпоинт) Основной триггер: перевод issue в статус → handle_estimate. Массовость — multi-select Plane.
POST /estimate?work_item=<id> (опц.) Программно произвести/обновить прогноз одной задачи (то же ядро, что статус-триггер) — удобство/диагностика, не основной путь
POST /estimate/backlog (опц.) Программно оценить backlog-задачи проекта — удобство; основной массовый путь — статус «Оценка»
GET /estimate?work_item=<id> (опц.) Прочитать текущий прогноз vs факт из task_estimates
GET /queue + read-only блок estimator; existing-поля не меняются

Существующие эндпоинты/контракты не изменяются. Webhook-контракт issue.updated не меняется — добавляется лишь распознавание ещё одного целевого статуса.

5. Изменения схемы БД

Новая аддитивная таблица task_estimates (CREATE TABLE IF NOT EXISTS, без правки существующих): work_item_id (ключ/UPSERT-цель) · task_id (нуллабелен до старта пайплайна) · repo · прогноз (forecast_tokens, forecast_seconds, forecast_cost_usd, forecast_story_points) · факт (actual_tokens, actual_seconds, actual_cost_usd, actual_story_points) · дельта (delta_* или вычисляемая) · source (status/manual/api) · estimate_count (число пере-оценок, опц.) · created_at · updated_at. Точные типы/индексы/уникальность (UNIQUE по work_item_id для идемпотентного UPSERT) — 06-adr. Существующие таблицы (tasks/agent_runs/jobs/…) — не изменяются (NFR-8).

6. Требования к новым/изменённым QG checks

Нет. Оценка — наблюдатель/продюсер, не Quality Gate; статус «Оценка» — операторский side-триггер, не ребро STAGE_TRANSITIONS. QG_CHECKS / check_* / machine-verdict-ключи / STAGE_TRANSITIONSне трогаются. Новых номерных артефактов pipeline (NN-*.md) и новых вердикт-парсеров нет (оценка публикуется в Plane/Telegram/task_estimates, не во frontmatter-гейтах).

7. Совместимость / регресс

  • Флаги (config.py, дефолты безопасные): estimator_enabled (kill-switch, env ORCH_ESTIMATOR_ENABLED), estimator_repos (CSV, env ORCH_ESTIMATOR_REPOS; пусто → self-hosting only). Доп. тюнинг (bootstrap-дефолты, пороги bucket, целевой возврат-статус, сглаживание массовой нагрузки) — конфиг-ключи на усмотрение 06-adr.
  • Откат = ORCH_ESTIMATOR_ENABLED=false → модуль инертен: статус «Оценка» не обрабатывается (applies=False до сети), ни записи в Plane, ни строки карточки, ни обращений к таблице; конвейер байт-в-байт до ORCH-020. Доп. откат «на уровне доски» — не создавать статус «Оценка» (fail-closed, BR-T5).
  • Область раската: по умолчанию self-hosting orchestrator; enduro-trails не затронут (скоуп estimator_repos пуст + на его доске статуса «Оценка» нет → fail-closed).
  • never-raise / fail-safe: все публичные функции и врезки изолированы (try/except → warning + безопасный дефолт). Сбой оценки/записи в Plane/возврата статуса/рендера карточки — не роняет конвейер (NFR-2/6/7).
  • Анти-disruption / анти-loop: активный job → no-op (BR-T6); возврат в Backlog — no-op-эхо (FR-T5). Машина стадий и in-flight задачи не затрагиваются.
  • Горячий путь не тронут: resolve_agent_model/resolve_agent_effort/_spawn — без изменений (NFR-3).
  • Инфра-предусловия (NFR-7): (a) статус «Оценка» на доске проекта (онбординг ORCH-009 → 23-й статус; группа — 06-adr/07-infra-requirements.md); (b) estimate-система Plane (1/2/3/5/8) для estimate_point; их отсутствие → fail-closed/best-effort пропуск, не падение.