- требование «Ручное закрытие загрузки» приведено к коду: два пути закрытия, error_code на каждом, раскладка поверхностей — описательно, а не SHALL - уборка своего торрента после отмены названа исключением по состоянию, а не по команде; убрана ложная гарантия «данных пользователя не касается» - в docs/review.md записан проскочивший дефект гарда окна после add и новый вопрос проходу adversary про асимметрию признака владения
69 lines
5.3 KiB
Markdown
69 lines
5.3 KiB
Markdown
## 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 о том же не заводится.
|