# Значение метабазы чистится на каждой точке входа в план, а три санитайзера не сводятся в один - **Дата:** 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` — общем доме набора закреплённых значений, через который идут и предпросмотр, и закрепление.