# Tasks

## 1. Граф в store

- [x] 1.1 Объявить `allowedTransitions map[State][]State` (from → to) в
  `internal/store/download.go` рядом с `terminalStates`, с комментарием об источнике
  истины и правиле самоперехода. Заполнить по таблице из `design.md`.
- [x] 1.2 Построить обратное отображение `to → {легальные from}` (для SQL-предиката)
  как package-level `var` через хелпер-инвертор; включать сам `to` (самопереход).

## 2. Гейт в setState

- [x] 2.1 В `setState` добавить предикат `AND state IN (<легальные источники для to>)`
  до/рядом с существующим гардом терминальности; аргументы через `placeholders`.
- [x] 2.2 На `n == 0` — диагностическое чтение текущего состояния (через
  `sqlx.QueryerContext`, если `e` его поддерживает) для точного сообщения:
  «not found» / «illegal transition <cur> → <to>» / терминал без revive.
- [x] 2.3 Сверить `PromoteCatched`: ребро `catched → downloading` присутствует в
  графе; оставить его собственный гард `state='catched'`, добавить комментарий-ссылку
  на граф.

## 3. Тест согласованности

- [x] 3.1 `TestTransitionGraphWellFormed`: все состояния в ключах и значениях графа —
  известные (из полного списка `State`); ни один список не содержит сам ключ
  (петли не перечисляются явно).
- [x] 3.2 `TestSetStateAllowsDeclaredEdges`: для набора объявленных не-revive рёбер
  (`downloading→completed`, `review→linking`, `recognizing→review`, …)
  `SetDownloadState` проходит.
- [x] 3.3 `TestSetStateRejectsUndeclaredEdges`: репрезентативные необъявленные
  (`review→done`, `downloading→done`, `completed→linking`) отклоняются, состояние не
  меняется.
- [x] 3.4 `TestSelfTransitionAllowed`: `deferred→deferred` проходит.
- [x] 3.5 `TestTerminalReviveOnlyViaActivate`: `failed→downloading` через
  `SetDownloadState` отклоняется, а через `ActivateIfNoOtherActive` (при свободном
  infohash) проходит.
- [x] 3.6 `TestCancelDeferReachableFromEveryNonTerminal`: инвариант generic-команд —
  для каждого не-терминального состояния `cancelled` — легальная цель, и `deferred` —
  легальная цель (кроме самого `deferred`, где это самопереход). Ловит класс дыры
  «забыли состояние» (напр. `linking` после краха).
- [x] 3.7 `TestPreflightDesyncEdges`: рёбра `reconcileToReality` из ревью/терминальных
  состояний при пропавшем источнике — `review/deferred/reverted/cancelled →
  orphaned` и `→ deleted` — проходят.

## 4. Проверка отсутствия регрессий

- [x] 4.1 `task test` — весь набор зелёный (существующие тесты воркера/store — сеть
  безопасности против слишком тесного графа).
- [x] 4.2 `task lint` — 0 issues.
- [x] 4.3 `openspec validate state-transition-graph --strict`.
