152 lines
13 KiB
Markdown
152 lines
13 KiB
Markdown
---
|
||
work_item: ORCH-119
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-17
|
||
model_used: claude-opus-4-8
|
||
---
|
||
|
||
# 01 — BRD / Bug-report: ORCH-119 — `00-business-request.md` всегда `TBD`, теряется source-backed контекст запроса
|
||
|
||
Work Item: **ORCH-119** · Repo: **orchestrator** · Стадия: analysis · Трек: **Bug** (укороченный маршрут, пропуск стадии `architecture`)
|
||
|
||
> 🐞 **Багфикс-трек (ORCH-019).** Облегчённый пакет (bug-report + обязательный регресс-тест), но
|
||
> все 4 файла analysis (гейт `check_analysis_complete` не меняется). Экономия — в пропуске стадии
|
||
> `architecture`, не в числе файлов.
|
||
>
|
||
> **Эскалация в full-cycle рассмотрена и отклонена.** Дефект — контейнерный data-flow + рендеринг,
|
||
> чинится **точным зеркалированием уже существующего прецедента `tasks.title`** (персист при создании
|
||
> задачи → чтение в `_materialize_deferred_branch`). Нет нового компонента, нового QG, политического
|
||
> решения или визуального артефакта → ADR/макет не требуется. Если разработчик в ходе фикса упрётся в
|
||
> архитектурное решение (напр. иной механизм персиста, влияющий на схему/контракты) — снять трек:
|
||
> `POST /bug-fast-track/escalate?work_item=ORCH-119` и пометить здесь `escalate: full-cycle`.
|
||
|
||
---
|
||
|
||
## 1. Бизнес-контекст и проблема
|
||
|
||
### Симптом (наблюдаемое)
|
||
Для **каждой** созданной задачи файл `docs/work-items/<id>/00-business-request.md` генерируется
|
||
с телом раздела «Description» равным буквальному `TBD`. Реальный текст запроса (описание Plane-issue,
|
||
обогащённое из Plane API) **не попадает** в персистентный артефакт. Пример — сам этот work item:
|
||
|
||
```
|
||
# Business Request: BUG: 00-business-request.md is always TBD, losing source-backed request context
|
||
Work Item ID: ORCH-119
|
||
## Description
|
||
TBD
|
||
```
|
||
|
||
### Последствие (бизнес-боль)
|
||
`00-business-request.md` — **точка входа конвейера** и источник для analyst (вход стадии `analysis`,
|
||
см. `PIPELINE_DOCS.md` §2). Когда тело всегда `TBD`:
|
||
- source-backed контекст запроса теряется из durable-артефакта репозитория (остаётся только эфемерно
|
||
в `task_content` analyst-job'а и в Plane);
|
||
- последующее чтение work item «глазами» (reviewer, человек, ретроспектива, петля уроков) видит пустой
|
||
бизнес-запрос — невозможно сверить, ту ли задачу решал конвейер;
|
||
- на **self-hosting** (`orchestrator`) задача почти всегда идёт **отложенным срезом ветки** (serial
|
||
gate, ORCH-088), где контекст теряется безвозвратно (см. §3, причина B).
|
||
|
||
### Причина симптома (установленный факт, по коду)
|
||
`src/webhooks/plane.py::_create_initial_docs` (строка ~925) **хардкодит** тело:
|
||
```python
|
||
content = f"# Business Request: {name}\n\nWork Item ID: {work_item_id}\n\n## Description\n\nTBD\n"
|
||
```
|
||
Функция принимает только `(repo, branch, work_item_id, name)` — **`description` ей не передаётся**,
|
||
хотя у вызывающего `start_pipeline` оно есть в области видимости и уже используется для analyst-job
|
||
(`task_desc`, строка ~725: `Description:\n{description}`). То есть данные **есть**, но в артефакт не
|
||
доходят.
|
||
|
||
### Локализация (куда смотреть разработчику) — два пути создания
|
||
|
||
**Путь A — прямой** (`serial_gate` не применим к репо):
|
||
`start_pipeline` (`src/webhooks/plane.py`) имеет `description` (строки ~518; обогащается из Plane API,
|
||
~539–551) → зовёт `_create_initial_docs(repo, branch, work_item_id, name)` (строка ~710) **без**
|
||
`description`. Достаточно дотянуть аргумент.
|
||
|
||
**Путь B — отложенный (критичный для self-hosting)** (`serial_gate_applies(repo)` → для `orchestrator`):
|
||
`start_pipeline` **не** создаёт ветку/доки (ORCH-088, анти-stale-base); срез релоцирован в
|
||
`src/agents/launcher.py::_materialize_deferred_branch` (строки ~514–538), который вызывает то же
|
||
`_create_initial_docs`, **располагая только `title`** из строки `tasks` (`description` нигде не
|
||
персистится). Установленный факт схемы: таблица `tasks` **не имеет** колонки `description`; `title`
|
||
персистится через `_ensure_column` (`src/db.py:125`) и читается в `_spawn`/`_materialize_deferred_branch`
|
||
именно так. ⇒ Чтобы путь B рендерил описание, `description` надо **сохранить durable при создании
|
||
задачи** (зеркало `tasks.title`).
|
||
|
||
### Предусловие истинности данных (установленный факт)
|
||
QG-0 (`_qg0_errors`, `src/webhooks/plane.py:490`) отклоняет создание при `description` короче 20
|
||
символов (строка ~500). ⇒ любая задача, дошедшая до `_create_initial_docs`, **гарантированно имеет
|
||
непустое осмысленное описание** — терять его тем более недопустимо. Защитный fallback на случай
|
||
пустого описания всё равно предусмотреть (NFR-2).
|
||
|
||
## 2. Объём (scope)
|
||
|
||
### В объёме
|
||
- Рендер фактического `description` (предпочтительно `description_stripped`, plain-text) в раздел
|
||
«Description» файла `00-business-request.md` — на **обоих** путях (A прямой, B отложенный).
|
||
- Durable-персист `description` при создании задачи (зеркало `tasks.title`), чтобы путь B имел доступ
|
||
к нему на момент claim.
|
||
- Защитный fallback при отсутствии/пустом описании (без падения).
|
||
- Обязательный регресс-тест (красный до фикса, зелёный после).
|
||
|
||
### Вне объёма
|
||
- Изменение `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict-ключей / семантики гейтов.
|
||
- Изменение поведения serial-gate / отложенного среза ветки ORCH-088 (только **дополнить** данными,
|
||
не менять момент/условие среза).
|
||
- Ретро-генерация `00-business-request.md` для **уже существующих** задач (только новые создания).
|
||
- Переформатирование/обогащение структуры самого `00-business-request.md` сверх вставки описания
|
||
(заголовки/название источника остаются как есть).
|
||
- Любая запись в Plane (артефакт пишется только в Gitea-ветку, как сейчас).
|
||
|
||
## 3. Заинтересованные стороны
|
||
- **Заказчик/оператор** — получает читаемый durable бизнес-запрос вместо `TBD`.
|
||
- **Агент analyst и reviewer** — могут сверять решённое с запросом по репозиторию.
|
||
- **Петля уроков / ретроспектива (ORCH-098)** — корректный контекст в артефакте.
|
||
- Приёмку результата выполняет конвейер (reviewer + Quality Gates), не аналитик.
|
||
|
||
## 4. Бизнес-требования (BR)
|
||
- **BR-1** — Раздел «Description» в `00-business-request.md` содержит **фактический текст запроса**
|
||
(из Plane-issue, как он используется для analyst-job'а), а не литерал `TBD`, для вновь создаваемых
|
||
задач.
|
||
- **BR-2** — Поведение одинаково на **обоих** путях создания: прямом (A) и отложенном срезе ветки (B,
|
||
self-hosting/serial-gate). Путь B — приоритетный сценарий (доминирует на `orchestrator`).
|
||
- **BR-3** — При отсутствующем/пустом описании артефакт создаётся с **явным безопасным fallback**-
|
||
маркером (напр. «описание отсутствует в источнике»), без падения создания задачи.
|
||
- **BR-4** — Сохранён состав/имена артефактов: создаётся ровно `00-business-request.md` по тому же
|
||
пути; downstream-конвейер (analyst и далее) не затронут.
|
||
|
||
## 5. Нефункциональные требования (NFR)
|
||
- **NFR-1 (обратная совместимость / never-break)** — изменение аддитивно: создание задачи **никогда**
|
||
не должно падать из-за нового рендера/персиста. Любая ошибка обогащения → деградация на безопасное
|
||
значение (fallback-маркер), а не отказ создания. Идемпотентность `_create_initial_docs` (422 = уже
|
||
существует → no-op) сохранена.
|
||
- **NFR-2 (целостность данных)** — описание рендерится **как есть** (plain-text `description_stripped`),
|
||
без обрезки/искажения; многострочный текст сохраняется. `00-business-request.md` — информационный
|
||
док (гейтом не парсится), поэтому markdown-спецсимволы в описании безопасны для гейтов.
|
||
- **NFR-3 (инварианты ORCH-088)** — момент и условие отложенного среза ветки не меняются; описание
|
||
лишь дополнительно переносится через durable-хранилище (зеркало `tasks.title`), анти-stale-base
|
||
логика цела.
|
||
- **NFR-4 (self-hosting-безопасность)** — фикс не деплоит/не рестартит прод, не трогает `main`, не
|
||
добавляет сетевых вызовов в горячий `claim_next_job`.
|
||
|
||
## 6. Допущения и ограничения
|
||
- `description`/`description_stripped` доступны в `start_pipeline` и достаточны как источник (уже
|
||
используются для analyst-job). Plane-обогащение (ORCH «name_missing/desc_missing» блок) остаётся
|
||
единственным источником описания — новых сетевых обращений не вводим.
|
||
- QG-0 гарантирует ≥20 символов описания для прошедших задач (см. §1) — нормальный путь всегда имеет
|
||
реальный текст.
|
||
- Персист описания следует **установленному прецеденту `tasks.title`** (аддитивная колонка через
|
||
`_ensure_column`); это не новое архитектурное решение.
|
||
|
||
## 7. Критерии успеха
|
||
Новые задачи получают `00-business-request.md` с реальным описанием на обоих путях; обязательный
|
||
регресс-тест красный до фикса и зелёный после; полный `pytest tests/ -q` зелёный. Детальные PASS/FAIL
|
||
— `03-acceptance-criteria.md`.
|
||
|
||
## 8. Риски
|
||
- Путь B забыт (чинят только прямой путь A) → на self-hosting баг остаётся. Митигируется обязательным
|
||
integration-тестом пути B (TC-03).
|
||
- Регресс схемы/создания задачи при добавлении персиста → митигируется аддитивным `_ensure_column` и
|
||
тестом обратной совместимости (TC-05). Детальные тех-риски архитектором не выпускаются (bug-track).
|