## 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`/`missingFiles` qBittorrent, отдельная тема).
- Ретеншн/авточистка терминальных задач (отдельная задача в TODO).

## Capabilities

### New Capabilities

- `state-reconciliation`: периодическая сверка записанного состояния
  загрузки с фактом на ФС (раздача в qBittorrent, разложенные хардлинки),
  состояния рассинхрона (`target_missing`/`orphaned`/`deleted`), их переходы
  и дебаунс; а также инвариант безопасного `Undo` (не снимать последнюю
  копию).

### Modified Capabilities

<!-- Граф состояний и Undo живут в docs/specs/workflow.md и
docs/specs/jellyfin-layout.md и ещё не перенесены в OpenSpec; меняем их
напрямую как живые спеки (см. Impact). Capability-дельт для них нет. -->

## 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 для
  столбца счётчика.
