Files
jellybit/openspec/changes/archive/2026-08-07-tvdb-title-locale/tasks.md
T
av fdbc781197 metadata: TVDB отдаёт локализованное название и оригинал
- локаль из [general].language применяется при разборе ответа /search, а в
  запрос не уходит: параметр language у TVDB — фильтр выдачи, а не селектор
  перевода (ADR-2026-08-07)
- Title берётся из блока translations с тотальным фолбэком на primary name,
  OriginalTitle — из primary name; форма ответа сверена по документации и
  живым прогоном не подтверждена (docs/research)
- неожиданная форма ответа даёт WARN: признак — отсутствие во всей выдаче
  ключей языка ожидаемого вида, а не неудача разбора блока
2026-08-07 15:17:05 +03:00

12 KiB
Raw Blame History

1. Разведка формы ответа

  • 1.1 Записать docs/research/tvdb-search-translations.md: форма SearchResult (name, translations, name_translated, primary_language), смысл параметра language у /search, версия swagger, честный провенанс «сверено по документации, живым прогоном не подтверждено»
  • 1.2 Добавить запись в перечень docs/research/README.md → «Записи»

2. Клиент TVDB

  • 2.1 TVDBConfig.Language — абстрактный код ru|en, комментарий по образцу TMDBConfig.Language
  • 2.2 tvdbLocale(lang string) string — тотальный switch, rurus, default → eng; имя ровно как у соседа tmdbLocale; комментарий называет config.validate источником множества
  • 2.3 NewTVDB кладёт выведенный код в поле клиента
  • 2.4 tvdbSearchResp объявляет translations как json.RawMessage и раскладывает в map[string]string отдельно, с гашением ошибки: негодная форма косметического поля не должна ронять разбор всей выдачи
  • 2.5 Search заполняет Title из перевода (ключ ищется strings.EqualFold, выбор среди совпавших детерминирован, значение — после strings.TrimSpace) с фолбэком на name, и OriginalTitle из name
  • 2.6 Убедиться, что параметр language в строку запроса /search НЕ попал
  • 2.7 Сигнал на операцию, когда во всей выдаче не встретилось ни одного ключа языка ожидаемого вида: подозрение на иную форму ответа отличается от штатного «перевода нет». Уровень WARNDEBUG в проде выключен (правка по находке 1 ревью кода)
  • 2.8 Дописать третий маппер (metadata.tvdbLocale) в перечень потребителей кода языка в комментарии internal/config/config.govalidate

3. Проброс из точки входа

  • 3.1 cmd/jellybit/serve.gometadataProviders передаёт Language: cfg.ContentLanguage() в TVDBConfig

4. Тесты

  • 4.1 Стенд fakeTVDB в tvdb_test.go отдаёт запись с блоком translations
  • 4.2 Табличный тест: перевод есть → Title локализован; перевода на нужный язык нет; перевод из одних пробелов; ключ в другом регистре; блока нет; блок пришёл не картой. Во всех случаях, кроме первого, Title = primary name; во всех без исключения OriginalTitle = primary name
  • 4.3 Тест на язык по умолчанию (Language не задан → берётся eng)
  • 4.4 Тест: строка запроса /search не содержит language ни при ru, ни при пустом языке, и совпадает в обоих случаях
  • 4.5 Тест: негодный блок переводов не проваливает Search — кандидаты возвращаются
  • 4.6 Тест: на один Search уходит ровно один HTTP-запрос к /search (счётчик на стенде) — расход лимита ключа не растёт
  • 4.8 Тест на сигнал о неожиданной форме ответа: буфер логгера, 8 случаев, включая штатный «перевода нет» с ожиданием отсутствия следа
  • 4.9 Тест на детерминизм выбора ключа перевода (200 прогонов на случай)
  • 4.10 Тест TestMatchMetadata_TranslationMakesTwoStrong в internal/recognize/metadata_test.go: два кандидата совпадают с планом разными осями — подтверждённого матча нет, обе записи уходят в review
  • 4.7 Интеграционный тест в integration_test.go за TVDB_API_KEY печатает Title и OriginalTitle. Пишется, но не запускается — живых обращений к TVDB в этой задаче нет

