78e9bc2583
Три параллельные ручно-синхронизируемые «лестницы» выбора media-объекта (_get_file_unique_id dict-map, _save_media_file_ids if/elif, _generate_html_media elif-каскад) сведены в одну точку правды: MEDIA_SOURCES (селектор → (media_obj, render_kind), 12 записей) + RenderCtx + RENDERERS (7 рендереров подняты дословно из старых веток). Все три консьюмера теперь ходят через таблицу; uid единообразно через getattr(file_unique_id). Байт-в-байт: fragment-oracle (media_fragment_replay + media_fragments.json, 24 кейса) без изменений, feed-golden без изменений. Единственная унификация — _save_media_file_ids теперь ключуется на message.media (как две другие лестницы), что заодно чинит латентную рассинхронизацию (в корпусе не встречается → оракул 0). test_stage5_media_table.py (48 тестов). Полный сюит 413 passed, PYTHONHASHSEED 1/42. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>