Files
jellybit/openspec/specs/file-layout/spec.md
T
avandClaude Opus 4.8 512567c8ba Рефакторинг границ capabilities: цепочка загрузка→матч→ревью→раскладка (openspec)
Привёл набор 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>
2026-07-03 21:17:51 +03:00

93 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`, в лог пишется предупреждение