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