# Отчёт ревью — `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 = "Denis‮Villeneuve"` → `label = "Dune (Denis‮Villeneuve, 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="/etc/passwd"` → каталог `/etc/passwd (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`), а не ревью. **Альтернативной реализации, с которой можно сдиффить решения, у конвейера нет.** На этом изменении — правило разбора и санитизации — именно такой проход на прошлом прогоне приносил независимый оракул.