Привёл набор capabilities в OpenSpec к цепочке обработки, чтобы имя capability отвечало одному поведению. Чисто по спекам, код и поведение системы не меняются. Change refactor-capability-boundaries (архивирован): - recognition разделён на recognition (разбор LLM) + metadata-match (сверка с базами) - review выделен из web-ui + мигрирован из docs/specs/review-ux.md - новые capability из docs/specs: file-layout, download-tracking, notifications - identity очищен до инфра-id; приём (инфохэши, дедуп, ядро приёма) — в ingest - уведомление о рассинхроне перенесено из state-reconciliation в notifications - дубль владения путём и безопасного undo оставлен в state-reconciliation Итог: 11 capabilities, openspec validate --strict проходит (+37/−11 требований). Источник истины по мигрированным темам переехал в openspec/specs (шапки в docs). Снят пункт беклога «Пересмотр набора capabilities». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.0 KiB
Конвенции раскладки Jellyfin
Источник истины переехал в OpenSpec —
openspec/specs/file-layout/(имена, хардлинки, коллизия, copy-fallback). Владение путём (superseded) и безопасный undo (nlink<=1) — вopenspec/specs/state-reconciliation/. Этот файл — справочный нарратив; при расхождении верна спека OpenSpec.
Целевые имена и структура, в которые jellybit раскладывает файлы хардлинками. Источники: Movies, Shows.
Фильмы
movies/
Дюна Часть вторая (2024) [tmdbid-693134]/
Дюна Часть вторая (2024).mkv
Дюна Часть вторая (2024).ru.srt
- Папка и файл —
Название (Год). - provider-id в имени папки (
[tmdbid-...]) добавляется при работе с базой — снимает неоднозначность для русских названий, которые Jellyfin иначе может опознать неверно. - Внешние субтитры —
Имя.<lang>[.flag].srt(флагиforced/sdh/default/hi), напр.…ru.forced.srt; база имени совпадает с именем видеофайла. Пары VobSub —.idx+.sub.
Сериалы
series/
Название (2024) [tvdbid-123456]/
Season 01/
Название (2024) S01E01.mkv
Название (2024) S01E02.mkv
- provider-id — на папке сериала.
- Сезоны —
Season 01, файлы —... SxxEyy.
Сопоставление источник → цель
Источник берём по пути из qBittorrent (save_path + относительное имя
файла из /torrents/files, которое уже содержит корневую папку
многофайловой раздачи; это уже хост-путь, path_map — фолбэк). Для каждого
распознанного файла (не каталога) создаётся хардлинк в
paths.movies/paths.series; целевые каталоги — mkdir (0755,
1000:1000). Исходный файл остаётся на месте (раздача продолжается),
inode общий — диск не дублируется.
Целевое имя строится из распознанных полей и санитизируется (без
разделителей пути, .., управляющих символов); финальный путь обязан
быть строго под библиотекой. Существующую цель не перезаписываем (тот
же inode → готово; другой файл → коллизия → review). Инварианты и undo —
в architecture.md → «Раскладка файлов».
Владение целевым путём
Целевой путь принадлежит одной загрузке. Когда новая раскладка
успешно ложится на путь, который раньше занимала другая загрузка (путь к
этому моменту свободен — иначе была бы коллизия → review, чужой файл
не перезаписываем), владение переходит к новой загрузке: прежние записи
file_link на этот путь помечаются статусом superseded и перестают
считаться целью прежней загрузки. Это нужно сверке с реальностью: иначе
повторная закачка того же фильма (например, в другом качестве) по тому же
пути ложно «воскрешала» бы удалённую задачу — см.
workflow.md → «Сверка с реальностью». superseded-ссылки
не считаются целью при сверке и не снимаются в Undo.
Желательно: целевой и исходный каталоги — на одной ФС/одном mount'е
(внутри контейнера это обеспечивает единая песочница /srv/media), тогда
работает дешёвый хардлинк. Если хардлинк невозможен (разные ФС или ФС без
поддержки жёстких ссылок), layout не падает, а копирует файл с
предупреждением в лог — см. architecture.md → «Раскладка файлов».
Безопасный undo (не снимать последнюю копию)
Undo снимает лишний хардлинк, а не единственный файл. Перед удалением
батча layout проверяет каждую цель: если исходный файл уже не существует
или у цели не осталось других жёстких ссылок (nlink <= 1), это —
последняя копия данных, и весь Undo отклоняется целиком (ошибка
ErrLastCopy), не сняв ни одной ссылки (частичный откат тоже стёр бы часть
данных). Так нарушенный инвариант «источник неприкосновенен» (источник
удалён вручную) не приводит к потере данных. Отсутствующую цель Undo
пропускает как уже снятую (идемпотентность). Связь с состояниями
рассинхрона — workflow.md → «Сверка с реальностью».
Крайние случаи
- Многофайловый фильм (части) — стэкинг по точному токену Jellyfin
(
… - part1/cd1); точный формат уточнить при реализации. - Редакции —
Имя (Год) [edition-Director's Cut]либо отдельные версии в папке фильма. - Двойная серия в одном файле —
… SxxEyy-Eyy. - Спецвыпуски —
Season 00. - Сезон-пак — серии в один
Season xx; смешанный пак — по per-file сезонам. - Несколько аудиодорожек — обычно внутри mkv, не наша забота.
- Аниме с абсолютной нумерацией — пересчёт в S·E, отдельная проработка (backlog.md).