---
work_item: ORCH-095
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-09
model_used: claude-opus-4-8
---

# 03 — Критерии приёмки (Acceptance Criteria): ORCH-095 — HTML-инъекция «<1м» в render_task_tracker

Work Item: **ORCH-095** · Repo: **orchestrator** · Стадия: analysis

Формат: каждый критерий имеет **PASS** (что должно быть истинно для приёмки) и **FAIL**
(что считается провалом). Любой машинный/ручной reviewer проверяет их буквально по файлам
репозитория.

---

## AC-1 — Стадия < 1 мин не ломает парсер Telegram

**Условие:** `render_task_tracker` для задачи, у которой хотя бы одна стадия длилась < 60 с,
выдаёт текст, безопасный для `parse_mode=HTML` (нет неэкранированного `<` в данных длительности).
- **PASS:** В выходном тексте подстрока длительности «меньше минуты» представлена как `&lt;1м`
  (или переформулированный безопасный вид `~0м` / `< 1 мин` без сырого `<`); `editMessageText`
  с этим текстом не вернул бы `400 can't parse entities: Unsupported start tag "1м"`. Юнит-тест
  на `_fmt_minutes(30)` / `render_task_tracker(...)` подтверждает отсутствие сырого `<` от
  длительности.
- **FAIL:** Текст содержит сырой `<1м` (или иной литерал `<`+нецифра) из данных длительности;
  тест на парсинг/наличие сырого `<` падает.

---

## AC-2 — Все динамические поля карточки HTML-безопасны (юнит)

**Условие:** Существует юнит-тест, проверяющий, что каждое подставляемое **данные-поле**
`render_task_tracker` экранировано: длительность, токены, стоимость (`$`), заголовок с
спецсимволами `< > &`, статус-лейбл, имя модели/эффорт.
- **PASS:** Тест рендерит карточку с заголовком, содержащим `<`, `>`, `&` (напр.
  `"A <b>x</b> & <1"`), и стадией < 1 мин; ассертит, что эти спецсимволы из ДАННЫХ
  присутствуют в выводе только в экранированном виде (`&lt;`/`&gt;`/`&amp;`) и НЕ как
  сырые теги; одновременно нет двойного экранирования (`&amp;lt;`).
- **FAIL:** Тест отсутствует, либо любое из перечисленных данных-полей попадает в текст без
  экранирования, либо обнаруживается двойное экранирование.

---

## AC-3 — Регресс намеренной разметки (ссылка-номер, форматирование)

**Условие:** После фикса намеренная HTML-разметка карточки продолжает рендериться валидной и
кликабельной.
- **PASS:** Кликабельный номер задачи (`<a href="…">ORCH-095</a>` от `plane_issue_link`)
  присутствует в выводе как валидный незаэкранированный `<a>`-тег; строки `🔗 PR #n`/`📦`
  (`_done_link`) и любое форматирование-обёртка рендерятся; существующие тесты
  `test_tracker_issue_link.py`/`test_notify_issue_links.py`/`test_telegram_tracker.py`
  зелёные. Двойного экранирования href/label нет.
- **FAIL:** Номер задачи перестал быть кликабельным (`<a>` заэкранирован в `&lt;a&gt;`), либо
  любой регресс-тест разметки красный.

---

## AC-4 — Застрявшая карточка возобновляет обновления

**Условие:** Карточка, ранее застрявшая на `400 can't parse entities` (класс ORCH-093), после
фикса снова обновляется.
- **PASS:** На следующем переходе стадии текст рендера больше не содержит небезопасной
  подстроки → `editMessageText` проходит (`200`); ИЛИ (если выбрана стратегия FR-4-опц.)
  перманентный parse-фейл классифицируется как повод переотправить свежую карточку, и
  `update_task_tracker` отправляет новую. Поведение покрыто тестом (рендер валиден → edit-путь
  не возвращает `EDIT_FAILED` из-за parse-ошибки).
- **FAIL:** После фикса карточка с прежним содержимым по-прежнему даёт `EDIT_FAILED` и не
  обновляется/не переотправляется; либо защита от дублей (ORCH-087) сломана — транзиентный
  фейл теперь плодит дубликаты карточек.

---

## AC-5 — never-raise, зелёный регресс, CHANGELOG

**Условие:** Контракт надёжности и гигиена изменения сохранены.
- **PASS:** `render_task_tracker`/`update_task_tracker`/`edit_telegram` не выбрасывают
  исключение наружу при любом входе (включая «битый» заголовок/None); `pytest tests/ -q`
  полностью зелёный; в `CHANGELOG.md` есть запись о фиксе ORCH-095; `STAGE_TRANSITIONS`/
  `QG_CHECKS`/`check_*`/схема БД не изменены (diff их не трогает).
- **FAIL:** Любой тест в `tests/` красный; обнаружено непойманное исключение в пути рендера;
  тронуты машина стадий/гейты/схема БД; нет записи в `CHANGELOG.md`.

---

## Сводная матрица AC ↔ FR/BR

| AC | Покрывает |
|----|-----------|
| AC-1 | BR-1, BR-3 / FR-1 |
| AC-2 | BR-2 / FR-2 |
| AC-3 | BR-4 / FR-3 |
| AC-4 | BR-5 / FR-4 |
| AC-5 | NFR-1, NFR-2 / FR-5 |
