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

134 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. Разведка формы ответа
- [x] 1.1 Записать `docs/research/tvdb-search-translations.md`: форма `SearchResult`
(`name`, `translations`, `name_translated`, `primary_language`), смысл
параметра `language` у `/search`, версия swagger, честный провенанс
«сверено по документации, живым прогоном не подтверждено»
- [x] 1.2 Добавить запись в перечень `docs/research/README.md` → «Записи»
## 2. Клиент TVDB
- [x] 2.1 `TVDBConfig.Language` — абстрактный код `ru`|`en`, комментарий по образцу
`TMDBConfig.Language`
- [x] 2.2 `tvdbLocale(lang string) string` — тотальный `switch`, `ru``rus`,
default → `eng`; имя ровно как у соседа `tmdbLocale`; комментарий называет
`config.validate` источником множества
- [x] 2.3 `NewTVDB` кладёт выведенный код в поле клиента
- [x] 2.4 `tvdbSearchResp` объявляет `translations` как `json.RawMessage` и
раскладывает в `map[string]string` отдельно, с гашением ошибки: негодная
форма косметического поля не должна ронять разбор всей выдачи
- [x] 2.5 `Search` заполняет `Title` из перевода (ключ ищется `strings.EqualFold`,
выбор среди совпавших детерминирован, значение — после `strings.TrimSpace`)
с фолбэком на `name`, и `OriginalTitle` из `name`
- [x] 2.6 Убедиться, что параметр `language` в строку запроса `/search` НЕ попал
- [x] 2.7 Сигнал на операцию, когда во всей выдаче не встретилось ни одного ключа
языка ожидаемого вида: подозрение на иную форму ответа отличается от
штатного «перевода нет». Уровень `WARN``DEBUG` в проде выключен
(правка по находке 1 ревью кода)
- [x] 2.8 Дописать третий маппер (`metadata.tvdbLocale`) в перечень потребителей
кода языка в комментарии `internal/config/config.go``validate`
## 3. Проброс из точки входа
- [x] 3.1 `cmd/jellybit/serve.go``metadataProviders` передаёт
`Language: cfg.ContentLanguage()` в `TVDBConfig`
## 4. Тесты
- [x] 4.1 Стенд `fakeTVDB` в `tvdb_test.go` отдаёт запись с блоком `translations`
- [x] 4.2 Табличный тест: перевод есть → `Title` локализован; перевода на нужный
язык нет; перевод из одних пробелов; ключ в другом регистре; блока нет;
блок пришёл не картой. Во всех случаях, кроме первого, `Title` = primary
name; во всех без исключения `OriginalTitle` = primary name
- [x] 4.3 Тест на язык по умолчанию (`Language` не задан → берётся `eng`)
- [x] 4.4 Тест: строка запроса `/search` не содержит `language` ни при `ru`, ни при
пустом языке, и совпадает в обоих случаях
- [x] 4.5 Тест: негодный блок переводов не проваливает `Search` — кандидаты
возвращаются
- [x] 4.6 Тест: на один `Search` уходит ровно один HTTP-запрос к `/search`
(счётчик на стенде) — расход лимита ключа не растёт
- [x] 4.8 Тест на сигнал о неожиданной форме ответа: буфер логгера, 8 случаев,
включая штатный «перевода нет» с ожиданием отсутствия следа
- [x] 4.9 Тест на детерминизм выбора ключа перевода (200 прогонов на случай)
- [x] 4.10 Тест `TestMatchMetadata_TranslationMakesTwoStrong` в
`internal/recognize/metadata_test.go`: два кандидата совпадают с планом
разными осями — подтверждённого матча нет, обе записи уходят в review
- [x] 4.7 Интеграционный тест в `integration_test.go` за `TVDB_API_KEY` печатает
`Title` и `OriginalTitle`. **Пишется, но не запускается** — живых обращений
к TVDB в этой задаче нет
## 5. Учёт нерешённого
- [x] 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. Гейт
- [x] 6.1 `openspec validate --strict tvdb-title-locale`
- [x] 6.2 `task gate` зелёный
## Критерии приёмки задачи
Пришли из `docs/tasks/items/tvdb-title-locale.md`; переписывать и занижать их
нельзя, исход по каждому идёт в доклад.
- [ ] A1 Запрос поиска TVDB содержит параметр языка, выведенный из
`[general].language` (оракул: тест на `httptest`-сервере в `tvdb_test.go`).
**Отменён решением 1 design.md**: параметр — фильтр выдачи, а не селектор
перевода. Вместо него проверяется обратное — параметра в запросе нет
(задача 4.4). Расхождение вынесено вопросом человеку
- [x] A2 При наличии перевода `Candidate.Title` приходит на языке настройки, при
отсутствии — равен primary name (оракул: табличный тест 4.2)
- [x] A3 `Candidate.OriginalTitle` у TVDB непуст и равен primary name (оракул: тот
же тест 4.2; интеграционный прогон за `TVDB_API_KEY` печатает оба поля —
**написан, но не запущен**, живых обращений к метабазе в задаче нет)
- [x] A4 Дельта-спека `metadata-match` заказывает локаль TVDB и фолбэк на оригинал
(оракул: `openspec validate --strict`)
## Приёмочные критерии от рубрики (ревью дизайна, проход `rubric`)
Рубрика порождена до чтения кода и дизайна. Пункты 1, 5, 7 закрываются **условно**:
они опираются на форму ответа `/search`, взятую из публичной документации, — стенд
`httptest` проверит поведение кода на предполагаемой форме, а не истинность
предположения.
- [x] R1 Фолбэк `Title` тотален по всем формам отсутствия перевода: блока нет; блок
пуст; ключа нет; значение пустое. Пустого `Title` при непустом `name` нет ни
на одной ветви. Оракул — строка табличного теста на каждую форму *(условно)*
- [x] R2 Строка запроса `/search` не содержит параметра языка ни при каком значении
настройки: утверждение об **отсутствии** ключа плюс равенство URL при `ru` и `en`
- [x] R3 `OriginalTitle` = primary name всегда, включая случай найденного перевода;
значение настройки языка его не смещает
- [x] R4 Язык имеет один источник и один вывод диалекта: ни константы
`tvdbDefaultLanguage`, ни фолбэка в конструкторе, ни второго места перевода
`ru`/`en``rus`/`eng`
- [x] R5 Разбор устойчив к форме, а не только к содержимому: лишние поля,
отсутствующие поля, `translations: null`, `translations` не картой, усечённое
тело — не паникуют и не проваливают `Search`; худший исход — фолбэк по R1.
Негодный ответ поиска **целиком** по-прежнему остаётся ошибкой *(условно)*
- [x] R6 Пустота значения проверяется **после** `TrimSpace`, а не до
- [x] R7 Ключ в неожиданной форме (иной регистр) не даёт молчаливого фолбэка: поиск
ключа регистронезависимый, исход зафиксирован тестом *(условно)*
- [x] R8 Число обращений к TVDB на одну сверку не растёт: один `Search` — один
HTTP-запрос, проверяется счётчиком на стенде
- [x] R9 Результат — функция ответа провайдера, а не порядка проходов: исход не
зависит от того, каким проходом пришёл кандидат; заполнение `OriginalTitle`
не заставляет следующий заход перебора искать по подменённому значению; на
фолбэке `Title == OriginalTitle` дедупликация ключей перебора не схлопывает
перебор до нуля запросов
- [x] R10 Секрет и контекст ведут себя как у остальных клиентов: ключ TVDB не
попадает в лог, в текст ошибки разбора и в сообщение с URL; `context`
доходит до запроса и отменяет его; таймаут из конфига
- [x] R11 У ответа есть предел (`maxBody` в `rawGet`), у выдачи — прежний потолок
кандидатов; второго канала без предела не заводится
- [x] R12 Факт фолбэка наблюдаем, но не шумен: один чекпоинт на операцию,
отсутствие перевода — не `ERROR`, недоступность опциональной метабазы
состояние загрузки не двигает