Files
orchestrator/docs/work-items/ORCH-095/03-acceptance-criteria.md

6.6 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used
work_item stage author_agent status created_at model_used
ORCH-095 analysis analyst ready-for-review 2026-06-09 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