Files
avandClaude Opus 4.8 098695011f Жизненный цикл: Defer запрещён из пре-источникового catched (MAJOR-6)
Команда 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>
2026-07-17 21:39:01 +03:00

65 lines
4.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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`».