recognize: название из метабазы санитизируется перед попаданием в план

- чистка стоит на каждой точке входа значения метабазы в план — сборка матча,
  копия кандидата для ревью, набор закреплённых значений источника и его
  чтение: гарантия, поставленная только на запись, обходится данными,
  сохранёнными прежними версиями
- название, непригодное как имя каталога (пустое или без единой буквы и
  цифры), не подставляется — раздача уходит в review с названной причиной
- гейт подтверждения матча не сдвинут: сравнение с планом идёт по значениям
  провайдера, чистится только копия, уходящая дальше
This commit is contained in:
av
2026-08-10 10:41:16 +03:00
parent bb278e8744
commit 9aecf757e0
26 changed files with 1653 additions and 53 deletions
+36 -33
View File
@@ -231,6 +231,20 @@ LLM; нет матча в базе или несколько кандидато
считаться как выбор кандидата из списка совпадений, так и ручное добавление
источника по id/URL (оба закрепляют провайдера и каноническое название).
Закрепляемое название источника SHALL проходить санитайзинг человекочитаемых
полей (см. `recognition`) и проверку пригодности как компонента пути (см.
`metadata-match`) — на **общей** точке сборки набора пинов источника, той же,
через которую строится предпросмотр. Отсюда следует свойство, на которое
опирается экран ревью: показанное для источника название и путь совпадают с тем,
что закрепится и разложится по выбору этого источника. Название, непригодное как
имя каталога, пином SHALL NOT становиться — в плане остаётся название
распознавания, а факт отказа SHALL быть наблюдаем в журнале.
Санитайзинг на закреплении SHALL применяться независимо от того, было ли значение
очищено при сохранении кандидата: гарантия чистоты не может держаться на времени
записи строки, иначе кандидаты, сохранённые прежними версиями, обходят её. Тот же
санитайзинг идемпотентен, поэтому на уже очищенном значении он ничего не меняет.
При закреплении выбранного/добавленного источника система SHALL best-effort
получить режиссёра этого источника из метабазы (credits по `provider:id`, см.
`metadata-match`) и закрепить его как override, чтобы он попал в эффективные поля
@@ -244,46 +258,35 @@ SHALL быть best-effort по отношению к qBittorrent: недост
NOT проваливать команду ревью. Это согласуется с инвариантом «авто-действие
только при подтверждённом матче».
#### Scenario: Выбор кандидата обновляет имя
#### Scenario: Выбор кандидата переливает каноническое имя
- **GIVEN** загрузка в ревью с пустым или неинформативным `display_name`
(например, «Unknown») и списком кандидатов
- **WHEN** пользователь выбирает кандидата, подтверждая матч
- **THEN** выбор кандидата закрепляется как и прежде
- **AND** `download.display_name` обновляется полным ярлыком
«Название (режиссёр, год)» (для сериала — со сводкой сезонов)
- **AND** раздача в qBittorrent переименовывается в то же имя
- **GIVEN** загрузка в ревью с кандидатами метабазы
- **WHEN** человек выбирает кандидата
- **THEN** провайдер, id и каноническое название закрепляются как override
- **AND** отображаемое имя загрузки обновляется полным ярлыком
#### Scenario: Ручное добавление источника обновляет имя
#### Scenario: Название источника показано ровно таким, каким закрепится
- **GIVEN** загрузка в ревью без совпадений в списке
- **WHEN** пользователь вручную добавляет источник по id/URL, подтверждая матч
- **THEN** источник закрепляется как и прежде
- **AND** `download.display_name` и имя раздачи в qBittorrent обновляются
полным ярлыком подтверждённого источника
- **GIVEN** кандидат, название которого содержит zero-width символ или
кириллический двойник внутри латинского слова
- **WHEN** строится список источников для экрана ревью
- **THEN** в строке источника и в его предпросмотре стоит очищенное название
- **AND** выбор этого источника закрепляет то же самое значение
#### Scenario: Выбор кандидата подтягивает режиссёра в ярлык
#### Scenario: Кандидат, сохранённый прежней версией, чистится на закреплении
- **GIVEN** загрузка в ревью, у выбранного кандидата в credits метабазы указан
режиссёр
- **WHEN** пользователь выбирает кандидата, подтверждая матч
- **THEN** режиссёр best-effort извлекается из метабазы и закрепляется override
- **AND** `download.display_name` получает полный ярлык с этим режиссёром
- **GIVEN** кандидат, чьё название записано в хранилище без санитайзинга
- **WHEN** человек выбирает этого кандидата
- **THEN** закрепляется санитизированное значение, а не то, что лежит в хранилище
#### Scenario: Режиссёр кандидата недоступен — выбор не ломается
#### Scenario: Непригодное название источника пином не становится
- **GIVEN** выбор кандидата, для которого credits недоступны или режиссёра нет
- **WHEN** пользователь подтверждает матч
- **THEN** выбор источника выполнен, режиссёр берётся из сохранённого контекста
или остаётся пустым
- **AND** команда ревью не возвращает ошибку
#### Scenario: Недоступность qBittorrent не ломает выбор кандидата
- **GIVEN** выбор кандидата в ревью
- **WHEN** переименование раздачи в qBittorrent завершается ошибкой
- **THEN** выбор кандидата и обновление `download.display_name` выполнены
- **AND** команда ревью не возвращает ошибку
- **GIVEN** кандидат, название которого не содержит ни одной буквы и ни одной
цифры
- **WHEN** человек выбирает этого кандидата
- **THEN** название пином не становится, в плане остаётся название распознавания
- **AND** провайдер, id и год закрепляются как обычно
- **AND** отказ закрепить название виден в журнале
### Requirement: Инфо и предпросмотр выбранного источника