work_item: ORCH-095 stage: analysis author_agent: analyst status: ready-for-review created_at: 2026-06-09 model_used: claude-opus-4-8 title: "HTML-безопасность динамических полей render_task_tracker (фикс инъекции «<1м»)" framework: pytest scope: > Покрывается: HTML-безопасность всех подставляемых данных в render_task_tracker (длительности < 1 мин, токены/стоимость, имя модели/эффорт, статус-лейбл, заголовок со спецсимволами), сохранность намеренной разметки ( номер задачи, _done_link), возобновление обновлений застрявшей карточки, never-raise. Вне покрытия: реальная сеть к Telegram Bot API (мокируется httpx), изменения STAGE_TRANSITIONS/QG_CHECKS/схемы БД (не трогаются). notes: > Тесты — изоляция от сети: httpx.post/get мокируются; БД — временная SQLite-фикстура с задачей и agent_runs (стадия < 60 с). Полный регресс pytest tests/ -q должен оставаться зелёным, включая существующие test_telegram_tracker.py / test_tracker_*.py / test_notifications_orphans.py / test_notify_issue_links.py. Регрессом считается: красный любой существующий тест трекера, заэкранированная намеренная разметка, двойное экранирование, непойманное исключение в пути рендера. tests: - id: TC-01 type: unit description: "_fmt_minutes для длительности < 60 с (напр. 30) не возвращает сырой '<1м': результат HTML-безопасен (<1м либо переформулированный '~0м'/'< 1 мин' без сырого '<')." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-02 type: unit description: "_fmt_minutes для граничных входов (0, None, нечисловое, ровно 60, большое значение) — never-raise и HTML-безопасный вывод во всех ветках." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-03 type: unit description: "render_task_tracker для задачи со стадией < 1 мин: в выходном тексте нет неэкранированного '<' из данных длительности; подстрока длительности безопасна для parse_mode=HTML." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-04 type: unit description: "render_task_tracker с заголовком, содержащим спецсимволы '<', '>', '&' (напр. 'A x & <1'): спецсимволы данных присутствуют только экранированными (</>/&), не как сырые теги; двойного экранирования (&lt;) нет." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-05 type: unit description: "Статус-лейбл (_card_status_label) и имя модели/эффорт, попадающие в текст карточки, экранированы (defence-in-depth): спецсимволы в них не ломают HTML." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-06 type: unit description: "Метрики токенов/стоимости (fmt_tokens/fmt_cost) в карточке HTML-безопасны: '$' и числовой формат не порождают сырых тегов." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-07 type: unit description: "Регресс намеренной разметки: кликабельный номер задачи (plane_issue_link -> ) присутствует в выводе как валидный незаэкранированный -тег; href/label не задвоены экранированием." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-08 type: unit description: "Регресс _done_link: для завершённой задачи строка '🔗 PR #n · 📦 Внедрено' рендерится валидной (ссылочная разметка не экранирована)." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-09 type: integration description: "update_task_tracker (edit-режим) с замоканным editMessageText: текст карточки со стадией < 1 мин принимается (мок ассертит отсутствие 'can't parse entities'-триггера, т.е. нет сырого '<1м' в payload text)." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-10 type: integration description: "Возобновление застрявшей карточки (AC-4): после фикса валидный рендер проходит edit-путь без EDIT_FAILED из-за parse-ошибки; защита от дублей сохранена — транзиентный (network) фейл по-прежнему НЕ плодит новую карточку." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-11 type: unit description: "never-raise: render_task_tracker на 'битых' входах (отсутствует задача, None-заголовок, нечисловые длительности) возвращает fallback-строку, не выбрасывает исключение." module: tests/test_tracker_html_escape.py expected: PASS - id: TC-12 type: integration description: "Полный регресс существующих тестов трекера (test_telegram_tracker.py, test_tracker_issue_link.py, test_tracker_status_line.py, test_notifications_orphans.py, test_notify_issue_links.py) остаётся зелёным после фикса." module: tests/test_telegram_tracker.py expected: PASS