Files
jellybit/openspec/changes/archive/2026-08-10-metadata-title-sanitize/specs/metadata-match/spec.md
T
av 9aecf757e0 recognize: название из метабазы санитизируется перед попаданием в план
- чистка стоит на каждой точке входа значения метабазы в план — сборка матча,
  копия кандидата для ревью, набор закреплённых значений источника и его
  чтение: гарантия, поставленная только на запись, обходится данными,
  сохранёнными прежними версиями
- название, непригодное как имя каталога (пустое или без единой буквы и
  цифры), не подставляется — раздача уходит в review с названной причиной
- гейт подтверждения матча не сдвинут: сравнение с планом идёт по значениям
  провайдера, чистится только копия, уходящая дальше
2026-08-10 10:41:16 +03:00

14 KiB

ADDED Requirements

Requirement: Санитайзинг названий кандидатов, уходящих в ревью

Система SHALL санитизировать названия кандидата (Title, OriginalTitle) тем же санитайзингом человекочитаемых полей плана (см. recognition) перед тем, как унести их из сверки дальше: в список кандидатов для ревью, в хранилище, на экран и в закрепляемое человеком значение. Причина та же, что у канонического названия: выбор кандидата человеком — полноправный путь подтверждения матча, и закреплённое им название становится именем каталога библиотеки в тех же условиях, что и название авто-матча.

Условия подтверждения сильного матча система SHALL проверять на значениях, как их отдал провайдер: санитайзинг кандидатов SHALL NOT влиять на эти значения. Порядок операций требование не нормирует — нормирует исход: гейт матча этой правкой не двигается, иначе кандидат, чьё название отличается от плана невидимым символом, начал бы совпадать там, где прежде уходил в review. Полнота списка кандидатов тоже SHALL остаться прежней.

Если название кандидата после санитайзинга непригодно как компонент пути (пусто либо без единой буквы и цифры), система SHALL сохранить кандидата в списке — он остаётся выбором человека и несёт provider_id и URL для внешней проверки, — но закрепление такого названия SHALL приводить к тому же исходу, что и у канонического: подстановки не происходит, в плане остаётся название распознавания.

OriginalTitle кандидата чистится наравне с Title, хотя в хранилище и на экран сегодня доходит только второе: первое уезжает в результат распознавания и в диагностику сухого прогона, и держать в одной структуре одно чистое поле и одно грязное — источник будущей ошибки.

Scenario: Название кандидата чистится перед показом и закреплением

  • GIVEN провайдер вернул кандидата, название которого содержит zero-width символ и кириллический двойник внутри латинского слова
  • WHEN кандидат попадает в список для ревью
  • THEN его название очищено тем же санитайзингом, что и поля плана
  • AND человек, выбравший этого кандидата, закрепляет очищенное название
  • AND в имя каталога библиотеки оно уходит в тех же условиях, что и название авто-матча (живой папки-якоря того же тайтла нет — см. file-layout, «Сходимость базы папки при подтверждённом матче»)

Scenario: Сравнение с планом идёт по значению провайдера

  • GIVEN кандидат, название которого отличается от названия плана только невидимым символом внутри слова
  • WHEN проверяются условия подтверждения сильного матча
  • THEN сравнение идёт по значению, как его отдал провайдер
  • AND исход подтверждения матча тот же, что был до этого изменения

MODIFIED Requirements

Requirement: Подтверждение матча и каноническое имя

При единичном сильном матче система SHALL брать из записи базы официальный provider (tmdb|tvdb|tvmaze) и provider_id, а также каноническое название и год, и подменять ими соответствующие поля плана (для сериала — с учётом внешнего тега TVDB/IMDb из externals, идущего в имя папки). Матч SHALL считаться подтверждённым только при ровно одном сильном кандидате; при нуле или нескольких кандидатах подтверждённого матча быть SHALL NOT (авто-раскладка не разрешается, кандидаты уходят в review). Работа с базами опциональна: при выключенных базах сверка не выполняется и подтверждённого матча нет.

Каноническое название — недоверенное значение внешнего сервиса, и перед подстановкой в план система SHALL применять к нему тот же санитайзинг человекочитаемых полей, что и к выводу LLM (см. recognition, требование «Санитайзинг человекочитаемых полей плана»). Подстановка несанитизированного значения SHALL NOT выполняться: план — источник имени каталога библиотеки, и значение, не прошедшее чистку, уходит в путь на диске.

