Files
jellybit/docs/tasks/items/dismiss-marker-lost.md
T
av 66d39297c5 docs: проект переведён на канон документов версии 4
- каталог задач: PLAN.md → ROADMAP.md с каноническими секциями, все 44
  записи получили тип, заголовки приведены к форме своего типа
- расхождения, найденные судьями канона: исключения инварианта «источник
  неприкосновенен», инвариант про один активный infohash, UTC в logging.md,
  поведение из architecture.md заменено ссылками на спеки
- триггеры профиля ревью переписаны под умолчание standard
2026-08-06 13:32:06 +03:00

3.4 KiB
Raw Blame History

🐞 Не терять маркер 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 — гейт Dismissable
  • web/templates/partials/download_main.html:99-112 — danger-zone
  • internal/worker/worker.go:973Cancel; :1001Dismiss
  • openspec/specs/state-reconciliation/spec.md — требование «Ручное закрытие»