Распознаём ручное удаление источника (раздача в qBittorrent) и/или цели (разложенные хардлинки) и отражаем его в состоянии задачи, без автодействий. - Новая capability state-reconciliation (OpenSpec): фоновая сверка по матрице «источник × цель» → состояния target_missing/orphaned/deleted, переходы и самовосстановление (healing). - worker: reconcileDesync в Poll (только разложенные/desync-задачи), дебаунс пропажи источника (порог [worker].source_missing_threshold) и синхронный preflight перед действиями (relink/recognize/apply/undo) — не доверяем state в БД. - layout.Undo: отказ снять последнюю копию (nlink<=1 или нет источника), отказ всего батча без частичного отката (ErrLastCopy). - store: единый список terminalStates для IsTerminal и FindActiveByInfohash (иначе семантика «активности» разъезжается), столбец source_miss_count, миграция 0003. - httpapi/web и Telegram: показ новых состояний и уведомления о рассинхроне. - Доки: workflow.md, jellyfin-layout.md, database.md (+0003), config. Change заархивирован в openspec/changes/archive, дельта влита в openspec/specs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.0 KiB
7.0 KiB
Why
Пришло время удалять просмотренные фильмы/сериалы, чтобы освобождать место
под новые. Удаляют их вручную — из qBittorrent (источник) или из
Jellyfin (целевые хардлинки). Сейчас jellybit этого не замечает: worker
поллит только задачи в downloading, терминальные задачи (done) с
реальностью не сверяет. Состояние в БД молча расходится с диском:
- Источник пропал, цель осталась. qBittorrent стирает скачанные файлы
при удалении раздачи. Хардлинк в библиотеке становится последней
ссылкой на inode, а обычный
Undo(unlinkцели) сотрёт единственную копию — прямая потеря данных. Инвариант «источник неприкосновенен» молчаливо перестаёт держаться: источника уже нет. - Цель пропала, источник остался. Файлы убрали из библиотеки, а
jellybit по-прежнему числит загрузку
done— состояние врёт.
Цель этого change — path 1: научить jellybit корректно распознавать ручное удаление и отражать его в состоянии, не предпринимая автоматических действий. Удаление средствами самого jellybit («единое окно», path 2) — отдельная будущая работа.
What Changes
- Фоновая сверка с реальностью.
workerрасширяет периодический поллинг: помимоdownloadingсверяет терминальные/desync-задачи с фактом на ФС — присутствие раздачи в qBittorrent (источник) и существование разложенных хардлинковfile_link.dst_path(цель). - Новые состояния FSM для рассинхрона (двумерная матрица «источник × цель»):
target_missing— источник на месте, цель удалена: доступен пользовательский флоу повторной привязки (relink); авто-действий нет.orphaned— источник пропал, цель на месте: «осиротевшая» раздача, библиотечный хардлинк — единственная копия.deleted— пропали и источник, и цель: терминально, действий больше нет.- Реальность «лечится» сама: если источник/цель снова появились, сверка возвращает задачу в согласованное состояние.
- Дебаунс пропажи источника. Раздача считается удалённой только после
Nподряд тиков без неё (qBittorrent мог рестартовать / API мигнул); любое появление сбрасывает счётчик. Порог — в конфиге. - Защита
Undoот потери данных.Undo/layout.Undoотказывается снимать хардлинк, если он последняя копия (nlink == 1) или исходный путь не существует — откат снимает лишний хардлинк, а не единственный файл; причина отказа сообщается явно. - Уведомление автору загрузки при переходе в
orphaned/target_missing(через существующийnotifier), чтобы рассинхрон не оставался незаметным.
Не входит в объём (Non-goals):
- Удаление раздач/файлов средствами самого jellybit (path 2, «единое окно»).
- Автоматический повторный прогон распознавания/раскладки при рассинхроне — только пометка состояния; relink инициирует человек.
- Сверка содержимого источника (manual delete файлов внутри живой
раздачи → это
error/missingFilesqBittorrent, отдельная тема). - Ретеншн/авточистка терминальных задач (отдельная задача в TODO).
Capabilities
New Capabilities
state-reconciliation: периодическая сверка записанного состояния загрузки с фактом на ФС (раздача в qBittorrent, разложенные хардлинки), состояния рассинхрона (target_missing/orphaned/deleted), их переходы и дебаунс; а также инвариант безопасногоUndo(не снимать последнюю копию).
Modified Capabilities
Impact
- Спеки: новая
openspec/specs/state-reconciliation/; правки живыхdocs/specs/workflow.md(граф состояний: +target_missing/orphaned/deleted, переходы) иdocs/specs/jellyfin-layout.md(инвариант безопасногоUndo). - Код:
internal/worker(расширениеPoll/reconcileна терминальные задачи, дебаунс, новые переходы, уведомления),internal/store(новые значенияstate, столбец счётчика промахов, миграция, запросы выборки desync-задач),internal/layout(Undoс проверкойnlink/наличия источника),internal/qbt(присутствие infohash — уже листаем все торренты),internal/httpapi+ web-UI (отображение новых состояний, блокировкаUndoдляorphaned),internal/config([worker]порог дебаунса). - Конфиг: новый ключ в
[worker](порог пропусков источника). - Совместимость: новые значения
state— аддитивно; миграция goose для столбца счётчика.