При подтверждённом матче база папки (имя+год) наследуется от живой папки-якоря того же (provider, provider_id) вместо печати заново из выхода LLM — так второй/последующий сезон ложится в ТУ ЖЕ папку, а не заводит рядом почти одинаковую. Отдельная сущность «тайтл» не вводится. - layout: Plan.FolderBase перекрывает базу в папке и именах файлов; TitleFolder разбирает dst_path в папку тайтла и базу (снятие тега). - store: LiveTitleFolders — dst_path живых ссылок того же матча. - worker: resolveFolderBase (живость якоря — по наличию папки на диске, os.Lstat, а не по статусу ссылки в БД) в linkPlan и в превью ревью (превью=применение); рассинхрон нескольких живых папок → review из linking (deferred→review в графе нет). Схема БД не менялась. Change series-folder-convergence заархивирован, требования влиты в openspec/specs/file-layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
5.1 KiB
Markdown
64 lines
5.1 KiB
Markdown
## Why
|
||
|
||
Второй/третий сезон одного сериала должен ложиться в ТУ ЖЕ папку, а не заводить
|
||
рядом почти одинаковую. Сейчас имя папки печатается заново из выхода LLM при
|
||
каждой раскладке (`layout.BuildLinks` из полей плана), и совпадение
|
||
`(provider, provider_id)` не гарантирует совпадение строки папки: LLM может дать
|
||
«Fargo» и «Фарго», год сезона вместо года сериала — и верно заматченный второй
|
||
сезон уедет в соседнюю папку. Это ядро «проблемы второго сезона»: она **не про
|
||
группировку, а про сходимость папки** (см. `docs/drafts/logical-title-model.md`
|
||
§3, §5.2).
|
||
|
||
## What Changes
|
||
|
||
- Вводим **правило сходимости папки** при построении плана раскладки: при
|
||
подтверждённом матче (`provider` и `provider_id` заданы) база папки
|
||
(`Название (Год)`) **наследуется** от живых `file_link` других загрузок того
|
||
же тайтла, а выход LLM для папки игнорируется.
|
||
- Якорь берём из **существующего `dst_path`** живых ссылок (`status IN
|
||
linked/copied/exists`) загрузок, чей current recognition имеет тот же
|
||
`(provider, provider_id)` — живые пути — истина (переименовали папку → сверка
|
||
уводит ссылки в `target_missing`, якорь исчезает, следующая загрузка печатает
|
||
заново).
|
||
- Унаследованная база применяется **и к папке, и к именам файлов внутри**
|
||
(episode/movie stem), чтобы `Fargo (2014) S02E01` совпадало с
|
||
`Fargo (2014) S01E01`. Провайдер-тег `[tvdbid-…]` по-прежнему берётся из
|
||
текущего `(provider, provider_id)`.
|
||
- **Рассинхрон** (несколько РАЗНЫХ живых папок с одним `(provider, provider_id)`,
|
||
случившийся до внедрения правила) → загрузка уходит в **review** с явной
|
||
причиной, якорь молча не выбираем.
|
||
- Отдельная сущность «тайтл» **не вводится**: это join по существующим таблицам
|
||
(`file_link → download → recognition(is_current)`), без новых
|
||
сущностей/таблиц. Схема БД не меняется.
|
||
|
||
## Capabilities
|
||
|
||
### New Capabilities
|
||
|
||
<!-- нет -->
|
||
|
||
### Modified Capabilities
|
||
|
||
- `file-layout`: добавляется правило сходимости базы папки при построении
|
||
целевого пути — при подтверждённом матче база наследуется от живых ссылок того
|
||
же `(provider, provider_id)`, а не печатается из выхода распознавания; несколько
|
||
расходящихся живых папок → уход в review с причиной «рассинхрон» вместо тихого
|
||
выбора (по прецеденту коллизии, которая тоже уводит в review из file-layout).
|
||
|
||
## Impact
|
||
|
||
- **Код:** `internal/layout` (возможность построить план с унаследованной базой
|
||
папки), `internal/worker` (`linkPlan`/построение плана раскладки: lookup якоря,
|
||
ветка рассинхрон→review), `internal/store` (новый read-метод: живые целевые
|
||
папки по `(provider, provider_id)`, исключая текущую загрузку).
|
||
- **Инварианты безопасности данных:** не трогаем. `download` остаётся мостом,
|
||
выход распознавания недоверенный, безопасность — на санитизации и проверке
|
||
пути под библиотекой. Никаких новых прав на источник.
|
||
- **Схема БД:** без изменений (только новый SELECT-join).
|
||
- **Границы:** нет якоря → папка из распознавания (как сейчас); нет матча
|
||
(`provider=none`) → авто-раскладки нет, всё через review (инвариант); смена
|
||
провайдера между сезонами якорь не склеит — редкий случай, штатно review
|
||
(осознанная цена, черновик §6.1).
|
||
- **Совместимость:** поведение первой загрузки тайтла не меняется; правило
|
||
влияет только на последующие вклады при наличии живого якоря.
|