# Ревью изменения `long-title-to-review` — триаж Отчёт агента `review-triage`. Записан оркестратором: харнесс блокирует запись файлов подагентами. ## Сводка - **Размер:** 9 изменённых файлов (+180/−5) плюс новый `internal/layout/name_length_test.go`. Продуктового кода ~47 строк. - **Метка:** `medium`, назначена человеком; разметчик дал `small`. Режим — по графу. - **Сигнал о заниженной метке:** от `review-code` — «`medium` адекватна, `small` был бы занижен: дифф трогает четыре слоя». Триаж поддержал основанием, которого сам сигнал не назвал: код причины `name_too_long` оседает в `downloads.error_code` и обратной правкой после мерджа не откатывается. - **Гейт:** зелёный целиком, подтверждён `autotests` независимо; ни одного `SKIP`, `-race` реально прогонялся. - **Находок на вход:** 14 плюс 1 promote-кандидат. **Осталось:** 5 (2 блокируют, 3 к исправлению), 3 понижены в гипотезы, 2 promote. ### План разметки с исходом | тема | дом | глубина | кто закрывает | исход | |---|---|---|---|---| | requirements | `openspec/specs/` + дельты | разбор | specs | закрыта, 3 находки | | autotests | `CLAUDE.md` → «Гейт» | — | autotests | закрыта, находок нет | | conventions | `docs/conventions/` | разбор | code | закрыта, 5 находок (склеены в 3) | | architecture | `docs/architecture.md` | разбор | basics | закрыта, 1 находка | | security | `docs/security.md` | разбор | basics | закрыта, находок нет | | operations | `docs/architecture.md` → «Эксплуатация» | разбор | basics | закрыта, 3 находки | | темы проекта | — | — | — | своих тем нет | Тем без отчёта нет. ## Блокирует мердж ### 1. На самом вероятном входе экран ревью не называет причину и теряет «Применить» - Файл: `internal/worker/review.go:1085-1095`, `:240`, `web/templates/partials/review_main.html:79` - Severity: major. Confidence: high. - Оракул: два дерева (`git archive HEAD` против того же среза с наложенным `git diff HEAD`), один вход — задача в `review` без записанной причины, название `strings.Repeat("ы", 200)`: ``` HEAD: preview_links=2 (HasLinks=true) → «Применить» показана Apply → failed, msg=970 байт "…file name too long" рабочее дерево: preview_links=0 (HasLinks=false) → формы /apply нет StateError="" → «Подтверди источник…» ``` - Последствие: путь «распознавание без подтверждённого матча → `review`» — штатный и самый частый вход в ревью. На нём экран советует подтвердить источник, который ни при чём. Изменение, чья цель «человек узнаёт причину», на этом входе делает диагностируемость хуже, чем была. - **Действие: развилка.** Сюда же сняты два спутника той же причины: текст печатается дважды (баннер + панель) и после смены источника в панели остаётся причина от плана, которого больше нет. - Найдено проходами: `specs`, `basics`; склеено триажем. **Отработано:** причина пустого предпросмотра считается на показе и приезжает отдельным полем (`ReviewData.PreviewError`), панель предпочитает её записанной. Человек выбрал вариант (а) на повторном чекпоинте. ### 2. Карточка Telegram в `review` не называет причину вовсе - Файл: `internal/tgbot/render.go:70-95`, `internal/tgbot/bot.go:396-415` - Severity: major. Confidence: high. - Оракул: рендер карточки на рабочем дереве, задача в `review` с записанной причиной `name_too_long` и пустым предпросмотром — упоминает причину `false`, кнопки «Применить» нет. Контроль: та же причина в состоянии `failed` (исход до изменения) печатала текст и давала кнопку «Повтор». - Последствие: владелец видит «Нужно подтверждение» без плана, без кнопки и без слова о длине имени. `proposal.md` обещал обратное; `docs/architecture.md` → «Единые точки» называет обе границы трансляции, обновлена была одна. - **Действие: развилка.** **Отработано:** карточка печатает причину для любого случая (вариант (а)), плюс своя ветка ответа в колбэке, как у коллизии. ## Стоит исправить сейчас ### 3. Штатный отказ краснит логи как сбой, и три предписанных реестра о новой ветке не знают - Файл: `internal/worker/worker.go:962-968`, `docs/conventions/errors.md:81-97`, `docs/database.md:210-214` - Severity: minor. Confidence: high. - Оракул: прогон ручного `Apply` с логгером Debug — `level=ERROR msg="command failed"`. `docs/conventions/logging.md:154` относит штатный конфликт к `DEBUG` и ставит `layout.ErrCollision` поимённо. - **Действие: инлайн.** **Отработано** целиком: `logCmd`, таблица `errors.md`, блок констант `database.md`, клауза 409 в дельте `review`. ### 4. Текст причины несёт непомещающееся имя целиком — 494 байта - Файл: `internal/layout/name.go:29-31` - Severity: minor. Confidence: high. - Оракул: `Apply` с названием `strings.Repeat("ы", 200)` → `msg_len_bytes=494`. - **Действие: инлайн.** **Отработано:** имя усекается серединой (`shorten`, 40 рун), точная длина остаётся числом. ### 5. Два сценария дельты не проверены ничем; комментарий у теста границы описывает не тот вход - Файл: дельта `file-layout`, `internal/layout/name_length_test.go:109-113` - Severity: minor. Confidence: high. - **Действие: инлайн.** **Отработано, и по одному из сценариев — не так, как предлагал триаж.** Попытка написать тест на «унаследованную базу» показала, что случай **недостижим**: папка-якорь лежит на диске и потому ≤ предела, а хвост имени файла (11 байт) не длиннее хвоста имени папки (≥ 11 байт). Сценарий переписан на верное утверждение, свойство закреплено тестом `TestApply_InheritedBaseAtLimitStillFits`. Комментарий у границы исправлен, кириллическая граница добита. ## Гипотезы без доказательства - Тупик с унаследованной базой не назван в тексте причины (`basics`, B-4). **Снято разбором:** случай недостижим, см. находку 5. - Рубрика R6 в `tasks.md` противоречит сценарию S6 дельты. **Снято:** после переписывания сценария противоречия нет. - Панель печатает любой `error_msg` без разбора кода, включая сырые тексты ошибок хранилища. Не отработано: тот же текст уже печатал баннер до изменения, нового канала не появилось. ## Promote candidates - **Язык текстов sentinel-ошибок:** русский у тех, что уходят на операторскую поверхность, английский у прочих; правила в `docs/conventions/errors.md` нет. - **Внешний текст в персистентной диагностике — только усечённым.** В проекте есть и `shorten`, и `naming.truncate`, но правила «то, что уезжает в `error_msg`, усекается на границе» нет; отсюда находка 4. ## Границы покрытия **Что запускалось.** Метка `medium`, режим по графу: `autotests`, `specs`, `code`, `basics`, `triage`. Ревью дизайна (`specs` + `rubric`) отработало отдельным чекпоинтом до кода. **Что не запускалось и почему.** Всё, что требует запуска — построенные пути атаки, замеры под нагрузкой, эксплуатационный постмортем, — даёт только метка `large`. Живые qBittorrent, LLM, метабазы и Telegram не трогались; рабочая БД не трогалась. **Чего не мог каждый проход.** `autotests` — отличить исполнение строки от её проверки. `specs` — судить о качестве распознавания. `basics` на `medium` — строить пути и снимать замеры. `code` — сверять с руководствами по стилю языка. Триаж — находить новое: он работает с чужими выводами. **Потолки проходов.** Ни один проход не сообщил ни своего потолка, ни числа находок за срезом. Это находка о прогоне: по молчанию нельзя отличить «показал всё» от «показал верхушку». **Не проверялось никем.** Решения проекта (`docs/adr/`) и записанные наблюдения (`docs/research/`) — процессные документы, прогон их не открывает; расхождение с записанным решением ловит `av-dev-docs:healthcheck`. Поимённая сверка с руководствами по стилю Go никем не задавалась. Альтернативной реализации, с которой можно сдиффить решения, у конвейера нет. **Осталось на человеке.** История инцидентов на umbar; поведение SQLite под реальным объёмом; завязка внешних потребителей на текущее поведение; суждение «этой функциональности не должно существовать»; качество распознавания; идиоматичность Go (проход упразднён 2026-08-04).