Ревью: preflight готовности источника для команд ревью (MAJOR-5)

Команды ревью проверяли только наличие раздачи в qBittorrent, но не её
готовность. Недокачанную задачу можно припарковать в deferred, затем
«Распознать заново» → recognizing → авто-раскладка (Rerecognize/Refine/
SetType не ставят force_review) → хардлинки на неполные файлы. Даже ручной
Apply не имел preflight завершённости.

Вводим ensureSourceReady (classify(t.State)==classReady) вместо
ensureSourcePresent во всех командах, которым нужен источник (Relink/
Rerecognize/Refine/SetType), и inline-проверку класса в Apply — последний
рубеж перед хардлинками. Недокачанный источник → отдельный sentinel
ErrNotReady (409) с actionable-текстом «торрент ещё качается» в web и
Telegram, без reconcile (состояние deferred/review легитимно).

Change review-readiness-preflight заархивирован, дельта влита в
openspec/specs/review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-08 16:40:49 +03:00
co-authored by Claude Opus 4.8
parent a43adea723
commit b1bca98738
16 changed files with 475 additions and 41 deletions
@@ -0,0 +1,45 @@
## 1. Preflight готовности источника
- [x] 1.1 В `internal/worker/errors.go` добавить sentinel
`var ErrNotReady = errors.New(...)` (отдельный от `ErrConflict`).
- [x] 1.2 В `internal/worker/reconcile.go` добавить `ensureSourceReady(ctx, d, op)`:
найти торрент через `torrentByInfohash`; нет источника →
`reconcileToReality(false)` + `ErrConflict «источник удалён из qBittorrent»`;
есть, но `classify(t.State) != classReady``ErrNotReady «op: торрент ещё
качается»` без `reconcileToReality`.
- [x] 1.3 Заменить `ensureSourcePresent` на `ensureSourceReady` в командах
`Rerecognize`, `Refine`, `SetType`, `Relink` (`internal/worker/review.go`);
удалить осиротевший `ensureSourcePresent`.
- [x] 1.4 В `Apply` добавить проверку класса на уже полученном торренте
(`classify(t.State) != classReady → ErrNotReady «торрент ещё качается»`),
без второго запроса к qBittorrent.
## 2. Трансляция ошибки в транспортах
- [x] 2.1 `internal/httpapi` `classifyErr`: кейс
`errors.Is(err, worker.ErrNotReady) → 409, «торрент ещё качается, дождитесь
докачки»` (отдельно от генерик-`ErrConflict`).
- [x] 2.2 `internal/tgbot`: в обработчике callback-действий ветка
`errors.Is(err, worker.ErrNotReady)` → сообщение «Торрент ещё качается…»,
иначе прежний `opErr(...)`.
## 3. Тесты
- [x] 3.1 Тест: недокачанный источник (`downloading`-класс) отклоняет
`Rerecognize`/`Refine`/`SetType`/`Relink` с `ErrNotReady`, состояние задачи
не меняется, авто-раскладка не запускается.
- [x] 3.2 Тест: недокачанный источник отклоняет `Apply` — хардлинки не
создаются, состояние не меняется.
- [x] 3.3 Тест: готовый источник (`classReady`) пропускает те же команды как
раньше (регресс не сломан); отсутствие источника по-прежнему приводит к
реальности и отказывает.
- [x] 3.4 Тест транспорта: `classifyErr(ErrNotReady) == 409` с конкретным
текстом (`internal/httpapi`).
## 4. Ревью и сверка
- [x] 4.1 `task test` и `task lint` зелёные.
- [x] 4.2 Ревью кода (второй чекпоинт) перед archive.
- [x] 4.3 `openspec validate review-readiness-preflight --strict` зелёный;
удалить `docs/backlog/review-major5-readiness-preflight.md` из беклога и
строку из индекса (суть переехала в спеку).