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

7.2 KiB

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

Контекст

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