Привёл набор 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>
6.0 KiB
file-layout Specification
Purpose
Раскладка распознанных файлов хардлинками под библиотеку Jellyfin: целевые
имена фильмов и сериалов (папка/файл, provider-id, сезоны), сопоставление
источник→цель, санитизация пути и запрет выхода за библиотеку, never-overwrite
(коллизия → review) и copy-fallback при невозможности хардлинка. Владение
целевым путём (superseded) и безопасный Undo (nlink<=1) —
в state-reconciliation.
Requirements
Requirement: Целевые имена фильмов
Фильм система SHALL раскладывать в папку и файл вида Название (Год), помещённые
под paths.movies. При подтверждённом матче в базе имя папки SHALL нести
provider-id ([tmdbid-…]/[tvdbid-…]) — он снимает неоднозначность русских
названий для Jellyfin. Внешние субтитры SHALL именоваться Имя.<lang>[.flag].srt
(флаги forced/sdh/default/hi), с базой имени, совпадающей с именем
видеофайла; пары VobSub — .idx + .sub.
Scenario: Фильм с provider-id
- GIVEN распознанный фильм «Дюна Часть вторая» (2024) с матчем TMDB
693134 - WHEN строится целевой путь
- THEN папка =
movies/Дюна Часть вторая (2024) [tmdbid-693134]/ - AND видеофайл =
Дюна Часть вторая (2024).mkv
Requirement: Целевые имена сериалов
Сериал система SHALL раскладывать под paths.series в папку Название (Год) с
provider-id на папке сериала, сезонными подпапками Season xx и файлами вида
Название (Год) SxxEyy.
Scenario: Серия сезона
- GIVEN распознанный сериал «Фарго» (2024) с матчем TVDB
123456, серия S01E02 - WHEN строится целевой путь
- THEN путь =
series/Фарго (2024) [tvdbid-123456]/Season 01/Фарго (2024) S01E02.mkv
Requirement: Сопоставление источник → цель хардлинками
Для каждого распознанного файла (не каталога) система SHALL создавать
хардлинк в paths.movies/paths.series; исходный путь берётся из
qBittorrent (save_path + относительное имя файла из /torrents/files, уже
включающее корневую папку многофайловой раздачи). Целевые каталоги SHALL
создаваться mkdir (0755, 1000:1000). Исходный файл система НЕ SHALL трогать —
раздача продолжается, inode общий, диск не дублируется.
Scenario: Хардлинк не дублирует данные
- GIVEN видеофайл раздачи под
paths.downloads - WHEN файл раскладывается
- THEN в библиотеке создаётся хардлинк на тот же inode
- AND исходный файл остаётся на месте
Requirement: Санитизация целевого пути и запрет выхода за библиотеку
Целевое имя система SHALL санитизировать (без разделителей пути, ..,
управляющих символов), а финальный путь SHALL проверять на строгое нахождение под
paths.movies/paths.series. Путь, выходящий за пределы библиотеки, система НЕ
SHALL создавать. Безопасность SHALL держаться на валидации пути, а не на доверии к
выходу LLM.
Scenario: Traversal отклоняется
- GIVEN распознанное имя, содержащее
../ - WHEN строится и проверяется целевой путь
- THEN путь отклоняется как выходящий за пределы библиотеки, хардлинк не создаётся
Requirement: Существующую цель не перезаписываем
Существующий целевой файл система НЕ SHALL перезаписывать. Если по целевому пути
уже лежит тот же inode — операция идемпотентна (готово); если другой файл —
это коллизия, и задача SHALL уходить в review.
Scenario: Коллизия уходит в review
- GIVEN по целевому пути уже лежит другой файл
- WHEN выполняется раскладка
- THEN файл не перезаписывается, задача переходит в
reviewс причиной коллизии
Requirement: Copy-fallback при невозможности хардлинка
Система SHALL при невозможности хардлинка (разные ФС или ФС без поддержки жёстких
ссылок) НЕ падать, а копировать файл с предупреждением в лог, помечая ссылку
статусом copied.
Scenario: Разные ФС — копирование
- GIVEN целевой и исходный каталоги на разных ФС
- WHEN выполняется раскладка файла
- THEN файл копируется, ссылка получает статус
copied, в лог пишется предупреждение