From 191f78bb863b788baea2e6c29cdc9db7c7f98c50 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Mon, 8 Jun 2026 09:51:46 +0000 Subject: [PATCH] analyst(ET): auto-commit from analyst run_id=361 --- docs/work-items/ORCH-067/01-brd.md | 158 ++++++++++++++ docs/work-items/ORCH-067/02-trz.md | 205 ++++++++++++++++++ .../ORCH-067/03-acceptance-criteria.md | 129 +++++++++++ docs/work-items/ORCH-067/04-test-plan.yaml | 181 ++++++++++++++++ 4 files changed, 673 insertions(+) create mode 100644 docs/work-items/ORCH-067/01-brd.md create mode 100644 docs/work-items/ORCH-067/02-trz.md create mode 100644 docs/work-items/ORCH-067/03-acceptance-criteria.md create mode 100644 docs/work-items/ORCH-067/04-test-plan.yaml diff --git a/docs/work-items/ORCH-067/01-brd.md b/docs/work-items/ORCH-067/01-brd.md new file mode 100644 index 0000000..9423539 --- /dev/null +++ b/docs/work-items/ORCH-067/01-brd.md @@ -0,0 +1,158 @@ +# BRD — ORCH-067: Telegram tracker (bump + статусы Plane + кликабельный номер задачи) + +Work Item: **ORCH-067** +Тип: **Багфикс + enhancement** +Приоритет: высокий +Компонент: Telegram live-tracker и уведомления оркестратора (`src/notifications.py`) +Расширяет: открытый баг seq=55 («bump не сработал, регресс ORCH-042») + +--- + +## 1. Бизнес-контекст и проблема + +Оркестратор ведёт по одной «живой карточке» (live-tracker) на каждую задачу в Telegram +(`src/notifications.py`). Карточка тихо обновляется на каждом переходе стадии, а отдельными +пингами шлются только события, требующие внимания владельца (approve-gate, деплой-фейл, +падение агента, ошибка задачи). + +Сейчас есть четыре боли: + +1. **bump не работает в проде.** Диагностика оператора: код режима `bump` в + `update_task_tracker` корректен (delete старого → sendMessage вниз → repoint + `tracker_message_id`), НО в проде `tracker_mode="edit"` (дефолт `src/config.py:408`), + а `ORCH_TRACKER_MODE=bump` не выставлен. Карточка обновляется edit-in-place и остаётся + «вверху» ленты, тонет под новыми сообщениями — наблюдатель не видит актуального + состояния без скролла. + +2. **Карточка показывает внутренние названия стадий, а не Plane-статусы.** После ввода + осмысленной статусной модели Plane (ORCH-066) карточка по-прежнему рендерит внутренние + ярлыки стадий (Анализ/Архитектура/…), а текущий статус задачи в терминах, понятных + наблюдателю в Plane (To Analyse → Analysis → In Review → … → Done), в шапке карточки + не отражён. Особенно теряется состояние **ожидания согласования BRD** = Plane-статус + `In Review`: сейчас это лишь строка «✅/⏸️ Подтверждение BRD … ⏳», не выраженная как + полноценный статус. + +3. **Номер задачи в карточке некликабелен.** `ORCH-066` в карточке — обычный текст; + чтобы открыть задачу в Plane, наблюдателю приходится искать её вручную. + +4. **Номер задачи некликабелен и во всех остальных уведомлениях орка** (approve-requested, + QG-fail, deploy SUCCESS/FAIL, Needs Input, прод-деплой и т. п.) — везде, где упоминается + `work_item_id`, это просто текст. + +## 2. Цель + +Сделать live-tracker и уведомления орка наблюдаемыми «из коробки»: +- bump работает по умолчанию (карточка падает вниз свежим сообщением при каждом обновлении, + ровно одна карточка на задачу, без спама и дублей); +- карточка явно показывает текущий Plane-статус по модели ORCH-066, включая человеческие + гейты (`⏸️ In Review` — согласование BRD, `⏸️ Awaiting Deploy` — ожидание Confirm Deploy, + `❓ Needs Input` — нужны уточнения); +- номер задачи кликабелен в карточке и во всех Telegram-уведомлениях орка и ведёт на + страницу задачи в Plane. + +## 3. Заинтересованные стороны + +- **Owner (Слава)** — основной потребитель карточки и уведомлений; источник 4 требований. +- **Агенты конвейера** — косвенно (карточка отражает их прогресс; поведение агентов не меняется). +- **Другие проекты (enduro-trails)** — общий инстанс/БД; изменения не должны вызывать регресс. + +## 4. Объём работ (scope) + +### 4.1. Требование 1 — bump по умолчанию +- Режим `bump` должен быть поведением по умолчанию: при каждом обновлении карточка + удаляется и пересоздаётся внизу ленты, одна карточка на задачу, тихо + (`disable_notification`), без дублей. +- Инвариант «одна карточка на задачу» сохраняется в обоих режимах (`edit` остаётся как + опция через env). +- Транзиентный фейл `send` не должен обнулять `tracker_message_id` и плодить дубли + (инвариант уже заложен в коде — сохранить). + +### 4.2. Требование 2 — статусы карточки как в Plane (модель ORCH-066) +- В шапке/верхней части карточки явно отображается **текущий Plane-статус** задачи по + модели ORCH-066. +- Полный маппинг состояний (имена — финальные из модели ORCH-066): + ``` + To Analyse → Analysis → In Review (⏸️ ожидание согласования BRD) → Architecture → + Development → Code-Review → Testing → Awaiting Deploy (⏸️ ожидание Confirm Deploy) → + Deploying → Monitoring after Deploy → Done + ``` + Ветки: `Needs Input` (аналитик задал вопросы), `Blocked`, `Rejected`, `Cancelled`. +- Человеческие гейты отражаются как ПОЛНОЦЕННЫЕ статусы с паузой: + - согласование BRD → «⏸️ In Review — ожидание согласования BRD»; + - ожидание прод-деплоя → «⏸️ Awaiting Deploy — ожидание Confirm Deploy»; + - вопросы аналитика → «❓ Needs Input — нужны уточнения». +- Существующая семантика строки «Подтверждение BRD» сохраняется (время ожидания/«твоё + время»), но статус карточки при этом явно показывает In Review (approve-pending). + +### 4.3. Требование 3 — кликабельный номер задачи в карточке +- `work_item_id` (напр. `ORCH-066`) в карточке — гиперссылка на страницу задачи Plane: + `https:////projects//issues//`. +- Источники частей URL: + - `PLANE_WEB_BASE` — из конфигурации (env, поле `plane_web_url` / `ORCH_PLANE_WEB_URL`; + значение прод — `plane.mva154.duckdns.org`); fail-safe: не задан → номер без ссылки; + - `workspace_slug` — `plane_workspace_slug` (уже есть в settings, прод — `ag_proj`); + - `project_id` — резолвится per-task по репозиторию задачи (ORCH / Sandbox); + - `issue_id` (UUID) — из БД: колонка `tasks.plane_issue_id`. +- Рендер через `ORCH-NNN` (`parse_mode=HTML` уже включён); + HTML в title/тексте экранируется, чтобы не сломать разметку. + +### 4.4. Требование 4 — кликабельный номер во ВСЕХ уведомлениях орка +- Единый хелпер (напр. `plane_issue_link(work_item_id, plane_issue_id, project_id) -> html`) + строит кликабельный номер с fail-safe; применяется во всех точках `send_telegram`/ + `notify_*`, где упоминается `work_item_id` (approve-requested, QG-fail, deploy + SUCCESS/FAIL, Needs Input, прод-деплой, alert'ы launcher/merge_gate/job_reaper/ + security_gate/reconciler/main). + +## 5. Вне объёма (out of scope) + +- Транспорт `send_telegram` / `edit_telegram` / `delete_telegram` (parse_mode HTML уже есть) — не трогать. +- Инвариант «одна карточка на задачу» — не нарушать (не плодить дубли). +- Логика `disable_notification` (карточка тихая; пингуют только alert-хелперы) — не трогать. +- `STAGE_TRANSITIONS`, Quality Gates, схема БД — НЕ менять. +- Изменение поведения агентов/конвейера. + +## 6. Зависимости + +- Маппинг статусов (требование 2) опирается на статусную модель ORCH-066. ORCH-066 уже в + конвейере на стадии deploy. Эту задачу делать ПОСЛЕ прода ORCH-066, чтобы имена статусов + совпали. Если ORCH-066 ещё не в проде на момент разработки — использовать согласованные + финальные имена из модели: To Analyse, Analysis, Code-Review, Awaiting Deploy, Deploying, + Monitoring after Deploy, In Review, Needs Input, Blocked, Cancelled, Done. +- Конфигурация `plane_web_url` / `plane_workspace_slug` уже существует в `src/config.py` + (ORCH-017); реестр проектов `src/projects.py` (`get_project_by_repo().plane_project_id`) + уже даёт per-task project_id. + +## 7. Fail-safe (обязательно) + +- Нет `PLANE_WEB_BASE` / нет `plane_issue_id` / нет `project_id` / loopback-база → + показывать номер БЕЗ ссылки, **не падать**. +- HTML-экранирование пользовательского текста (title и пр.) во всех сообщениях с + `parse_mode=HTML`. +- Bump: транзиентный фейл `send` не обнуляет `tracker_message_id` и не плодит дубли. +- Любая ошибка построения статуса/ссылки никогда не должна ронять рендер карточки или + отправку уведомления (degrade gracefully). + +## 8. Критерии успеха (Definition of Done) + +- Bump работает из коробки: карточка падает вниз при обновлении, одна на задачу. +- Карточка показывает Plane-статус новой модели, включая `⏸️ In Review` (согласование BRD), + `⏸️ Awaiting Deploy`, `❓ Needs Input`. +- Номер задачи кликабелен в карточке И во всех уведомлениях орка (ведёт на страницу Plane). +- Fail-safe покрыт тестами (нет URL/plane_id/project → без ссылки, не падает; + HTML-экранирование). +- `pytest tests/ -q` зелёный. +- Документация обновлена в том же PR: `CLAUDE.md` (раздел нотификаций/tracker), + `CHANGELOG.md`, ADR per-work-item. + +## 9. Риски + +- **Регресс enduro-trails.** Смена дефолта `tracker_mode` на bump меняет поведение для всех + проектов. Митигация: bump уже реализован и протестирован концептуально; инвариант «одна + карточка» сохранён; env-переключатель `edit` остаётся. +- **Поломка HTML-разметки** при неэкранированном title → сообщение не доставится. Митигация: + обязательное `html.escape` + тесты. +- **Источник «истинного» Plane-статуса** для веток, не выводимых из `tasks.stage` + (Needs Input/Blocked/Rejected/Cancelled, Deploying/Monitoring), при запрете на изменение + схемы БД — архитектурное решение (ADR), с обязательным fail-safe (без сети не падать). +- **Self-hosting.** Орк правит сам себя; обязательна страховка через staging (8501) перед + прод-деплоем; прод-контейнер не ронять в рамках задачи. diff --git a/docs/work-items/ORCH-067/02-trz.md b/docs/work-items/ORCH-067/02-trz.md new file mode 100644 index 0000000..e50c162 --- /dev/null +++ b/docs/work-items/ORCH-067/02-trz.md @@ -0,0 +1,205 @@ +# ТЗ — ORCH-067: Telegram tracker (bump + статусы Plane + кликабельный номер задачи) + +Work Item: **ORCH-067** +Документ описывает КОНКРЕТНЫЕ изменения кода/конфигурации/тестов и документации. +Архитектурные развилки помечены `[ARCH]` — решение принимает архитектор (ADR), здесь +зафиксированы только требования и ограничения к ним. + +--- + +## 0. Задействованные модули `src/` + +| Модуль | Роль в задаче | +|---|---| +| `src/config.py` | Дефолт `tracker_mode`; поле `plane_web_url`/`plane_workspace_slug` (уже есть). | +| `src/notifications.py` | Основные изменения: bump-дефолт, статус-строка карточки, хелпер ссылки, применение хелпера в `notify_*`. | +| `src/plane_sync.py` | Источник имён статусов/маппинга ORCH-066 (`_PLANE_NAME_TO_KEY`, `_STAGE_TO_STATE_KEY`); при необходимости reverse-map UUID→имя `[ARCH]`. | +| `src/projects.py` | `get_project_by_repo(repo).plane_project_id` — per-task project_id для ссылки. | +| `src/db.py` | Чтение `tasks.plane_issue_id`, `tasks.repo` (без изменений схемы). | +| `src/stage_engine.py`, `src/agents/launcher.py`, `src/merge_gate.py`, `src/job_reaper.py`, `src/security_gate.py`, `src/reconciler.py`, `src/main.py` | Точки `send_telegram`, где есть `work_item_id` — применить хелпер ссылки (требование 4). | + +Изменения API (HTTP endpoints) — **нет**. Изменения схемы БД — **нет**. Новые QG checks — **нет**. + +--- + +## 1. Требование 1 — bump по умолчанию + +### 1.1. Изменение +- `src/config.py` (~стр. 408): сменить дефолт + `tracker_mode: str = "edit"` → `tracker_mode: str = "bump"`. +- Обновить docstring-комментарий рядом (ORCH-042): отметить, что **дефолт теперь `bump`**, + `edit` остаётся доступен через `ORCH_TRACKER_MODE=edit`. + +### 1.2. Без изменений (сохранить инвариант) +- Логика `update_task_tracker` (`src/notifications.py`, ветка `if mode == "bump"`): + `delete_telegram(old)` best-effort → `send_telegram(text, disable_notification=True)` → + `set_tracker_message_id` ТОЛЬКО при `new_mid is not None`. Не менять. +- `send_telegram`/`edit_telegram`/`delete_telegram` — не трогать. + +### 1.3. Прод-аспект +- Для прод-инстанса орка можно дополнительно выставить `ORCH_TRACKER_MODE=bump` в `.env` + на хосте (как страховку), но код должен работать «из коробки» и без env. Канон env — + `.env.example` (обновить, если там фигурирует tracker_mode). + +--- + +## 2. Требование 2 — статус-строка карточки по модели ORCH-066 + +### 2.1. Новый чистый хелпер маппинга +Добавить в `src/notifications.py` функцию, возвращающую отображаемый Plane-статус для +карточки на основе доступных данных задачи. Сигнатура (ориентир): +```python +def plane_status_label(task_row) -> str: + """Вернуть строку текущего Plane-статуса для шапки карточки (с emoji). + Никогда не падает: на неизвестном входе -> разумный дефолт по stage.""" +``` +Хелпер обязан быть чистым/детерминированным от входных данных и **никогда не бросать** +исключения (любая ошибка → дефолт по `stage`, рендер карточки не ломается). + +### 2.2. Маппинг внутреннее состояние → Plane-статус (обязательные строки) +Имена статусов — финальные из модели ORCH-066 (см. `_PLANE_NAME_TO_KEY` в `plane_sync.py`). + +| Источник (данные задачи в БД) | Plane-статус (отображение в карточке) | +|---|---| +| `stage == "created"` | `To Analyse` | +| `stage == "analysis"`, BRD-clock не запущен | `Analysis` | +| `stage == "analysis"`, `brd_review_started_at` есть, `brd_review_ended_at` пуст | `⏸️ In Review — ожидание согласования BRD` | +| `stage == "architecture"` | `Architecture` | +| `stage == "development"` | `Development` | +| `stage == "review"` | `Code-Review` | +| `stage == "testing"` | `Testing` | +| `stage == "deploy"` (ожидание Confirm Deploy) | `⏸️ Awaiting Deploy — ожидание Confirm Deploy` | +| `stage == "done"` | `Done` | + +Ветки (Needs Input / Blocked / Rejected / Cancelled / Deploying / Monitoring after Deploy): +- `❓ Needs Input — нужны уточнения` — состояние «аналитик задал вопросы»; +- `Blocked`, `Rejected`, `Cancelled`, `Deploying`, `Monitoring after Deploy`. + +`[ARCH]` **Источник сигнала для веток, не выводимых из `tasks.stage`** (Needs Input, +Blocked, Rejected, Cancelled, Deploying, Monitoring after Deploy): +- запрещено менять схему БД (нельзя добавлять колонку-флаг); +- варианты для архитектора: (а) best-effort чтение живого Plane-статуса + (`fetch_issue_state` + reverse-map UUID→имя через `get_project_states`/ + `_PLANE_NAME_TO_KEY`) с обязательным fail-safe (нет сети/ответа → деградация на + stage-маппинг, без задержки, блокирующей конвейер); (б) только stage-выводимые статусы, + а ветки — по уже имеющимся сигналам (например, In Review через brd-clock). +- ОБЯЗАТЕЛЬНО к покрытию (DoD): `⏸️ In Review`, `⏸️ Awaiting Deploy`, `❓ Needs Input`. + In Review полностью выводится из brd-clock (см. таблицу) и должен работать без сети. + +### 2.3. Встраивание в `render_task_tracker` +- В `render_task_tracker` (`src/notifications.py`) добавить в шапку/верх карточки отдельную + СТРОКУ статуса (под заголовком `🛠️ ORCH-NNN · ` / над разделителем `bar`), + напр.: `📍 <status_label>`. +- Существующие строки по стадиям (`✅ done` / `🔄 active`), строка «Подтверждение BRD», + тоталы токенов/стоимости, done-строка с PR/⏱️ — СОХРАНИТЬ (семантику не ломать). +- Семантика строки «Подтверждение BRD» (⏸️+⏳ при ожидании, ✅ при пройденном гейте) + сохраняется; новая статус-строка дублирует её смысл в терминах Plane-статуса. + +--- + +## 3. Требование 3 + 4 — кликабельный номер задачи + +### 3.1. Единый хелпер +Добавить в `src/notifications.py`: +```python +def plane_issue_link(work_item_id, plane_issue_id=None, project_id=None, repo=None) -> str: + """Вернуть HTML с кликабельным номером задачи (<a href=...>ORCH-NNN</a>), + либо просто html.escape(work_item_id), если ссылку построить нельзя. + Никогда не падает.""" +``` +Поведение: +- База URL: `settings.plane_web_url` → fallback `settings.plane_api_url`; loopback-база + (`localhost`/`127.0.0.1`/…) трактуется как «нет web URL» (переиспользовать + `_is_loopback_base`). +- `workspace_slug`: `settings.plane_workspace_slug`. +- `project_id`: явный аргумент → иначе резолв по `repo` через + `get_project_by_repo(repo).plane_project_id`. +- `issue_id`: `plane_issue_id` (UUID из `tasks.plane_issue_id`). +- URL-шаблон: `{web_base}/{workspace}/projects/{project_id}/issues/{issue_id}/`. +- Текст ссылки = `html.escape(work_item_id)`; `href` = `html.escape(url, quote=True)`. +- **Fail-safe:** если не хватает любого из (`web_base` валидный/не loopback, `workspace`, + `project_id`, `plane_issue_id`) → вернуть `html.escape(work_item_id)` (номер без ссылки). +- Логика построения URL уже существует в `_build_plane_issue_link` (ORCH-017) — допустимо + переиспользовать/обобщить её, разнеся «текст-ссылки = номер» и «текст-ссылки = `✅ Задача + в Plane`», чтобы не дублировать резолв проекта и loopback-guard. + +### 3.2. Применение в карточке (требование 3) +- В `render_task_tracker` заголовок строится из `work_item_id`. Заменить + `html.escape(work_item_id)` в обоих вариантах заголовка (done / not-done) на + `plane_issue_link(work_item_id, plane_issue_id, repo=repo)` — номер становится + кликабельным. +- Для этого `render_task_tracker` должен дополнительно выбрать из БД `repo` и + `plane_issue_id` (расширить существующий `SELECT` по `tasks`). Схему НЕ менять — колонки + уже есть. +- `title` уже экранируется (`html.escape(title)`) — сохранить. + +### 3.3. Применение во всех уведомлениях (требование 4) +Во всех точках `send_telegram`/`notify_*`, где в тексте есть `work_item_id`, заменить +«сырой» номер на `plane_issue_link(...)`. Перечень точек (из `src`): +- `src/notifications.py`: `notify_approve_requested`, `notify_error` + (и любые будущие notify_* с work_item_id); +- `src/stage_engine.py`: все `send_telegram(...)` с `work_item_id` + (≈ строки 613, 672, 719, 776, 820, 916, 971, 1057, 1134, 1192, 1228, 1257, 1355, 1367, + 1425, 1447, 1601 — проверить каждую: применять ТОЛЬКО где упоминается номер задачи); +- `src/agents/launcher.py`: deploy-failed alert (≈685–686), agent-failed alert (≈698–699), + alert ≈821–822; +- `src/merge_gate.py` (≈431–432); +- `src/job_reaper.py` (≈395–396); +- `src/security_gate.py` (≈673–674); +- `src/reconciler.py` (≈449); +- `src/main.py` (≈45–47). + +`[ARCH]` Способ доступа к `plane_issue_id`/`project_id` в каждой точке (часто там уже есть +`work_item_id`, но не обязательно `plane_issue_id`): хелпер должен уметь резолвить +недостающее по `repo`/БД, оставаясь fail-safe. Допустимо добавить тонкую обёртку, которая по +`work_item_id`/`task_id` достаёт `repo`+`plane_issue_id` из БД и зовёт `plane_issue_link` +(аналогично существующему `_get_task_link_fields`). Везде, где данных нет — деградация на +просто номер, без падения. + +### 3.4. HTML-экранирование +- `parse_mode=HTML` уже стоит в `send_telegram`/`edit_telegram`. Любой пользовательский + текст (title, описания, причины QG-fail, сообщения об ошибках), попадающий в сообщение с + ссылками, должен экранироваться `html.escape`, чтобы не сломать `<a>`-разметку. + +--- + +## 4. Конфигурация + +- `plane_web_url` (env `ORCH_PLANE_WEB_URL`) — уже существует (`src/config.py`), значение + прод — `plane.mva154.duckdns.org` (схему `https://` учесть при сборке URL). + Дополнительных полей конфигурации не требуется. +- `tracker_mode` — сменить дефолт на `bump` (раздел 1). +- Обновить `.env.example`, если в нём фигурируют `ORCH_TRACKER_MODE` / `ORCH_PLANE_WEB_URL` + (канон секретов/настроек — `.env.example`, не коммитить реальные секреты). + +--- + +## 5. Артефакты pipeline, которые должны быть созданы/обновлены + +- `docs/work-items/ORCH-067/06-adr/ADR-NNN-*.md` — архитектурное решение (минимум: источник + «истинного» Plane-статуса для веток при запрете изменения схемы БД; дефолт bump; единый + хелпер ссылки). +- `CLAUDE.md` — раздел про нотификации/tracker (дефолт bump; статус-строка карточки; + кликабельный номер в карточке и уведомлениях). +- `CHANGELOG.md` — запись ORCH-067. +- `docs/architecture/README.md` — при необходимости синхронизировать описание tracker'а. + +--- + +## 6. Ограничения (что НЕ трогать) + +- Транспорт `send_telegram`/`edit_telegram`/`delete_telegram`. +- Инвариант «одна карточка на задачу». +- Логику `disable_notification` (карточка тихая; пингуют только alert-хелперы). +- `STAGE_TRANSITIONS`, Quality Gates, схему БД. +- Поведение агентов/конвейера. + +--- + +## 7. Замечания по самохостингу + +Орк правит сам себя в проде (общий инстанс/БД с enduro-trails): +- НЕ перезапускать прод-контейнер `orchestrator` в рамках задачи. +- Обязательная страховка через `deploy-staging` (8501) до прод-деплоя. +- Смена дефолта `tracker_mode` затрагивает ВСЕ проекты — проверить отсутствие регресса для + enduro-trails (тесты + staging-наблюдение карточки). diff --git a/docs/work-items/ORCH-067/03-acceptance-criteria.md b/docs/work-items/ORCH-067/03-acceptance-criteria.md new file mode 100644 index 0000000..5283bdd --- /dev/null +++ b/docs/work-items/ORCH-067/03-acceptance-criteria.md @@ -0,0 +1,129 @@ +# Acceptance Criteria — ORCH-067 + +Work Item: **ORCH-067** +Каждый критерий формулирует чёткое условие PASS/FAIL. Привязка к тестам — в `04-test-plan.yaml`. + +--- + +## Группа A — Bump по умолчанию (Требование 1) + +### AC-1 — дефолт tracker_mode = bump +- **PASS:** `Settings().tracker_mode == "bump"` без выставленного env `ORCH_TRACKER_MODE`. +- **FAIL:** дефолт остался `"edit"` или иное. + +### AC-2 — bump-поведение: одна карточка падает вниз +- **PASS:** при втором (и последующем) вызове `update_task_tracker` для задачи с уже + сохранённым `tracker_message_id` вызывается `delete_telegram(old_id)` (best-effort), + затем `send_telegram(...)` с `disable_notification=True`, затем `set_tracker_message_id` + на новый id. В чате остаётся ровно одна карточка на задачу. +- **FAIL:** карточка редактируется на месте при дефолте; либо появляются дубли; либо новая + карточка отправляется со звуком (`disable_notification` не True). + +### AC-3 — bump fail-safe: транзиентный фейл send не обнуляет указатель +- **PASS:** если `send_telegram` вернул `None` (нет креды/транзиентный фейл), + `tracker_message_id` НЕ перезаписывается в `None` и дубликат в рамках вызова не создаётся. +- **FAIL:** указатель обнулён или создан второй card-месседж в одном вызове. + +### AC-4 — режим edit остаётся доступен через env +- **PASS:** при `ORCH_TRACKER_MODE=edit` поведение прежнее (editMessageText, fallback на + новый месседж только при EDIT_GONE). +- **FAIL:** edit-режим сломан/недоступен. + +--- + +## Группа B — Статус-строка карточки по модели ORCH-066 (Требование 2) + +### AC-5 — статус-строка присутствует в карточке +- **PASS:** `render_task_tracker(task_id)` содержит явную строку текущего Plane-статуса + (напр. `📍 <status>`) в шапке/верхней части карточки. +- **FAIL:** статус-строки нет. + +### AC-6 — корректный маппинг stage → Plane-статус +- **PASS:** для всех stage-выводимых состояний строка статуса соответствует таблице ТЗ §2.2: + `created→To Analyse`, `analysis→Analysis`, `architecture→Architecture`, + `development→Development`, `review→Code-Review`, `testing→Testing`, + `deploy→Awaiting Deploy`, `done→Done`. +- **FAIL:** хотя бы один stage маппится на неверное имя/внутренний ярлык. + +### AC-7 — In Review (ожидание согласования BRD) как полноценный статус +- **PASS:** при `stage == "analysis"`, `brd_review_started_at` задан и + `brd_review_ended_at` пуст — статус-строка явно отражает `⏸️ In Review` с пометкой + «ожидание согласования BRD»; при этом существующая строка «Подтверждение BRD …» с ⏸️/⏳ + сохранена. Работает без сетевых вызовов. +- **FAIL:** In Review теряется/не показан как статус, либо строка «Подтверждение BRD» исчезла. + +### AC-8 — Awaiting Deploy и Needs Input отражены +- **PASS:** состояние ожидания Confirm Deploy показывается как + `⏸️ Awaiting Deploy — ожидание Confirm Deploy`; состояние вопросов аналитика — как + `❓ Needs Input — нужны уточнения`. +- **FAIL:** любое из этих состояний не отражено в статус-строке. + +### AC-9 — рендер карточки никогда не падает +- **PASS:** при любой ошибке построения статуса (битые данные, недоступный источник) + `render_task_tracker` возвращает корректную карточку (деградация на stage-маппинг или + fallback-строку), исключение наружу не выходит. +- **FAIL:** `render_task_tracker` бросает исключение. + +--- + +## Группа C — Кликабельный номер в карточке (Требование 3) + +### AC-10 — номер задачи в карточке — гиперссылка +- **PASS:** при наличии `plane_web_url` (не loopback), `plane_workspace_slug`, `project_id` + (резолв по repo) и `plane_issue_id` карточка содержит + `<a href="https://<base>/<ws>/projects/<pid>/issues/<issue_id>/">ORCH-NNN</a>`. +- **FAIL:** номер выводится сырым текстом при наличии всех данных, либо URL собран неверно. + +### AC-11 — fail-safe ссылки в карточке +- **PASS:** при отсутствии любого из (web_base/не-loopback, workspace, project_id, + plane_issue_id) карточка показывает номер БЕЗ ссылки (`html.escape(work_item_id)`) и не + падает. +- **FAIL:** падение, пустая ссылка `<a href="">`, либо битый `<a>` тег. + +--- + +## Группа D — Кликабельный номер во всех уведомлениях (Требование 4) + +### AC-12 — единый хелпер ссылки +- **PASS:** существует `plane_issue_link(...)`, возвращающий HTML-ссылку при достаточных + данных и `html.escape(work_item_id)` при недостаточных; никогда не бросает. +- **FAIL:** хелпера нет, либо он падает на неполных данных. + +### AC-13 — хелпер применён во всех уведомлениях с work_item_id +- **PASS:** во всех точках `send_telegram`/`notify_*` из ТЗ §3.3, где упоминается + `work_item_id` (`notify_approve_requested`, `notify_error`, alert'ы stage_engine, + launcher, merge_gate, job_reaper, security_gate, reconciler, main), номер задачи + кликабелен (при наличии данных) и ведёт на ту же страницу Plane. +- **FAIL:** хотя бы одна такая точка выводит номер сырым текстом при наличии данных. + +### AC-14 — HTML-экранирование пользовательского текста +- **PASS:** title/причины/сообщения с потенциальным HTML (`<`, `>`, `&`) экранируются + `html.escape`; разметка `<a>` остаётся валидной; сообщение проходит `parse_mode=HTML`. +- **FAIL:** неэкранированный текст ломает разметку (тест с title, содержащим `<b>`/`&`, + обнаруживает поломку). + +--- + +## Группа E — Нерегресс и качество + +### AC-15 — инварианты транспорта/нотификаций сохранены +- **PASS:** `send_telegram`/`edit_telegram`/`delete_telegram` не изменены по сигнатуре/ + семантике; карточка тихая (`disable_notification=True`); инвариант «одна карточка на + задачу» соблюдён; `STAGE_TRANSITIONS`/QG/схема БД не тронуты. +- **FAIL:** изменён транспорт, карточка пингует, появились дубли, тронута схема БД/QG. + +### AC-16 — нет регресса для enduro-trails +- **PASS:** существующие тесты нотификаций (`test_notify_approve_links.py`, + `test_notify_done_regression.py` и др.) проходят; поведение карточки для не-ORCH проектов + без новых Plane-статусов деградирует корректно (alias-fallback, без ссылки при нехватке + данных). +- **FAIL:** падение существующих тестов или сломанная карточка для enduro. + +### AC-17 — весь набор тестов зелёный +- **PASS:** `pytest tests/ -q` зелёный. +- **FAIL:** любой упавший тест. + +### AC-18 — документация обновлена в том же PR +- **PASS:** обновлены `CLAUDE.md` (раздел нотификаций/tracker), `CHANGELOG.md`, + создан ADR per-work-item. +- **FAIL:** функционал изменён, документация — нет (reviewer → REQUEST_CHANGES). diff --git a/docs/work-items/ORCH-067/04-test-plan.yaml b/docs/work-items/ORCH-067/04-test-plan.yaml new file mode 100644 index 0000000..073e1b3 --- /dev/null +++ b/docs/work-items/ORCH-067/04-test-plan.yaml @@ -0,0 +1,181 @@ +work_item: ORCH-067 +description: > + План тестов для ORCH-067 (Telegram tracker: bump по умолчанию, статус-строка + карточки по модели Plane ORCH-066, кликабельный номер задачи в карточке и во + всех уведомлениях орка). Сеть изолируется: send_telegram/edit_telegram/ + delete_telegram подменяются рекордерами (как в tests/conftest.py и + tests/test_notify_approve_links.py); БД — временный SQLite, сидируемый фикстурой. + +tests: + # --- Группа A: bump по умолчанию (AC-1..AC-4) --- + - id: TC-01 + type: unit + description: "Дефолт Settings().tracker_mode == 'bump' без env ORCH_TRACKER_MODE" + module: tests/test_tracker_bump_default.py + asserts: "AC-1" + expected: PASS + + - id: TC-02 + type: unit + description: > + bump-поведение: при повторном update_task_tracker с сохранённым + tracker_message_id вызывается delete_telegram(old) -> send_telegram(..., + disable_notification=True) -> set_tracker_message_id(new). Одна карточка. + module: tests/test_tracker_bump_default.py + asserts: "AC-2" + expected: PASS + + - id: TC-03 + type: unit + description: > + bump fail-safe: send_telegram вернул None (нет креды/транзиент) -> + tracker_message_id не обнуляется, дубликат в вызове не создаётся. + module: tests/test_tracker_bump_default.py + asserts: "AC-3" + expected: PASS + + - id: TC-04 + type: unit + description: "ORCH_TRACKER_MODE=edit -> прежнее edit-поведение (editMessageText)" + module: tests/test_tracker_bump_default.py + asserts: "AC-4" + expected: PASS + + # --- Группа B: статус-строка карточки (AC-5..AC-9) --- + - id: TC-05 + type: unit + description: "render_task_tracker содержит явную строку текущего Plane-статуса" + module: tests/test_tracker_status_line.py + asserts: "AC-5" + expected: PASS + + - id: TC-06 + type: unit + description: > + Маппинг stage -> Plane-статус по таблице ТЗ §2.2: created->To Analyse, + analysis->Analysis, architecture->Architecture, development->Development, + review->Code-Review, testing->Testing, deploy->Awaiting Deploy, done->Done + (параметризованный тест по всем stage). + module: tests/test_tracker_status_line.py + asserts: "AC-6" + expected: PASS + + - id: TC-07 + type: unit + description: > + analysis + brd_review_started_at задан + brd_review_ended_at пуст -> + статус '⏸️ In Review' (ожидание согласования BRD); строка 'Подтверждение + BRD' с ⏸️/⏳ сохранена; без сетевых вызовов. + module: tests/test_tracker_status_line.py + asserts: "AC-7" + expected: PASS + + - id: TC-08 + type: unit + description: > + Awaiting Deploy ('ожидание Confirm Deploy') и Needs Input ('нужны + уточнения') корректно отражаются в статус-строке. + module: tests/test_tracker_status_line.py + asserts: "AC-8" + expected: PASS + + - id: TC-09 + type: unit + description: > + render_task_tracker не падает при битых/недоступных данных статуса + (деградация на stage-маппинг/fallback, исключение не наружу). + module: tests/test_tracker_status_line.py + asserts: "AC-9, AC-16" + expected: PASS + + # --- Группа C: кликабельный номер в карточке (AC-10..AC-11) --- + - id: TC-10 + type: unit + description: > + При полных данных (plane_web_url не loopback, workspace, project_id по repo, + plane_issue_id) карточка содержит <a href=".../issues/<id>/">ORCH-NNN</a> + с корректным URL. + module: tests/test_tracker_issue_link.py + asserts: "AC-10" + expected: PASS + + - id: TC-11 + type: unit + description: > + Fail-safe ссылки в карточке: при отсутствии любого из (web_base/не-loopback, + workspace, project_id, plane_issue_id) номер выводится html.escape без <a>, + рендер не падает. Параметризовать по каждому отсутствующему полю. + module: tests/test_tracker_issue_link.py + asserts: "AC-11" + expected: PASS + + # --- Группа D: единый хелпер и уведомления (AC-12..AC-14) --- + - id: TC-12 + type: unit + description: > + plane_issue_link(...) возвращает HTML-ссылку при достаточных данных и + html.escape(work_item_id) при недостаточных; никогда не бросает (в т.ч. на + None-аргументах и loopback-базе). + module: tests/test_plane_issue_link.py + asserts: "AC-12" + expected: PASS + + - id: TC-13 + type: unit + description: > + notify_approve_requested: номер задачи кликабелен (ведёт на страницу Plane), + сохранён call-to-action 'Approved', ровно одно notifying-сообщение. + module: tests/test_notify_issue_links.py + asserts: "AC-13" + expected: PASS + + - id: TC-14 + type: unit + description: > + notify_error: номер задачи кликабелен при наличии данных, деградирует на + сырой номер без падения при их отсутствии. + module: tests/test_notify_issue_links.py + asserts: "AC-13, AC-12" + expected: PASS + + - id: TC-15 + type: integration + description: > + Точки send_telegram в stage_engine/launcher/merge_gate/job_reaper/ + security_gate/reconciler/main, где есть work_item_id, используют + plane_issue_link (или эквивалент) — номер кликабелен. Проверка рекордером + send_telegram на представительных alert-путях (deploy fail, agent fail, + QG fail, прод-деплой). + module: tests/test_notify_issue_links.py + asserts: "AC-13" + expected: PASS + + - id: TC-16 + type: unit + description: > + HTML-экранирование: title с '<b>'/'&'/'>' экранируется, <a>-разметка + остаётся валидной, сообщение не ломается под parse_mode=HTML (карточка и + уведомления). + module: tests/test_tracker_issue_link.py + asserts: "AC-14" + expected: PASS + + # --- Группа E: нерегресс (AC-15..AC-18) --- + - id: TC-17 + type: integration + description: > + Инварианты: карточка отправляется с disable_notification=True; одна карточка + на задачу; транспорт send/edit/delete не изменён по семантике. + module: tests/test_tracker_bump_default.py + asserts: "AC-15" + expected: PASS + + - id: TC-18 + type: integration + description: > + Нерегресс существующих тестов нотификаций (test_notify_approve_links.py, + test_notify_done_regression.py) и корректная деградация карточки для + enduro-trails без новых Plane-статусов. + module: tests/test_notify_done_regression.py + asserts: "AC-16, AC-17" + expected: PASS