Files
jellybit/docs/tasks/items/dismiss-marker-lost.md
T
av f42db0a275 sprint: набран спринт 2026-08-06 под цель распознавания
- в наборе шесть задач: локаль TVDB и confidence-гейт под цель, плюс баги и
  техдолг помимо неё
- взятым дописаны разделы своего типа: «Затрагивает», критерии с оракулами,
  воспроизведение
- решены развилки: оба расхождения код↔спека правятся спекой (и потому стали
  chore), опрос qBittorrent тормозится бэкоффом до минутного потолка
2026-08-06 13:57:33 +03:00

70 lines
4.8 KiB
Markdown
Raw 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.
# 🧹 Зафиксировать в спеке разделение 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` — требование «Ручное закрытие»