- в наборе шесть задач: локаль TVDB и confidence-гейт под цель, плюс баги и техдолг помимо неё - взятым дописаны разделы своего типа: «Затрагивает», критерии с оракулами, воспроизведение - решены развилки: оба расхождения код↔спека правятся спекой (и потому стали chore), опрос qBittorrent тормозится бэкоффом до минутного потолка
70 lines
4.8 KiB
Markdown
70 lines
4.8 KiB
Markdown
# 🧹 Зафиксировать в спеке разделение Cancel и Dismiss по состояниям
|
||
|
||
- **Тип:** chore
|
||
- **Категория:** Ядро продукта
|
||
- **Зачем:** спека обещает dismiss из любого состояния, код осознанно даёт Cancel для активных и Dismiss для терминальных — расходится буква, а не поведение
|
||
|
||
Найдено аудитом capability **state-reconciliation** (сверка код↔спека).
|
||
Пред-существующее, вне scope пачки lifecycle-задач.
|
||
|
||
## Суть
|
||
|
||
Спека `openspec/specs/state-reconciliation/spec.md` (требование «Ручное
|
||
закрытие»): команда `dismiss` доступна из **любого** состояния кроме `deleted`
|
||
**во всех транспортах**, и переход SHALL помечаться `error_code = user_dismiss`.
|
||
|
||
В веб-UI danger-zone «Закрыть» (dismiss) гейтится только для терминальных
|
||
состояний: `Dismissable = IsTerminal() && !deleted && !cancelled`
|
||
(`internal/httpapi/download.go:130`, шаблон
|
||
`web/templates/partials/download_main.html:99-112`). Для НЕ-терминальных
|
||
(`stuck`, `deferred`, `downloading`, `review`) закрытие в UI идёт кнопкой
|
||
«Отменить» → `Cancel` (`internal/worker/worker.go:973`), которая пишет **пустой**
|
||
`error_code`, а не `user_dismiss`.
|
||
|
||
## Насколько больно
|
||
|
||
Функционально сценарии проходят: `Cancel` тоже даёт `cancelled` и не трогает
|
||
файлы/раздачу, семантика для пользователя идентична. Состояния без доступного
|
||
«закрытия» нет (кроме `deleted`/`cancelled`). Теряется только маркер
|
||
`user_dismiss` в `error_code` — расхождение с буквой спеки и небольшая потеря
|
||
наблюдаемости (в аналитике/логах не отличить «пользователь закрыл активную» от
|
||
«пользователь отменил»). Отсюда низкий приоритет.
|
||
|
||
## Решение (2026-08-06): B — привести спеку к коду
|
||
|
||
Разделение осознанное: `Cancel` — стоп-кран для активных состояний, `Dismiss` —
|
||
закрытие терминальных. Требование «Ручное закрытие» уточняется: на
|
||
не-терминальных состояниях закрытие из интерфейса реализуется `Cancel`'ом, и
|
||
называется, какой `error_code` при этом ожидается.
|
||
|
||
Вариант A (звать `Dismiss` из веб-UI на не-терминальных ради единого маркера)
|
||
отклонён: он меняет рабочее поведение ради маркера в диагностике.
|
||
|
||
Кода задача не трогает: наблюдаемое поведение остаётся прежним, меняется
|
||
заявленное.
|
||
|
||
## Затрагивает
|
||
|
||
- `openspec/specs/state-reconciliation/spec.md` — требование «Ручное закрытие»:
|
||
доступность `dismiss` по состояниям и ожидаемый `error_code`;
|
||
- дельта-спека change'а — сценарий закрытия не-терминальной загрузки из веб-UI;
|
||
- `internal/httpapi/download.go`, `web/templates/partials/download_main.html` —
|
||
только чтение, правок не предполагается.
|
||
|
||
## Критерии приёмки
|
||
|
||
- Требование спеки различает `Cancel` и `Dismiss` по состояниям и называет
|
||
`error_code` для каждого пути закрытия (оракул: `openspec validate --strict`).
|
||
- В спеке есть сценарий «пользователь закрывает не-терминальную загрузку из
|
||
веб-UI» с исходом `cancelled` и названным `error_code` (оракул: тот же
|
||
прогон).
|
||
- Гейт `Dismissable` в коде и danger-zone шаблона остаются как есть (оракул:
|
||
`git diff --stat` в отчёте ревью — файлов под `internal/` и `web/` нет).
|
||
|
||
## Ссылки
|
||
|
||
- `internal/httpapi/download.go:130` — гейт `Dismissable`
|
||
- `web/templates/partials/download_main.html:99-112` — danger-zone
|
||
- `internal/worker/worker.go:973` — `Cancel`; `:1001` — `Dismiss`
|
||
- `openspec/specs/state-reconciliation/spec.md` — требование «Ручное закрытие»
|