# metadata-match Specification

## Purpose
Сверка распознанного плана с внешними базами метаданных (TMDB/TVDB/TVMaze):
поиск записи по нескольким названиям с нормализацией, локаль запроса,
подтверждение единичного сильного матча (официальный `provider_id` +
каноническое имя/год) и сбор кандидатов с URL для ручного выбора в `review`.
Разбор сигналов моделью — в `recognition`.
## Requirements
### Requirement: Сверка с базой по нескольким названиям

При сверке плана с включёнными базами метаданных система SHALL искать по
нескольким названиям в порядке убывания силы ключа: сначала по
`original_title`, затем по локализованному `title`, затем по `provider_hint`.
Этот перебор названий SHALL выполняться в рамках каждого прохода сверки — проходы
(с годом и безгодовой fallback) определяет требование «Безгодовой второй проход
сверки как fallback». Поиск SHALL останавливаться, как только очередной запрос
дал единичный сильный матч (ровно один кандидат с совпадением названия и года);
ранняя остановка действует в пределах прохода. Запрос с названием, нормализованно
совпадающим с уже выполненным, система SHALL пропускать, чтобы не обращаться к
базе повторно с тем же ключом.

Кандидаты для ручного выбора в review система SHALL собирать из всех выполненных
заходов обоих проходов с дедупликацией по `provider:id` и общим потолком.

#### Scenario: Иностранный фильм находится по оригинальному названию

- **GIVEN** план с `title` «Тёмный рыцарь», `original_title` «The Dark Knight», год 2008
- **WHEN** выполняется сверка с базой
- **THEN** первый запрос идёт по «The Dark Knight»
- **AND** при единичном сильном матче дальнейшие запросы (по `title`, `provider_hint`) не выполняются

#### Scenario: Фолбэк на локализованное название

- **GIVEN** план, для которого запрос по `original_title` не дал единичного сильного матча
- **WHEN** продолжается сверка
- **THEN** выполняется запрос по локализованному `title`
- **AND** при отсутствии матча и там — запрос по `provider_hint`

#### Scenario: Дублирующий запрос пропускается

- **GIVEN** план, у которого `original_title` нормализованно совпадает с `title`
- **WHEN** выполняется сверка
- **THEN** база запрашивается этим названием один раз, повторный заход по `title` не делается

### 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

### Requirement: Локаль запроса к TMDB

Локаль запросов к TMDB SHALL выводиться из глобальной настройки `language`
(`ru`|`en`, дефолт `en`): `ru` → `ru-RU`, `en` → `en-US`. Отдельной настройки
локали у TMDB быть SHALL NOT — глобальный `language` единственный источник.

Эту локаль система SHALL передавать параметром `language` как в запрос поиска,
так и в запрос credits (режиссёр). На стороне поиска локаль влияет ТОЛЬКО на
локализованное поле `Title`/`Name`; поле `original_title`/`original_name`
остаётся на языке оригинала, поэтому оригинальная сторона сравнения не
затрагивается. На стороне credits передача локали — best-effort: имена людей
провайдер локализует не всегда, при отсутствии перевода имя остаётся на языке
оригинала, и это не проваливает выборку режиссёра.

#### Scenario: Локаль по умолчанию — английская

- **GIVEN** TMDB включён, глобальный `language` не задан в конфиге
- **WHEN** выполняется поиск фильма
- **THEN** запрос содержит `language=en-US`
- **AND** в кандидате `Title` приходит на английском, а `OriginalTitle` — на языке оригинала

#### Scenario: language=ru даёт русскую локаль

- **GIVEN** TMDB включён, глобальный `language` = `ru`
- **WHEN** выполняется поиск фильма с русской локализацией
- **THEN** запрос содержит `language=ru-RU`
- **AND** в кандидате `Title` приходит на русском, а `OriginalTitle` — на языке оригинала

#### Scenario: Локаль передаётся и в запрос режиссёра

- **GIVEN** подтверждённый матч TMDB и глобальный `language` = `ru`
- **WHEN** выполняется запрос credits за режиссёром
- **THEN** запрос содержит `language=ru-RU`
- **AND** при отсутствии локализованного имени режиссёр остаётся на языке оригинала, выборка не проваливается

