4 Commits

Author SHA1 Message Date
agent_coder c8e741ca41 refactor(render): этап 5b — зарегистрированные фиксы медиа-таблицы §3.13/§3.14 (#32)
Поверх байт-в-байт 5a — только фиксы реестра:
- §3.14: закрывающий </div> эмитится в КАЖДОЙ ветке media-рендера (раньше
  несбалансированный div в конце фрагмента авто-закрывался bleach позже). На
  фрагмент-оракуле изменились ровно 2 кейса (file_unique_id_none,
  webpage_without_photo); в feed-голденах — чисто РЕЛОКАЦИЯ </div> с конца поста
  на место сразу после пустого media-div (счётчик </div> не меняется, контент
  идентичен). Затронуты каналы с webpage-без-фото (theyforcedme без изменений).
- §3.13: гард >100MB теперь на ЛЮБОЙ media-объект (не только message.video);
  collection-only, байты фида не трогает.

test_stage5b_media_fixes.py (14 тестов). Полный сюит 427 passed, PYTHONHASHSEED
1/42. Golden строго по §3 (проверял независимо: net-теги 0, только </div>-релокация).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:30:40 +03:00
agent_coder d8af0aeb44 refactor(render): этап 2 — футер merged-групп из настоящего Message, выпил конвертера (#29)
Docker Image CI / build (pull_request) Has been cancelled
Убрал обратный ход данных: 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>
2026-07-06 17:39:49 +03:00
agent_coder 0963c140a3 refactor(render): этап 1 — sanitizer.py + санитайз per-post в пайплайне (#28)
Часть эпика рефакторинга рендер-пайплайна (#34). Один bleach-конфиг на весь
проект, один санитайз на пост, ноль thread-hop'ов в циклах (раньше конфиг был
скопирован трижды и разъехался; RSS-путь делал to_thread + новый CSSSanitizer +
вложенную функцию НА КАЖДЫЙ пост, хотя _render_pipeline уже в worker-треде).

Новый sanitizer.py — единственная bleach-конфигурация: ALLOWED_TAGS (union трёх
старых копий, вкл. s/del), ALLOWED_PROTOCOLS=['http','https','tg'],
sanitize_html() зовёт bleach.clean(..., protocols=..., strip=True) (оба не-дефолт),
fail-CLOSED (html.escape при любом исключении), единое имя лога
html_sanitization_error + log_context. _render_pipeline санитайзит каждый пост
после рендера (в своём worker-треде); rss_generator −71 строка.

Реестр §3:
- §3.1 s/del теперь в фидах (раньше выживали только в одиночном посте);
- §3.2 fail-open → fail-closed (вкл. single-post/JSON путь);
- §3.3 <hr class="post-divider"> реально виден (join ПОСЛЕ санитайза; hr не в
  whitelist, поэтому раньше вырезался strip=True);
- §3.4 несбалансированный фрагмент нормализуется в границах своего поста;
- §3.5 гранулярный fail-closed HTML-фида (падает один пост, не весь фид);
- §3.16 три имени санитайз-логов объединены в html_sanitization_error+log_context.

Golden-эталоны обновлены: КАЖДЫЙ изменённый байт привязан к пункту §3 (net-diff
тегов: только +<s>/§3.1 и +<hr>/§3.3; <div> сбалансирован +/- = §3.4;
после снятия этих токенов + нормализатора все 6 голденов побайтово old==new).
Оракул этапа 0 зелёный. Полный сюит 346 passed (+14 sanitizer-тестов).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 17:05:53 +03:00
agent_coder dc9a23f801 test(render): этап 0 — golden-эталоны фидов как оракул эквивалентности (#27)
Docker Image CI / build (pull_request) Has been cancelled
Первый этап эпика рефакторинга рендер-пайплайна (#34): снимает полные
snapshot'ы выходов generate_channel_rss (RSS XML) и generate_channel_html
(HTML) на записанном РЕАЛЬНОМ корпусе (4 канала: bladerunnerblues, embedoka,
meow_design, theyforcedme) и закрепляет тестом сравнения. Оракул для всех
последующих этапов: изменение байтов фида без ссылки на пункт реестра §3
спеки = регрессия. Никакой продакшн рендер-код не тронут — только тест-инфра.

- tests/golden_replay.py: реплей-загрузчик (распикливает recorded/*.cache|
  *.chatinfo, monkeypatch tg_cache.cached_get_chat_history/cached_get_chat —
  буквально прод-путь cache-hit) + генератор goldens + нормализаторы.
- tests/test_stage0_golden.py: оракул (11 тестов — RSS×4, HTML×4 + guards).
- tests/test_data/golden/: 8 замороженных snapshot'ов (~1.9 МБ).
- tests/conftest.py: пин TZ=UTC (naive-даты kurigram интерпретируются в
  локальной tz → без пина pubDate дрейфует между машинами).

Нормализации (симметрично golden+actual, минимальные, каждая с источником
недетерминизма): strip <lastBuildDate> (feedgen now()), strip <generator>
(страховка), сортировка <div class="message-flags"> (merged-флаги —
list(set(...)), hash-порядок; убирается коммитом этапа 2 по §3.8).

Детерминизм подтверждён 3 способами: двойной in-process захват, разные
PYTHONHASHSEED, регенерация в отдельном процессе — нормализованный вывод
байт-идентичен. Полный сюит 332 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 07:39:40 +03:00