Команда Defer гардила только IsTerminal() и потому принимала catched (торрент ещё не добавлен в qBittorrent). Defer из catched уводил задачу в лимбо → необратимый deleted: processCatched листает только catched и больше её не подхватывал, а последующие команды через отсутствие источника выводили deleted (ноль исходящих рёбер), хотя байты .torrent лежат в download_torrent. - Worker.Defer отклоняет catched с ErrConflict (транслируется в 409 / редирект с сообщением); прочие не-терминальные состояния, где раздача уже есть, принимает как раньше. - Снято мёртвое ребро графа catched → deferred (allowedTransitions); инвариант «deferred из каждого не-терминального» уточнён: кроме пре-источникового catched. catched — единственное состояние без раздачи среди не-терминальных. - Тесты: Defer из catched отклоняется и не меняет состояние; инвариант графа обновлён + негативная проверка ребра. - OpenSpec: MODIFIED «Команды ревью и их эффекты» (review) с позитивным и негативным сценариями; change заархивирован, дельта влита в спеку. - Беклог: закрыта review-major6-defer-catched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.9 KiB
4.9 KiB
Why
Команда Defer («Позже») сейчас гардит только IsTerminal(), поэтому
принимает и пре-источниковое состояние catched (торрент ещё НЕ добавлен в
qBittorrent). Defer из catched уводит задачу в лимбо → необратимый deleted
(находка ревью MAJOR-6):
catched → deferred:processCatchedлистает толькоcatchedи задачу больше не видит → торрент никогда не добавится в qBittorrent.- Из
deferredдальше тупик:Apply→ «нет плана»;Rerecognize/Refine→ensureSourceReadyне находит раздачу → сверка (sourcePresent=false,targetPresent=false) выводитdeleted, а уdeletedНОЛЬ исходящих рёбер → задача необратима, хотя байты.torrentлежат вdownload_torrent. - Плюс
deletedсемантически неверен: уcatchedничего не качалось и не раскладывалось.
Defer до появления источника бессмысленен: «отложить на потом» нечего — задача
ещё не дошла до ревью. Пре-источниковое catched — единственное такое
состояние (все состояния от downloading и далее уже имеют раздачу в
qBittorrent; приёмное падение qbit_add терминально и Defer его уже отклоняет).
What Changes
- Defer отклоняет пре-источниковое состояние
catchedс конфликтом (ErrConflict) и понятным сообщением: отложить можно только после добавления торрента в qBittorrent. Прочие не-терминальные состояния (downloading/completed/recognizing/review/linking/stuck/deferred), где раздача уже есть, Defer принимает как и раньше. - Граф переходов теряет ребро
catched → deferred— раз Defer его больше не выполняет, ребро мёртвое; граф остаётся тесным надмножеством реальных переходов. Инвариант «deferred— легальная цель из каждого не-терминального состояния» уточняется: кроме пре-источниковогоcatched. - UI/HTTP уже не предлагает Defer для
catched: кнопка «🕗 Позже» живёт только на экране ревью (review/deferred), карточкаcatchedлишь самополлингом ждёт перехода вdownloading. Прямой вызов Defer дляcatchedтеперь отклоняется доменным гардом; транспорт транслирует отказ по своему каналу: REST — 409 «действие недоступно в текущем состоянии» (classifyErr), веб-путь/ui/downloads/{id}/defer— PRG-редирект (303) на/review/{id}?err=…с нейтральным сообщением (как прочие отказы команд ревью).
Capabilities
New Capabilities
Нет.
Modified Capabilities
review: требование «Команды ревью и их эффекты» — уточняет допустимые исходные состояния команды Позже (Defer): любое не-терминальное, кроме пре-источниковогоcatched(там нет раздачи и нечего откладывать).
Impact
- Спеки: дельта
review(одно MODIFIED-требование с негативным сценарием). Требование графа переходов вdownload-tracking(декларативное, конкретные рёбра не перечисляет) не меняется — снятие ребраcatched → deferredиз единого источника истины в коде ему не противоречит. - Код:
internal/worker/review.go—Defer(гард противcatched);internal/store/download.go— убратьStateDeferredиз исходящихcatchedи уточнить комментарий инвариантаdeferred. - Тесты:
internal/worker/review_test.go— Defer изcatchedотклоняется, изreviewпо-прежнему работает;internal/store/transition_test.go— инвариант «deferredиз каждого не-терминального, кромеcatched».