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

91 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. Код
- [x] 1.1 В `internal/recognize/recognize.go` прогнать `match.Title` через
`sanitizeTitle` перед подстановкой в `plan.Title`; год, режиссёр и провайдер
оставить как есть
- [x] 1.2 Непригодный результат чистки (пусто либо ни одной буквы и цифры):
подстановку не выполнять, прежнее название сохранить, причину передать **в**
`decide`, а не дописывать к готовому решению (см. design, Решение 4)
- [x] 1.3 Логировать на `Debug`, когда чистка реально изменила каноническое
название — тем же способом, что `sanitizeField` (названия не секреты)
- [x] 1.4 Чистить `Title`/`OriginalTitle` кандидата на **копии**, в момент
`append` в `candidates` (`internal/recognize/metadata.go`). Место накопления не
двигать — оно стоит до `if match != nil { continue }`, и перенос обеднил бы
список кандидатов при подтверждённом матче с заблокированным авто. Срез `cands`
не мутировать: `for _, c := range cands` даёт копию структуры (все поля
`metadata.Candidate` скалярные), поэтому сравнение остаётся на значениях
провайдера и гейт матча не сдвигается
- [x] 1.5 Закрепление кандидата с непригодным названием (`chooseCandidateLocked`,
`internal/worker/review.go`): пин названия не ставится, в плане остаётся
название распознавания — тот же исход, что у канонического названия
## 2. Тесты
- [x] 2.1 Табличный тест на четыре входа из «Воспроизведения» задачи: три ZWSP
(`U+200B`), `Dune<RLO>gnp.mkv` (`U+202E`), `Dune\nHACK`, `Dunа` с кириллической
`а` — проверяет `plan.Title` и `Decision.Auto`
- [x] 2.2 Граничные случаи непригодного названия: три ZWSP (пусто после чистки) и
вырожденные `"."`, `"..."`, `"-"`, `" . "` — план сохраняет прежнее название,
`Auto=false`, причина названа, год и провайдер подставлены
- [x] 2.5 Идемпотентность: `sanitizeTitle(sanitizeTitle(x)) == sanitizeTitle(x)`
по тому же набору входов, что и 2.1
- [x] 2.6 Кандидаты, уходящие в ревью, очищены: те же четыре входа в названии
кандидата — в `Result.Candidates` значения санитизированы
- [x] 2.7 Гейт матча не сдвинулся: кандидат, отличающийся от плана только
zero-width внутри слова, по-прежнему **не** даёт подтверждённого матча
(сравнение идёт по значению провайдера)
- [x] 2.8 Список кандидатов не обеднел: при подтверждённом матче от первого
провайдера кандидаты остальных провайдеров того же ключа по-прежнему в списке
- [x] 2.9 Закрепление кандидата с названием `-`: пин названия не поставлен, в
плане остаётся название распознавания, каталог с мусорным именем не строится
- [x] 2.3 Нормальное название матча: значение в плане совпадает с ответом базы,
`Auto=true` при прочих чистых условиях
- [x] 2.4 Прогнать существующие тесты `internal/recognize` и `internal/metadata`
**без правки ожиданий** — регресс на TMDB виден сразу
## 3. Гейт и спеки
- [x] 3.1 `task gate` зелёный
- [x] 3.2 `openspec validate --strict metadata-title-sanitize`
## Критерии приёмки задачи
Приходят из постановки `tasks/items/metadata-title-sanitize.md`, здесь — дословно.
- [x] A1 Все четыре входа из «Воспроизведения» дают либо санитизированный
`plan.Title`, либо уход в review — но не авто-раскладку с исходным значением.
**Оракул:** тот самый падающий тест из отчёта триажа, перенесённый в дерево
- [x] A2 Название, схлопывающееся санитизацией в пустую строку, не порождает
каталог с пустым именем и не роняет раскладку. **Оракул:** табличный тест на
границе, случай «три ZWSP»
- [x] A3 Поведение TMDB на нормальных названиях не изменилось. **Оракул:**
существующие тесты `internal/recognize` и `internal/metadata` зелёные без правок
ожиданий
- [x] A4 Место санитизации названо требованием спеки, а не только кодом.
**Оракул:** `openspec validate --strict` на дельте
## 4. Правки по ревью кода
- [x] 4.1 Санитайзинг канонического названия перенесён в `buildMatch`
единственную точку сборки `Match`; `recognize.go` и `decide` читают уже чистое
значение и не пересчитывают условие каждый по-своему
- [x] 4.2 Чистка и проверка пригодности названия источника перенесены в
`sourcePins` — общий дом набора пинов, через который идут и превью, и
закрепление; «превью = применение» держится конструкцией
- [x] 4.3 Кандидаты, сохранённые прежней версией, чистятся на закреплении:
гарантия не держится на времени записи строки
- [x] 4.4 Отказ закрепить непригодное название виден в журнале — атрибут
`title_pinned` у существующей записи `review candidate chosen`
- [x] 4.5 Дельта `review` — требование «Подтверждение матча обновляет
отображаемое имя» дополнено санитайзингом на закреплении и свойством
«превью = применение»
- [x] 4.6 Тесты: `TestChooseCandidate_DirtyLegacyTitleSanitized`,
`TestBuildSources_PreviewMatchesApply` (оба падают без правки)
- [x] 4.7 Чистка названия и на **чтении** пина (`applyOverrides`): пин,
закреплённый прежней версией, обычным «Применить» не переписывается, и без
этого доезжал до имени каталога дословно (находка эксплуатационного прохода)
- [x] 4.8 `naming.sanitize` снимает категорию Cf: дельта `recognition`
обосновывает отказ чистить `director` в плане тем, что его чистит рендер, — а
рендер снимал только C0, и RLO из credits разворачивал карточку Telegram
(находка триажа)