# Значение метабазы чистится на каждой точке входа в план, а три санитайзера не сводятся в один

- **Дата:** 2026-08-10
- **Источник:**
  [openspec/changes/archive/2026-08-10-metadata-title-sanitize/design.md](../../openspec/changes/archive/2026-08-10-metadata-title-sanitize/design.md),
  разделы `Decisions` (Решения 1, 1a, 3) и `Non-Goals`

## Контекст

Название, приходящее из TMDB/TVDB/TVMaze, попадает в имя каталога библиотеки
Jellyfin. Выход LLM мы чистим и считаем недоверенным; название из метабазы того
же обращения не получало, хотя приходит так же — из-за периметра. Наблюдаемый
исход: каталог из невидимых символов выглядит пустым, кириллическая буква внутри
латинского слова даёт вторую папку, неотличимую от первой, и авто-раскладка это
пропускала.

Разбор показал, что точка входа не одна. Их четыре, и каждая ведёт в имя
каталога: сборка подтверждённого матча, копия кандидата, уходящая на экран
ревью и в хранилище, набор закреплённых значений выбранного человеком
источника и **чтение** уже закреплённого значения.

## Решение

**Чистка стоит на каждой из четырёх точек, а не в одной «правильной».**

Цитата из `design.md`, Решение 1a:

> Закрываются обе одной и той же чисткой, но в трёх местах — по одному на
> каждую точку, где значение метабазы входит в домен.

Плюс четвёртая, добавленная по находке эксплуатационного прохода: чистка **на
чтении** закреплённого значения. Гарантия чистоты не может держаться на времени записи строки —
кандидаты и закреплённые значения, сохранённые прежними версиями, обходят её,
а обычное
«Применить» ничего не перезаписывает. Санитайзинг идемпотентен, поэтому лишние
точки на уже чистом значении не делают ничего; это же свойство сделано
нормативным и покрыто тестом.

Отдельно: **гейт подтверждения матча чистка не двигает.** Сравнение кандидата с
планом идёт по значениям провайдера, чистится только копия, уходящая дальше.
Причина в том, что `normalize` и санитайзинг не эквивалентны: невидимый символ
внутри слова `normalize` превращает в пробел, а санитайзинг удаляет — чистка до
сравнения превратила бы часть нынешних «в review» в «авто».

## Рассмотренные варианты

- **Свести три санитайзера проекта в один.** Отвергнуто: у них разный предмет —
  `recognize.SanitizeTitle` чистит значение, `layout.sanitizeComponent` —
  компонент пути под требования файловой системы, `naming.sanitize` —
  отображаемый ярлык. Свёртка гомоглифов — визуально неотличимых букв из разных алфавитов — внутри
  `sanitizeComponent` сломала бы
  правило сходимости базы папки: она гоняется и по имени, прочитанному с диска.
- **Закрыть только авто-путь, ручной отдать отдельной задаче.** Отвергнуто на
  чекпоинте: спека `metadata-match` сама называет ручной выбор **основным**
  путём подтверждения матча — починка коснулась бы менее употребимой половины,
  а спека утверждала бы свойство, которого нет.
- **Чистить в клиентах метабаз.** Отвергнуто: пришлось бы повторять в трёх
  клиентах и в каждом следующем, а проверка «в плане нет грязных полей»
  перестала бы читаться в одном месте.
- **Разовая правка данных вместо чистки на чтении.** Отвергнута как более
  дорогая и не закрывающая следующего читателя.
- **Полная нормализация Unicode** (NFC/NFKC плюс полная таблица визуально
  совпадающих символов Unicode) — Non-Goal. Цель — предсказуемое и сверяемое значение, а не исчерпывающая защита
  от визуального совпадения; курируемая кирилло-латинская таблица закрывает
  реальный случай.

## Что осталось нерешённым намеренно

**Каталог с невидимым символом, уже созданный в библиотеке, кодом не лечится.**
Правило сходимости базы папки наследует имя от живой папки-якоря, и очистка
извлечённой базы напечатала бы рядом вторую, чистую папку — то есть ровно тот
исход с двумя каталогами, против которого затевалось изменение. Лечение —
переименовать папку руками, после чего сходимость подхватит новое имя.
Изменение закрывает появление новых таких каталогов, а не существующие.

## Цена

Точек чистки четыре вместо одной, и правило «значение метабазы чистится на
входе в домен» держится на ревью, а не на линтере. Взамен свойство «показанное
на экране совпадает с тем, что ляжет на диск» держится устройством кода: чистка
стоит в `sourcePins` — общем доме набора закреплённых значений, через который
идут и предпросмотр, и закрепление.
