Files
avandClaude Opus 4.8 90fd8640ed Машина состояний: декларативный граф легальных переходов
Единый источник истины `allowedTransitions` (from → {разрешённые to}) в
internal/store; `setState` сверяет переход дополнительным SQL-предикатом
`state IN (<легальные источники>)` — необъявленное ребро (и не самопереход)
отклоняется атомарно, с точным сообщением. Гейт ортогонален гарду
терминальности: ребро из терминального состояния проходит только через
ActivateIfNoOtherActive. Без внешней библиотеки-FSM (обоснование — design.md).

Граф выведен построчно из воркера; ревью дизайна поймало 8 preflight-рёбер
(reconcileToReality → orphaned/deleted) и linking→cancel/defer после краха.
Тест-инвариант «cancel/defer достижимы из любого не-терминального» ловит класс
пропущенного ребра. Фикстуры тестов, форсившие состояния через SetDownloadState,
переведены на прямой UPDATE (forceState).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 09:09:17 +03:00

51 lines
3.6 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.
# 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`.