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
@@ -0,0 +1,69 @@
## MODIFIED Requirements
### Requirement: Подтверждение матча обновляет отображаемое имя
Система SHALL при подтверждении матча в ревью запускать обновление отображаемого
имени загрузки по подтверждённому распознаванию (см. capability `ingest`):
переливать **полный ярлык** имени — «Название (режиссёр, год)», для сериала со
сводкой сезонов — в `download.display_name` и в имя раздачи qBittorrent, без
нового вызова LLM. Имя строится из эффективных полей (override → распознавание с
вложенным матчем → сохранённый контекст). Подтверждением матча SHALL
считаться как выбор кандидата из списка совпадений, так и ручное добавление
источника по id/URL (оба закрепляют провайдера и каноническое название).
Закрепляемое название источника SHALL проходить санитайзинг человекочитаемых
полей (см. `recognition`) и проверку пригодности как компонента пути (см.
`metadata-match`) — на **общей** точке сборки набора пинов источника, той же,
через которую строится предпросмотр. Отсюда следует свойство, на которое
опирается экран ревью: показанное для источника название и путь совпадают с тем,
что закрепится и разложится по выбору этого источника. Название, непригодное как
имя каталога, пином SHALL NOT становиться — в плане остаётся название
распознавания, а факт отказа SHALL быть наблюдаем в журнале.
Санитайзинг на закреплении SHALL применяться независимо от того, было ли значение
очищено при сохранении кандидата: гарантия чистоты не может держаться на времени
записи строки, иначе кандидаты, сохранённые прежними версиями, обходят её. Тот же
санитайзинг идемпотентен, поэтому на уже очищенном значении он ничего не меняет.
При закреплении выбранного/добавленного источника система SHALL best-effort
получить режиссёра этого источника из метабазы (credits по `provider:id`, см.
`metadata-match`) и закрепить его как override, чтобы он попал в эффективные поля
и в ярлык. Недоступность credits или отсутствие режиссёра SHALL NOT проваливать
выбор источника: режиссёр остаётся из более низкого слоя (сохранённый контекст)
или пустым. Так режиссёр из метабазы появляется и на **основном** пути
подтверждения — ручном выборе кандидата, а не только при авто-матче.
Обновление SHALL выполняться после успешного закрепления выбора кандидата и
SHALL быть best-effort по отношению к qBittorrent: недоступность клиента SHALL
NOT проваливать команду ревью. Это согласуется с инвариантом «авто-действие
только при подтверждённом матче».
#### Scenario: Выбор кандидата переливает каноническое имя
- **GIVEN** загрузка в ревью с кандидатами метабазы
- **WHEN** человек выбирает кандидата
- **THEN** провайдер, id и каноническое название закрепляются как override
- **AND** отображаемое имя загрузки обновляется полным ярлыком
#### Scenario: Название источника показано ровно таким, каким закрепится
- **GIVEN** кандидат, название которого содержит zero-width символ или
кириллический двойник внутри латинского слова
- **WHEN** строится список источников для экрана ревью
- **THEN** в строке источника и в его предпросмотре стоит очищенное название
- **AND** выбор этого источника закрепляет то же самое значение
#### Scenario: Кандидат, сохранённый прежней версией, чистится на закреплении
- **GIVEN** кандидат, чьё название записано в хранилище без санитайзинга
- **WHEN** человек выбирает этого кандидата
- **THEN** закрепляется санитизированное значение, а не то, что лежит в хранилище
#### Scenario: Непригодное название источника пином не становится
- **GIVEN** кандидат, название которого не содержит ни одной буквы и ни одной
цифры
- **WHEN** человек выбирает этого кандидата
- **THEN** название пином не становится, в плане остаётся название распознавания
- **AND** провайдер, id и год закрепляются как обычно
- **AND** отказ закрепить название виден в журнале