- каталог задач: PLAN.md → ROADMAP.md с каноническими секциями, все 44 записи получили тип, заголовки приведены к форме своего типа - расхождения, найденные судьями канона: исключения инварианта «источник неприкосновенен», инвариант про один активный infohash, UTC в logging.md, поведение из architecture.md заменено ссылками на спеки - триггеры профиля ревью переписаны под умолчание standard
3.4 KiB
🐞 Не терять маркер user_dismiss при закрытии не-терминальной загрузки из веб-UI
- Тип: fix
- Категория: Ядро продукта
- Зачем: Функционально ок (Cancel даёт cancelled), но маркер user_dismiss в error_code теряется; расхождение с буквой спеки (аудит 2026-07-17)
- Теги: goal:state-integrity
Найдено аудитом 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 — расхождение с буквой спеки и небольшая потеря
наблюдаемости (в аналитике/логах не отличить «пользователь закрыл активную» от
«пользователь отменил»). Отсюда низкий приоритет.
Развилка (решить до кода)
- A — привести код к спеке: веб-UI на не-терминальных тоже зовёт
Dismissради единого маркераuser_dismiss; либоCancelпишетuser_dismiss. - B — привести спеку к коду: зафиксировать осознанное разделение (
Cancelдля активных,Dismissдля терминальных) — уточнить требование, что стоп-кран на не-терминальных реализуетсяCancel'ом, и определить, какойerror_codeожидается.
Сначала решить, осознанно ли разделение Cancel/Dismiss; если да — вероятно B.
Ссылки
internal/httpapi/download.go:130— гейтDismissableweb/templates/partials/download_main.html:99-112— danger-zoneinternal/worker/worker.go:973—Cancel;:1001—Dismissopenspec/specs/state-reconciliation/spec.md— требование «Ручное закрытие»