- предел длины компонента (255 байт) проверяется в BuildLinks до первой операции с ФС: ни каталога, ни ссылки при отказе не создаётся - причина пустого предпросмотра считается на показе (ReviewData.PreviewError) и печатается в панели действий и в карточке Telegram: у задачи без записанной причины взять её больше неоткуда
12 KiB
Ревью изменения 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).