layout: непомещающееся целевое имя уводит задачу в review вместо failed

- предел длины компонента (255 байт) проверяется в BuildLinks до первой
  операции с ФС: ни каталога, ни ссылки при отказе не создаётся
- причина пустого предпросмотра считается на показе (ReviewData.PreviewError)
  и печатается в панели действий и в карточке Telegram: у задачи без
  записанной причины взять её больше неоткуда
This commit is contained in:
av
2026-08-10 12:16:44 +03:00
parent 1710e5a9d5
commit b9f0929d0c
31 changed files with 1511 additions and 17 deletions
@@ -0,0 +1,47 @@
## Why
Название бывает длиннее, чем файловая система разрешает назвать файл. Сегодня
раскладка узнаёт об этом от ядра, уже начав работу: задача падает в `failed`, а
владелец видит вместо карточки ревью английскую строку `file name too long`.
Состояние `failed` терминальное — починить случай нечем, хотя чинится он одной
подсказкой на ревью, ровно как занятый целевой путь.
## What Changes
- Раскладка проверяет длину имени **до** первой операции с файловой системой:
каждый компонент целевого пути (папка тайтла, папка сезона, имя файла) обязан
помещаться в предел, который держат распространённые файловые системы Linux.
- Не помещается — раскладка отказывается целиком, не создав ни одного каталога и
ни одной ссылки, и задача уходит в `review` со своей причиной («имя не
помещается»), а не в `failed` с текстом системной ошибки.
- Владелец правит название подсказкой на ревью и применяет заново — путь тот же,
что при коллизии цели.
## Capabilities
### New Capabilities
Новых нет.
### Modified Capabilities
- `file-layout`: новое требование о непомещающемся целевом имени — проверка до
файловых операций, отказ целиком, уход в `review` с доменной причиной.
- `review`: панель действий при пустом предпросмотре печатает записанную причину
вместо общего «Подтверди источник». Пустой предпросмотр наступает от коллизии,
от непомещающегося имени и от невалидного плана — текст один на всех и в трёх
случаях из четырёх неверен.
## Impact
- `internal/layout` — проверка длины компонентов в `BuildLinks` (чистая функция,
до `mkdir`/`link`) и своя доменная ошибка вместо системной.
- `internal/worker` — ветка перевода задачи в `review` по этой ошибке, рядом с
веткой коллизии цели; свой код причины для корреляции.
- Веб-UI и Telegram — панель действий и карточка ревью называют причину, по
которой плана нет; причина считается на показе, поэтому относится к текущему
плану. Ревью показало, что взять её из записанного поля недостаточно: на самом
частом входе оно пусто.
- `internal/httpapi` — трансляция новой доменной ошибки в конфликт, а не в сбой
сервера; `internal/tgbot` — своя ветка ответа, как у коллизии.
- Внешних зависимостей, схемы БД и конфига изменение не трогает.