tasks: два дефекта из ревью tvdb-title-locale взяты в спринт
- metadata-title-sanitize: название из метабазы подставляется в план мимо санитизации и уезжает в имя каталога дословно - long-title-to-review: имя длиннее ~237 байт роняет раскладку в failed вместо отправки на ревью
This commit is contained in:
@@ -10,3 +10,5 @@
|
|||||||
|
|
||||||
- [✨ Узаконить confidence-гейт авто-раскладки в спеке и сделать его выключаемым (дефолт 0.7)](items/auto-link-confidence-gate.md) — Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
|
- [✨ Узаконить confidence-гейт авто-раскладки в спеке и сделать его выключаемым (дефолт 0.7)](items/auto-link-confidence-gate.md) — Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
|
||||||
- [🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой](items/background-error-noise.md) — недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации
|
- [🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой](items/background-error-noise.md) — недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации
|
||||||
|
- [🐞 Санитизировать название из метабазы перед подстановкой в план](items/metadata-title-sanitize.md) — plan.Title = match.Title подставляется ПОСЛЕ sanitizePlan — название из TMDB/TVDB уезжает в имя каталога Jellyfin дословно, с невидимыми символами и гомоглифами, и авто-раскладка это пропускает
|
||||||
|
- [🐞 Отправлять на ревью раздачу с непомещающимся именем вместо отказа](items/long-title-to-review.md) — название длиннее ~237 байт роняет раскладку в failed с текстом системной ошибки: пользователь видит «file name too long» вместо карточки ревью, где это чинится подсказкой
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# 🐞 Отправлять на ревью раздачу с непомещающимся именем вместо отказа
|
||||||
|
|
||||||
|
- **Тип:** fix
|
||||||
|
- **Категория:** Ядро продукта
|
||||||
|
- **Зачем:** название длиннее ~237 байт роняет раскладку в failed с текстом системной ошибки: пользователь видит «file name too long» вместо карточки ревью, где это чинится подсказкой
|
||||||
|
- **Теги:** sprint:2026-08-06
|
||||||
|
|
||||||
|
Длина компонента пути ограничена файловой системой (255 байт на имя, минус
|
||||||
|
расширение и суффиксы — практический потолок около 237). Название такой длины
|
||||||
|
приходит из метабазы или из распознавания штатно: длинные оригинальные заголовки
|
||||||
|
и сериалы с подзаголовком в имя укладываются не всегда.
|
||||||
|
|
||||||
|
Сегодня это не проверяется нигде: раскладка доходит до `link(2)`, получает от ядра
|
||||||
|
`file name too long`, и задача уходит в `failed` с текстом системной ошибки в
|
||||||
|
поле ошибки. Для владельца это тупик — состояние терминальное, а причина написана
|
||||||
|
языком ядра.
|
||||||
|
|
||||||
|
Правильный исход — **review**: там человек правит название подсказкой, ровно как
|
||||||
|
при коллизии цели. Коллизия уже так и обрабатывается, то есть путь в системе
|
||||||
|
есть и его надо переиспользовать, а не изобретать.
|
||||||
|
|
||||||
|
Провенанс: ревью изменения `tvdb-title-locale`, находка AD4 —
|
||||||
|
[отчёт триажа](../../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md).
|
||||||
|
Понижена там не по доказательности, а по принадлежности к диффу: путь этим
|
||||||
|
изменением не тронут.
|
||||||
|
|
||||||
|
## Воспроизведение
|
||||||
|
|
||||||
|
Оракул из отчёта: подать раздачу, у которой имя целевого файла складывается в
|
||||||
|
250 байт.
|
||||||
|
|
||||||
|
```
|
||||||
|
n=250 → Apply err=… file name too long, results=0
|
||||||
|
```
|
||||||
|
|
||||||
|
Наблюдаемо: задача в состоянии `failed`, в поле ошибки — текст системной ошибки,
|
||||||
|
ни одной ссылки не создано, карточки ревью нет.
|
||||||
|
|
||||||
|
Ожидаемо: задача в `review` с названной причиной «имя не помещается», раскладка
|
||||||
|
не начата, подсказка человека чинит случай.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/layout` — проверка длины компонента до `link(2)` и доменная ошибка
|
||||||
|
вместо системной;
|
||||||
|
- `internal/worker` — ветка перевода задачи в `review` по этой ошибке, рядом с
|
||||||
|
существующей веткой коллизии цели;
|
||||||
|
- `openspec/specs/file-layout/spec.md` — требование о непомещающемся имени;
|
||||||
|
`state-reconciliation` — если переход в `review` заказывается там;
|
||||||
|
- текст причины в карточке ревью: веб-UI и Telegram показывают её человеку.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Раздача с именем в 250 байт уходит в `review` с доменной причиной, а не в
|
||||||
|
`failed` с текстом ядра. **Оракул:** тест на временном каталоге прогона,
|
||||||
|
воспроизводящий случай из «Воспроизведения», — проверяет состояние задачи и
|
||||||
|
код причины.
|
||||||
|
- Ни одна ссылка не создана до отказа: частичной раскладки батча не остаётся.
|
||||||
|
**Оракул:** тот же тест — проверяет, что каталог цели пуст.
|
||||||
|
- Причина, показанная человеку, не содержит текста системной ошибки. **Оракул:**
|
||||||
|
утверждение теста на текст причины плюс конвенция
|
||||||
|
[errors.md](../../conventions/errors.md) — перевод доменной ошибки на внешней
|
||||||
|
границе.
|
||||||
|
- Проверка длины стоит **до** первой операции с файловой системой. **Оракул:**
|
||||||
|
чтение диффа на ревью; тест на пустоту каталога цели его подтверждает.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Обрезать или переименовывать название самостоятельно нельзя — это решение
|
||||||
|
человека, у него для этого есть подсказка на ревью. Предел длины зависит от
|
||||||
|
файловой системы; зашивать 255 как универсальную константу не стоит, но и
|
||||||
|
выяснять предел у ядра в рантайме ради этой задачи не требуется — достаточно
|
||||||
|
консервативного значения с объяснением, откуда оно.
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# 🐞 Санитизировать название из метабазы перед подстановкой в план
|
||||||
|
|
||||||
|
- **Тип:** fix
|
||||||
|
- **Категория:** Ядро продукта
|
||||||
|
- **Зачем:** plan.Title = match.Title подставляется ПОСЛЕ sanitizePlan — название из TMDB/TVDB уезжает в имя каталога Jellyfin дословно, с невидимыми символами и гомоглифами, и авто-раскладка это пропускает
|
||||||
|
- **Теги:** sprint:2026-08-06
|
||||||
|
|
||||||
|
Название, пришедшее из метабазы, подставляется в план **после** того, как план
|
||||||
|
прошёл санитизацию: `plan.Title = match.Title` стоит ниже `sanitizePlan`.
|
||||||
|
Значение уезжает в имя каталога библиотеки, не сверенное ни с чем — гейт
|
||||||
|
авто-раскладки сверяет `normalize(c.Title) || normalize(c.OriginalTitle)` и
|
||||||
|
проходит по второму полю, а в путь идёт первое.
|
||||||
|
|
||||||
|
`layout.sanitizeComponent` ниже по потоку снимает `/\:*?"<>|` и байты `< 0x20`,
|
||||||
|
но категорию Cf (невидимые управляющие) не трогает и гомоглифы не сворачивает.
|
||||||
|
Итог — два визуально неотличимых каталога в медиатеке.
|
||||||
|
|
||||||
|
**Класс пре-существующий, и это важно для рамок:** тот же путь у TMDB, задача
|
||||||
|
`tvdb-title-locale` его только распространила на второго провайдера. Инвариант
|
||||||
|
«целевой путь строго под библиотекой» **держится** — проверено на `Dune/../../etc`,
|
||||||
|
`" .. "`, `"..."` и на 400 символах, выхода из песочницы нет. Поэтому `major`,
|
||||||
|
а не `critical`.
|
||||||
|
|
||||||
|
Провенанс: ревью изменения `tvdb-title-locale`, находка 2 —
|
||||||
|
[отчёт триажа](../../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md).
|
||||||
|
|
||||||
|
## Воспроизведение
|
||||||
|
|
||||||
|
Падающий тест на реальном `Recognizer` (оракул добыт триажом, не рассуждением).
|
||||||
|
Подать кандидата метабазы с названием и посмотреть на `plan.Title` и решение
|
||||||
|
гейта:
|
||||||
|
|
||||||
|
| Вход | Что происходит |
|
||||||
|
| --- | --- |
|
||||||
|
| три символа ZWSP (`U+200B`) | `auto=true`, в план уезжают невидимки; `sanitizeTitle` дал бы пустую строку |
|
||||||
|
| `Dune<RLO>gnp.mkv` (`U+202E`) | `auto=true`, имя каталога переворачивается при отображении |
|
||||||
|
| `Dune\nHACK` | `auto=true`, перевод строки доезжает до плана |
|
||||||
|
| `Dunа` с кириллической `а` | `auto=true`, каталог визуально неотличим от латинского `Dune` |
|
||||||
|
|
||||||
|
Во всех четырёх случаях значение в плане отличается от того, что дал бы
|
||||||
|
санитайзер, и ни одно не остановлено гейтом.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/recognize/recognize.go` — порядок подстановки `match.Title` и
|
||||||
|
`match.OriginalTitle` относительно `sanitizePlan`;
|
||||||
|
- `internal/layout` — состав `sanitizeComponent`, если решим закрывать категорию
|
||||||
|
Cf и гомоглифы здесь, а не на подстановке (выбор места — часть задачи);
|
||||||
|
- поведение **обоих** провайдеров, TMDB и TVDB: правка меняет уже работающий
|
||||||
|
TMDB, и это её главный риск;
|
||||||
|
- `openspec/specs/metadata-match/spec.md` либо `file-layout` — чьим требованием
|
||||||
|
станет «название из метабазы санитизируется перед попаданием в путь».
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Все четыре входа из «Воспроизведения» дают либо санитизированный `plan.Title`,
|
||||||
|
либо уход в review — но не авто-раскладку с исходным значением. **Оракул:**
|
||||||
|
тот самый падающий тест из отчёта триажа, перенесённый в дерево.
|
||||||
|
- Название, схлопывающееся санитизацией в пустую строку, не порождает каталог с
|
||||||
|
пустым именем и не роняет раскладку. **Оракул:** табличный тест на границе,
|
||||||
|
случай «три ZWSP».
|
||||||
|
- Поведение TMDB на нормальных названиях не изменилось. **Оракул:** существующие
|
||||||
|
тесты `internal/recognize` и `internal/metadata` зелёные без правок ожиданий.
|
||||||
|
- Место санитизации названо требованием спеки, а не только кодом. **Оракул:**
|
||||||
|
`openspec validate --strict` на дельте.
|
||||||
|
|
||||||
|
## Рамки
|
||||||
|
|
||||||
|
Гомоглифы сворачивать не обязательно — достаточно сделать значение в пути
|
||||||
|
предсказуемым и сверяемым; полноценная нормализация Unicode это отдельный
|
||||||
|
разговор. Инвариант «целевой путь строго под библиотекой» уже держится, ломать
|
||||||
|
его правкой нельзя. Правка задевает работающий TMDB — регресс на нём дороже
|
||||||
|
самого дефекта.
|
||||||
Reference in New Issue
Block a user