# Ревью изменения `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).
