## Why Фоновая сверка может ложно «воскресить» завершённую загрузку, если её целевой путь переиспользовала другая загрузка. Реальный инцидент: фильм скачали в 4K (download A), затем удалили его из qBittorrent и Jellyfin — сверка увела A в терминальный `deleted` (нет ни источника, ни цели). После этого тот же фильм скачали в 1080p (download B); он распознался как тот же фильм и сделал хардлинк по **тому же** `dst_path`. На следующем тике сверка для A увидела, что файл по пути снова существует (хотя это файл B), и вывела `deleted → orphaned` с ложным уведомлением «источник потерян». Корень: `targetPresent` считает цель присутствующей по факту существования **пути**, не проверяя, что лежащая там раскладка принадлежит **этой** загрузке. Один `dst_path` может оказаться «своим» сразу для двух загрузок. ## What Changes - Вводим инвариант **«один целевой путь — один владелец»**: цель загрузки считается присутствующей, только если разложенные по её путям ссылки всё ещё принадлежат именно ей. Когда новая раскладка ложится на путь, ранее занятый другой загрузкой (путь к тому моменту свободен — иначе была бы коллизия → review), владение переходит к новой загрузке, а ссылки прежней на этот путь помечаются вышедшими из обращения (новый статус `file_link` — `superseded`). - Делаем `deleted` действительно **терминальным** для сверки: исключаем его из набора сверяемых состояний — завершённую начисто задачу больше не переоценивают (источник к ней не вернётся из-за идемпотентности терминальных задач, а цель отбирается переходом владения). - Приводим спеку в соответствие с графом состояний: убираем из требования «самовосстановление» возврат из `deleted` (он противоречил диаграмме `deleted --> [*]` в `workflow.md` и под новым инвариантом нереализуем). - Коллизия остаётся как есть: если файл прежней загрузки **всё ещё на месте**, новая раскладка не перезаписывает его, а уходит в review. ## Capabilities ### New Capabilities (нет) ### Modified Capabilities - `state-reconciliation`: присутствие цели определяется по **владению**, а не по факту существования пути; вводится переход владения путём при повторной раскладке; `deleted` исключается из сверки и из самовосстановления. ## Impact - **Затрагиваемый код:** - `internal/worker/reconcile.go` — убрать `StateDeleted` из `desyncStates`; `targetPresent`/`isLaidOut` уже считают целью только `linked/copied/exists`, новый статус `superseded` отсекается автоматически. - `internal/worker/review.go` (`linkPlan`) — после фиксации своих ссылок пометить чужие `file_link` на тех же `dst_path` как `superseded`; покрывает и авто-раскладку, и ручной `Apply` (обе идут через `linkPlan`). - `internal/store/recognition.go` — новый метод стора (supersede чужих ссылок по списку `dst_path`). - `internal/layout/layout.go` — константа статуса `StatusSuperseded`. - **Без миграции схемы:** `file_link.status` — `TEXT`, новое значение enum-а не меняет таблицу. ER-схема `docs/specs/database.md` не меняется. - **Документация:** `docs/specs/workflow.md` (раздел «Сверка с реальностью» — `deleted` терминален) и `docs/specs/jellyfin-layout.md` (переход владения путём при повторной раскладке) — `file-layout` ещё не перенесён в OpenSpec, источник истины по нему — `docs/specs/`. - **Поведение пользователя:** исчезают ложные уведомления `orphaned`/ `target_missing` по уже удалённым загрузкам; повторная закачка того же фильма в другом качестве больше не «трогает» прежнюю задачу.