Files
avandClaude Opus 4.8 1369a9cabe Приём: дедуп по target_missing/orphaned + стоп-кран «Закрыть»
Два дубля-близнеца на один инфохэш рождались, когда повторный приём
попадал на запись в target_missing: дедуп искал только активную задачу,
а target_missing терминален → заводилась новая загрузка, воркер усыновлял
уже присутствующий торрент и раскладывал его.

- Приём: критерий дедупа расширен до «блокирующей повторный приём» =
  активные ∪ {target_missing, orphaned}. Повторный приём такого инфохэша
  привязывается к существующей записи (спящей, без обращения к qBittorrent),
  а не плодит близнеца. Прочие терминальные (done/cancelled/failed/reverted/
  deleted) повторный приём не блокируют — осознанная свежая попытка. Новый
  read-метод FindReingestBlockingByInfohash (приоритет активной над desync);
  общий active-гард не тронут.
- Команда «Закрыть» (Dismiss) — универсальный стоп-кран из любого состояния,
  кроме deleted → cancelled (error_code=user_dismiss). Только меняет статус:
  файлы (в т.ч. хардлинки done/orphaned) и раздачу qBittorrent не трогает,
  в отличие от «Удалить». Веб — danger-зона внизу страницы; Telegram —
  кнопка с подтверждением; из cancelled — идемпотентный no-op.
- Транспорты при дедупе на desync-запись сообщают адресно (target_missing —
  привязать заново/закрыть; orphaned — закрыть и добавить заново); веб при
  дедупе ведёт на страницу существующей записи.

Спеки: ingest (дедуп), state-reconciliation (стоп-кран); граф переходов
допополнен рёбрами <терминал>→cancelled. OpenSpec change
dedup-target-missing-and-dismiss заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 20:15:37 +03:00

4.9 KiB
Raw Permalink Blame History

ADDED Requirements

Requirement: Ручное закрытие загрузки (стоп-кран)

Система SHALL предоставлять пользователю команду «Закрыть» (dismiss), доступную из любого состояния, кроме deleted, во всех транспортах (веб-UI и Telegram, опц. REST). Команда SHALL переводить загрузку в терминальное cancelled, убирая её из активного списка/внимания, и SHALL служить универсальным стоп-краном для любой зависшей или спорной загрузки (в т.ч. лишнего дубля-близнеца в target_missing, чьи файлы уже разложены другой загрузкой). Для загрузки в deleted команда доступна SHALL NOT (состояние строго терминально); в cancelled команда SHALL быть идемпотентным no-op.

Команда SHALL только менять статус и SHALL NOT производить никаких действий с файлами или раздачей: система SHALL NOT вызывать qBittorrent (раздача не снимается, продолжает раздаваться) и SHALL NOT удалять либо создавать хардлинки под paths.movies/series — в т.ч. из done/orphaned существующие библиотечные ссылки сознательно остаются на месте. Синхронный source-preflight «Закрыть» выполнять SHALL NOT (источник в действии не участвует).

Переход SHALL помечаться error_code = "user_dismiss" (человекочитаемая причина — в error_msg и логе перехода), отличающим стоп-кран от отклонения на ревью и от удаления. Новый статус для этого система вводить SHALL NOT — переиспользуется существующее терминальное cancelled (сверка его не переоценивает). Из cancelled пользователю остаётся доступной перепривязка (relink), если он передумает.

В интерфейсе команда SHALL размещаться в отдельной «danger zone» (напр. внизу страницы загрузки), обособленно от штатных действий.

Scenario: Закрытие записи без цели не трогает раздачу

  • GIVEN загрузка в target_missing: источник присутствует в qBittorrent, целевых хардлинков нет (напр. её файлы разложены другой загрузкой)
  • WHEN пользователь даёт команду «Закрыть»
  • THEN запись переходит в cancelled с error_code = "user_dismiss"
  • AND раздача с файлами в qBittorrent не удаляется
  • AND запись пропадает из активного списка

Scenario: Закрытие done оставляет библиотечные файлы на месте

  • GIVEN загрузка в done с существующими библиотечными хардлинками
  • WHEN пользователь даёт команду «Закрыть»
  • THEN запись переходит в cancelled с error_code = "user_dismiss"
  • AND библиотечные хардлинки не удаляются
  • AND раздача в qBittorrent не снимается

Scenario: Закрытие зависшей загрузки

  • GIVEN загрузка в stuck (или failed/deferred)
  • WHEN пользователь даёт команду «Закрыть»
  • THEN запись переходит в cancelled
  • AND источник в qBittorrent не трогается

Scenario: «Закрыть» недоступна для deleted

  • GIVEN загрузка в deleted
  • WHEN пользователь пытается вызвать «Закрыть»
  • THEN команда недоступна, состояние остаётся deleted

Scenario: Закрытую запись можно привязать заново

  • GIVEN запись, закрытая командой «Закрыть» в cancelled
  • WHEN пользователь даёт команду «Привязать заново»
  • THEN запись уходит на перераспознавание с ручным подтверждением (как relink из cancelled)