analyst(ET): auto-commit from analyst run_id=783
All checks were successful
CI / test (push) Successful in 1m10s

This commit is contained in:
2026-06-17 14:01:48 +03:00
parent f982c167b3
commit 5646552f02
4 changed files with 410 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
---
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,
~539551) → зовёт `_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` (строки ~514538), который вызывает то же
`_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).

View File

@@ -0,0 +1,86 @@
---
work_item: ORCH-119
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-17
model_used: claude-opus-4-8
---
# 02 — ТЗ (TRZ): ORCH-119 — source-backed генерация `00-business-request.md`
Work Item: **ORCH-119** · Repo: **orchestrator** · Стадия: analysis · Трек: **Bug**
> Bug-track: стадия `architecture` пропускается, поэтому ТЗ конкретизирует затрагиваемые модули и
> требование к данным. ТЗ описывает **что должно измениться и где**; точная форма реализации (имена
> символов, сигнатуры) — за разработчиком, в рамках указанного прецедента `tasks.title`.
## 1. Сводка изменения
Source-backed `description` (текст запроса из Plane-issue) должен попадать в раздел «Description»
файла `00-business-request.md` вместо хардкода `TBD`. Для этого: (1) `description` рендерится в тело
артефакта; (2) `description` **персистится при создании задачи** (зеркало `tasks.title`), чтобы
отложенный путь среза ветки (ORCH-088, доминирует на self-hosting) имел к нему доступ на момент claim.
Изменение аддитивно, never-break, fail-safe.
## 2. Задействованные модули / пути
| Путь | Действие |
|------|----------|
| `src/webhooks/plane.py` | изменить — `_create_initial_docs`: принять `description`, рендерить его в тело вместо `TBD`; рекомендуется выделить чистый рендер-хелпер (напр. `_render_business_request(work_item_id, name, description) -> str`) для unit-тестируемости без сети |
| `src/webhooks/plane.py` | изменить — `start_pipeline`: (а) прямой путь — передать `description` в `_create_initial_docs` (строка ~710); (б) персистить `description` при создании задачи (рядом со стампом `title`) |
| `src/agents/launcher.py` | изменить — `_materialize_deferred_branch`: прочитать персистнутое `description` из строки `tasks` и передать в `_create_initial_docs` (зеркало того, как уже читается/используется `title`) |
| `src/db.py` | изменить — аддитивная колонка `tasks.description` через `_ensure_column` (паттерн `tasks.title`, строка ~125); хелпер чтения/записи при необходимости; **не менять** базовый `CREATE TABLE tasks` |
| `tests/test_orch119_business_request.py` | создать — регресс + edge-кейсы (см. `04-test-plan.yaml`) |
| `CHANGELOG.md` | изменить — запись о фиксе (правило сопровождения) |
## 3. Функциональные требования
### FR-1 — Рендер описания в артефакт (BR-1)
Тело раздела «Description» в `00-business-request.md` = фактический `description` (предпочтительно
`description_stripped`, plain-text), без обрезки/искажения, многострочный текст сохраняется. Заголовок
(`# Business Request: {name}`) и `Work Item ID` — без изменений.
### FR-2 — Прямой путь (BR-2, путь A)
`start_pipeline` при `serial_gate` НЕ применим → передаёт `description` в `_create_initial_docs`;
артефакт создаётся с реальным описанием в одном вызове.
### FR-3 — Отложенный путь / персист (BR-2, путь B — критичный)
`description` персистится durable при создании задачи (зеркало `tasks.title`).
`_materialize_deferred_branch` читает его из строки `tasks` и передаёт в `_create_initial_docs`, так
что артефакт, материализованный на момент claim analyst-job, содержит реальное описание. Момент/условие
отложенного среза (ORCH-088) **не меняются** — только источник данных дополняется (NFR-3).
### FR-4 — Fallback и устойчивость (BR-3, NFR-1)
Пустое/отсутствующее/нечитаемое `description` → явный безопасный маркер (напр.
`_(описание отсутствует в источнике)_`), **без падения** создания задачи. Любая ошибка рендера/чтения
персиста → деградация на fallback-маркер, не отказ. Идемпотентность сохранена: повторная
материализация (Gitea 422 = файл уже существует) → no-op, ранее записанное тело не перезаписывается.
## 4. Изменения API
Нет. Эндпоинты не добавляются и не меняются. Запись артефакта остаётся в Gitea-ветку через
существующий `contents` API; в Plane ничего не пишется.
## 5. Изменения схемы БД
Аддитивная колонка **`tasks.description TEXT`** через `_ensure_column` (идемпотентный ALTER, no-op на
существующей таблице — safe на боевой БД), строго по прецеденту `tasks.title`. Базовый
`CREATE TABLE tasks` не трогается. Индексы не требуются. Для уже существующих задач колонка `NULL`
(ретро-генерация — вне объёма).
> Допустимая эквивалентная реализация (на усмотрение разработчика): переиспользовать уже доступный
> в `_spawn`/`_materialize_deferred_branch` источник данных, если он durable до момента claim. Если
> такой надёжно нет — канон именно колонка `tasks.description` (как `tasks.title`). Решение не должно
> вводить сетевой вызов в горячий путь claim (NFR-4).
## 6. Требования к новым/изменённым QG checks
Нет. `00-business-request.md` — информационный документ (гейтом не парсится, `PIPELINE_DOCS.md` §2§3).
`STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict-ключи — байт-в-байт не трогаются.
## 7. Совместимость / регресс
- **Обратная совместимость:** аддитивная колонка + аддитивный аргумент с дефолтом; существующие задачи
и enduro-trails не затронуты (для них тоже просто рендерится их описание — улучшение, не регресс).
- **Kill-switch:** отдельный флаг не требуется — изменение это исправление дефекта (улучшение
«всегда»), не рискованная фича; безопасность обеспечивается fail-safe fallback и never-break-контрактом.
- **Обратимость:** revert PR полностью возвращает прежнее поведение (колонка остаётся, инертна).
- **Self-hosting:** не деплоит/не рестартит прод, не трогает `main`, без новых сетевых вызовов в
`claim_next_job` (NFR-4). Анти-stale-base инвариант ORCH-088 цел (NFR-3) — перед правкой
`_materialize_deferred_branch`/отложенного среза свериться с `docs/work-items/ORCH-088/06-adr/`
(TRACEABILITY).

View File

@@ -0,0 +1,109 @@
---
work_item: ORCH-119
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-17
model_used: claude-opus-4-8
---
# 03 — Критерии приёмки: ORCH-119 — source-backed `00-business-request.md`
Work Item: **ORCH-119** · Repo: **orchestrator** · Стадия: analysis · Трек: **Bug**
Формат: каждый критерий — **PASS** (что должно быть истинно для приёмки) и **FAIL** (что считается
провалом). Reviewer/тесты проверяют буквально по файлам репозитория.
---
## AC-1 — Реальное описание в артефакте вместо `TBD`
**Условие:** при создании задачи с непустым описанием раздел «Description» файла
`00-business-request.md` содержит фактический текст запроса.
- **PASS:** тело раздела «Description» равно переданному `description` (plain-text); подстрока с
реальным текстом присутствует; литерал `TBD` как ВСЁ тело раздела отсутствует.
- **FAIL:** раздел «Description» = `TBD` (или иной плейсхолдер) при наличии непустого описания на входе.
---
## AC-2 — Прямой путь создания (путь A)
**Условие:** когда `serial_gate` не применим, `start_pipeline` передаёт `description` в
`_create_initial_docs`.
- **PASS:** артефакт, созданный прямым путём, содержит реальное описание (см. AC-1).
- **FAIL:** на прямом пути `description` не доходит до артефакта (остаётся `TBD`).
---
## AC-3 — Отложенный путь / self-hosting (путь B, критичный)
**Условие:** когда `serial_gate_applies(repo)` (напр. `orchestrator`), описание персистится при
создании задачи и рендерится при материализации ветки на момент claim
(`launcher._materialize_deferred_branch`).
- **PASS:** артефакт, материализованный отложенным путём, содержит реальное описание; описание
durable-доступно из строки `tasks` (не теряется между созданием задачи и claim).
- **FAIL:** на отложенном пути описание утрачено → артефакт = `TBD`; либо описание нигде не
персистится до момента claim.
---
## AC-4 — Безопасный fallback при пустом описании
**Условие:** описание отсутствует/пустое/нечитаемо.
- **PASS:** артефакт создаётся с явным безопасным fallback-маркером (не «голый» `TBD`-баг), создание
задачи не падает.
- **FAIL:** создание задачи падает/бросает исключение, либо тихо теряет контекст без маркера.
---
## AC-5 — Никаких изменений гейтов / схемы сверх аддитивной колонки
**Условие:** правка не трогает машинерию конвейера.
- **PASS:** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, имена/семантика `check_*`, machine-verdict-ключи
— байт-в-байт прежние; единственное изменение схемы — аддитивная `tasks.description` (или
эквивалент без сетевого вызова в claim); базовый `CREATE TABLE tasks` не тронут.
- **FAIL:** изменены гейты/переходы/вердикт-ключи; неаддитивная миграция; сетевой вызов добавлен в
`claim_next_job`.
---
## AC-6 — Идемпотентность и обратная совместимость
**Условие:** повторная материализация и существующие задачи.
- **PASS:** повторный вызов (`_create_initial_docs`, Gitea 422) — no-op, не перезаписывает тело;
`_ensure_column` идемпотентен (no-op при существующей колонке); enduro-trails и прочие репо не
затронуты негативно.
- **FAIL:** повторная материализация затирает/дублирует артефакт; миграция падает на боевой БД;
регресс для других репо.
---
## AC-7 — Обязательный регресс-тест (red→green) и зелёный полный прогон
**Условие:** дефект зафиксирован тестом.
- **PASS:** `tests/test_orch119_business_request.py::TC-01` падает на коде ДО фикса (доказывает баг) и
проходит ПОСЛЕ; полный `pytest tests/ -q` зелёный.
- **FAIL:** регресс-теста нет, либо он зелёный и до фикса (ничего не доказывает), либо полный прогон
красный.
---
## AC-8 — Сопровождение
**Условие:** документация/история обновлены в том же PR.
- **PASS:** `CHANGELOG.md` содержит запись о фиксе ORCH-119.
- **FAIL:** изменён функционал без обновления `CHANGELOG.md`.
---
## Сводная матрица AC ↔ FR/BR
| AC | Покрывает |
|----|-----------|
| AC-1 | BR-1 / FR-1 |
| AC-2 | BR-2 / FR-2 |
| AC-3 | BR-2 / FR-3 / NFR-3 |
| AC-4 | BR-3 / FR-4 / NFR-1 |
| AC-5 | BR-4 / NFR-4 |
| AC-6 | NFR-1 / FR-4 |
| AC-7 | BR-1..BR-3 (доказательство) |
| AC-8 | правило сопровождения |

View File

@@ -0,0 +1,64 @@
work_item: ORCH-119
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-17
model_used: claude-opus-4-8
title: "Source-backed генерация 00-business-request.md (фикс хардкода TBD)"
framework: pytest
scope: >
Покрывается: рендер фактического description в 00-business-request.md вместо литерала TBD на обоих
путях создания (прямой A и отложенный срез ветки B / self-hosting ORCH-088), durable-персист
description (зеркало tasks.title), безопасный fallback при пустом описании, аддитивность схемы и
обратная совместимость. ВНЕ покрытия: реальные сетевые вызовы Gitea/Plane (мокаются), ретро-генерация
артефактов для уже существующих задач.
notes: >
TC-01 — ОБЯЗАТЕЛЬНЫЙ регресс-тест: красный на коде ДО фикса (доказывает баг хардкода TBD), зелёный
ПОСЛЕ. Сетевые вызовы (_create_gitea_branch / _create_initial_docs httpx, Plane API) мокаются —
тесты без сети/прода. Рекомендуется тестировать чистый рендер-хелпер (_render_business_request) на
уровне unit, а пути A/B — на уровне integration с моками httpx и временной SQLite-БД. Полный регресс
pytest tests/ -q должен оставаться зелёным. Перед правкой отложенного среза ветки свериться с
docs/work-items/ORCH-088/06-adr/ (анти-stale-base инвариант, TRACEABILITY).
tests:
- id: TC-01
type: unit
description: "ОБЯЗАТЕЛЬНЫЙ РЕГРЕСС. Рендер 00-business-request.md при непустом description содержит фактический текст запроса в разделе 'Description' и НЕ равен литералу TBD. Красный до фикса (хардкод TBD), зелёный после."
module: tests/test_orch119_business_request.py
expected: PASS
- id: TC-02
type: unit
description: "Fallback: при пустом/whitespace/None description рендер даёт явный безопасный маркер (напр. 'описание отсутствует в источнике'), функция не бросает исключение."
module: tests/test_orch119_business_request.py
expected: PASS
- id: TC-03
type: integration
description: "Путь B (отложенный, self-hosting): description персистнут при создании задачи и доступен из строки tasks; launcher._materialize_deferred_branch рендерит реальное описание в артефакт (мок httpx; description не теряется между созданием и claim)."
module: tests/test_orch119_business_request.py
expected: PASS
- id: TC-04
type: integration
description: "Путь A (прямой, serial_gate не применим): start_pipeline передаёт description в _create_initial_docs; артефакт содержит реальное описание (мок httpx, перехват записанного content)."
module: tests/test_orch119_business_request.py
expected: PASS
- id: TC-05
type: integration
description: "Обратная совместимость схемы: init_db на пустой БД и на БД со старой таблицей tasks (без колонки description) проходит; _ensure_column идемпотентен (повторный init_db — no-op); создание задачи не падает."
module: tests/test_orch119_business_request.py
expected: PASS
- id: TC-06
type: unit
description: "Целостность данных: многострочное описание со спецсимволами markdown рендерится без обрезки/искажения; идемпотентность — повторная материализация (Gitea 422) не перезаписывает уже записанное тело."
module: tests/test_orch119_business_request.py
expected: PASS
- id: TC-07
type: unit
description: "Анти-регресс гейтов: STAGE_TRANSITIONS / реестр QG_CHECKS / имена check_* импортируются без изменений (00-business-request.md остаётся информационным, не гейтится)."
module: tests/test_orch119_business_request.py
expected: PASS