Единый источник истины `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>
3.6 KiB
3.6 KiB
Tasks
1. Граф в store
- 1.1 Объявить
allowedTransitions map[State][]State(from → to) вinternal/store/download.goрядом сterminalStates, с комментарием об источнике истины и правиле самоперехода. Заполнить по таблице изdesign.md. - 1.2 Построить обратное отображение
to → {легальные from}(для SQL-предиката) как package-levelvarчерез хелпер-инвертор; включать самto(самопереход).
2. Гейт в setState
- 2.1 В
setStateдобавить предикатAND state IN (<легальные источники для to>)до/рядом с существующим гардом терминальности; аргументы черезplaceholders. - 2.2 На
n == 0— диагностическое чтение текущего состояния (черезsqlx.QueryerContext, еслиeего поддерживает) для точного сообщения: «not found» / «illegal transition → » / терминал без revive. - 2.3 Сверить
PromoteCatched: реброcatched → downloadingприсутствует в графе; оставить его собственный гардstate='catched', добавить комментарий-ссылку на граф.
3. Тест согласованности
- 3.1
TestTransitionGraphWellFormed: все состояния в ключах и значениях графа — известные (из полного спискаState); ни один список не содержит сам ключ (петли не перечисляются явно). - 3.2
TestSetStateAllowsDeclaredEdges: для набора объявленных не-revive рёбер (downloading→completed,review→linking,recognizing→review, …)SetDownloadStateпроходит. - 3.3
TestSetStateRejectsUndeclaredEdges: репрезентативные необъявленные (review→done,downloading→done,completed→linking) отклоняются, состояние не меняется. - 3.4
TestSelfTransitionAllowed:deferred→deferredпроходит. - 3.5
TestTerminalReviveOnlyViaActivate:failed→downloadingчерезSetDownloadStateотклоняется, а черезActivateIfNoOtherActive(при свободном infohash) проходит. - 3.6
TestCancelDeferReachableFromEveryNonTerminal: инвариант generic-команд — для каждого не-терминального состоянияcancelled— легальная цель, иdeferred— легальная цель (кроме самогоdeferred, где это самопереход). Ловит класс дыры «забыли состояние» (напр.linkingпосле краха). - 3.7
TestPreflightDesyncEdges: рёбраreconcileToRealityиз ревью/терминальных состояний при пропавшем источнике —review/deferred/reverted/cancelled → orphanedи→ deleted— проходят.
4. Проверка отсутствия регрессий
- 4.1
task test— весь набор зелёный (существующие тесты воркера/store — сеть безопасности против слишком тесного графа). - 4.2
task lint— 0 issues. - 4.3
openspec validate state-transition-graph --strict.