Files
avandClaude Opus 4.8 7d8a455e47 Логирование: классификация доменных ошибок (500→409/400) + конвенции
Штатные конфликты и промахи ввода возвращались голым fmt.Errorf, поэтому
classifyErr отправлял их в 500 «внутренняя ошибка» вместо 409/400 (и logCmd
писал ERROR вместо DEBUG). Продолжение f8fb4fa (Tier A), по итогам ревью Fable.

Классификация ошибок:
- новый sentinel worker.ErrInvalidInput → 400 для валидации ввода команд
  (refine/set type/ignore/add source/set provider/choose candidate);
- обёртки %w ErrConflict в Cancel/Retry/Defer/Undo (штатный конфликт состояния);
- classifyErr: ErrInvalidInput→400, layout.ErrCollision→409 (коллизия цели
  штатно уводит в review); ветка ErrCollision в tgbot (сообщение + refreshCard);
- logCmd относит ErrInvalidInput и ErrCollision в DEBUG «command rejected».

Конвенции (docs/conventions):
- logging.md: публичные команды воркера = доменная граница (лог один раз,
  logCmd); таблица уровней доменных отказов (граница команды vs асинхронная
  стадия); правило про *url.Error/секреты в URL; канон категории
  state transition; уровень повторяющихся сбоев фоновых циклов;
- errors.md: таблица маппинга ошибка→статус; развилка «транзиентный ответ vs
  персистентная диагностика» решена как (а) — error_msg/reasons на review-экране
  и tg-карточке = операторская поверхность владельца (сырой текст ок, секреты
  запрещены; аудит подтвердил, что секреты туда не текут).

Унификация категории лога state transition: cancel/retry/relink/recovery
переведены с семантических msg на общий state transition (from/to) — весь
жизненный цикл собирается одним jq-фильтром.

Мелочи: reason-коды linkPlan в const-блок; httpapi лог-поля id→download_id и
msg «… failed»; комментарий «почему» у parseIgnored; preview build failure в
ReviewData DEBUG→WARN.

Беклог: задача сведена к остатку (ext.* ERROR-шторм при недоступном qBittorrent
+ эскалация устойчивого сбоя тика), понижена в приоритете.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 14:57:12 +03:00

23 lines
1.8 KiB
Go

package worker
import "errors"
// ErrConflict — операция недопустима в текущем состоянии загрузки (напр. apply
// вне review/deferred, undo вне done). Это нормальный конфликт состояния, а не
// сбой сервера: транспорт матчит его через errors.Is и отвечает 409, не 500.
var ErrConflict = errors.New("conflict")
// ErrNotReady — источник (раздача) присутствует в qBittorrent, но ещё не
// докачан (не в классе classReady), поэтому команда, ведущая к распознаванию
// или раскладке, отклонена: хардлинки на неполные файлы недопустимы. Отдельно
// от ErrConflict (тоже 409), потому что причина actionable — «дождись докачки»
// — и транспорт показывает её конкретным текстом, а не генериком конфликта.
var ErrNotReady = errors.New("source not ready")
// ErrInvalidInput — команда отклонена из-за некорректного пользовательского
// ввода (пустая подсказка, неизвестный тип/провайдер, пустой id, кандидат не из
// текущей рекогниции). Это промах пользователя, а не сбой сервера: транспорт
// матчит его через errors.Is и отвечает 400, а логирующая граница домена пишет
// DEBUG (адресат — пользователь, он уже получил ответ на поверхности).
var ErrInvalidInput = errors.New("invalid input")