docs(plans): update render pipeline refactor spec to v6
Bump the specification version from v5 to v6 and replace the synthetic golden‑corpus with a recorded real‑message corpus from production cache. Add detailed inventory statistics, clarify handling of None‑date fixtures, and update test‑data handling notes. Adjust related documentation sections to reflect the new corpus and its impact on golden tests.
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
# Рефакторинг пайплайна рендера: спецификация (v5)
|
||||
# Рефакторинг пайплайна рендера: спецификация (v6)
|
||||
|
||||
Статус: пять раундов адверсариального ревью — четыре у постоянного критика +
|
||||
независимый холодный проход (журнал в §8), блокеров нет. Холодный проход
|
||||
независимо подтвердил все line-refs и эмпирические утверждения спеки.
|
||||
v6: golden-корпус переведён с синтетики на записанные реальные сообщения
|
||||
(решение владельца, обоснование в этапе 0 и §8).
|
||||
База: ветка `refactor/render-pipeline` режется от **f9550d8**
|
||||
(`feat/kurigram-2.2.23`); все line-refs спеки действительны для этого коммита.
|
||||
Мёрж рефакторинга в main блокируется решением по kurigram-PR.
|
||||
@@ -150,20 +152,62 @@ Golden-эталоны (§5, этап 0) обновляются только ко
|
||||
|
||||
### Этап 0 — golden-эталоны фидов (до любых правок кода)
|
||||
|
||||
Фикстурный набор синтетических сообщений (naive-даты): одиночный пост,
|
||||
media_group, time-based кластер, merged-группа с несколькими текстами, poll,
|
||||
webpage-превью с фото и без, forward, реакции (обычные / custom / paid /
|
||||
пустой список), текст с зачёркиванием (`<s>`/`<del>` через entities — без
|
||||
такой фикстуры §3.1 в golden невидим).
|
||||
**Корпус — записанные РЕАЛЬНЫЕ сообщения с прод-сервера, не синтетика (v6).**
|
||||
Источник: рабочий кэш бриджа `data/tgcache/` на сервере — там уже лежат
|
||||
пиклы ровно нужного формата: `{channel}.cache` =
|
||||
`{'timestamp', 'limit', 'messages': List[Message]}`
|
||||
(`tg_cache._save_history_to_cache`) и `{channel}.chatinfo` (для
|
||||
`cached_get_chat`). Отобранные ПАРЫ файлов копируются с сервера в
|
||||
`tests/test_data/recorded/` и коммитятся как замороженный корпус. Состав
|
||||
(какие каналы) выбирает владелец — контент этих каналов попадает в репо.
|
||||
|
||||
Сценарии exclude_flags/exclude_text в golden-фикстуры НЕ входят: фильтры не
|
||||
меняют байты выживших постов; их семантика (membership / regex) закрепляется
|
||||
парой unit-тестов — дешевле и меньше площадь флаки-рисков.
|
||||
Реплей: тестовый загрузчик распикливает файлы напрямую (`timestamp`/`limit`
|
||||
игнорируются — никакой проверки свежести) и monkeypatch'ит
|
||||
`tg_cache.cached_get_chat_history` / `cached_get_chat` на возврат записанных
|
||||
объектов. Это буквально прод-путь cache-hit: сериализация и структура
|
||||
объектов — те же, что видит рендер в бою.
|
||||
|
||||
**None-date фикстура в этап 0 НЕ входит**: текущий код падает на ней в
|
||||
ДЕФОЛТНОМ пути (naive/aware TypeError в sort-ключе групп, см. §1) — снять
|
||||
golden невозможно. Она добавляется в golden-набор коммитом этапа 4 со ссылкой
|
||||
на §3.11–12 (это ДОБАВЛЕНИЕ фикстуры, а не обновление эталона).
|
||||
Почему реальные, а не моки: (а) настоящие kurigram-объекты закрывают класс
|
||||
ложной зелени «мок и код согласованно ждут атрибут, которого нет у реального
|
||||
объекта» — холодный проход (§8, раунд 5) пометил его как неловимый на моках;
|
||||
(б) naive-даты, реальные entities/reactions/webpage-структуры и комбинации
|
||||
форматирования достаются бесплатно.
|
||||
|
||||
**Первое действие этапа — проверка совместимости пиклов**: кэш сервера
|
||||
записан прод-версией kurigram, корпус распикливается под 2.2.23 (база
|
||||
рефакторинга f9550d8). ВЫПОЛНЕНО — см. «Статус» ниже: 90/90 + 89/89 без
|
||||
ошибок, фолбэк не понадобился.
|
||||
|
||||
**Инвентаризация покрытия**: мини-скрипт проходит по корпусу и печатает
|
||||
media-типы, наличие media_groups / time-кластеров / forward / reply /
|
||||
реакций (обычные, custom, paid, пустой объект) / webpage с фото и без /
|
||||
зачёркивания (`<s>`/`<del>` в entities). Дыры покрытия закрываются
|
||||
СИНТЕТИЧЕСКОЙ добавкой — только для недостижимого в записанном.
|
||||
|
||||
**Статус (2026-07-05): оба шага уже выполнены.** Снапшот прод-кэша снят:
|
||||
`data/tgcache_prod_2026-07-05/` (90 `.cache` + 89 `.chatinfo`, 53 МБ, вне
|
||||
git — `data/` в .gitignore; отбор пар в `tests/test_data/recorded/` — задача
|
||||
этапа 0). Совместимость подтверждена: ВСЕ файлы распикливаются под kurigram
|
||||
2.2.23 без ошибок. Инвентарь (8582 сообщения): PHOTO 5397, text-only 1609,
|
||||
WEB_PAGE 764 (с фото 581 / без 183), VIDEO 653, DOCUMENT 51, ANIMATION 33,
|
||||
POLL 31, VIDEO_NOTE 15, STICKER 13, AUDIO 8, VOICE 6, GIVEAWAY и
|
||||
GIVEAWAY_WINNERS по 1; media_group-членов 3159, forward 670, reply 357,
|
||||
time-кластерных пар (gap≤5s) ~294; реакции: обычных 17981, custom 1018,
|
||||
paid 593, ПУСТЫХ reactions-объектов 17 (§3.15-edge покрыт реальными
|
||||
данными!); зачёркиваний 150 (§3.1 покрыт); все 8582 даты naive (посылка
|
||||
спеки подтверждена); None-date — 0. Синтетика нужна ТОЛЬКО для: None-date
|
||||
(этап 4) и отсутствующих в корпусе типов — PAID_MEDIA, STORY, LIVE_PHOTO,
|
||||
CHECKLIST, CONTACT/LOCATION/VENUE/DICE/GAME/INVOICE/UNSUPPORTED
|
||||
(fragment-уровень этапа 5a).
|
||||
|
||||
Сценарии exclude_flags/exclude_text в golden НЕ входят: фильтры не меняют
|
||||
байты выживших постов; их семантика (membership / regex) закрепляется парой
|
||||
unit-тестов — дешевле и меньше площадь флаки-рисков.
|
||||
|
||||
**None-date пост в этап 0 НЕ входит** (в записанных данных его и не бывает):
|
||||
текущий код падает на нём в ДЕФОЛТНОМ пути (naive/aware TypeError в
|
||||
sort-ключе групп, см. §1). Синтетическая None-date фикстура добавляется в
|
||||
HTML-golden коммитом этапа 4 со ссылкой на §3.11–12.
|
||||
|
||||
Снимаются и кладутся в `tests/test_data/golden/`:
|
||||
|
||||
@@ -181,7 +225,7 @@ golden невозможно. Она добавляется в golden-набор
|
||||
прогонами) вырезается regex'ом и в golden, и в актуальном выводе;
|
||||
`<generator>` в feedgen 1.0.0 версии НЕ содержит — его нормализация не
|
||||
обязательна, оставлена как дешёвая страховка от апгрейда библиотеки;
|
||||
- фикстуры не содержат постов, где `pubDate` берётся из `datetime.now()`
|
||||
- корпус не содержит постов, где `pubDate` берётся из `datetime.now()`
|
||||
(это только None-date случай — он исключён, см. выше);
|
||||
- порядок merged-флагов ДО этапа 2 недетерминирован: `list(set(...))`
|
||||
(rss_generator.py:260–265) зависит от PYTHONHASHSEED процесса, который из
|
||||
@@ -193,10 +237,10 @@ golden невозможно. Она добавляется в golden-набор
|
||||
tests/test_new_media_types.py): иначе golden, снятый на dev-машине,
|
||||
краснеет на CI — свежий checkout генерирует новый `secrets.token_hex`
|
||||
в `data/media_digest.key`, и все digest'ы в URL меняются;
|
||||
- фикстуры подаются monkeypatch'ем `tg_cache.cached_get_chat` /
|
||||
`cached_get_chat_history` — lazy-import внутри фид-функций разрешает имена
|
||||
поздно, поэтому патч модуля tg_cache работает (образец —
|
||||
test_stage4_eventloop.py);
|
||||
- корпус подаётся monkeypatch'ем `tg_cache.cached_get_chat` /
|
||||
`cached_get_chat_history` (возврат распикленных записанных объектов) —
|
||||
lazy-import внутри фид-функций разрешает имена поздно, поэтому патч модуля
|
||||
tg_cache работает (образец — test_stage4_eventloop.py);
|
||||
- запись media-id пинится: monkeypatch `upsert_media_file_ids_bulk_sync`
|
||||
(или `DB_PATH` → tmp_path) — иначе golden-тесты с медиа-фикстурами пишут
|
||||
в реальную `./data/media_file_ids.db` (`DB_PATH` cwd-относителен,
|
||||
@@ -218,8 +262,11 @@ sanitizer), этап 2 (пп.6, 8, 15 + снятие нормализации ф
|
||||
webpage-без-фото меняет и feed-level байты: до 5b незакрытый div
|
||||
дозакрывался bleach'ем в конце фрагмента).
|
||||
|
||||
DoD: снапшоты в репо, тест сравнения зелёный и детерминированный (два прогона
|
||||
подряд — идентичные байты), любое изменение рендера валит его с внятным диффом.
|
||||
DoD: записанный корпус + отчёт инвентаризации + снапшоты в репо; тест
|
||||
сравнения зелёный и детерминированный (два прогона подряд — идентичные
|
||||
байты); любое изменение рендера валит его с внятным диффом. Пиклы корпуса
|
||||
сцеплены с версией kurigram — при апгрейде библиотеки корпус перезаписывается
|
||||
с сервера (зафиксировать в README тестов).
|
||||
|
||||
### Этап 1 — `sanitizer.py` + санитайз в пайплайне (ишью #22, обновить)
|
||||
|
||||
@@ -457,6 +504,13 @@ PAID_MEDIA, WEB_PAGE с фото (пустой media-div!), **и edge-ветки
|
||||
без фото (незакрытый div — воспроизводится в 5a буквально), file_unique_id
|
||||
is None, channel_username is None, гейт webpage-превью «text ≤ 10».
|
||||
|
||||
Источник сообщений для fragment-снапшотов (v6): ЗАПИСАННЫЙ КОРПУС этапа 0 —
|
||||
для всех типов, которые в нём есть (по инвентарю: PHOTO, VIDEO, ANIMATION,
|
||||
VIDEO_NOTE, AUDIO, VOICE, STICKER, DOCUMENT, POLL, WEB_PAGE, GIVEAWAY,
|
||||
GIVEAWAY_WINNERS). Моки — ТОЛЬКО для отсутствующих в корпусе типов
|
||||
(PAID_MEDIA, STORY, LIVE_PHOTO, CHECKLIST и прочая экзотика) — ограничение
|
||||
инвариант-теста «ложная зелень на моках» сужается до этих типов.
|
||||
|
||||
```python
|
||||
# Single source of truth: media type -> (object selector, render kind).
|
||||
# kind=None means "selector-only entry": the object participates in file-id
|
||||
@@ -694,3 +748,23 @@ find_file_id_in_message); архитектурные решения призна
|
||||
каветт §3.12 про будущие даты.
|
||||
|
||||
Отклонённых претензий нет; по всем спорным пунктам достигнут консенсус.
|
||||
|
||||
### v6 — golden-корпус: записанные реальные сообщения (решение владельца)
|
||||
|
||||
Не раунд ревью — изменение дизайна по решению владельца проекта, с
|
||||
немедленной эмпирической проверкой:
|
||||
|
||||
- Корпус этапа 0 переведён с синтетики на записанные кэши прод-сервера
|
||||
(`data/tgcache/*.cache|.chatinfo` — пиклы того же формата, что читает
|
||||
прод-путь cache-hit). Мотив: холодный проход (раунд 5) пометил класс
|
||||
«мок и код согласованно ждут несуществующий атрибут» как неловимый на
|
||||
моках — реальные объекты закрывают его целиком.
|
||||
- Снапшот кэша снят (90 каналов), совместимость пиклов со старым kurigram
|
||||
проверена под 2.2.23: 179/179 файлов без ошибок.
|
||||
- Инвентарь покрытия: 8582 сообщения, все даты naive (посылка спеки
|
||||
подтверждена данными); покрыты в т.ч. edge-кейсы §3.1 (зачёркивания: 150)
|
||||
и §3.15 (пустые reactions-объекты: 17), webpage с/без фото, ~294
|
||||
time-кластерные пары. Синтетика сузилась до None-date и отсутствующих
|
||||
типов (PAID_MEDIA, STORY, LIVE_PHOTO, CHECKLIST, прочая экзотика).
|
||||
- Fragment-снапшоты этапа 5a — тоже на корпусе, где тип доступен;
|
||||
ограничение инвариант-теста сузилось до mock-only типов.
|
||||
|
||||
Reference in New Issue
Block a user