## Context

Фоновая сверка (`internal/worker/reconcile.go`) выводит состояние уже
разложенной задачи из матрицы «источник × цель» (`deriveState`). Цель
определяется `targetPresent` — проверкой `os.Lstat` по `dst_path` ссылок
последнего батча со «статусом раскладки» (`isLaidOut`: `linked`/`copied`/
`exists`). Источник дебаунсится, цель — нет.

Проблема: `targetPresent` проверяет существование **пути**, а не
принадлежность лежащего там файла данной загрузке. Поэтому, когда другая
загрузка разложилась по тому же `dst_path` (например, повторная закачка
того же фильма в другом качестве), сверка прежней загрузки видит «цель
вернулась» и ложно выводит её из `deleted` в `orphaned` (с уведомлением).

`deleted` уже терминален для идемпотентности (`store.terminalStates`
включает его — снимается `idempotency_key`), но при этом всё ещё
присутствует в `desyncStates`, т.е. сверка его переоценивает.

Обе точки раскладки (авто-апплай после распознавания и ручной `Apply`)
сходятся в `linkPlan` (`internal/worker/review.go`), который вызывает
`store.CreateFileLinks` и затем `transition` в `done`.

## Goals / Non-Goals

**Goals:**

- Цель загрузки при сверке считается присутствующей только если файлы по её
  путям — её собственная раскладка (инвариант «один путь — один владелец»).
- При повторной раскладке на освободившийся чужой путь владение переходит к
  новой загрузке; прежние ссылки на этот путь выводятся из обращения.
- `deleted` перестаёт переоцениваться сверкой (терминален и в этом смысле).
- Документация (`docs/specs/`) и спека `state-reconciliation` приведены в
  соответствие с графом состояний (`deleted --> [*]`).

**Non-Goals:**

- Мульти-версии Jellyfin (4K + 1080p рядом) — отдельная фича, movie-only,
  не входит в этот change.
- Изменение поведения коллизии (занятый путь → review) — остаётся как есть.
- Идентификация цели по inode/устройству — сознательно отвергнута (см.
  Decisions).
- Миграция `file-layout` в OpenSpec — вне рамок; правки раскладки идут в
  `docs/specs/`.

## Decisions

### Решение 1: владение выражаем статусом `file_link`, а не inode

Вводим статус `superseded` (`layout.StatusSuperseded`). При успешной
раскладке в `linkPlan` после `CreateFileLinks` помечаем чужие ссылки на те
же `dst_path`:

```sql
UPDATE file_link SET status = 'superseded'
WHERE dst_path = ? AND download_id != ? AND status IN ('linked','copied','exists')
```

`targetPresent`/`isLaidOut` уже считают целью только `linked`/`copied`/
`exists`, поэтому `superseded` отсекается **без изменений** в логике сверки.
Помечаем только пути, которые сами реально разложили (результаты со статусом
из `isLaidOut`) — коллизии и пропуски владение не отбирают.

**Почему не inode:** завязка на номер inode — низкоуровневая, не выражает
домен, требует хранить и сверять числа, осмысленные только для ФС, и
усложняет тесты. Статус `file_link` остаётся в нашей доменной модели и
переиспользует существующую логику `isLaidOut`. Миграция схемы не нужна:
`file_link.status` — `TEXT`.

### Решение 2: `deleted` вне сверки

Убираем `store.StateDeleted` из `desyncStates`. Завершённую начисто задачу
больше не переоценивают. Это безопасно: источник к терминальной задаче не
вернётся (идемпотентность снимается только для активных), а цель отбирается
переходом владения (Решение 1) — оба пути «воскрешения» закрыты.

### Решение 3: точка вызова supersede — `linkPlan`

`linkPlan` — единственная воронка обеих раскладок (авто и ручной `Apply`).
Supersede вызываем там после фиксации своих ссылок и до/рядом с переходом в
`done`, под тем же `w.mu`, в той же логической операции. Отдельный метод
стора (напр. `SupersedeForeignLinks(ctx, downloadID, dstPaths)`).

### Решение 4: приведение спеки и доков

- `state-reconciliation` (дельта этого change): сверка не трогает `deleted`;
  присутствие цели — по владению; самовосстановление из `deleted` убрано.
- `docs/specs/workflow.md` — в «Сверке с реальностью» уточнить, что
  `deleted` терминален (без healing); граф уже это рисует.
- `docs/specs/jellyfin-layout.md` — добавить переход владения путём при
  повторной раскладке на освободившийся путь.

## Risks / Trade-offs

- **Гонок нет:** и раскладка, и сверка идут под `w.mu` (per-worker),
  supersede и `CreateFileLinks` — последовательно в `linkPlan`.
- **Частичное владение** (сериал, где новая раскладка заняла лишь часть
  путей прежней): прежняя загрузка теряет владение только перехваченными
  путями. Пока хоть одна её неперехваченная ссылка (`isLaidOut`)
  существует на ФС, `targetPresent` возвращает true и задача остаётся
  `done`; в `target_missing` она уйдёт, лишь когда пропадут и собственные
  файлы. Это корректное отражение реальности, не регресс.
- **Старые данные:** ранее ложно «воскрешённые» задачи в БД останутся в
  своём состоянии до следующего тика; после деплоя `deleted`-задачи просто
  перестанут трогаться, а ошибочно ставшие `orphaned` исправятся вручную при
  необходимости (точечно, не автоматической миграцией — инцидент единичный).
- **`superseded` — новое значение enum-а:** учесть в местах, где статус
  интерпретируется (undo/листинги UI), чтобы такие ссылки не показывались
  как активная цель и не участвовали в undo как «снимаемые».
