## 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 о том же не заводится.
