Files
av 42d5b73a04 docs: перевод документации на канон av-dev
- Раскладка docs/ приведена к канону 2: заведены passport/architecture/
  database/security/review и research; docs/specs, drafts, backlog, review/
  и BRIEF.md разобраны и удалены, беклог переехал в docs/tasks (34 задачи,
  6 целей, слаги на английский).
- Нарративы specs удалены как дубли openspec-спек после поимённой сверки;
  остаток заведён задачами (редактор маппинга ревью, крайние случаи
  именования), отказ от сущности title промоутнут в ADR.
- Проектные копии агентов и скиллов ревью/пайплайна удалены в пользу
  плагинов av-dev-pm и av-dev-pipeline; в task gate добавлен шаг canon
  вместо er-schema.
2026-08-04 09:27:26 +03:00

82 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. qbt — удаление раздачи
- [x] 1.1 В `internal/qbt/qbt.go` добавить `Delete(ctx, hashes []string, deleteFiles bool) error`:
`POST /api/v2/torrents/delete`, форма `hashes=<h1>|<h2>|…` + `deleteFiles`.
Логирование `ExtCall{Operation:"torrents/delete"}` как у `Add`; 2xx/пустой
ответ — успех, отсутствие хеша ошибкой не считать.
- [x] 1.2 Тест `internal/qbt/qbt_test.go`: форма запроса delete (hashes через `|`,
`deleteFiles=true`), обработка ответа.
## 2. layout — снятие ссылок без гарда последней копии
- [x] 2.1 Добавить в `Layouter` и реализацию `internal/layout/layout.go` путь
снятия целевых ссылок **без** `ErrLastCopy` (напр. `Remove(ctx, links)`):
безусловный `unlink` только своих ссылок строго под `paths.movies`/`series`;
санитизация/проверка «под библиотекой» сохраняется, `paths.downloads` не
трогаем.
## 3. worker — команда Delete
- [x] 3.1 Расширить интерфейс `QBittorrent` воркера методом `Delete(...)`
(`internal/worker/worker.go`); проброс реализации `qbt.Client`.
- [x] 3.2 В `internal/worker/review.go` добавить `Delete(ctx, id string) error`:
под `w.mu`; `GetDownload`; предусловие `d.State ∈ {done, orphaned,
target_missing}`, иначе `ErrConflict`. Порядок: (а) снять цель как в `Undo`
(`LatestBatchID`/`ListFileLinksByBatch`/фильтр `isLaidOut`, `superseded`
пропускаем) через новый `layouter.Remove`, затем `DeleteFileLinksByBatch`; (б)
`qbt.Delete(d.Infohashes, deleteFiles=true)` (хеши уже загружены с задачей,
отдельный store-геттер не нужен) — ошибку присутствующей раздачи пробросить
(не метить `deleted`), отсутствие раздачи ошибкой не считать; (в)
`transition(ctx, d, StateDeleted, "user_delete", <причина>)`. Delete
source-preflight (`ensureSourceReady`/`reconcileToReality`) НЕ вызывает —
толерантен к отсутствию источника.
- [x] 3.3 Логировать удаление как осознанное пользовательское снятие источника
(`slog`, без секретов): id, состояние-источник, снятые ссылки, `code=user_delete`.
## 4. Транспорт HTTP/веб + подтверждение
- [x] 4.1 Роут `POST /ui/downloads/{id}/delete` → хендлер (по образцу
`handleUndo`), финализация через `surfaceAction`; трансляция `ErrConflict`
409/фрагмент как у прочих действий, а ошибка qBittorrent (не `ErrConflict`) —
внятный отказ пользователю (не тихий успех), не 200-без-эффекта.
- [x] 4.2 Флаг `Deletable` (состояние ∈ {done, orphaned, target_missing}) в
расчётах кнопок (`httpapi.go:648`, `download.go:108`).
- [x] 4.3 Danger-секция в самом низу `web/templates/partials/download_main.html`:
кнопка «Удалить» → confirm (htmx-своп фрагмента «Точно удалить? …
[Да, удалить] [Отмена]») → `POST …/delete`. Деградация без htmx
(`<details>`/отдельный шаг), ошибка на htmx-пути = 200 + фрагмент.
- [ ] 4.4 (Опц., НЕ в этом change) JSON-роут `POST /api/downloads/{id}/delete`.
Осознанно отложено: REST в спеке помечен опциональным, а деструктивный endpoint
без естественного шага подтверждения заводим только по реальной нужде.
## 5. Транспорт Telegram + подтверждение
- [x] 5.1 В `internal/tgbot` добавить действие `delete`: callback `delete:<id>`
показывает confirm-keyboard (`delete_confirm:<id>` / «Отмена»); удаление — на
подтверждающем callback (`Reviewer.Delete`). Кнопка «Удалить» — в карточке
задачи для состояний done/orphaned/target_missing.
- [x] 5.2 Ветка ошибок как у прочих действий (`ErrConflict` → понятный отказ;
ошибка qBittorrent → отдельное сообщение об отказе, не тихий успех), после
успеха — `answer` + `refreshCard`.
## 6. Тесты воркера
- [x] 6.1 Delete из `done`: сняты ссылки + вызван `qbt.Delete(deleteFiles=true)`
+ переход `deleted`/`user_delete`.
- [x] 6.2 Delete из `orphaned`: последняя копия снята (гард выключен), отсутствие
раздачи не ошибка, переход `deleted`.
- [x] 6.3 Delete из `target_missing`: снятие цели идемпотентно (нет живых ссылок),
раздача снесена, переход `deleted`.
- [x] 6.4 Delete из `review`/иного — отказ `ErrConflict`, состояние не меняется.
- [x] 6.5 Ошибка `qbt.Delete` при присутствующей раздаче — задача остаётся, в
`deleted` не уходит; повторный delete идемпотентен.
## 7. Ревью и сверка
- [x] 7.1 `task test` и `task lint` зелёные.
- [x] 7.2 Ревью кода (второй чекпоинт) перед archive.
- [x] 7.3 `openspec validate download-delete --strict` зелёный.
- [x] 7.4 Синк нарратива `docs/specs/workflow.md`: явные пользовательские рёбра
`done/orphaned/target_missing → deleted` (delete) и различие Undo vs delete;
удалить `docs/tasks/items/udalenie-edinoe-okno.md` и строку из индекса беклога.