Files
jellybit/openspec/changes/archive/2026-08-10-long-title-to-review/review/report.md
T
av b9f0929d0c layout: непомещающееся целевое имя уводит задачу в review вместо failed
- предел длины компонента (255 байт) проверяется в BuildLinks до первой
  операции с ФС: ни каталога, ни ссылки при отказе не создаётся
- причина пустого предпросмотра считается на показе (ReviewData.PreviewError)
  и печатается в панели действий и в карточке Telegram: у задачи без
  записанной причины взять её больше неоткуда
2026-08-10 12:16:44 +03:00

12 KiB
Raw Blame History

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