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

14 KiB
Raw Blame History

Отчёт ревью — metadata-title-sanitize

Стадия: ревью кода после apply, до archive. Отчёт триажа записан оркестратором (проход review-triage пишет только во временный каталог).

Сводка

Размер / сложность / метка среднее / незнакомое / large (максимум по осям)
Обоснование метки триггер «заводится или меняется правило идентичности, слияния или разбора — построение целевых путей и санитизация имён» (docs/review.md → «Триггеры метки»)
Режим по графу
База диффа HEAD~1, рабочее дерево
Гейт зелёный, прогнан триажом самостоятельно: 14 шагов OK, ни одного SKIP, -race реально выполнился, diff-coverage 46/46 строк (100%)
Находок на входе 16 (+1 замер): specs 4, code 3, architecture 3, adversary 3, ops 3, autotests 0
После дедупликации по причине 11 причин + 1 найденная самим триажом
Итог 0 блокирующих, 1 «стоит исправить сейчас», 3 гипотезы, 2 promote

Сигнал о заниженной метке: review-code возражений не подал. Второго, независимого от него корректора на этом прогоне не было — basics не запускался, своих тем проекта не размечено.

План разметки и исход по каждой теме

тема дом глубина закрывает исход
requirements дельты change + openspec/specs/{metadata-match,recognition} разбор specs закрыта, 4 находки (minor)
autotests CLAUDE.md → «Гейт» autotests закрыта, 0 находок
conventions docs/conventions/* разбор code закрыта, 3 находки (minor), нарушений конвенций 0
architecture docs/architecture.md + источник docs/passport.md доказательство architecture закрыта, 3 находки (minor)
security docs/security.md доказательство adversary закрыта, 2 построенных пути (major) + 1 свойство
operations docs/architecture.md «Эксплуатация» + источник docs/database.md доказательство ops закрыта, 3 постмортема (2 major) + 1 замер

Тем без отчёта нет. Тем без дома план не содержал.

Что стало с каждой причиной

# причина кто нашёл статус
1 превью источника расходится с применением architecture + adversary закрыта: общий дом пинов sourcePins; TestBuildSources_PreviewMatchesApply
2 кандидат, лежащий в БД грязным, пиннит несанитизированное название specs + code + adversary закрыта: чистка в sourcePins; TestChooseCandidate_DirtyLegacyTitleSanitized
3 пин, закреплённый до деплоя, читается дословно ops закрыта: чистка на чтении в applyOverrides; TestApplyOverrides_LegacyDirtyPinSanitized
4 предикат пригодности считается дважды из сырого значения specs + code закрыта: decide читает уже чистое Match.Title
5 отказ закрепить название молчит specs + code закрыта: атрибут title_pinned
6 Match.Title остаётся сырым, чистка в двух местах architecture закрыта: единственная точка сборки buildMatch
7 specs/review обещает закрепление безусловно specs закрыта: заведена дельта review
8 грязный каталог-якорь наследуется дальше ops не чинится сознательно, см. H1
9 название от LLM гейта пригодности не получает adversary вне объёма, см. P1
10 набор пинов пишется пятью транзакциями ops пре-существующее, см. H2
11 resolveFolderBase даёт SCAN по recognition ops (замер) пре-существующее, см. H3
12 режиссёр и название доезжают до карточки с RLO и zero-width триаж закрыта: naming.sanitize снимает Cf

Блокирует мердж

Пусто. Обе major-находки враждебного прохода и обе major эксплуатационного отработаны, каждая с падающим-без-правки тестом в дереве. Потолок не срабатывал.

Стоит исправить сейчас — 1 находка (отработана)

Название и режиссёр из метабазы доезжают до карточки Telegram, шапки веб-UI и имени раздачи с RLO и zero-width

  • Файл: internal/naming/naming.go, sanitize
  • Severity: minor · Confidence: high
  • Оракул (прогон триажа): director = "DenisVilleneuve"label = "Dune (DenisVilleneuve, 2021)"; naming.sanitize снимал только C0 (r < 0x20) и не трогал категорию Cf.
  • Последствие: дельта recognition этого изменения обосновывает отказ чистить director в плане тем, что «его очистка применяется при рендере отображаемого имени». Рендер этой гарантии для Cf не давал — то есть изменение записывало в спеку свойство, которого в коде нет. RLO из credits разворачивает отображение имени в карточке Telegram, которая для единственного пользователя и есть интерфейс.
  • Найдено проходом: триаж, при добыче оракула на утверждение дельты. Ни один проход этого не заявлял: тема requirements сверяла дельту с кодом изменения, а обоснование дельты уводило в пакет, которого дифф не касался.
  • Отработано инлайн: naming.sanitize снимает unicode.Cf, тест TestSanitize_StripsFormatChars.

Гипотезы без доказательства

H1. Грязный каталог-якорь наследует имя всем последующим раздачам того же матча. Оракула нет (постмортем-рассуждение), severity снижена до minor. Предложение прохода — чистить базу, извлечённую из якоря, — дало бы вторую папку рядом с существующей, то есть исход, против которого затевалась задача. planBase и так гоняет sanitizeComponent по FolderBase, на уровне ФС база безопасна; речь только о невидимке внутри имени существующей папки. Задача не заводится: лечение — переименовать папку руками, записано риском в design.md.

H2. Набор пинов пишется пятью отдельными транзакциями. Пре-существующее, диффом не заведено (дифф добавил только присваивание переменной ради лог-атрибута). Окно — миллисекунды между пятью UPSERT в локальный SQLite, пути отказа никто не строил. Дом — цель state-integrity.

H3. resolveFolderBase даёт SCAN по recognition без индекса на (provider, provider_id). Замер прохода ops: 12.9 мс при 100 загрузках и 20000 file_link. Команды замера в выводе прохода нет, триаж его не воспроизводил — число несётся как заявленное, не как проверенное. Пре-существующее; дом есть — tasks/items/scale-100-downloads.md.

Promote candidates

P1. Гейт пригодности названия стоит у источников, а не на границе layout. Путь построен триажом: title="-" → каталог - (2021); title="…" → каталог … (2021); title="..."layout: empty title after sanitization; title="etcpasswd" → каталог etcpasswd (2021). Инвариант «целевой путь строго под библиотекой» не нарушен (underRoot держит, полноширинный не разделитель), «существующее не перезаписываем» тоже (коллизия → review). Отсюда minor. Претензия к правилу: UsableTitle зовут три вызывающих со стороны метабазы, а настоящая граница — вход в layout. Класс тот же, что у задачи long-title-to-review; расширить её, а не заводить новую.

P2. ident.Parse на входных границах остаётся правилом без механизации. Стоячий разрыв из CLAUDE.md, кандидат в internal/archrules.

Отсев вкусовщины

По проектному списку «Типовые ложноположительные» не выброшено ничего — ни одна входящая находка под его пункты не подошла. По общим критериям выброшены две: «правило разложено по трём пакетам» (после переноса чистки последствия за пределами чтения не осталось, а отдельный дом правила у layout дельта recognition легитимизирует дословно) и «атрибут title_pinned конфлатит два случая» (исход в обоих один, последствия нет).

Границы покрытия

Запускалось: autotests, specs, code, architecture, adversary, ops, triage — метка large, режим по графу. Стадия ревью дизайна прогонялась отдельно составом specs, rubric, architecture; её находки отработаны до реализации.

Не запускалось: basics — своих тем проекта не размечено, принимать нечего; следствие — второго, независимого от code, сигнала о заниженной метке не было. Проход независимой реализации в конвейере отсутствует (снят по стоимости), idiom упразднён 2026-08-04.

Ни один из шести проходов не сообщил свой потолок и величину среза. По молчанию «показал всё» неотличимо от «показал первые N» — это находка о прогоне.

Правки оркестратора не видел ни один проход. Все шесть отчётов сняты с состояния кода до исправлений; семь закрытых причин — правки, по которым ревью не проводилось. Их проверка сводится к зелёному гейту со 100% diff-coverage, трём новым падающим-без-правки тестам и чтению кода в точках единственности (buildMatch, sourcePins, applyOverrides). Находка №12 — прямое следствие: она живёт в пакете, которого дифф не касался.

Не проверит ни один проход (docs/review.md → «Недоступно проверке»): история инцидентов на umbar; поведение SQLite под реальным объёмом и профилем; завязка внешних потребителей (Jellyfin, закладки) на текущее поведение; суждение «этой функциональности не должно существовать»; качество распознавания как такового.

Перестали проверять сознательно: идиоматичность Go — с 2026-08-04, различение «идиоматично против распространено» не спрашивает никто; класс обратимый, пересмотр — задача quality-review-agents.

Решения и наблюдения проекта прогон не читал: docs/adr/ и docs/research/ — процессные документы. Расхождение изменения с записанным решением ловит сверка документации (av-dev-docs:healthcheck), а не ревью.

Альтернативной реализации, с которой можно сдиффить решения, у конвейера нет. На этом изменении — правило разбора и санитизации — именно такой проход на прошлом прогоне приносил независимый оракул.