9.0 KiB
work_item, stage, author_agent, status, created_at, model_used
| work_item | stage | author_agent | status | created_at | model_used |
|---|---|---|---|---|---|
| ORCH-020 | architecture | architect | proposed | 2026-06-17 | claude-opus-4-8 |
ADR-0054: Оценка задачи — операторский статус-триггер «Оценка» + детерминированная эвристика по истории
Сквозной (cross-cutting) ADR. Детальное решение задачи —
docs/work-items/ORCH-020/06-adr/ADR-001-task-estimation-status-trigger.md.
Статус: Proposed · Дата: 2026-06-17 · Источник: ORCH-020
Контекст
Заказчик планирует бэклог вручную и хочет видеть прогноз стоимости / времени / токенов / сложности
(story points {1,2,3,5,8}) до отправки задачи в работу. Ключевое требование триггера (после REJECT
2026-06-17): оценка — операторский жест в Plane (перевод issue в выделенный статус «Оценка»,
массово через multi-select), а не невидимый авто-шаг на start_pipeline. Шаг 2 (адаптивный выбор
модели) — вне объёма.
Решение пересекает несколько подсистем (webhook-роутинг, plane_sync, БД, notifications, stage_engine
done-хук), поэтому фиксируется сквозным ADR. Опирается на установленные инварианты платформы:
- Семейство операторских action-статусов STOP (ORCH-090) / Confirm Deploy (ORCH-059): fail-closed
.get("<key>")-ветка вhandle_issue_updated, ключ намеренно отсутствует в_DEFAULT_STATES. - Write-guard ORCH-117 — все записи в Plane изолированы от тест/worktree-процессов.
- leaf-паттерн (serial_gate/coverage_gate/bug_fast_track/lessons): never-raise, kill-switch, скоуп
*_repos(пусто → self-hosting only), read-only блок вGET /queue. - determinization-политика ORCH-118 (
llm-usage-policy.md): не вводить avoidable LLM-пути.
Решение
Вводим третий член семейства action-статусов — «Оценка» — как side-механизм, делегирующий новому leaf
src/estimator.py. Механизм прогноза — детерминированная эвристика по истории (чистые функции, без
LLM-вызова). Аддитивно, под kill-switch, скоуп self-hosting, never-raise, fail-safe:
- Триггер (D4).
_PLANE_NAME_TO_KEY["Оценка"]="estimate"(НЕ в_DEFAULT_STATES); fail-closed веткаproj_states.get("estimate")→handle_estimate(off-loopto_thread, зеркалоhandle_stop). Взаимоисключение жестов — по различию UUID статусов, не по порядку. - Анти-disruption + авто-возврат + анти-loop (D5). Guard
applies(repo)ПЕРВЫМ (локально) +has_active_job_for_task(активный job → no-op, не выдёргивать in-flight). После оценки —set_issue_backlog;backlogне совпадает ни с одной триггер-веткой → возврат = no-op-эхо. - Механизм прогноза (D1/D2/D3). Без LLM: прогноз = средние токены/время/стоимость похожих
done-задач (repo+trackORCH-019, через read-onlyusage.py-агрегаты), bootstrap при пустой истории; story-points — чистая функция-бакетизатор поforecast_cost_usdс конфигурируемыми порогами. - Запись в Plane (D6). Прогноз story-points →
set_issue_estimate_point(FK на estimate-point, резолвvalue→uuid); факт →set_issue_point(устойчивый int); коммент →add_comment. Все под_guard_allows_write(ORCH-117); отсутствие estimate-системы → best-effort пропуск + лог (NFR-7). - Персистентность (D7). Новая аддитивная таблица
task_estimates(UNIQUE(work_item_id), UPSERT-идемпотентность пере-оценки;task_idнуллабелен — issue на бэклоге). Фундамент петли калибровки (ORCH-8). - Поверхности (D8). Пункт «Оценка» (время·токены·стоимость) в общей Telegram-карточке
(
notifications, never-raise, ORCH-087/095-совместимо). - Факт на
done(D9). Best-effort врезка вstage_engine.advance_stage(блокnext_stage=="done", после terminal-sync): факт изusage.py→set_actual+set_issue_point;estimate_pointне перезаписывается.
Главное архитектурное решение — отказ от LLM-оценщика (D1). Причины: NFR-5 (massive multi-select
умножил бы LLM-вызовы и конкурировал бы за единственный транспорт launcher._spawn с боевыми агентами,
рискуя обслуживанием enduro), NFR-4 (стоимость самой оценки), и политика ORCH-118 (размер задачи
деривируем из tool-сигналов — суждение LLM не требуется). Контракт estimate() — граница расширения под
будущий гибрид без переписывания вызывающих (но он сейчас НЕ строится).
Инварианты (нормативно)
- Оценка — наблюдатель/продюсер, НЕ Quality Gate и НЕ переход стадии.
STAGE_TRANSITIONS/ реестр и именаQG_CHECKS/ семантикаcheck_*/ machine-verdict-ключи (verdict:/result:/deploy_status:/staging_status:/security_status:/coverage_status:) / схемы существующих таблиц — байт-в-байт не тронуты. Статус «Оценка» не добавляет ребра в машину стадий. - Горячий путь не тронут:
resolve_agent_model/resolve_agent_effort/_spawnбез изменений (Шаг 2 вне объёма — отдельный work item с зависимостью на ORCH-13). - Схема БД: ровно одна аддитивная таблица
task_estimates(CREATE TABLE IF NOT EXISTS); существующие таблицы не изменяются (NFR-8). Hot-pathclaim_next_job/очередь её не читают. - Self-hosting-безопасность: модуль только читает/пишет свою таблицу, читает usage-агрегаты и пишет в
Plane/Telegram — не деплоит, не рестартит прод-контейнер, не трогает
main/force-push, без процессов. - never-raise / обратимость: все публичные функции и врезки изолированы;
estimator_enabled=false/ доска без статуса «Оценка» / репо внеestimator_repos→ байт-в-байт как до ORCH-020 (enduro и текущий orchestrator не затронуты). - Без kill-switch обхода write-guard: записи
estimate_point/point/коммент/состояние подчиняются ORCH-117 (anti-drift: тест-процесс физически не пишет в боевой Plane).
Последствия
Оператор получает прогноз для планирования бэклога одним массовым жестом; пере-оценка идемпотентна;
заложен леджер прогноз↔факт под петлю калибровки (ORCH-8). Цена — net-new интеграция с Plane-estimate API
(estimate_point — FK; смягчено best-effort/fail-safe + устойчивым int-point) и начальные пороги
story-points (смягчено конфигурируемостью + леджером). Решение сознательно консервативно (детерминировано,
обратимо, согласовано с ORCH-118) и не требует arch:major-change (аддитивный leaf по устоявшемуся
паттерну, без новой стадии/правки таблиц/смены БД). Детали, альтернативы и риски —
docs/work-items/ORCH-020/06-adr/ADR-001-task-estimation-status-trigger.md,
docs/work-items/ORCH-020/07-infra-requirements.md, 08-data-requirements.md, 10-tech-risks.md.