### Requirement: Нормализация названий при сравнении

Нормализация названий для гейта сильного матча SHALL сводить букву `ё` к `е`,
чтобы написания, различающиеся только `ё`/`е`, считались одним названием.

Нормализация SHALL дополнительно сворачивать кирилло-латинские homoglyph-двойники
по той же курируемой таблице, что применяется при санитайзинге плана
(capability `recognition`), чтобы визуально совпадающие название плана и кандидата
базы, различающиеся лишь скриптом отдельных букв, считались одним названием. Это
defense-in-depth на случай двойников со стороны кандидата: гейт — точка
безопасно-критичного решения об авто-матче и SHALL быть устойчив к двойникам
независимо от предшествующего санитайзинга.

#### Scenario: «Тёмный» и «Темный» совпадают

- **GIVEN** план с названием «Тёмный рыцарь» и кандидат базы «Темный рыцарь»
- **WHEN** сравниваются нормализованные названия
- **THEN** они считаются совпадающими

#### Scenario: Двойник у кандидата не мешает матчу

- **GIVEN** план с латинским названием `Harold` и кандидат базы, где то же слово
  содержит кириллический символ-двойник
- **WHEN** сравниваются нормализованные названия
- **THEN** они считаются совпадающими

### Requirement: Кандидат несёт URL для внешней проверки

Каждый кандидат внешней базы метаданных (`metadata.Candidate`) SHALL нести
поле `URL` — ссылку на страницу элемента (фильма/сериала) на сайте
провайдера. URL SHALL формироваться клиентом провайдера при поиске
(`Search`) и сохраняться в таблице `metadata_candidate`. Отображение этой
ссылки на экране ревью — забота `review`/`web-ui`, не данного требования.

Формат URL для каждого провайдера:

- **TMDB**: `https://www.themoviedb.org/movie/{id}` (фильм) или
  `https://www.themoviedb.org/tv/{id}` (сериал) — тип контента известен из
  запроса `Query.Type`
- **TVDB**: `https://www.thetvdb.com/dereferrer/movie/{id}` (фильм) или
  `https://www.thetvdb.com/dereferrer/series/{id}` (сериал) — тип контента
  известен из запроса `Query.Type`
- **TVMaze**: `https://www.tvmaze.com/shows/{id}` — URL SHALL использовать
  нативный id TVMaze, а не внешний тег (TVDB/IMDb), чтобы ссылка вела на
  TVMaze-страницу

#### Scenario: Кандидат TMDB с корректной ссылкой

- **GIVEN** TMDB найден кандидат-фильм с id `603` («Матрица»)
- **WHEN** клиент TMDB формирует Candidate
- **THEN** `URL` = `https://www.themoviedb.org/movie/603`

#### Scenario: Кандидат TVMaze с нативной ссылкой

- **GIVEN** TVMaze найден сериал с id `169` («Фарго»), внешний тег — TVDB id `269613`
- **WHEN** клиент TVMaze формирует Candidate
- **THEN** `URL` = `https://www.tvmaze.com/shows/169`
- **AND** `TagProvider`/`TagID` остаются `tvdb`/`269613` (тег папки Jellyfin не меняется)

#### Scenario: URL сохраняется в БД

- **GIVEN** результат поиска с кандидатами
- **WHEN** кандидаты сохраняются в таблицу `metadata_candidate`
- **THEN** значение `url` SHALL быть записано в колонку `url`
- **AND** при последующей загрузке данных ревью url доступен без повторной
  генерации

### Requirement: Безгодовой второй проход сверки как fallback

Система SHALL выполнять второй проход сверки без года как fallback: когда поиск с
годом не дал подтверждённого единичного сильного матча, а год плана известен
(`year > 0`), выполняется тот же перебор названий (в порядке `original_title` →
`title` → `provider_hint`) и провайдеров, но с запросом БЕЗ года. Второй проход
SHALL выполняться только как
fallback: если первый проход подтвердил матч или год плана неизвестен, второго
прохода быть SHALL NOT.

