## 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](../../../docs/adr/ADR-2026-08-06-spec-follows-code-on-narrow-window.md); второй ADR о том же не заводится.