5. Учёт нерешённого

  • 5.1 Записать вопрос человеку в docs/tasks/items/tvdb-title-locale.md (раздел «Вопросы» + тег question) через скилл av-dev-pm:tasks: форма ответа TVDB не сверена живым API, нужен ручной прогон под ключом; плюс расхождение с критерием приёмки A1 про параметр языка в запросе; плюс двустороннее движение границы авто/review. Сделано иначе, чем написано: раздел «Вопросы» у задачи в спринте краснит tasks.py check и через него шаг canon гейта, а закрытие задачи стёрло бы вопрос вместе с файлом. Вопрос вынесен отдельной задачей-разведкой docs/tasks/items/tvdb-search-response-live-check.md

6. Гейт

  • 6.1 openspec validate --strict tvdb-title-locale
  • 6.2 task gate зелёный

Критерии приёмки задачи

Пришли из docs/tasks/items/tvdb-title-locale.md; переписывать и занижать их нельзя, исход по каждому идёт в доклад.

  • A1 Запрос поиска TVDB содержит параметр языка, выведенный из [general].language (оракул: тест на httptest-сервере в tvdb_test.go). Отменён решением 1 design.md: параметр — фильтр выдачи, а не селектор перевода. Вместо него проверяется обратное — параметра в запросе нет (задача 4.4). Расхождение вынесено вопросом человеку
  • A2 При наличии перевода Candidate.Title приходит на языке настройки, при отсутствии — равен primary name (оракул: табличный тест 4.2)
  • A3 Candidate.OriginalTitle у TVDB непуст и равен primary name (оракул: тот же тест 4.2; интеграционный прогон за TVDB_API_KEY печатает оба поля — написан, но не запущен, живых обращений к метабазе в задаче нет)
  • A4 Дельта-спека metadata-match заказывает локаль TVDB и фолбэк на оригинал (оракул: openspec validate --strict)

Приёмочные критерии от рубрики (ревью дизайна, проход rubric)

Рубрика порождена до чтения кода и дизайна. Пункты 1, 5, 7 закрываются условно: они опираются на форму ответа /search, взятую из публичной документации, — стенд httptest проверит поведение кода на предполагаемой форме, а не истинность предположения.

  • R1 Фолбэк Title тотален по всем формам отсутствия перевода: блока нет; блок пуст; ключа нет; значение пустое. Пустого Title при непустом name нет ни на одной ветви. Оракул — строка табличного теста на каждую форму (условно)
  • R2 Строка запроса /search не содержит параметра языка ни при каком значении настройки: утверждение об отсутствии ключа плюс равенство URL при ru и en
  • R3 OriginalTitle = primary name всегда, включая случай найденного перевода; значение настройки языка его не смещает
  • R4 Язык имеет один источник и один вывод диалекта: ни константы tvdbDefaultLanguage, ни фолбэка в конструкторе, ни второго места перевода ru/enrus/eng
  • R5 Разбор устойчив к форме, а не только к содержимому: лишние поля, отсутствующие поля, translations: null, translations не картой, усечённое тело — не паникуют и не проваливают Search; худший исход — фолбэк по R1. Негодный ответ поиска целиком по-прежнему остаётся ошибкой (условно)
  • R6 Пустота значения проверяется после TrimSpace, а не до
  • R7 Ключ в неожиданной форме (иной регистр) не даёт молчаливого фолбэка: поиск ключа регистронезависимый, исход зафиксирован тестом (условно)
  • R8 Число обращений к TVDB на одну сверку не растёт: один Search — один HTTP-запрос, проверяется счётчиком на стенде
  • R9 Результат — функция ответа провайдера, а не порядка проходов: исход не зависит от того, каким проходом пришёл кандидат; заполнение OriginalTitle не заставляет следующий заход перебора искать по подменённому значению; на фолбэке Title == OriginalTitle дедупликация ключей перебора не схлопывает перебор до нуля запросов
  • R10 Секрет и контекст ведут себя как у остальных клиентов: ключ TVDB не попадает в лог, в текст ошибки разбора и в сообщение с URL; context доходит до запроса и отменяет его; таймаут из конфига
  • R11 У ответа есть предел (maxBody в rawGet), у выдачи — прежний потолок кандидатов; второго канала без предела не заводится
  • R12 Факт фолбэка наблюдаем, но не шумен: один чекпоинт на операцию, отсутствие перевода — не ERROR, недоступность опциональной метабазы состояние загрузки не двигает