Гейт сильного матча (нормализованное совпадение названия и год кандидата в пределах
±1, ровно один кандидат) при этом SHALL оставаться неизменным — безгодовой проход
расширяет только выдачу запроса, но не ослабляет условие подтверждения, поэтому
инвариант «авто-раскладка только при подтверждённом единичном сильном матче» не
затрагивается. Дополнительно в безгодовом проходе система SHALL требовать
известный год кандидата: запись с неизвестным годом (год подтвердить нечем) во
втором проходе подтверждённым матчем быть SHALL NOT и уходит кандидатом в review
(в первом, с-годом, проходе прежняя leniency к неизвестному году сохраняется).
Кандидаты для ручного выбора в review система SHALL собирать из обоих проходов с
той же дедупликацией по `provider:id` и общим потолком.

#### Scenario: Год плана off-by-one — авто-матч восстанавливается без года

- **GIVEN** запись есть в базе, но год плана отличается от её года ровно на 1
  (жёсткий exact-year фильтр запроса отсёк её в первом проходе)
- **WHEN** первый проход (с годом) не дал сильного матча
- **THEN** выполняется второй проход без года
- **AND** единичный кандидат проходит гейт (название совпадает, год бьётся ±1) —
  матч подтверждается

#### Scenario: Год плана расходится больше чем на 1 — кандидат только в review

- **GIVEN** запись есть в базе, но год плана отличается от её года больше чем на 1
- **WHEN** второй проход без года возвращает эту запись единственным кандидатом
- **THEN** гейт отклоняет её по году (расхождение больше ±1) — подтверждённого
  матча нет
- **AND** кандидат собирается для выбора в review (человек получает кандидата
  вместо пустого списка)

#### Scenario: Первый проход подтвердил матч — второго нет

- **GIVEN** первый проход (с годом) дал единичный сильный матч
- **WHEN** завершается сверка
- **THEN** второй (безгодовой) проход не выполняется

#### Scenario: Год неизвестен — второго прохода нет

- **GIVEN** план без года (`year` = 0)
- **WHEN** первый проход не дал матча
- **THEN** второй проход не выполняется (он был бы идентичен первому)

#### Scenario: Кандидат с неизвестным годом в безгодовом проходе — только review

- **GIVEN** год плана известен, а pass 1 (с годом) не дал матча
- **WHEN** безгодовой проход возвращает единственного кандидата с совпадающим
  названием, но неизвестным годом
- **THEN** подтверждённого матча нет (год кандидата не подтверждён) — авто-раскладка
  не делается
- **AND** кандидат собирается для выбора в review

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

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

### Requirement: Локализованное название кандидата TVDB

Кандидат TVDB SHALL нести локализованное название в поле `Title` и название на
языке оригинала в поле `OriginalTitle`. Язык локализации задаёт та же глобальная
настройка `language`, что и для TMDB, — правило единственного источника языка
живёт в требовании «Локаль запроса к TMDB» и здесь не переписывается.

Источник локализованного названия — блок переводов в ответе поиска TVDB (карта
«код языка → название»). `OriginalTitle` SHALL брать primary name записи — это
название на языке оригинала и ось сравнения при матче.

Фолбэк SHALL быть тотальным: если перевода на нужный язык нет, его значение
пусто после обрезки пробелов или блока переводов нет вовсе, `Title` SHALL быть
равен primary name. Пустого `Title` при непустом primary name быть SHALL NOT.
В `Title` SHALL попадать значение перевода после обрезки пробелов.

Ключ языка SHALL искаться регистронезависимо: молчаливый фолбэк из-за регистра
ключа неотличим от отсутствия перевода и в эксплуатации не диагностируется. Если
условию отвечает несколько ключей, выбор SHALL быть детерминированным — один и
тот же ответ провайдера обязан давать один и тот же `Title`.

Локаль TVDB SHALL влиять только на разбор ответа и SHALL NOT сужать выдачу
поиска: параметр языка в запрос поиска не передаётся. Причина — в
`docs/research/tvdb-search-translations.md`; здесь заказано поведение, а не
устройство чужого API. Тем самым разница с TMDB намеренна: у TMDB локаль едет в
запрос, у TVDB читается из ответа.

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

