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

10 KiB
Raw Blame History

Задачи

1. Код

  • 1.1 В internal/layout завести константу предела длины компонента (255 байт) с комментарием, называющим источник значения (NAME_MAX у ext4/xfs/btrfs) и обе стороны риска.

  • 1.2 Завести layout.ErrNameTooLong рядом с ErrCollision; текст ошибки называет непомещающийся компонент и его длину в байтах.

  • 1.3 В BuildLinks проверять длину компонентов целевого пути под корнем библиотеки — после проверки underRoot, до возврата ссылки. Отказ целиком (return nil, err), как для прочей невалидности плана.

  • 1.4 В worker.linkPlan добавить ветку errors.Is(err, layout.ErrNameTooLong)review с новым кодом причины name_too_long, перед общей веткой reasonBuild. Код завести в своде причин рядом с reasonCollision.

  • 1.5 Панель действий ревью при пустом предпросмотре печатает записанную причину загрузки, а при её отсутствии — прежний общий текст (web/templates/partials/review_main.html, поле вью в internal/httpapi/review.go).

2. Тесты

  • 2.1 BuildLinks с названием, дающим имя файла длиннее предела, возвращает ошибку, обёртывающую ErrNameTooLong; ни одного каталога на диске не появилось.
  • 2.2 То же для папки тайтла: название укладывается в имя файла, но папка с годом и provider-тегом длиннее предела.
  • 2.3 Проверка считает байты, а не руны: кириллическое название той же длины в знаках упирается вдвое раньше латинского. Тест на границе: помещается ровно 255 байт, 256 — нет.
  • 2.4 Путь, выходящий за библиотеку, и одновременно слишком длинное имя дают ошибку выхода за библиотеку, а не длины (приоритет проверок).
  • 2.5 worker: раскладка с непомещающимся именем уводит задачу в review с кодом name_too_long, каталог цели пуст, в error_msg нет текста системной ошибки.
  • 2.6 Раскладка нормальной длины ведёт себя как прежде (регрессия на существующих тестах пакета).
  • 2.7 Название не обрезается и не переименовывается: на непомещающемся входе ни одной ссылки не возвращено, а не возвращена усечённая.
  • 2.8 Мерится финальный компонент: имя, проходящее впритык, не растёт ни на одном шаге сборки — суффикс субтитров (.ru.forced) на границе отклоняется.
  • 2.9 Вырожденные входы не роняют проверку: пустая строка, одни пробелы, невалидный UTF-8, очень длинная строка.
  • 2.10 Панель действий: с записанной причиной и пустым предпросмотром печатается причина; без причины — прежний общий текст.

4. Правки по ревью кода

  • 4.1 Причина пустого предпросмотра считается на показе (ReviewData.PreviewError) и предпочитается записанной: задача без записанной причины иначе оставляет экран без объяснения, а после смены источника записанная устаревает.
  • 4.2 Карточка Telegram называет ту же причину, когда плана в ней нет; в колбэке — своя ветка ответа для ErrNameTooLong, как у коллизии.
  • 4.3 logCmd: ErrNameTooLong — штатный отказ, DEBUG, а не ERROR.
  • 4.4 Реестры: строка в таблицу маппинга docs/conventions/errors.md, константа предела в блок числовых пределов docs/database.md, клауза про 409 — в дельту review.
  • 4.5 Текст причины несёт имя усечённым (shorten, 40 рун), точную длину — числом: имя целиком занимало пол-экрана и оседало в БД.
  • 4.6 Сценарий «унаследованная база» переписан: случай недостижим — папка-якорь ≤ предела, а хвост имени файла не длиннее хвоста имени папки. Свойство закреплено тестом.
  • 4.7 Тесты: PreviewError при пустой причине состояния; подсказка доводит до done; унаследованная база в предел помещается; карточка Telegram и колбэк; shorten; кириллическая граница добита, неверный комментарий исправлен.

3. Гейт и спеки

  • 3.1 openspec validate --strict long-title-to-review — зелёный.
  • 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).