Голый magnet без dn/контекста заставлял namer звать LLM на пустом входе, и модель галлюцинировала мусорное имя («Unknown»), которое писалось и в display_name, и в rename qBittorrent, а заодно ломало UI-фолбэк на распознанное название. Верное каноническое имя, вычисляемое позже при распознавании, никуда не переливалось. - naming: гард пустого входа в DeriveName (нет контекста и подсказки → "" без вызова LLM) + детерминированный форматтер FormatTitleYear. - qbt: операция RenameTorrent (переименование существующей раздачи). - store: SetDisplayName — обновление имени постфактум без гарда состояния. - worker: refreshDisplayNameLocked/RefreshDisplayName — перелив канонического имени (эффективный план) в display_name + best-effort rename раздачи по реальному t.Hash; авто-триггер при подтверждении матча (choose/manual add). - web-ui: кнопка «Обновить имя» на странице загрузки (htmx-своп заголовка, деградация без JS), видимая при наличии распознавания (вкл. done/orphaned). Спека: дельты ingest/review/web-ui влиты в openspec/specs; change refresh-display-name заархивирован. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
5.4 KiB
Markdown
68 lines
5.4 KiB
Markdown
## Why
|
||
|
||
При бедном первичном приёме (голый magnet без `dn` и без контекста) вывод имени
|
||
на шаге добавления зовёт LLM на пустом входе, и модель галлюцинирует мусорное имя
|
||
(наблюдалось «Unknown»). Это имя пишется и в `download.display_name`, и в `rename`
|
||
qBittorrent, и вдобавок ломает UI-фолбэк на распознанное название (`downloadTitle`
|
||
отдаёт непустой `display_name`). Позже распознавание по дереву файлов даёт верное
|
||
имя (в наблюдавшемся случае — «Harold and the Purple Crayon», 2024), но это имя
|
||
никуда не переливается: и карточка загрузки, и раздача в qBittorrent остаются с
|
||
«Unknown».
|
||
|
||
## What Changes
|
||
|
||
- **Гард пустого входа при выводе имени.** Если и контекст, и подсказка (`dn`
|
||
magnet / имя `.torrent`) пусты, система не зовёт LLM и считает имя не
|
||
выведенным (`display_name` остаётся пустым, `rename` не задаётся). Убирает
|
||
корень «Unknown» — приём не присваивает имя из пустоты.
|
||
- **Обновление имени по подтверждённому распознаванию.** Появляется поведение
|
||
«перелить» уже вычисленное каноническое имя (из распознавания / выбранного
|
||
кандидата метабазы) в `download.display_name` и в имя раздачи qBittorrent.
|
||
Детерминированный формат `Title (Year)` (для сериала — по аналогии с текущим
|
||
выводом имени), **без нового вызова LLM** — источник уже сохранён
|
||
(`recognition.title/year`, `metadata_candidate.title/year`).
|
||
- **Авто:** при подтверждении матча в ревью (выбор кандидата) — согласуется с
|
||
инвариантом «авто-действие только при подтверждённом матче».
|
||
- **Ручной:** кнопка «Обновить имя» на карточке загрузки.
|
||
- **Переименование существующей раздачи в qBittorrent.** Новая операция клиента
|
||
qBittorrent `rename` для уже добавленной раздачи (сейчас `rename` действует
|
||
только при `add`). Адресация — по infohash своей раздачи.
|
||
|
||
## Capabilities
|
||
|
||
### New Capabilities
|
||
|
||
Нет.
|
||
|
||
### Modified Capabilities
|
||
|
||
- `ingest`: требование «Вывод имени через LLM» ужесточается гардом пустого входа
|
||
(нет ни контекста, ни подсказки → LLM не вызывается, имя считается не
|
||
выведенным). Добавляется требование «Обновление отображаемого имени по
|
||
подтверждённому распознаванию» (источник имени, детерминированный формат,
|
||
запись `display_name`, переименование раздачи qBittorrent; точки входа — авто
|
||
и ручная; политика перезаписи).
|
||
- `review`: подтверждение матча (команда выбора/применения кандидата) SHALL
|
||
запускать обновление отображаемого имени по подтверждённому распознаванию.
|
||
- `web-ui`: на карточке/странице загрузки появляется htmx-действие «Обновить
|
||
имя», обновляющее заголовок на месте.
|
||
|
||
## Impact
|
||
|
||
- **Код:**
|
||
- `internal/naming/naming.go` — гард пустого входа в `DeriveName`.
|
||
- `internal/qbt` — новая операция `RenameTorrent(ctx, hash, name)` (`POST
|
||
/api/v2/torrents/rename`).
|
||
- `internal/store` — метод обновления `download.display_name` пост-фактум.
|
||
- `internal/worker` — перелив имени при подтверждении кандидата в ревью.
|
||
- `internal/httpapi` — эндпоинт ручного обновления имени + htmx-партиал
|
||
заголовка; кнопка в шаблоне карточки/страницы загрузки.
|
||
- **Инварианты безопасности данных:** изменение чисто косметическое
|
||
(`display_name` + ярлык раздачи в qBittorrent). Пути на диске
|
||
(`paths.movies`/`series`), распознавание и раскладка НЕ затрагиваются; rename
|
||
адресуется по infohash своей раздачи.
|
||
- **БД:** структура не меняется (используется существующий столбец
|
||
`download.display_name`); миграция не требуется.
|
||
- **Внешние вызовы:** дополнительный вызов qBittorrent `rename` при обновлении
|
||
имени (логируется как `ext.service=qbittorrent`). Новых LLM-вызовов нет.
|