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

116 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Задачи
## 1. Код
- [x] 1.1 В `internal/layout` завести константу предела длины компонента (255
байт) с комментарием, называющим источник значения (`NAME_MAX` у
ext4/xfs/btrfs) и обе стороны риска.
- [x] 1.2 Завести `layout.ErrNameTooLong` рядом с `ErrCollision`; текст ошибки
называет непомещающийся компонент и его длину в байтах.
- [x] 1.3 В `BuildLinks` проверять длину компонентов целевого пути **под корнем
библиотеки** — после проверки `underRoot`, до возврата ссылки. Отказ
целиком (`return nil, err`), как для прочей невалидности плана.
- [x] 1.4 В `worker.linkPlan` добавить ветку `errors.Is(err, layout.ErrNameTooLong)`
`review` с новым кодом причины `name_too_long`, перед общей веткой
`reasonBuild`. Код завести в своде причин рядом с `reasonCollision`.
- [x] 1.5 Панель действий ревью при пустом предпросмотре печатает записанную
причину загрузки, а при её отсутствии — прежний общий текст
(`web/templates/partials/review_main.html`, поле вью в
`internal/httpapi/review.go`).
## 2. Тесты
- [x] 2.1 `BuildLinks` с названием, дающим имя файла длиннее предела, возвращает
ошибку, обёртывающую `ErrNameTooLong`; ни одного каталога на диске не
появилось.
- [x] 2.2 То же для папки тайтла: название укладывается в имя файла, но папка с
годом и provider-тегом длиннее предела.
- [x] 2.3 Проверка считает **байты**, а не руны: кириллическое название той же
длины в знаках упирается вдвое раньше латинского. Тест на границе:
помещается ровно 255 байт, 256 — нет.
- [x] 2.4 Путь, выходящий за библиотеку, и одновременно слишком длинное имя дают
ошибку **выхода за библиотеку**, а не длины (приоритет проверок).
- [x] 2.5 `worker`: раскладка с непомещающимся именем уводит задачу в `review` с
кодом `name_too_long`, каталог цели пуст, в `error_msg` нет текста
системной ошибки.
- [x] 2.6 Раскладка нормальной длины ведёт себя как прежде (регрессия на
существующих тестах пакета).
- [x] 2.7 Название не обрезается и не переименовывается: на непомещающемся входе
ни одной ссылки не возвращено, а не возвращена усечённая.
- [x] 2.8 Мерится финальный компонент: имя, проходящее впритык, не растёт ни на
одном шаге сборки — суффикс субтитров (`.ru.forced`) на границе отклоняется.
- [x] 2.9 Вырожденные входы не роняют проверку: пустая строка, одни пробелы,
невалидный UTF-8, очень длинная строка.
- [x] 2.10 Панель действий: с записанной причиной и пустым предпросмотром
печатается причина; без причины — прежний общий текст.
## 4. Правки по ревью кода
- [x] 4.1 Причина пустого предпросмотра считается **на показе**
(`ReviewData.PreviewError`) и предпочитается записанной: задача без
записанной причины иначе оставляет экран без объяснения, а после смены
источника записанная устаревает.
- [x] 4.2 Карточка Telegram называет ту же причину, когда плана в ней нет; в
колбэке — своя ветка ответа для `ErrNameTooLong`, как у коллизии.
- [x] 4.3 `logCmd`: `ErrNameTooLong` — штатный отказ, `DEBUG`, а не `ERROR`.
- [x] 4.4 Реестры: строка в таблицу маппинга `docs/conventions/errors.md`,
константа предела в блок числовых пределов `docs/database.md`, клауза про
409 — в дельту `review`.
- [x] 4.5 Текст причины несёт имя усечённым (`shorten`, 40 рун), точную длину —
числом: имя целиком занимало пол-экрана и оседало в БД.
- [x] 4.6 Сценарий «унаследованная база» переписан: случай недостижим — папка-якорь
≤ предела, а хвост имени файла не длиннее хвоста имени папки. Свойство
закреплено тестом.
- [x] 4.7 Тесты: `PreviewError` при пустой причине состояния; подсказка доводит до
`done`; унаследованная база в предел помещается; карточка Telegram и колбэк;
`shorten`; кириллическая граница добита, неверный комментарий исправлен.
## 3. Гейт и спеки
- [x] 3.1 `openspec validate --strict long-title-to-review` — зелёный.
- [x] 3.2 `task gate` — зелёный, покрытие изменённых строк полное.
## Критерии приёмки (из постановки)
- **A1.** Раздача с именем в 250 байт уходит в `review` с доменной причиной, а не
в `failed` с текстом ядра. **Оракул:** тест на временном каталоге прогона,
воспроизводящий случай из «Воспроизведения», — проверяет состояние задачи и код
причины.
- **A2.** Ни одна ссылка не создана до отказа: частичной раскладки батча не
остаётся. **Оракул:** тот же тест — проверяет, что каталог цели пуст.
- **A3.** Причина, показанная человеку, не содержит текста системной ошибки.
**Оракул:** утверждение теста на текст причины плюс конвенция
`docs/conventions/errors.md` — перевод доменной ошибки на внешней границе.
- **A4.** Проверка длины стоит **до** первой операции с файловой системой.
**Оракул:** чтение диффа на ревью; тест на пустоту каталога цели его
подтверждает.
## Приёмочные критерии из рубрики (ревью дизайна)
Свойства, порождённые до чтения кода. Каждое проверяется тестом или чтением
диффа; те, что уже покрыты задачами выше, названы ссылкой на них.
- **R1.** Единица предела — байты UTF-8, не руны (задача 2.3).
- **R2.** Отказ целиком, без самостоятельного усечения (задачи 2.1, 2.7).
- **R3.** Мерится ровно та строка, что уйдёт в `mkdir`/`link`, — после
санитизации и всех суффиксов (задача 2.8).
- **R4.** Приоритет над проверкой песочницы (задача 2.4).
- **R5.** Исход отказа определён и починим человеком; случай, где подсказка не
помогает, назван спекой отдельным сценарием, а не умолчанием.
- **R6.** Вердикт — функция от уже зафиксированного, а не от порядка загрузок.
Держится арифметикой: унаследованная от живой папки база отказа по длине дать
не может, поэтому наличие якоря на исход не влияет
(`TestApply_InheritedBaseAtLimitStillFits`).
- **R7.** Превью и применение дают одинаковый вердикт по одному входу — потому
что зовут одну функцию, а не потому, что так договорились.
- **R8.** Константа предела несёт провенанс и обе стороны ошибки (задача 1.1).
- **R9.** Предел пути целиком (`PATH_MAX`) назван и исключён с посчитанной
причиной — в `design.md`, Non-Goals.
- **R10.** Детерминизм и чистота: проверка не ходит на диск, не спрашивает ядро,
ни один вход не даёт паники (задача 2.9).
- **R11.** Отказ наблюдаем один раз и без чужого текста: логирующий чекпоинт
один, текст причины без системной ошибки и без секретов (задача 2.5).
- **R12.** Регрессия: нормальная раскладка, идемпотентный повтор и copy-fallback
ведут себя как прежде (задача 2.6).