Подозрение на иную форму ответа SHALL оставлять диагностический след. Признаком
служит **отсутствие во всей выдаче хотя бы одного ключа языка ожидаемого вида**,
а не неудача разбора блока: `null`, пустая карта и словарь кодов другого вида
разбираются без ошибки и потому признаком быть SHALL NOT. Штатное «перевода на
этот язык нет» (ключи ожидаемого вида есть, нужного среди них нет) следа
оставлять SHALL NOT — иначе сигнал неотличим от рутины. Уровень следа задают
конвенции логирования проекта и здесь не нормируются; требуется наблюдаемость,
а не конкретный уровень.

Настоящее требование не изменяет условий подтверждения матча — они заданы
требованиями «Подтверждение матча и каноническое имя» и «Безгодовой второй
проход сверки как fallback». Заполнение `OriginalTitle` расширяет множество
названий кандидата, по которым идёт сравнение, с одного до двух. Исход гейта при
этом монотонным SHALL NOT считаться: там, где сильный кандидат был один, их
может стать двое, и тогда подтверждённого матча нет, а записи уходят кандидатами
в review — по действующему требованию, без исключений для TVDB.

#### Scenario: Перевод на язык настройки есть

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `哪吒之魔童降世` и переводом `rus` = «Нэчжа»
- **THEN** `Candidate.Title` = «Нэчжа»
- **AND** `Candidate.OriginalTitle` = `哪吒之魔童降世`

#### Scenario: Перевода на язык настройки нет — фолбэк на оригинал

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `Fargo` и переводами без ключа `rus`
- **THEN** `Candidate.Title` = `Fargo`
- **AND** `Candidate.OriginalTitle` = `Fargo`

#### Scenario: Перевод есть, но пустой

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с primary name `Fargo` и переводом `rus` из одних пробелов
- **THEN** `Candidate.Title` = `Fargo`
- **AND** `Candidate.OriginalTitle` = `Fargo`

#### Scenario: Язык по умолчанию — английский

- **GIVEN** TVDB включён, глобальный `language` не задан в конфиге
- **WHEN** поиск возвращает запись с переводами `eng` и `rus`
- **THEN** `Candidate.Title` берётся из перевода `eng`

#### Scenario: Ключ перевода в другом регистре

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** поиск возвращает запись с переводом под ключом `RUS` вместо `rus`
- **THEN** `Candidate.Title` берётся из этого перевода

#### Scenario: Блок переводов отсутствует или пришёл негодной формой

- **GIVEN** TVDB включён с любым значением `language`
- **WHEN** поиск возвращает записи без блока переводов либо с блоком, который не разбирается картой
- **THEN** `Candidate.Title` = primary name записи
- **AND** `Candidate.OriginalTitle` = primary name записи
- **AND** разбор ответа поиска не проваливается, кандидаты возвращаются
- **AND** остаётся диагностический след о подозрении на иную форму ответа

#### Scenario: Коды языка не того вида — след остаётся

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** вся выдача поиска несёт блоки переводов с ключами другого вида (например, двухбуквенными), либо `null`, либо пустые
- **THEN** `Candidate.Title` = primary name у каждой записи
- **AND** остаётся диагностический след о подозрении на иную форму ответа

#### Scenario: Перевода на язык нет — следа не остаётся

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** выдача несёт блоки переводов с ключами ожидаемого вида, но без нужного языка
- **THEN** `Candidate.Title` = primary name у каждой записи
- **AND** диагностического следа не остаётся: это штатный исход, а не подозрение

#### Scenario: Запрос поиска не сужается языком

- **GIVEN** TVDB включён, глобальный `language` = `ru`
- **WHEN** выполняется поиск
- **THEN** строка запроса поиска не содержит параметра языка
- **AND** строка запроса совпадает с той, что уходит при `language` = `en`

#### Scenario: Перевод сделал сильных кандидатов двумя — матч не подтверждён

- **GIVEN** план с `title` «Нэчжа» и `original_title` `Ne Zha`, год известен
- **WHEN** поиск TVDB возвращает две записи в пределах года ±1: одну с primary
  name `哪吒之魔童降世` и переводом `rus` «Нэчжа», другую с primary name `Ne Zha`
- **THEN** гейт даёт двух сильных кандидатов вместо одного
- **AND** подтверждённого матча нет, обе записи уходят кандидатами в review

### 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** исход подтверждения матча тот же, что был до этого изменения

