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