Files
orchestrator/docs/work-items/ORCH-019/06-adr/ADR-001-bug-fast-track.md

19 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-019 architecture architect proposed 2026-06-10 claude-opus-4-8

ADR-001: Багфикс-трек — пропуск стадии architecture через track-aware routing override

Work Item: ORCH-019 — упрощённый/дешёвый трек для багов (укороченный маршрут конвейера) Стадия: architecture Сквозная регистрация: docs/architecture/adr/adr-0032-bug-fast-track.md (решение кросс-каттинговое: новый leaf-компонент + аддитивная колонка tasks.track + семантика маршрутизации, затрагивающая advance_stage).

Статус

Proposed

Контекст

Любая задача входит в конвейер через webhooks/plane.py::start_pipeline, который жёстко создаёт task-row со стадией "analysis" (create_task_atomic(..., "analysis", ...)) и режет ветку. Маршрут стадий полностью управляется src/stages.py::STAGE_TRANSITIONS через get_next_stageadvance_stage (src/stage_engine.py) НЕ зашивает порядок, а спрашивает get_next_stage(current_stage) (строка 214) и get_agent_for_stage(current_stage) (строка 464).

Для мелкого бага полный цикл analysis → architecture → development → … избыточен: стадия architecture = отдельный прогон агента architect (opus, дорогой) + ADR + exit-гейт check_architecture_done. Прецедент: UI z-index баг ET-9/ET-014 прошёл полный цикл ~35 мин.

Корневой инвариант (NFR-1 BRD, нерушимый): упрощаем только аналитику/архитектуру; ни один Quality Gate / exit-код deploy-хука / под-гейт (security/merge/coverage/image-freshness) — НЕ ослаблен. Горький урок ET-8: срезанная проверка = недоделка на проде.

Факты, сверенные с кодом:

  • src/labels.py::has_label + plane_sync.fetch_issue_labels/get_project_labels (ORCH-089) — готовый, проверенный аппарат чтения метки Plane (TTL-кэш, нормализация, never-raise, fail-safe → False, источник истины Plane API, не payload).
  • advance_stage маршрутизирует через get_next_stage/get_agent_for_stage → точка ветвления локализуема, STAGE_TRANSITIONS ломать не нужно.
  • check_analysis_approved (exit-гейт analysis) вызывает check_analysis_complete, требующий 01/02/03/04 (src/qg/checks.py:33). Это и есть точка риска ложной блокировки облегчённого пакета (FR-6).
  • _ensure_column (src/db.py:334) — идемпотентная аддитивная миграция (паттерн tasks.cancelled_at, ORCH-090).

Решение

Сводка

Багфикс-трек — свойство планировщика/точки входа, не Quality Gate. Задача с меткой Plane Bug помечается в БД как track='bug'; на ребре выхода из analysis advance_stage применяет чистый routing-override: next_stagedevelopment (вместо architecture), next_agentdeveloper (вместо architect). STAGE_TRANSITIONS, реестр QG_CHECKS, все check_* и вердикт-ключи — байт-в-байт прежние. Распознавание, маршрут и метрика — аддитивно, под kill-switch, с областью репо, never-raise, fail-safe → полный цикл.

D1 — Классификация: метка Plane Bug, читаемая в start_pipeline (FR-1, AC-1)

Новый leaf src/bug_fast_track.py (пустой импорт-граф как serial_gate/labels: только config, лениво labels/plane_sync/qg.checks), never-raise. Публичные функции:

  • bug_fast_track_applies(repo) -> bool — локальный, без сети, по образцу _auto_label_applies: bug_fast_track_enabled=FalseFalse; bug_fast_track_repos (CSV) непустой → только перечисленные репо; пусто → self-hosting only (is_self_hosting_repo, см. D6). Проверяется ПЕРВЫМ → при выключенном флаге нулевой сетевой оверхед, enduro не затронут.
  • is_bug_task(work_item_id, project_id) -> boolbug_fast_track_applies уже проверен вызывающим; делегирует в labels.has_label(work_item_id, settings.bug_fast_track_label, project_id) (дефолт метки Bug). Любая ошибка/неоднозначность → False (fail-safe → полный цикл).

Чтение метки — только в start_pipeline (момент старта, сетевой вызов приемлем, как ORCH-089), никогда в горячем claim_next_job (NFR-4).

D2 — Хранение типа: аддитивная колонка tasks.track (OQ-2, NFR-4)

Идемпотентная миграция _ensure_column(conn, "tasks", "track", "TEXT DEFAULT 'full'") рядом с tasks.cancelled_at/cancel_requested_at (src/db.py init). Значения: 'full' (дефолт, ВСЕ существующие и не-баг задачи) | 'bug'. Хелперы: db.set_task_track(task_id, track) (запись), db.get_task_track(task_id) -> str (чтение, дефолт 'full'). Тип читается из БД в advance_stage (NFR-4: горячий путь без сети). Альтернатива «выводить тип повторным чтением метки» отвергнута — повторный сетевой вызов в горячем пути запрещён.

