ЭПИК: рефакторинг пайплайна рендера (спека v5, этапы 0–6) #34

Open
opened 2026-07-05 20:29:11 +03:00 by agent_vscode · 3 comments
Collaborator

Зонтичное ишью рефакторинга рендер-пайплайна фидов (RSS/HTML). Источник истины — спека plans/2026-07-05-render-pipeline-refactor.md (v6, пять раундов адверсариального ревью; журнал — §8 спеки).

СТАТУС (2026-07-07): этапы 0–5 влиты в main, зелёные

main tip cd758d5 — полный сюит 443 passed, включая golden-оракул. Мёрж-верификация архитектором: стек собран (4∪5 без конфликтов), суммарная дельта golden по 4 реальным каналам = ровно §3.1 (<s>/del), §3.3 (<hr>), §3.6 (реакции-span), §3.8 (порядок флагов); всё прочее NET-0 (переверстка). Поэтапная атрибуция сошлась: этап 3 golden-дельта 0 (чистый рефактор), этап 4 — 0 на реальных каналах (None-date в корпусе нет), ни один media-URL не изменился.

Осталось: этап 6 (косметика, #33) — PR ещё нет.

Чеклист этапов

  • #27 этап 0 — golden-оракул → PR #35 merged
  • #28 этап 1 — sanitizer.py + санитайз per-post → PR #37 merged
  • #29 этап 2 — футер merged-групп из настоящего Message → PR #38 merged
  • #30 этап 3 — слияние близнецов вокруг _prepare_feed_posts → PR #39 merged
  • #31 этап 4 — группировка без deepcopy + фикс naive/aware краша → PR #40 merged
  • #32 этап 5 — таблица медиа-типов (5a+5b) → PR #41 merged
  • #33 этап 6 — косметика (обёртка поста, константы стилей, чистка) — последним

Живые баги, закрытые по пути (в main)

  • naive/aware TypeError → 500 на любом фиде с None-date постом (дефолтный путь) — фикс в этапе 4;
  • FloodWait истории → 400 вместо 429 — этап 3 (§3.9);
  • вырезаемые <hr>-разделители, fail-open санитайз (stored-XSS путь), незакрытый media-div — этапы 1/5.

Дальше по общему плану (см. коммент с порядком реализации)

После этапа 6 — кэш-эпик (#23–#26). Кросс-зависимость: аллоулист снапшота #23 обязан совпасть с таблицей MEDIA_SOURCES (теперь она в main, этап 5) — иначе cache-hit ломает рендер новых медиа-типов. #24 (lowercase channel key) при мёрже переснимает render-golden. Детали — комментарии к #23/#24.

Правила для исполнителя (дублируются в каждом ишью)

  • Один этап = один коммит; после каждого pytest полностью зелёный.
  • Golden-эталоны обновляются ТОЛЬКО коммитом со ссылкой на пункт реестра §3.
  • Правки существующих тестов: (а) по пункту реестра §3 с комментарием-ссылкой; (б) API relocation с пометкой.
  • Комментарии в коде — только English. Точечные правки.
  • Новые тесты — на naive-датах (kurigram-прод), не aware-UTC.

История: #22 (этап 1 по v1 спеки) закрыто как superseded этим набором.

Зонтичное ишью рефакторинга рендер-пайплайна фидов (RSS/HTML). Источник истины — спека `plans/2026-07-05-render-pipeline-refactor.md` (**v6**, пять раундов адверсариального ревью; журнал — §8 спеки). ## СТАТУС (2026-07-07): этапы 0–5 влиты в main, зелёные main tip `cd758d5` — полный сюит **443 passed**, включая golden-оракул. Мёрж-верификация архитектором: стек собран (4∪5 без конфликтов), суммарная дельта golden по 4 реальным каналам = ровно §3.1 (`<s>`/`del`), §3.3 (`<hr>`), §3.6 (реакции-span), §3.8 (порядок флагов); всё прочее NET-0 (переверстка). Поэтапная атрибуция сошлась: этап 3 golden-дельта 0 (чистый рефактор), этап 4 — 0 на реальных каналах (None-date в корпусе нет), ни один media-URL не изменился. Осталось: **этап 6** (косметика, #33) — PR ещё нет. ## Чеклист этапов - [x] **#27** этап 0 — golden-оракул → PR #35 ✅ merged - [x] **#28** этап 1 — sanitizer.py + санитайз per-post → PR #37 ✅ merged - [x] **#29** этап 2 — футер merged-групп из настоящего Message → PR #38 ✅ merged - [x] **#30** этап 3 — слияние близнецов вокруг _prepare_feed_posts → PR #39 ✅ merged - [x] **#31** этап 4 — группировка без deepcopy + фикс naive/aware краша → PR #40 ✅ merged - [x] **#32** этап 5 — таблица медиа-типов (5a+5b) → PR #41 ✅ merged - [ ] **#33** этап 6 — косметика (обёртка поста, константы стилей, чистка) — последним ## Живые баги, закрытые по пути (в main) - **naive/aware TypeError → 500 на любом фиде с None-date постом** (дефолтный путь) — фикс в этапе 4; - FloodWait истории → 400 вместо 429 — этап 3 (§3.9); - вырезаемые `<hr>`-разделители, fail-open санитайз (stored-XSS путь), незакрытый media-div — этапы 1/5. ## Дальше по общему плану (см. коммент с порядком реализации) После этапа 6 — кэш-эпик (#23–#26). Кросс-зависимость: аллоулист снапшота #23 обязан совпасть с таблицей `MEDIA_SOURCES` (теперь она в main, этап 5) — иначе cache-hit ломает рендер новых медиа-типов. #24 (lowercase channel key) при мёрже переснимает render-golden. Детали — комментарии к #23/#24. ## Правила для исполнителя (дублируются в каждом ишью) - Один этап = один коммит; после каждого `pytest` полностью зелёный. - Golden-эталоны обновляются ТОЛЬКО коммитом со ссылкой на пункт реестра §3. - Правки существующих тестов: (а) по пункту реестра §3 с комментарием-ссылкой; (б) API relocation с пометкой. - Комментарии в коде — только English. Точечные правки. - Новые тесты — на naive-датах (kurigram-прод), не aware-UTC. История: #22 (этап 1 по v1 спеки) закрыто как superseded этим набором.
Author
Collaborator

Обновление: спека → v6 (golden-корпус — записанные реальные сообщения вместо синтетики; #27 переписано соответственно).

Статус предусловий этапа 0 — уже выполнены вне ишью:

  • снапшот прод-кэша снят: data/tgcache_prod_2026-07-05/ (90 .cache + 89 .chatinfo, 53 МБ, вне git);
  • совместимость пиклов проверена под kurigram 2.2.23: 179/179 файлов без ошибок;
  • инвентарь покрытия готов (8582 сообщения, все даты naive; покрыты зачёркивания §3.1, пустые reactions-объекты §3.15, webpage±фото, ~294 time-кластерные пары; отсутствуют только PAID_MEDIA/STORY/LIVE_PHOTO/CHECKLIST и пр. экзотика — они остаются на моках fragment-уровня этапа 5).

Кодеру в #27 остаётся: отбор каналов из снапшота → tests/test_data/recorded/ → реплей-загрузчик → снятие golden.

**Обновление: спека → v6** (golden-корпус — записанные реальные сообщения вместо синтетики; #27 переписано соответственно). Статус предусловий этапа 0 — уже выполнены вне ишью: - снапшот прод-кэша снят: `data/tgcache_prod_2026-07-05/` (90 `.cache` + 89 `.chatinfo`, 53 МБ, вне git); - совместимость пиклов проверена под kurigram 2.2.23: 179/179 файлов без ошибок; - инвентарь покрытия готов (8582 сообщения, все даты naive; покрыты зачёркивания §3.1, пустые reactions-объекты §3.15, webpage±фото, ~294 time-кластерные пары; отсутствуют только PAID_MEDIA/STORY/LIVE_PHOTO/CHECKLIST и пр. экзотика — они остаются на моках fragment-уровня этапа 5). Кодеру в #27 остаётся: отбор каналов из снапшота → `tests/test_data/recorded/` → реплей-загрузчик → снятие golden.
Author
Collaborator

Кросс-зависимость с эпиком оптимизации кешей (#23–#26, спека plans/cache-optimization-spec.md)

Эпики независимы по коду, но пересекаются в 4 точках. Координация зафиксирована комментариями в #23/#24.

  1. #23 (pickle→CachedMessage) ↔ #27 golden + #32 MEDIA_SOURCES — КОРРЕКТНОСТЬ. После #23 прод-путь cache-hit кормит рендер CachedMessage, а не Message. Аллоулист снапшота #23 сейчас НЕ покрывает новые медиа-типы (story/live_photo/paid_media/giveaway/checklist/… + poll.description_media), которые читает рендер → на cache-hit они молча пропадают. Контракт «поля, которые трогает рендер» = таблица MEDIA_SOURCES (#32). Детали и рекомендация по сиквенсингу (#23 после #32) — в комментарии #23.

  2. #23 Package B (префиксный limit) ЧИНИТ то, что §7 спеки рендера объявлял «отложенным». Вечный miss-цикл dual-consumer каналов (RSS limit*2 / HTML limit) закрывается префиксной выдачей истории в #23 — механизм лучше, чем «limit в имени файла» из §7. §7 спеки рендера поправлен: не «отложено», а «делается в #23».

  3. #24 (lowercase channel key) → переснять feed-golden рендера (#27). Меняет подписанные media-URL и футер-ссылки. Кто мёржится вторым — ребейзит golden. Детали — в #24.

  4. #25/#26 (Package D/E, /media hot path) ↔ #32 инвариант-тест. Инвариант-тест этапа 5a вызывает api_server.find_file_id_in_message. Package E рефакторит /media — НЕ переименовывать/не удалять find_file_id_in_message без правки #32, иначе тест краснеет.

Сводно по порядку: идеальная последовательность — сначала PR #21 (kurigram) в main, затем эпик рендера (особенно #32, фиксирующий MEDIA_SOURCES), затем кэш-эпик (#23 выводит аллоулист из MEDIA_SOURCES). При параллельной работе пункты 1 и 3 — обязательные точки синхронизации.

## Кросс-зависимость с эпиком оптимизации кешей (#23–#26, спека `plans/cache-optimization-spec.md`) Эпики независимы по коду, но пересекаются в 4 точках. Координация зафиксирована комментариями в #23/#24. 1. **#23 (pickle→`CachedMessage`) ↔ #27 golden + #32 MEDIA_SOURCES — КОРРЕКТНОСТЬ.** После #23 прод-путь cache-hit кормит рендер `CachedMessage`, а не `Message`. Аллоулист снапшота #23 сейчас НЕ покрывает новые медиа-типы (story/live_photo/paid_media/giveaway/checklist/… + poll.description_media), которые читает рендер → на cache-hit они молча пропадают. Контракт «поля, которые трогает рендер» = таблица `MEDIA_SOURCES` (#32). Детали и рекомендация по сиквенсингу (#23 после #32) — в комментарии #23. 2. **#23 Package B (префиксный limit) ЧИНИТ то, что §7 спеки рендера объявлял «отложенным».** Вечный miss-цикл dual-consumer каналов (RSS `limit*2` / HTML `limit`) закрывается префиксной выдачей истории в #23 — механизм лучше, чем «limit в имени файла» из §7. §7 спеки рендера поправлен: не «отложено», а «делается в #23». 3. **#24 (lowercase channel key) → переснять feed-golden рендера (#27).** Меняет подписанные media-URL и футер-ссылки. Кто мёржится вторым — ребейзит golden. Детали — в #24. 4. **#25/#26 (Package D/E, /media hot path) ↔ #32 инвариант-тест.** Инвариант-тест этапа 5a вызывает `api_server.find_file_id_in_message`. Package E рефакторит /media — НЕ переименовывать/не удалять `find_file_id_in_message` без правки #32, иначе тест краснеет. **Сводно по порядку:** идеальная последовательность — сначала PR #21 (kurigram) в main, затем эпик рендера (особенно #32, фиксирующий MEDIA_SOURCES), затем кэш-эпик (#23 выводит аллоулист из MEDIA_SOURCES). При параллельной работе пункты 1 и 3 — обязательные точки синхронизации.
Author
Collaborator

Порядок реализации для одного кодера (канонический)

Макро: весь рендер-эпик → влить в main → кэш-эпик. Так снимаются все кросс-зависимости, golden пересматривается ровно один раз, кросс-эпик merge-конфликтов нет.

Старт: refactor/render-pipeline от актуального main (88ac436+).

Блок 1 — рендер (этап = коммит, между ними pytest зелёный):

  1. #27 этап 0 — golden (ПЕРВЫМ, до любого кода; оракул)
  2. #28 этап 1 — sanitizer
  3. #29 этап 2 — футер
  4. #30 этап 3 — слияние близнецов (1→2→3 строго последовательно)
  5. #31 этап 4 — группировка (после #30)
  6. #32 этап 5 — таблица медиа-типов, 5a→5b (после #30; с #31 порядок любой)
  7. #33 этап 6 — косметика (последним)
    влить в main.

Блок 2 — кэш (ветки от обновлённого main, каждый PR-юнит отдельно):
8. #23 Package A+B+F — аллоулист снапшота выводится из готовой MEDIA_SOURCES (#32)
9. #24 Package C — строго после #23; переснять render-golden в этом же PR (lowercase меняет media-URL)

Свободно: #25 (D), #26 (E) — ни от чего не зависят, можно в любой момент отдельными PR. Ограничение: не переименовывать find_file_id_in_message (инвариант-тест #32).

Обоснование «рендер раньше кэша»: #23 выводит аллоулист из финальной MEDIA_SOURCES вместо ручного переоткрытия (иначе тихо ломает cache-hit-рендер новых типов); #24 переснимает golden, который уже в main; оба эпика правят post_parser/api_server — последовательность на main убирает merge-ад.

## Порядок реализации для одного кодера (канонический) Макро: **весь рендер-эпик → влить в main → кэш-эпик.** Так снимаются все кросс-зависимости, golden пересматривается ровно один раз, кросс-эпик merge-конфликтов нет. **Старт:** `refactor/render-pipeline` от актуального `main` (88ac436+). **Блок 1 — рендер (этап = коммит, между ними `pytest` зелёный):** 1. #27 этап 0 — golden (ПЕРВЫМ, до любого кода; оракул) 2. #28 этап 1 — sanitizer 3. #29 этап 2 — футер 4. #30 этап 3 — слияние близнецов *(1→2→3 строго последовательно)* 5. #31 этап 4 — группировка *(после #30)* 6. #32 этап 5 — таблица медиа-типов, 5a→5b *(после #30; с #31 порядок любой)* 7. #33 этап 6 — косметика *(последним)* → **влить в main.** **Блок 2 — кэш (ветки от обновлённого main, каждый PR-юнит отдельно):** 8. #23 Package A+B+F — аллоулист снапшота выводится из готовой `MEDIA_SOURCES` (#32) 9. #24 Package C — строго после #23; **переснять render-golden** в этом же PR (lowercase меняет media-URL) **Свободно:** #25 (D), #26 (E) — ни от чего не зависят, можно в любой момент отдельными PR. Ограничение: не переименовывать `find_file_id_in_message` (инвариант-тест #32). Обоснование «рендер раньше кэша»: #23 выводит аллоулист из финальной `MEDIA_SOURCES` вместо ручного переоткрытия (иначе тихо ломает cache-hit-рендер новых типов); #24 переснимает golden, который уже в main; оба эпика правят post_parser/api_server — последовательность на main убирает merge-ад.
Sign in to join this conversation.