- чистка стоит на каждой точке входа значения метабазы в план — сборка матча, копия кандидата для ревью, набор закреплённых значений источника и его чтение: гарантия, поставленная только на запись, обходится данными, сохранёнными прежними версиями - название, непригодное как имя каталога (пустое или без единой буквы и цифры), не подставляется — раздача уходит в review с названной причиной - гейт подтверждения матча не сдвинут: сравнение с планом идёт по значениям провайдера, чистится только копия, уходящая дальше
83 lines
7.2 KiB
Markdown
83 lines
7.2 KiB
Markdown
# Значение метабазы чистится на каждой точке входа в план, а три санитайзера не сводятся в один
|
||
|
||
- **Дата:** 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` — общем доме набора закреплённых значений, через который
|
||
идут и предпросмотр, и закрепление.
|