Files
jellybit/openspec/changes/archive/2026-08-06-cancel-dismiss-split-wording/proposal.md
T
av 0b02a8c224 specs: в state-reconciliation разведены Cancel и Dismiss по состояниям
- требование «Ручное закрытие загрузки» приведено к коду: два пути закрытия,
  error_code на каждом, раскладка поверхностей — описательно, а не SHALL
- уборка своего торрента после отмены названа исключением по состоянию, а не
  по команде; убрана ложная гарантия «данных пользователя не касается»
- в docs/review.md записан проскочивший дефект гарда окна после add и новый
  вопрос проходу adversary про асимметрию признака владения
2026-08-06 15:50:41 +03:00

5.3 KiB
Raw Blame History

Why

Требование «Ручное закрытие загрузки (стоп-кран)» обещает команду «Закрыть» (dismiss) «из любого состояния, кроме deleted, во всех транспортах» и метку error_code = "user_dismiss" на переходе. Доменная команда worker.Dismiss это выполняет, но поверхности её так не показывают: веб-UI держит «Закрыть» в danger-зоне только для терминальных состояний (Dismissable = IsTerminal() && !deleted && !cancelled), а закрытие не-терминальной загрузки идёт кнопкой «Отменить»/«Отклонить» → worker.Cancel, которая пишет пустой error_code. Telegram показывает «Закрыть» ещё по третьей раскладке — для failed/stuck и для done/orphaned/ target_missing, а на review/deferred даёт «Отклонить» → Cancel.

Расходится буква, а не поведение: Cancel тоже даёт cancelled, файлы и раздачу не трогает, семантика для пользователя та же. Теряется маркер user_dismiss в диагностике — по error_code не отличить «пользователь закрыл активную» от «пользователь отменил». Пока спека утверждает единый путь закрытия, разрыв невидим и находится заново каждым аудитом capability.

What Changes

Меняется заявленное, не наблюдаемое. Кода изменение не трогает.

  • Требование «Ручное закрытие загрузки (стоп-кран)» разводит два уровня: гейты доменных путей (Cancel — не-терминальные, Dismiss — любое кроме deleted; это нормативно) и раскладку кнопок по поверхностям (описана как сегодняшнее состояние, нормативной не объявляется).
  • Требование называет error_code на каждом пути закрытия: Dismiss"user_dismiss" (нормативно), Cancel (отмена активной / отклонение на ревью) → сегодня пустой (описательно — собственный код у Cancel не запрещён, вопрос открыт). Прежняя формулировка обещала user_dismiss на любом закрытии.
  • Требование называет гарантию, которая держится: страница загрузки веб-UI даёт путь закрытия из каждого не-deleted и не-cancelled состояния; какой именно путь — выбор поверхности, а не обязательство домена.
  • Отрицание побочных эффектов остаётся при Dismiss; для Cancel названо единственное исключение — уборка воркером собственного, только что добавленного торрента при отмене в окне после add (дом исключения — download-tracking).
  • Названо, что оба пути замещают прежнюю диагностику записи, а не только Cancel.
  • Добавляются сценарии закрытия не-терминальной загрузки из веб-UI, закрытия терминальной, замещения диагностики и расхождения маркера по поверхностям.
  • Названо известное ограничение: маркер сегодня кодирует поверхность, а не намерение; разрыв описан с ценой, а не замолчан.

Capabilities

New Capabilities

Нет.

Modified Capabilities

  • state-reconciliation: требование «Ручное закрытие загрузки (стоп-кран)» — доступность закрытия по состояниям и поверхностям, error_code на каждом пути закрытия, сценарии закрытия не-терминальной загрузки.

Impact

  • openspec/specs/state-reconciliation/spec.md — одно требование и его сценарии.
  • internal/httpapi/download.go, web/templates/partials/download_main.html, web/templates/partials/card.html, internal/tgbot/render.go, internal/worker/worker.goтолько чтение, правок не предполагается. Отсутствие правок под internal/ и web/ — критерий приёмки задачи.
  • Прецедент приёма — ADR-2026-08-06-spec-follows-code-on-narrow-window; второй ADR о том же не заводится.