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
2026-06-14 11:35:14 +03:00
2026-06-28 21:29:08 +03:00

Jellybit

Jellybit — связующий сервис между qBittorrent и Jellyfin. Принимает magnet-ссылку вместе с текстовым контекстом, ставит загрузку в qBittorrent, дожидается её завершения, распознаёт содержимое (фильм или сериал, сезоны и серии) и раскладывает готовые файлы по конвенциям библиотеки Jellyfin.

Полный замысел и причины — в BRIEF.md.

Зачем

Arr-стек (prowlarr/radarr/sonarr) плохо ложится на русские трекеры, аниме и ручные раздачи. Jellybit намеренно сокращает путь: одна точка входа → готовая раскладка для Jellyfin, без каталога индексаторов и сложных правил качества. Распознавание делает LLM, которому помогает переданный человеком контекст и (опционально) внешние базы метаданных.

Как работает

  1. Точка входа принимает magnet + контекст (HTTP API, веб-UI, Telegram-бот или CLI).
  2. Загрузка ставится в qBittorrent в выделенную категорию.
  3. Сервис отслеживает завершение загрузки.
  4. По именам файлов, контексту и (опц.) базам метаданных определяется фильм/сериал и нужная раскладка.
  5. Файлы хардлинкаются в библиотеку Jellyfin — источник остаётся в раздаче, место на диске не дублируется.
  6. После раскладки сервис (опц.) просит Jellyfin пересканировать медиатеку, чтобы новые файлы быстрее появились в проигрывателе.

При высокой уверенности раскладка выполняется автоматически, иначе — уходит на подтверждение человеку.

Статус

Рабочий прототип с полным сквозным путём: приём magnet → загрузка в qBittorrent → распознавание (LLM + опционально базы метаданных TMDB/TVDB/TVMaze) → раскладка в библиотеку хардлинками, автоматически при уверенном результате либо через подтверждение человеком. Транспорты приёма: REST API, веб-UI, Telegram-бот и CLI (jellybit add).

Из источников пока поддержан magnet; .torrent и обычные ссылки — в планах. См. дорожную карту.

Документация

Разработка идёт по Spec-Driven Development через OpenSpec: изменение сначала описывается спекой, потом реализуется.

  • openspec/ — OpenSpec: актуальные capability-спеки в openspec/specs/, изменения (proposal → design → tasks → archive) в openspec/changes/. Capabilities постепенно переносятся сюда из docs/specs/.
  • docs/conventions/ — конвенции кода (как пишем): логирование (logging.md), конфигурация (config.md), ошибки (errors.md).
  • docs/specs/ — спецификации устройства системы (архитектурный обзор + ещё не перенесённые в OpenSpec темы). Начать с architecture.md.
  • docs/adr/ — журнал архитектурных решений (почему так).
  • docs/drafts/ — черновики: планы, идеи, нерешённое.

Стек

Go (один статический бинарь), SQLite (modernc.org/sqlite + sqlx, миграции goose), HTTP — chi + html/template + htmx, конфигурация — TOML, логи — структурированный JSON (slog). Подробнее — в architecture.md.

Конфигурация

Конфигурация — один файл TOML. По умолчанию ищется config.toml в рабочей директории; путь переопределяется опцией --config=path. Образец со всеми секциями и описанием каждого поля (назначение, диапазон значений, единицы измерения) — config.example.toml; скопируй его в config.toml и заполни под себя. Конфиг валидируется на старте: при ошибке сервис не стартует.

Секреты (пароль qBittorrent, ключи LLM/метабаз, токен Telegram) в репозиторий не коммитятся — их подставляет деплой прямо в файл. Доступ к внешним сервисам (LLM, базы метаданных, Telegram) при необходимости идёт через HTTP-прокси — поле proxy в соответствующих секциях. Правила — в docs/conventions/config.md.

Разработка

Нужны Go 1.26 и Task. Полный список задач — task --list.

cp config.example.toml config.toml   # локально: db_path -> ./jellybit.db
task setup                           # golangci-lint + git-хуки lefthook
task tidy                            # go mod tidy
task run                             # go run ./cmd/jellybit --config ./config.toml
task test lint                       # тесты и golangci-lint
task build                           # статический бинарь (linux/amd64) для сервера
task image                           # docker-образ из готового бинаря

Отладка распознавания на реальной раздаче (только чтение, без раскладки):

jellybit recognize <infohash> --dry-run [--context "..."] --config ./config.toml

Берёт торрент из qBittorrent по infohash, прогоняет распознавание (LLM + метабазы) и печатает план: тип/название/год, матч в базе, решение авто/review и превью целевых путей — то, что создалось бы при Apply.

Доставка

Рассчитан на домашний медиа-сервер. Артефакты репозитория — статический бинарь (task build) и Dockerfile (упаковка в distroless/static). Образ собирается на сервере из доставленного бинаря, поэтому Go-тулчейн на сервере не нужен. В distroless нет shell/curl, поэтому HEALTHCHECK зовёт сам бинарь: jellybit healthcheck (GET /healthz по порту из конфига, exit 0/1).

Контейнер: user 1000:1000, порт 8080 на хост, mount /srv/media (единая песочница для хардлинков) + том /config (ro, config.toml, восстановим при деплое) + data-том /data (SQLite, бекапить); к qBittorrent — по сети Docker. Конкретная деплой-обвязка (плейбук, секреты) держится в отдельном приватном репозитории и в комплект не входит.

S
Description
No description provided
Readme
2.5 MiB
Languages
Go 93.3%
CSS 2.4%
HTML 2.3%
Python 1.7%
JavaScript 0.2%
Other 0.1%