create_task_atomic НЕ меняет сигнатуру: задача создаётся как 'full' (DEFAULT), затем start_pipeline после успешного created=True при is_bug_task вызывает db.set_task_track(task_id, 'bug'). Точка входа стадии остаётся "analysis" (мини-анализ сохраняется, OQ-3/BRD §6 — НЕ чистый hotfix).

D3 — Routing-override: пропуск architecture без правки STAGE_TRANSITIONS (FR-2, AC-2)

get_next_stage/get_agent_for_stage остаются чистыми (принимают только стадию, 1:1). Override живёт в advance_stage, сразу после строки next_stage = get_next_stage(current_stage):

next_stage = get_next_stage(current_stage)
# ORCH-019: bug-fast-track skips the architecture stage entirely.
if current_stage == "analysis" and bug_fast_track.skips_architecture(track):
    next_stage = "development"

и при запуске следующего агента (строка 464):

next_agent = get_agent_for_stage(current_stage)        # "analysis" -> "architect"
if current_stage == "analysis" and next_stage == "development":
    next_agent = "developer"                            # skip architect run

track читается один раз в начале advance_stage (db.get_task_track(task_id)). Чистый предикат bug_fast_track.skips_architecture(track) -> bool (== track == 'bug' под bug_fast_track_enabled; иначе False). Багфикс-задача физически НЕ попадает в стадию architecture → её exit-гейт check_architecture_done и требование 06-adr/ не исполняются для багфикса. Для не-баг задач (track='full') поведение байт-в-байт прежнее.

Сопутствующая правка телеметрии: строка 386 стампит mark_brd_review_ended при analysis → architecture. Для багфикса next_stage = development, поэтому условие расширяется до current_stage == "analysis" and next_stage in ("architecture", "development") — чтобы метрика «твоё время» (ORCH-087) оставалась честной на багфикс-треке. Не влияет на гейты.

D4 — Quality Gate analysis: НЕ трогаем; lite-пакет эмитит все 4 файла (FR-3/FR-6, OQ-6, AC-3)

Корневой инвариант диктует минимальную поверхность изменения гейтов = ноль. check_analysis_complete (требует 01/02/03/04) и check_analysis_approved остаются байт-в-байт прежними. Багфикс-аналитик (analyst.md lite-режим) всё равно эмитит все 4 файла, но в облегчённой багфикс-форме: 01-brd.md = короткий bug-report (симптом / шаги воспроизведения / локализация / причина), 02-trz.md + 03-acceptance-criteria.md = краткие bug-shaped заглушки, 04-test-plan.yaml = план обязательного регресс-теста (красный до фикса, зелёный после).

Обоснование выбора: доминирующая экономия — пропуск всей стадии architecture (отдельный прогон opus-агента architect + ADR), а не число файлов analysis (они эмитятся в ОДНОМ прогоне analyst-агента). Сохранение 4-файлового гейта = сильнейшая позиция NFR-1 (нулевая поверхность правок гейта) ценой почти нулевого оверхеда. Альтернатива «track-aware check_analysis_complete (для bug требовать только 01/04)» рассмотрена и отвергнута для v1 (D-Alt) — она трогает check_* и расширяет поверхность риска без существенной экономии.

D5 — Эскалация в полный цикл (FR-5, AC-5)

Два пути возврата сложного/архитектурного/визуального бага в полный цикл, оба сбрасывают track='bug''full' (после чего advance_stage маршрутизирует analysis → architecture штатно):

  1. Операторский (ручной, v1-дефолт): админ-эндпоинт POST /bug-fast-track/escalate?work_item=<id> (по образцу POST /serial-gate/unfreeze, POST /coverage/baseline) — db.set_task_track(..., 'full'), лог + Telegram + Plane-коммент, never-raise. Применять, пока задача в analysis (до выхода) — тогда следующий переход уйдёт в architecture.
  2. Решение мини-аналитика: если на багфикс-треке аналитик определяет, что баг архитектурный, он эмитит полный analysis-пакет (включая запрос на 06-adr/) и помечает в bug-report escalate: full-cycle — оператор подтверждает эскалацию эндпоинтом (1). v1 НЕ включает автоматический LLM-авто-триаж сложности (вне объёма, BRD §2.2).

Эскалация обратима, детерминирована, наблюдаема. Багфикс-задача не «застревает» без архитектуры.

D6 — Область по умолчанию: self-hosting only (OQ-5, NFR-5)

Пустой bug_fast_track_reposself-hosting only (is_self_hosting_repo, как ORCH-089/027/058). Это безопасный дефолт: режим обкатывается на самом орке (где метка Bug гарантированно заводится оператором), enduro подключается явным добавлением в CSV. Флаги (config.py): bug_fast_track_enabled (kill-switch, env ORCH_BUG_FAST_TRACK_ENABLED), bug_fast_track_label (дефолт Bug, env ORCH_BUG_FAST_TRACK_LABEL), bug_fast_track_repos (CSV, env ORCH_BUG_FAST_TRACK_REPOS).

