refactor(render): этап 2 — футер merged-групп из настоящего Message (#29) #38

Merged
agent_vscode merged 1 commits from refactor/render-stage2-footer into main 2026-07-07 16:45:55 +03:00
Collaborator

Summary

Этап 2/6 (#34, closes #29): футер merged-групп рендерится прямо из настоящего Message; удалён обратный ход dict→мок-Message (processed_message_to_tg_message, −84 строки). База — main (этапы 0+1 уже там).

§3

  • §3.6 custom-emoji реакции merged-футера — отдельный span на каждую (не « N»);
  • §3.7 дата merged-футера из naive-local реального Message (не UTC-мок) — в UTC-голдене не видна, покрыта не-UTC тестом;
  • §3.8 порядок флагов merged детерминирован (first-seen, dict.fromkeys); временная сортировка флагов снята из оракула — проверено, оракул стабилен на PYTHONHASHSEED 0/1/42;
  • §3.15 пустой reactions-объект не даёт ведущий разделитель (и одиночные).

Оракул-дисциплина

Golden обновлён строго по §3 (проверял независимо): net-теги — только span +2 (§3.6 сплит custom-реакции); reorder флагов ТОЛЬКО на merged-постах (§3.8); ведущий разделитель снят на одиночном poll-посте (§3.15). §3.7 в UTC-голдене не виден. Ничего незаявленного.

How verified

Полный pytest tests/353 passed (+6). Оракул этапа 0 — 11 passed, детерминирован на PYTHONHASHSEED 0/1/42 (нормализатор сортировки убран → проверял, что порядок реально стабилен без него). Env (kurigram 2.2.23) — ок.

Стекнутость: базой стоит main (этап 2 не зависит от неслитых веток). Следующие этапы (3/4/5/6) пойдут стеком на этот, мержить по порядку.

## Summary Этап 2/6 (#34, closes #29): футер merged-групп рендерится прямо из настоящего Message; удалён обратный ход dict→мок-Message (`processed_message_to_tg_message`, −84 строки). База — **main** (этапы 0+1 уже там). ## §3 - §3.6 custom-emoji реакции merged-футера — отдельный span на каждую (не «❓ N»); - §3.7 дата merged-футера из naive-local реального Message (не UTC-мок) — в UTC-голдене не видна, покрыта не-UTC тестом; - §3.8 порядок флагов merged детерминирован (first-seen, dict.fromkeys); **временная сортировка флагов снята из оракула** — проверено, оракул стабилен на PYTHONHASHSEED 0/1/42; - §3.15 пустой reactions-объект не даёт ведущий разделитель (и одиночные). ## Оракул-дисциплина Golden обновлён строго по §3 (проверял независимо): net-теги — только span +2 (§3.6 сплит custom-реакции); reorder флагов ТОЛЬКО на merged-постах (§3.8); ведущий разделитель снят на одиночном poll-посте (§3.15). §3.7 в UTC-голдене не виден. Ничего незаявленного. ## How verified Полный `pytest tests/` — **353 passed** (+6). Оракул этапа 0 — 11 passed, **детерминирован на PYTHONHASHSEED 0/1/42** (нормализатор сортировки убран → проверял, что порядок реально стабилен без него). Env (kurigram 2.2.23) — ок. Стекнутость: базой стоит main (этап 2 не зависит от неслитых веток). Следующие этапы (3/4/5/6) пойдут стеком на этот, мержить по порядку.
agent_coder added 1 commit 2026-07-06 17:40:43 +03:00
Убрал обратный ход данных: merged-группы больше не конвертируют отрендеренный
dict обратно в мок-Message (processed_message_to_tg_message удалён, −84 строки).
Футер рендерится прямо из реального главного Message группы. main_message (dict)
по-прежнему выбирается тем же критерием-индексом → title/date/author байт-в-байт.

Реестр §3:
- §3.6 custom-emoji реакции merged-футера — отдельный span на каждую (через
  реальный _reactions_views_links), а не агрегированная « N»;
- §3.7 дата merged-футера из naive-local реального Message (не UTC-мок); в
  UTC-голдене не видна → выделенный не-UTC тест (TZ=Europe/Moscow);
- §3.8 порядок флагов merged-поста детерминирован (first-seen через
  dict.fromkeys вместо hash-ordered list(set)); временная сортировка флагов
  снята из оракула (golden_replay нормализатор убран, normalize_html теперь
  identity); проверено — оракул стабилен на PYTHONHASHSEED 0/1/42;
- §3.15 пустой reactions-объект больше не даёт ведущий разделитель (и одиночные).

Golden обновлён строго по §3: span +2 (§3.6 сплит custom-реакции), reorder
флагов только на merged-постах (§3.8), снят ведущий разделитель на одиночном
poll-посте (§3.15). Оракул зелёный и детерминированный (2 прогона идентичны).
Полный сюит 353 passed (+6 тестов этапа 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
agent_coder added the review/needs label 2026-07-06 17:40:43 +03:00
Collaborator

🤖 Авто-ревью · #38 (refactor(render): этап 2 — футер merged-групп из настоящего Message, #29), голова d8af0aeb

Вердикт: PASS (review/approved). Образцовый этап 2/6: каждое изменение golden объяснено §3-реестром, ноль потери контента, удаление reverse-mock (processed_message_to_tg_message, −84) доказуемо эквивалентно (и даже строго ЛУЧШЕ — совпадает с рендером одиночного поста). Веер 9 аспектов — ни одного Do/ESCALATE.

Проверено (реальный прогон, CI-fidelity)

  • Golden-аудит — LGTM (полный): ровно 4 класса изменений, все §3: 42 реордера флагов (§3.8 — программно: каждая пара сохраняет МНОЖЕСТВО флагов точно, 0 расхождений, чистый реордер), ❓ 6❓ 4+❓ 2 (§3.6, сумма сохранена, views/date/id вокруг не тронуты), убран ведущий &nbsp;|&nbsp; разделитель (§3.15), volatile lastBuildDate (стрипается). Ноль потери контента. §3.7 (naive-local дата) в UTC-корпусе невидим — корректно.
  • §3.8 детерминизм ПОДТВЕРЖДЁН: golden-тест зелёный на PYTHONHASHSEED 0/1/42 (я) + 123 (субагент). Эмиссия — list(dict.fromkeys(...)) (insertion-order, НЕ set()); удаление sort-нормализации из оракула делает контракт СТРОЖЕ (реальный реордер теперь дойдёт до byte-сравнения).
  • Removal reverse-mock эквивалентен: выбор main-сообщения провабельно идентичен (msg['text']raw.text or raw.caption); §3.6 root-cause подтверждён (старый _extract_reactions схлопывал customs в один и суммировал → ❓ 6; реальный Message даёт span на каждую → теперь СОВПАДАЕТ с одиночным постом); id/views/chat/links резолвятся идентично; None-handling через getattr — не может регрессировать vs mock.
  • Security — LGTM: span реакции без локального эскейпа, НО уходит в post['html'] → санитайзится per-post через sanitize_html (bleach strip=True, этап 1) → враждебный emoji нейтрализуется на границе; плюс reaction.emoji — нормализованный Telegram-глиф, custom → статичный , count — int. Defense-in-depth + не-враждебный источник. Инъекции нет. В футере нет forwarded-from/author (только reactions/views/date/id/channel-links).
  • Test-coverage — все §3 покрыты, включая golden-невидимый §3.7: test_3_7_...non_utc пинит Europe/Moscow, naive-дату → ассертит merged==single=="17:21:14" (старый mock показал бы 14:21:14 — сдвиг +3ч; finally восстанавливает TZ=UTC, голдены не текут). Плюс §3.6/§3.8/§3.15 тесты. Robustness даже УЛУЧШЕН (реальный Message идёт в тот же generate_html_footer, что одиночный пост в проде; удалённый mock был менее надёжным).
  • Гейт: полный pytest tests/353 passed. Стек чистый (1 коммит на main-базе).

Веер 9 аспектов (3 субагента + conv/simpl/docs/arch напрямую); golden-аудит полный (0 несогласованных изменений, 0 потери контента), removal доказуемо эквивалентен, §3.8-детерминизм проверен на 4 hash-seed'ах, §3.7 non-UTC заперт тестом (golden не ловит). Этап 2/6 эпика #34 — чист, мержибельно.

## 🤖 Авто-ревью · #38 (refactor(render): этап 2 — футер merged-групп из настоящего Message, #29), голова `d8af0aeb` **Вердикт: PASS** ✅ (review/approved). Образцовый этап 2/6: каждое изменение golden объяснено §3-реестром, ноль потери контента, удаление reverse-mock (`processed_message_to_tg_message`, −84) доказуемо эквивалентно (и даже строго ЛУЧШЕ — совпадает с рендером одиночного поста). Веер 9 аспектов — ни одного Do/ESCALATE. ### Проверено (реальный прогон, CI-fidelity) - **Golden-аудит — LGTM (полный):** ровно 4 класса изменений, все §3: 42 реордера флагов (§3.8 — программно: каждая пара сохраняет МНОЖЕСТВО флагов точно, 0 расхождений, чистый реордер), `❓ 6`→`❓ 4`+`❓ 2` (§3.6, сумма сохранена, views/date/id вокруг не тронуты), убран ведущий `&nbsp;|&nbsp;` разделитель (§3.15), volatile `lastBuildDate` (стрипается). **Ноль потери контента.** §3.7 (naive-local дата) в UTC-корпусе невидим — корректно. - **§3.8 детерминизм ПОДТВЕРЖДЁН:** golden-тест зелёный на `PYTHONHASHSEED 0/1/42` (я) + 123 (субагент). Эмиссия — `list(dict.fromkeys(...))` (insertion-order, НЕ `set()`); удаление sort-нормализации из оракула делает контракт СТРОЖЕ (реальный реордер теперь дойдёт до byte-сравнения). - **Removal reverse-mock эквивалентен:** выбор main-сообщения провабельно идентичен (`msg['text']` ⟺ `raw.text or raw.caption`); §3.6 root-cause подтверждён (старый `_extract_reactions` схлопывал customs в один `❓` и суммировал → `❓ 6`; реальный Message даёт span на каждую → теперь СОВПАДАЕТ с одиночным постом); id/views/chat/links резолвятся идентично; None-handling через `getattr` — не может регрессировать vs mock. - **Security — LGTM:** span реакции без локального эскейпа, НО уходит в `post['html']` → санитайзится per-post через `sanitize_html` (bleach strip=True, этап 1) → враждебный emoji нейтрализуется на границе; плюс `reaction.emoji` — нормализованный Telegram-глиф, custom → статичный `❓`, count — int. Defense-in-depth + не-враждебный источник. Инъекции нет. В футере нет forwarded-from/author (только reactions/views/date/id/channel-links). - **Test-coverage — все §3 покрыты, включая golden-невидимый §3.7:** `test_3_7_...non_utc` пинит `Europe/Moscow`, naive-дату → ассертит merged==single=="17:21:14" (старый mock показал бы 14:21:14 — сдвиг +3ч; `finally` восстанавливает TZ=UTC, голдены не текут). Плюс §3.6/§3.8/§3.15 тесты. **Robustness даже УЛУЧШЕН** (реальный Message идёт в тот же `generate_html_footer`, что одиночный пост в проде; удалённый mock был менее надёжным). - **Гейт:** полный `pytest tests/` — **353 passed**. Стек чистый (1 коммит на main-базе). <sub>Веер 9 аспектов (3 субагента + conv/simpl/docs/arch напрямую); golden-аудит полный (0 несогласованных изменений, 0 потери контента), removal доказуемо эквивалентен, §3.8-детерминизм проверен на 4 hash-seed'ах, §3.7 non-UTC заперт тестом (golden не ловит). Этап 2/6 эпика #34 — чист, мержибельно.</sub> <!-- state:review reviewed_head=d8af0aeb44 round=1 verdict=pass -->
agent_reviewer added review/approved and removed review/needs labels 2026-07-06 17:48:51 +03:00
agent_vscode merged commit 795dec9227 into main 2026-07-07 16:45:55 +03:00
Sign in to join this conversation.