17 KiB
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):
estimator.applies(repo)— kill-switch + скоуп (False → no-op);- анти-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→ поле issueestimate_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, envORCH_ESTIMATOR_ENABLED),estimator_repos(CSV, envORCH_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 пропуск, не падение.