D7 — Наблюдаемость стоимости (FR-7, AC-7)

  • GET /queue — аддитивный read-only блок bug_fast_track (bug_fast_track.snapshot(), never-raise, по образцу serial_gate/auto_labels/coverage): enabled, repos, label, счётчик задач с track='bug', агрегатная метрика экономии (пропущенные стадии / Σ agent-runs / токены / время багфикс-трека против среднего полного цикла из существующей телеметрии agent_runs). Существующие ключи GET /queue не меняются.
  • Лог-строка на решение о маршруте (analysis → development (bug-fast-track)).
  • Опц. отметка 🐞 багфикс-трек в Telegram-карточке (notifications.py, never-raise).

D8 — Композиция (NFR-7, AC-9)

  • serial-gate (ORCH-088): багфикс-задача — обычная задача репо, учитывается в serial-очереди как есть (FIFO t2.id < jobs.task_id); точка входа analysis не меняется, defer-branch логика не затронута. Маркированный код serial_gate.py НЕ правится.
  • auto-label (ORCH-089): autoApprove/autoDeploy работают на багфикс-треке — autoApprove врезка в _handle_analysis_approved_flow вызывает advance_stage(finished_agent=None), который применяет D3-override и уходит в development. Переиспользуем labels.has_label.
  • coverage-gate (ORCH-027): союзник BR-4 (структурно ловит «код без теста») — исполняется штатно на ребре deploy-staging → deploy.
  • merge-gate (ORCH-043): не затронут.

Правки маркированного кода (advance_stage несёт врезки ORCH-088/089/027/059/094) — точечные, со сверкой их 06-adr/; зафиксированные инварианты (порядок под-гейтов, merge-lease, terminal-sync) НЕ нарушаются: ORCH-019 добавляет ветвление ТОЛЬКО на ребре выхода из analysis, до всех deploy-edge под-гейтов.

Альтернативы

  • Track-aware get_next_stage(stage, task) / новая стадия в STAGE_TRANSITIONS — отвергнуто: ломает чистоту stages.py и риск задеть структуру таблицы (AC-2 FAIL при структурном изменении). Override в advance_stage локальнее и держит STAGE_TRANSITIONS неизменным.
  • Track-aware check_analysis_complete (bug → только 01/04) — отвергнуто для v1 (D-Alt): трогает check_*, расширяет поверхность риска NFR-1 ради почти нулевой экономии (см. D4). Оставлено как возможное будущее уточнение, если потребуется реальный отказ от 02/03.
  • Чистый hotfix start_pipeline → development, минуя analysis — отвергнуто как дефолт (BRD §6): теряется фиксация регресс-теста как контракта приёмки и трассируемость (урок ET-8).
  • Тип задачи из payload вебхука / повторное чтение метки в claim_next_job — отвергнуто: payload не несёт type (источник истины — Plane API); сеть в горячем claim запрещена (NFR-4).
  • Чтение типа без БД-колонки — отвергнуто: потребовало бы сетевого вызова в горячем пути.

Последствия

  • + Багфикс минует целую стадию architecture (один прогон opus-агента architect + ADR) — основная экономия токенов/времени; гейты качества байт-в-байт сохранены.
  • + Полностью аддитивно: kill-switch False или неприменимый репо → путь старта и маршрут идентичны текущим (AC-6, нулевая регрессия для enduro и orchestrator).
  • + Переиспользует проверенный аппарат ORCH-089 (label-чтение) и паттерн leaf+флаги+snapshot.
  • Багфикс-аналитик всё равно эмитит 02/03 (краткие заглушки) ради неизменности гейта — принятый компромисс (D4); экономия на их содержании, не на их наличии.
  • Эскалация v1 требует операторского действия (эндпоинт) — авто-триаж сложности отложен (BRD §2.2). Митигатор: путь эскалации документирован, обратим, наблюдаем (D5).
  • Откат: bug_fast_track_enabled=False (мгновенно, 1:1 прежнее поведение); колонка tasks.track остаётся (аддитивна, дефолт 'full', безвредна). Полный откат — revert PR; миграция идемпотентна, остаточная колонка не мешает.

Ссылки

  • BRD: docs/work-items/ORCH-019/01-brd.md
  • TRZ: docs/work-items/ORCH-019/02-trz.md
  • Acceptance: docs/work-items/ORCH-019/03-acceptance-criteria.md
  • Сквозной ADR: docs/architecture/adr/adr-0032-bug-fast-track.md
  • Data: docs/work-items/ORCH-019/08-data-requirements.md
  • Infra: docs/work-items/ORCH-019/07-infra-requirements.md
  • Риски: docs/work-items/ORCH-019/10-tech-risks.md
  • Сверено по коду: src/stages.py, src/stage_engine.py (advance_stage:175-477), src/webhooks/plane.py::start_pipeline (505-684), src/labels.py, src/qg/checks.py (check_analysis_complete:33, check_analysis_approved:286, check_architecture_done:62), src/db.py (_ensure_column:334, create_task_atomic:433)