Если после санитайзинга каноническое название оказывается непригодным как компонент пути — пустым либо вырожденным, то есть не содержащим ни одной буквы и ни одной цифры (., .., -, только пунктуация), — система SHALL сохранить в плане прежнее название и SHALL NOT разрешать авто-раскладку: причина уходит в перечень причин решения, раздача попадает в review. Проверка пригодности SHALL стоять после санитайзинга, а не до него. Ронять распознавание или раскладку такой матч SHALL NOT — год, провайдер и provider_id при этом подставляются как обычно.

Санитайзинг, изменивший каноническое название, но оставивший его пригодным, авто-раскладку SHALL NOT блокировать: в план идёт очищенное значение, и оно предсказуемо — ради этого чистка и стоит.

При подтверждённом матче система SHALL дополнительно попытаться получить из базы режиссёра (TMDB/TVDB credits) и вложить его в план (director) как недоверенное косметическое значение для вывода отображаемого имени. Тот же способ выборки режиссёра по provider:id SHALL быть доступен при закреплении вручную выбранного в ревью кандидата (см. review), т.к. основной путь подтверждения матча — ручной выбор, а не авто. Выборка режиссёра SHALL быть best-effort: её недоступность, отсутствие в базе или провайдер без режиссёра (напр. TVMaze) SHALL NOT проваливать распознавание/матч/выбор — director остаётся пустым, а имя выводится без режиссёра или из более низкого слоя (сохранённый контекст). Режиссёр из метабазы SHALL иметь приоритет над режиссёром из контекста (более проверенный источник).

Режиссёр — недоверенное человекочитаемое поле: он SHALL NOT участвовать в структурной валидации/гейте авто-раскладки, а его очистка (управляющие символы, пробелы, лимит длины) применяется при рендере отображаемого имени, а не в plan-санитайзинге.

Scenario: Единичный матч даёт id и каноническое имя

  • GIVEN поиск вернул ровно одного сильного кандидата TMDB для фильма
  • WHEN матч подтверждается
  • THEN план получает provider=tmdb, provider_id, каноническое название и год

Scenario: Каноническое название чистится перед подстановкой

  • GIVEN подтверждённый единичный матч, каноническое название которого содержит zero-width символ, перевод строки или кириллический двойник внутри латинского слова
  • WHEN каноническое название подставляется в план
  • THEN в плане оказывается санитизированное значение
  • AND авто-раскладка остаётся разрешённой, если прочие условия выполнены
  • AND когда база имени папки печатается из распознавания (живой папки-якоря того же тайтла нет — см. file-layout, «Сходимость базы папки при подтверждённом матче»), в имя каталога уходит очищенное значение

Scenario: Название базы непригодно как компонент пути — раздача уходит в review

  • GIVEN подтверждённый единичный матч, каноническое название которого после санитайзинга пусто (целиком состояло из zero-width символов) либо не содержит ни одной буквы и ни одной цифры (., ..., -)
  • WHEN каноническое название подставляется в план
  • THEN название плана остаётся прежним (подстановки не происходит)
  • AND решение auto/review содержит причину «название из базы непригодно как имя каталога», авто-раскладка не разрешается
  • AND распознавание не проваливается, год и провайдер подставлены

Scenario: Нормальное название матча не меняется

  • GIVEN подтверждённый единичный матч с обычным каноническим названием
  • WHEN каноническое название подставляется в план
  • THEN значение в плане совпадает с тем, что отдала база (санитайзинг на чистом значении ничего не меняет)
  • AND авто-раскладка остаётся разрешённой, если прочие условия выполнены

Scenario: Матч подтягивает режиссёра

  • GIVEN подтверждённый единичный матч TMDB для фильма, у которого в credits указан режиссёр
  • WHEN матч подтверждается
  • THEN в план вкладывается director из credits
  • AND отображаемое имя может использовать этого режиссёра

Scenario: Режиссёр недоступен — матч не ломается

  • GIVEN подтверждённый матч, для которого выборка режиссёра недоступна или провайдер режиссёра не отдаёт
  • WHEN матч подтверждается
  • THEN director остаётся пустым
  • AND матч подтверждён, распознавание не проваливается

Scenario: Несколько кандидатов — матч не подтверждён

  • GIVEN поиск вернул более одного подходящего кандидата
  • WHEN оценивается матч
  • THEN подтверждённого матча нет, кандидаты собираются для выбора в review