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