diff --git a/docs/work-items/ORCH-080/01-brd.md b/docs/work-items/ORCH-080/01-brd.md new file mode 100644 index 0000000..772c8e6 --- /dev/null +++ b/docs/work-items/ORCH-080/01-brd.md @@ -0,0 +1,73 @@ +# 01-BRD — ORCH-080: убрать Telegram link-preview (логотип Plane) в уведомлениях трекера + +Work Item ID: ORCH-080 +Эпик: ORCH-052 (под-задача ORCH-52g) +Тип: Доработка (UX уведомлений) +Приоритет: LOW (косметика) +Зона: `src/notifications.py` + +## 1. Контекст и проблема + +Каждая задача в Telegram сопровождается одной live-карточкой трекера (`src/notifications.py`, +ORCH-042/066/067). С ORCH-067 в карточке появился **кликабельный номер задачи** — +`ORCH-NNN`. + +Telegram по умолчанию разворачивает **link-preview** (web page preview) для первой ссылки +в сообщении. Из-за ссылки на Plane под каждым сообщением трекера раскрывается крупный +баннер-превью **«Plane — Modern project management»**. + +**Жалоба (Слава, 08.06):** баннер уродует ленту чата и дублируется на каждой задаче/каждом +обновлении карточки (особенно заметно в дефолтном режиме `bump`, где карточка пересоздаётся +на каждом переходе). + +## 2. Диагностика (код-аудит `src/notifications.py`) + +| Функция | Эндпоинт | Текущий JSON-payload | Превью | +|---------|----------|----------------------|--------| +| `send_telegram()` (стр. 52-62) | `POST /sendMessage` | `chat_id`, `text`, `parse_mode: HTML`, `disable_notification` | **разворачивается** (нет `disable_web_page_preview`) | +| `edit_telegram()` (стр. 165-174) | `POST /editMessageText` | `chat_id`, `message_id`, `text`, `parse_mode: HTML` | **разворачивается** (нет `disable_web_page_preview`) | + +Причина баннера: оба payload **не содержат** ключ `disable_web_page_preview`. Telegram Bot API +по умолчанию (отсутствие ключа) включает превью. + +`delete_telegram()` (`/deleteMessage`) превью не порождает — правки не требует. + +## 3. Бизнес-цель + +Карточка трекера и уведомления в Telegram **не должны** показывать баннер link-preview Plane, +при этом ссылка на задачу **остаётся кликабельной**. + +## 4. Бизнес-требования + +- **BR-1.** В payload `sendMessage` (`send_telegram`) присутствует `disable_web_page_preview: True`. +- **BR-2.** В payload `editMessageText` (`edit_telegram`) присутствует `disable_web_page_preview: True`. +- **BR-3.** Баннер-превью Plane больше не появляется ни под карточкой трекера (оба режима + `bump`/`edit`), ни под отдельными notify-сообщениями, которые идут через `send_telegram` + (`notify_approve_requested`, `notify_error`, alert'ы стадий) — все они используют тот же + низкоуровневый примитив. +- **BR-4.** Кликабельная ссылка `` на задачу в Plane сохраняется (`parse_mode: HTML` + не меняется). +- **BR-5.** Контракт **never-raise** сохранён: отправка/редактирование никогда не валит + оркестратор; `pytest` зелёный. + +## 5. Не-цели (вне скоупа) + +- Не менять текст/формат/верстку карточки. +- Не трогать `parse_mode` (HTML нужен для ``). +- Не трогать bump/edit-логику (`update_task_tracker`), репойнт `tracker_message_id`, + delete-семантику. +- Не вводить флаги/конфиг — поведение «без превью» безусловное (превью никому не нужно). +- Не трогать схему БД. + +## 6. Заинтересованные лица + +- **Слава (Owner)** — инициатор, конечный наблюдатель ленты Telegram. + +## 7. Грабли / координация + +- Файл `src/notifications.py` затрагивает также ORCH-067 (и потенциально другие задачи эпика). + Сверить, что правки (две строки) не конфликтуют при merge. +- Один репозиторий с ORCH-74 → по ORCH-026 действует сериализация merge. + Запускать **после** того как ORCH-74 доедет в `main` (или когда конвейер свободен), + чтобы не плодить параллельный merge в `orchestrator`. +- Деплой — штатный через **Confirm Deploy** (self-hosting, ORCH-059). diff --git a/docs/work-items/ORCH-080/02-trz.md b/docs/work-items/ORCH-080/02-trz.md new file mode 100644 index 0000000..d60278e --- /dev/null +++ b/docs/work-items/ORCH-080/02-trz.md @@ -0,0 +1,102 @@ +# 02-TRZ — ORCH-080: убрать Telegram link-preview в уведомлениях трекера + +Work Item ID: ORCH-080 +Зона изменений: `src/notifications.py` (две строки) + +## 1. Задействованные модули `src/` + +- `src/notifications.py` — **единственный** изменяемый модуль: + - `send_telegram(text, disable_notification=False)` — обёртка `POST .../sendMessage`. + - `edit_telegram(message_id, text)` — обёртка `POST .../editMessageText`. + +Косвенно затронуты (поведение улучшается без изменения их кода — они вызывают изменённые +примитивы): `update_task_tracker` (bump+edit), `notify_approve_requested`, `notify_error`, +а также вызовы `send_telegram` из `launcher`/`stage_engine` (alert'ы деплоя/падений). + +## 2. Изменения кода + +### 2.1. `send_telegram()` — добавить ключ в JSON-payload `httpx.post` + +В словаре `json={...}` вызова `sendMessage` (текущие стр. 55-60) добавить строку: + +```python +"disable_web_page_preview": True, +``` + +Итоговый payload: +```python +json={ + "chat_id": s.telegram_chat_id, + "text": text, + "parse_mode": "HTML", + "disable_notification": disable_notification, + "disable_web_page_preview": True, +}, +``` + +### 2.2. `edit_telegram()` — добавить ключ в JSON-payload `httpx.post` + +В словаре `json={...}` вызова `editMessageText` (текущие стр. 168-173) добавить строку: + +```python +"disable_web_page_preview": True, +``` + +Итоговый payload: +```python +json={ + "chat_id": s.telegram_chat_id, + "message_id": message_id, + "text": text, + "parse_mode": "HTML", + "disable_web_page_preview": True, +}, +``` + +> Примечание: Telegram Bot API исторически принимает top-level `disable_web_page_preview` +> для `sendMessage`/`editMessageText` (актуальная схема также поддерживает +> `link_preview_options.is_disabled`, но top-level флаг остаётся валиден и совместим). +> Используем top-level флаг — минимальная, обратносовместимая правка, как указано в задаче. + +## 3. Изменения API + +Нет изменений внутреннего HTTP API оркестратора. Меняется только тело исходящих запросов к +Telegram Bot API (добавлен один булев ключ в payload двух методов). + +## 4. Изменения схемы БД + +Нет. + +## 5. Требования к новым QG checks + +Нет. Новые Quality Gate проверки не вводятся. + +## 6. Конфиг / флаги + +Нет. Поведение «без превью» — безусловное (kill-switch не требуется: превью трекера +не нужно никому, риск регрессии нулевой; правка обратимая одной строкой). +`parse_mode`, `disable_notification`, bump/edit-логика — без изменений. + +## 7. Артефакты, обновляемые по pipeline + +- `CHANGELOG.md` — запись в `## [Unreleased]` (тип `fix:` — косметика UX уведомлений). +- Документация: правка `src/notifications.py` затрагивает поведение, описанное в + `CLAUDE.md` (раздел «Нотификации / Telegram live-tracker») и + `docs/architecture/README.md` (компонент Notifications). Достаточно короткой ремарки, + что карточка/уведомления шлются без web-page-preview (по желанию архитектора — определить + объём в ADR; ADR не обязателен для столь малой косметики, решение за архитектором). + +## 8. Контракты-инварианты (не нарушать) + +- **never-raise**: обе функции по-прежнему ловят все исключения (`try/except: pass`/`return`) + и не валят оркестратор. +- Возвращаемые значения не меняются: `send_telegram` → `message_id|None`, + `edit_telegram` → `EDIT_*`. +- `parse_mode: "HTML"` сохранён в обоих payload (иначе `` сломается). +- `disable_notification` в `send_telegram` сохранён (карточка тихая). +- Инвариант «одна карточка на задачу» (bump/edit) не затрагивается. + +## 9. Commit / ветка + +- Ветка: `feature/ORCH-080-orch-52g-telegram-link-preview` (существует). +- Commit: `fix: disable Telegram link-preview in tracker notifications (ORCH-080)`. diff --git a/docs/work-items/ORCH-080/03-acceptance-criteria.md b/docs/work-items/ORCH-080/03-acceptance-criteria.md new file mode 100644 index 0000000..964f564 --- /dev/null +++ b/docs/work-items/ORCH-080/03-acceptance-criteria.md @@ -0,0 +1,59 @@ +# 03-Acceptance Criteria — ORCH-080 + +Work Item ID: ORCH-080 + +Каждый критерий имеет явное условие PASS/FAIL. + +## AC-1 — `disable_web_page_preview` в payload `sendMessage` + +- **PASS:** JSON-payload вызова `httpx.post(.../sendMessage)` в `send_telegram()` содержит + ключ `"disable_web_page_preview"` со значением `True`. +- **FAIL:** ключ отсутствует или `False`. +- **Проверка:** unit-тест (мок `httpx`) инспектирует `httpx.post.call_args.kwargs["json"]`. + +## AC-2 — `disable_web_page_preview` в payload `editMessageText` + +- **PASS:** JSON-payload вызова `httpx.post(.../editMessageText)` в `edit_telegram()` содержит + ключ `"disable_web_page_preview"` со значением `True`. +- **FAIL:** ключ отсутствует или `False`. +- **Проверка:** unit-тест (мок `httpx`) инспектирует `httpx.post.call_args.kwargs["json"]`. + +## AC-3 — баннер link-preview Plane исчез в карточке трекера + +- **PASS:** в реальном чате Telegram карточка трекера задачи (режимы `bump` и `edit`) + больше не показывает баннер «Plane — Modern project management». +- **FAIL:** баннер всё ещё разворачивается. +- **Проверка:** ручная верификация на staging (8501) после деплоя — наблюдение карточки в + Telegram. Автоматически косвенно покрыто AC-1/AC-2 (payload содержит флаг). + +## AC-4 — ссылка на задачу остаётся кликабельной + +- **PASS:** в карточке/уведомлениях номер задачи `ORCH-NNN` остаётся кликабельной ссылкой + `` на issue в Plane; `parse_mode: "HTML"` сохранён в обоих payload. +- **FAIL:** `parse_mode` изменён/удалён, либо ссылка перестала рендериться как ``. +- **Проверка:** unit-тест проверяет, что `"parse_mode": "HTML"` присутствует в обоих payload; + существующие тесты ссылок (`test_notify_issue_links.py`) остаются зелёными. + +## AC-5 — сохранены существующие поля payload + +- **PASS:** `send_telegram` payload по-прежнему содержит `chat_id`, `text`, `parse_mode`, + `disable_notification`; `edit_telegram` payload — `chat_id`, `message_id`, `text`, + `parse_mode`. Возвращаемые значения функций не изменились + (`send_telegram → message_id|None`, `edit_telegram → EDIT_*`). +- **FAIL:** любое из перечисленных полей удалено/переименовано, либо изменился контракт + возврата. +- **Проверка:** unit-тесты payload + существующие тесты трекера/классификации исходов. + +## AC-6 — never-raise сохранён, pytest зелёный + +- **PASS:** при сетевой/HTTP-ошибке `send_telegram`/`edit_telegram` не бросают исключение + (возврат `None`/`EDIT_FAILED`); вся сюита `pytest tests/ -q` зелёная. +- **FAIL:** любое исключение наружу или красный pytest. +- **Проверка:** существующие тесты never-raise (`test_resilience.py`, + `test_telegram_tracker.py`) + полный прогон. + +## AC-7 — документация обновлена в том же PR + +- **PASS:** `CHANGELOG.md` содержит запись об ORCH-080; при необходимости — короткая ремарка + в `CLAUDE.md`/`docs/architecture/README.md` о подавлении link-preview. +- **FAIL:** функционал изменён, документация не обновлена (Reviewer → REQUEST_CHANGES). diff --git a/docs/work-items/ORCH-080/04-test-plan.yaml b/docs/work-items/ORCH-080/04-test-plan.yaml new file mode 100644 index 0000000..5177196 --- /dev/null +++ b/docs/work-items/ORCH-080/04-test-plan.yaml @@ -0,0 +1,76 @@ +work_item: ORCH-080 +description: > + Подавление Telegram link-preview (disable_web_page_preview: True) в payload + send_telegram (sendMessage) и edit_telegram (editMessageText). Сохранить + parse_mode HTML, disable_notification, never-raise и контракты возврата. + +tests: + - id: TC-01 + type: unit + description: > + send_telegram() кладёт "disable_web_page_preview": True в JSON-payload + httpx.post(.../sendMessage). Проверка через мок httpx и инспекцию + httpx.post.call_args.kwargs["json"]. + module: tests/test_link_preview_disabled.py + expected: PASS + + - id: TC-02 + type: unit + description: > + edit_telegram() кладёт "disable_web_page_preview": True в JSON-payload + httpx.post(.../editMessageText). Проверка через мок httpx и инспекцию + payload. + module: tests/test_link_preview_disabled.py + expected: PASS + + - id: TC-03 + type: unit + description: > + Регрессия parse_mode: оба payload (sendMessage и editMessageText) + по-прежнему содержат "parse_mode": "HTML" — ссылка остаётся + кликабельной (AC-4). + module: tests/test_link_preview_disabled.py + expected: PASS + + - id: TC-04 + type: unit + description: > + Регрессия полей send_telegram: payload содержит chat_id, text, + parse_mode, disable_notification; disable_notification прокидывается + из аргумента (True/False) без изменений (AC-5). + module: tests/test_link_preview_disabled.py + expected: PASS + + - id: TC-05 + type: unit + description: > + Контракты возврата не изменились: send_telegram возвращает message_id + при ok:true, None при отсутствии креденшелов/ошибке; edit_telegram + возвращает EDIT_OK при ok:true (AC-5, AC-6). + module: tests/test_link_preview_disabled.py + expected: PASS + + - id: TC-06 + type: unit + description: > + never-raise: при httpx.post бросающем исключение send_telegram->None и + edit_telegram->EDIT_FAILED, без проброса исключения (AC-6). + module: tests/test_link_preview_disabled.py + expected: PASS + + - id: TC-07 + type: integration + description: > + Полный прогон существующей сюиты трекера/уведомлений остаётся зелёным + (нет регрессий bump/edit-логики, классификации исходов, ссылок): + pytest tests/test_telegram_tracker.py tests/test_tracker_bump.py + tests/test_notify_issue_links.py tests/test_resilience.py. + module: tests/test_telegram_tracker.py + expected: PASS + + - id: TC-08 + type: integration + description: > + Вся сюита pytest tests/ -q зелёная (общая регрессия, AC-6). + module: tests/ + expected: PASS