38 lines
5.5 KiB
Markdown
38 lines
5.5 KiB
Markdown
---
|
||
work_item: ORCH-095
|
||
stage: architecture
|
||
author_agent: architect
|
||
status: accepted
|
||
created_at: 2026-06-09
|
||
model_used: claude-opus-4-8
|
||
---
|
||
|
||
# 10 — Технические риски: ORCH-095 — HTML-безопасность данных live-карточки
|
||
|
||
Work Item: **ORCH-095** · Repo: **orchestrator** · Стадия: architecture
|
||
|
||
> Информационный (гейтом не парсится). Перечисляет риски реализации и их митигейшн.
|
||
|
||
## Реестр рисков
|
||
|
||
| ID | Риск | Вер. | Влия. | Митигейшн |
|
||
|----|------|------|-------|-----------|
|
||
| TR-1 | **Двойное экранирование** уже-экранированных полей (`esc_title`, href/label внутри `plane_issue_link`) → `<` в выводе, визуальный мусор / регресс AC-2 | Сред. | Сред. | D1/D5: явный реестр M-слотов (markup) — через `_esc` НЕ проходят; `esc_title` остаётся единственной точкой escape заголовка; тест AC-2 ассертит отсутствие `<` |
|
||
| TR-2 | **Случайное экранирование разметки-обёртки** (`num_html`/`link_for`/`_done_link`) → `<a>` превращается в `<a>`, номер задачи перестаёт быть кликабельным (регресс BR-4/AC-3) | Низ. | Выс. | D5: M-слоты неприкосновенны; регресс-тесты `test_tracker_issue_link.py`/`test_notify_issue_links.py`/`test_telegram_tracker.py` зелёные; AC-3 проверяет наличие валидного `<a href>` в выводе |
|
||
| TR-3 | **Пропущен новый/существующий D-слот** (забыли обернуть `_esc`) → инъекция возвращается на другом поле | Низ. | Сред. | D3 defence-in-depth (обернуть ВСЕ D-поля разом); тест-инвариант AC-2 рендерит карточку с `< > &` в данных и ассертит отсутствие сырых спецсимволов из данных в выводе (свойство `render_task_tracker`, не пер-поле) |
|
||
| TR-4 | **Регресс never-raise**: `_esc(str(x))` на «битом» входе (объект с падающим `__str__`) бросает исключение в пути рендера (нарушение NFR-1) | Низ. | Сред. | FR-5: `_esc` сам never-raise (try/except → fallback-строка); путь `render_task_tracker`/`update_task_tracker` уже обёрнут `try/except` (строки 654/745); тест AC-5 с «битым» входом |
|
||
| TR-5 | **Застрявшая карточка не восстановилась** (задача завершилась до деплоя → нет будущего рендера) | Сред. | Низ. | Принятая known-limitation (D4): авто-recovery работает только при предстоящем переходе стадии; вне окна — Telegram-48ч (унаследовано ORCH-087); BR-5 ограничен карточками в окне |
|
||
| TR-6 | **Скрытая регрессия инвариантов соседних маркеров** (ORCH-087 анти-дубль, ORCH-091 суммирование `_stage_line`) при правке тела `_stage_line`/`render_task_tracker` | Низ. | Выс. | D6: изменение аддитивно (лишь оборачивает уже вычисленные значения в `_esc`), не меняет состав/порядок строк, логику подавления откатов и суммирования; полный регресс `pytest tests/ -q` зелёный (NFR-2) |
|
||
| TR-7 | **Self-hosting**: фикс деплоится на общий прод-инстанс (затронуты и enduro-trails) | Низ. | Сред. | NFR-3: изменение только слоя рендера; `STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД не тронуты; обязательная страховка `deploy-staging` (8501) перед прод-деплоем; прод `orchestrator` не рестартится в рамках разработки |
|
||
|
||
## Сводный вывод
|
||
|
||
Доминирующий класс рисков — **регресс рендера** (двойное экранирование / случайное экранирование
|
||
разметки / пропущенный D-слот), полностью покрываемый тест-инвариантом AC-2 + существующими
|
||
регресс-тестами трекера (AC-3/AC-5). Изменение **локализовано** в `src/notifications.py` (слой
|
||
рендера уведомлений), аддитивно к маркерам ORCH-042/067/087/091, не затрагивает машину стадий,
|
||
Quality Gates, схему БД, транспортные примитивы и режимы трекера. Остаточный риск для
|
||
прод-конвейера (self-hosting) — **низкий**: контракт never-raise сохранён, откат — обычный revert
|
||
PR без операций над данными. Эскалация `arch:major-change` **не требуется**; возврат в анализ
|
||
**не требуется** (ТЗ реализуемо без нарушения архитектурных принципов).
|