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