- чистка стоит на каждой точке входа значения метабазы в план — сборка матча, копия кандидата для ревью, набор закреплённых значений источника и его чтение: гарантия, поставленная только на запись, обходится данными, сохранёнными прежними версиями - название, непригодное как имя каталога (пустое или без единой буквы и цифры), не подставляется — раздача уходит в review с названной причиной - гейт подтверждения матча не сдвинут: сравнение с планом идёт по значениям провайдера, чистится только копия, уходящая дальше
14 KiB
Отчёт ревью — 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="/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), а не ревью.
Альтернативной реализации, с которой можно сдиффить решения, у конвейера нет. На этом изменении — правило разбора и санитизации — именно такой проход на прошлом прогоне приносил независимый оракул.