Беклог: перенос из Tududi в docs/backlog (файл на задачу + индекс)
Tududi оказался неудобен для ведения беклога проекта — переходим на файлы в репозитории. Каждая задача — отдельный markdown в docs/backlog/ (48 файлов), плюс индекс README.md со списком по приоритетам и хуками. Тело файла хранит исходное описание (контекст, решения, ссылки на спеки/ADR/черновики). CLAUDE.md: источник истины по беклогу теперь docs/backlog/; Tududi понижен до инбокса сырых идей. Живые ссылки в спеках (recognition, architecture, review-ux, jellyfin-layout) «в беклоге (Tududi)» переписаны на прямые ссылки на файлы беклога. Перенесённые задачи удалены из Tududi; завершённые оставлены как история. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,17 +93,19 @@ OpenSpec (пилот — `ingest`). До переноса источник ис
|
||||
|
||||
## Задачи и беклог
|
||||
|
||||
- **Единственный источник беклога — Tududi**, проект `jellybit` (MCP-сервер
|
||||
`tududi`, project_id 14). Там задачи с приоритетами (высокий/средний/
|
||||
низкий) и описанием (контекст, принятые решения, ссылки на спеки/ADR/
|
||||
черновики в теле задачи). Ищи, заводи и закрывай задачи через
|
||||
MCP-инструменты `tududi` (`list_tasks`, `create_task`, `update_task`,
|
||||
`complete_task`, `search`).
|
||||
- **Единственный источник беклога — каталог [docs/backlog/](docs/backlog/README.md)**:
|
||||
одна задача = один markdown-файл (`docs/backlog/<slug>.md`), плюс
|
||||
индекс [README.md](docs/backlog/README.md) со списком по приоритетам
|
||||
(высокий/средний/низкий) и хуками. В теле файла — контекст, принятые
|
||||
решения, шаги и ссылки на спеки/ADR/черновики. Заводи задачу новым файлом
|
||||
и строкой в индексе; закрытую (реализованную) — удаляй, суть переезжает в
|
||||
`docs/specs`/`docs/adr`.
|
||||
- Спекулятивные задачи (ещё без решения «делаем») помечены префиксом
|
||||
`[идея]` в названии — их сперва прорабатываем.
|
||||
- Отдельного файла-беклога в репозитории больше нет: `docs/backlog.md`
|
||||
перенесён в Tududi. Старая версия при необходимости доступна в истории
|
||||
git.
|
||||
- **Tududi — только инбокс сырых идей** (проект `jellybit`, project_id 14).
|
||||
Беклог там больше не ведём; идея из Tududi становится задачей, когда её
|
||||
оформляют файлом в `docs/backlog/`. Прежняя единая `docs/backlog.md`
|
||||
доступна в истории git.
|
||||
|
||||
## Язык
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Беклог
|
||||
|
||||
Единый список будущих задач по проекту: то, что уже решили сделать, и идеи,
|
||||
которые ещё надо обдумать. Это **источник истины по беклогу** — одна задача = один
|
||||
файл в этом каталоге. Не план реализации и не спецификация: принятое и
|
||||
реализованное переезжает в [`docs/specs`](../specs)/[`docs/adr`](../adr), а сам
|
||||
пункт беклога удаляется.
|
||||
|
||||
Приоритет — грубая оценка «ценность / стоимость», не обязательство к порядку.
|
||||
Спекулятивные пункты (ещё без решения «делаем») помечены префиксом `[идея]` в
|
||||
названии — их сперва надо проработать. Пункты, помеченные _(ревью 2026-07-08)_,
|
||||
пришли из тщательного ревью ingest/worker/жизненного цикла (см. общий тег в теле).
|
||||
|
||||
Tududi (проект `jellybit`) больше **не** держит беклог — он служит только
|
||||
инбоксом сырых идей. Прежде чем идея станет задачей, её оформляют файлом здесь.
|
||||
|
||||
## Высокий
|
||||
|
||||
- [Проблема второго сезона (сходимость папки сериала)](vtoroy-sezon-shodimost-papki.md) — Второй/третий сезон должен ложиться в ТУ ЖЕ папку сериала, а не заводить рядом почти…
|
||||
- [Раздачи с докачиванием (merge при повторном добавлении)](merge-dokachivanie.md) — Свежий сериал раздают по мере выхода: торрент с 5 из 10 эпизодов позже перезаливают…
|
||||
- [Удаление средствами jellybit («единое окно», path 2)](udalenie-edinoe-okno.md) — Распознавание ручного удаления и пометка рассинхрона уже сделаны (state-reconciliation…
|
||||
- [Ретеншн и очистка БД](retention-ochistka-bd.md) — Терминальные задачи (done/cancelled/failed/reverted), их попытки recognition с сырыми…
|
||||
- [Eval-харнес распознавания (корпус кейсов + метрика точности)](eval-harness-raspoznavaniya.md) — Распознавание — ядро продукта, но смена модели или правка промпта сейчас вслепую…
|
||||
- [НФТ: масштаб до 100 одновременных загрузок (потолок — 1000)](masshtab-100-zagruzok.md) — Потолок по нагрузке нигде не зафиксирован: воркер, поллинг qBittorrent, пул LLM-вызовов и…
|
||||
- [Гейт дозаписи хешей в dedup-ветке CreateDownloadIfNoActive (F1)](review-f1-gate-dozapisi-heshey.md) — dedup-ветка дописывает все хеши в найденную задачу без гарда — риск инварианта ≤1 активной _(ревью 2026-07-08)_
|
||||
- [Readiness-preflight: запрет авто-раскладки недокачанных файлов (MAJOR-5)](review-major5-readiness-preflight.md) — САМОЕ ОПАСНОЕ: авто-раскладка недокачанных файлов рвёт целостность библиотеки _(ревью 2026-07-08)_
|
||||
- [Retry/stall семантика: сброс базиса таймаута + простой от начала, а не от возраста торрента (MAJOR-1, MAJOR-2)](review-major1-2-retry-stall.md) — таймаут и простой отсчитываются от возраста торрента, а не от начала загрузки _(ревью 2026-07-08)_
|
||||
- [Восстановление zombie downloading при пропаже источника из qBittorrent (MAJOR-3)](review-major3-zombie-downloading.md) — торрент пропал из qBittorrent в downloading → задача вечный зомби, никто не двигает _(ревью 2026-07-08)_
|
||||
|
||||
## Средний
|
||||
|
||||
- [Словарь единого языка (ubiquitous language)](ubiquitous-language-slovar.md) — Свести термины домена в один глоссарий, чтобы пользователь, документация, код и агент…
|
||||
- [Агенты-ревьюверы качества (наименования, архитектура, конвенции, стиль)](agenty-revyuvery-kachestva.md) — Набор узких сабагентов-ревьюверов поверх ревью-процесса из CLAUDE
|
||||
- [[идея] Сила совпадения кандидата и пересмотр распознавания/матчинга](sila-sovpadeniya-kandidata.md) — ИДЕЯ (сперва проработать)
|
||||
- [История переходов загрузки](istoriya-perehodov-zagruzki.md) — Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто инициировал…
|
||||
- [Машина состояний на go-библиотеке](mashina-sostoyaniy-biblioteka.md) — Сейчас FSM реализована вручную в worker
|
||||
- [Привязка уведомлений к источнику в ботах (мульти-бот)](uvedomleniya-multi-bot.md) — Уведомления и запросы подтверждения должен получать тот, кто прислал загрузку: автор…
|
||||
- [Улучшения UI: показывать матч с записью метабазы в Telegram](telegram-match-metabazy.md) — Web-сторона реализована: страница загрузки /download/{id} и экран ревью показывают, с…
|
||||
- [[идея] Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки](slozhnye-serialnye-razdachi.md) — ИДЕЯ (проработать крайние случаи)
|
||||
- [Аниме с абсолютной нумерацией](anime-absolyutnaya-numeraciya.md) — Релизы аниме часто нумеруют серии сквозным числом (#137) без сезонов, а Jellyfin ждёт…
|
||||
- [Добавление торрентов файлом/ссылкой — «единое окно»](dobavlenie-edinoe-okno.md) — Поддержать источники помимо magnet:
|
||||
- [Бэкап SQLite](backup-sqlite.md) — architecture
|
||||
- [Глубокий healthcheck и статус зависимостей](healthcheck-zavisimosti.md) — /healthz проверяет только сам сервис
|
||||
- [Обучение на правках человека (few-shot из прошлых ревью)](obuchenie-na-pravkah.md) — Когда человек поправил матч, тип или нумерацию — сохранять это как пример и подмешивать…
|
||||
- [Гейт авто-раскладки по confidence: спека vs код](gate-confidence-spec-vs-code.md) — Аудит спек↔код (2026-07-03) нашёл расхождение в модели уверенности
|
||||
- [Привязка внешних субтитров к серии (сериалы)](vneshnie-subtitry.md) — Аудит спек↔код (2026-07-03): спека recognition требует «внешние субтитры SHALL…
|
||||
- [Раздачи-копии диска (DVD/BluRay: VIDEO_TS/BDMV)](disk-kopii-video-ts-bdmv.md) — Иногда для очень редких фильмов скачивается не один видеофайл, а полная копия диска…
|
||||
- [Сделать фавиконку для jellybit](favicon.md) — мелкая косметика: иконка вкладки/PWA для веб-UI
|
||||
- [Sweep застрявших linking при рестарте + фикс persist-failure (MAJOR-4)](review-major4-sweep-linking.md) — задачи застревают в linking при рестарте; заодно фикс persist-failure _(ревью 2026-07-08)_
|
||||
- [Defer из catched → лимбо → необратимый deleted (MAJOR-6)](review-major6-defer-catched.md) — Defer из ещё-не-добавленного catched уводит задачу в необратимый deleted _(ревью 2026-07-08)_
|
||||
- [processCatched: promote-without-add если торрент уже в qBittorrent (F2)](review-f2-promote-without-add.md) — торрент уже в qBittorrent → processCatched зациклен на Add вместо promote _(ревью 2026-07-08)_
|
||||
- [Cancel во время add оставляет неуправляемый торрент в qBittorrent (F3/NIT-13)](review-f3-cancel-during-add.md) — Cancel во время add оставляет неуправляемый торрент в qBittorrent _(ревью 2026-07-08)_
|
||||
- [transition() глотает ошибки перед созданием хардлинков (MINOR-7)](review-minor7-transition-errors.md) — transition() глотает ошибку перед хардлинками — задача застревает в review _(ревью 2026-07-08)_
|
||||
- [.torrent поверх magnet при дедупе теряет байты — потерян upgrade-путь (F6)](review-f6-torrent-over-magnet.md) — .torrent поверх magnet при дедупе теряет байты — потерян upgrade-путь _(ревью 2026-07-08)_
|
||||
|
||||
## Низкий
|
||||
|
||||
- [Панель действий ревью вне htmx-свопа блока источника](panel-review-vne-swap.md) — При выборе источника одним кликом обновляется только блок источника (#source-block)…
|
||||
- [Мгновенные обновления через SSE](sse-obnovleniya.md) — Живые обновления прогресса сейчас на htmx-поллинге (фаза 2 веб-UI) — просто и работает…
|
||||
- [Версии/качество одного тайтла (репаки, апгрейд 1080p → 2160p)](versii-kachestvo-repaki.md) — По калибровке болей (2026-07-02) — не боль, из приоритета выпало
|
||||
- [[идея] Многоступенчатая верификация привязки](mnogostupenchataya-verifikaciya.md) — ИДЕЯ (требует проработки)
|
||||
- [Выбор из нескольких находок метабазы в Telegram](telegram-vybor-nahodok.md) — Когда распознавание даёт несколько подходящих кандидатов в метабазе, предлагать их в…
|
||||
- [Проверка свободного места перед copy-fallback](svobodnoe-mesto-copy-fallback.md) — Когда хардлинк невозможен (EXDEV/ENOTSUP/…), layout копирует файл, дублируя место на диске
|
||||
- [Кэш метабаз (и опционально LLM)](kesh-metabaz.md) — Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и тем же…
|
||||
- [[идея] guessit как сервис-спутник](guessit-sputnik.md) — ИДЕЯ
|
||||
- [[идея] Завершение загрузки через webhook](webhook-zavershenie-zagruzki.md) — ИДЕЯ (решим по опыту эксплуатации)
|
||||
- [Авторизация веб-UI (на будущее)](avtorizaciya-web-ui.md) — Решено для v1: без авторизации в доверенной LAN, опц
|
||||
- [Современный Web-UI как PWA](web-ui-pwa.md) — Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое, отзывчивое…
|
||||
- [Идентичность инфохэшей: split v1/v2 одного торрента + крафт-магнет отравляет владение (F4, F5)](review-f4-f5-infohash-identity.md) — split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_
|
||||
- [Приём/UI: мелкие фиксы границ и парсинга (F7, F8, F9, F10, N2)](review-f7-f10-ingest-ui-fixes.md) — мелочи приёма/UI: oversized→500, гонки дедупа, парсинг magnet, cap контекста, руны _(ревью 2026-07-08)_
|
||||
- [Жизненный цикл: мелкие находки (MINOR-8, MINOR-9, NIT-11, NIT-12)](review-lifecycle-minor.md) — claim-token распознавания, I/O под глобальным mutex, мелкие lookup/retry _(ревью 2026-07-08)_
|
||||
- [Нити приёма: NoName в контексте, устаревшие комментарии, лог без причины, bencode-аллокации (N1, N3, N4, N5)](review-ingest-nits.md) — косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_
|
||||
@@ -0,0 +1,7 @@
|
||||
# Агенты-ревьюверы качества (наименования, архитектура, конвенции, стиль)
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Набор узких сабагентов-ревьюверов поверх ревью-процесса из CLAUDE.md, каждый со своей оптикой: соответствие наименований словарю единого языка, соблюдение архитектурных границ (единое ядро/тонкие транспорты, инварианты безопасности данных), конвенций (ошибки, логирование, конфиг, TZ), стиля кода и поиск дублирования. Запускаются как чекпоинт перед archive/коммитом. Развивает ревью-процесс OpenSpec в сторону воспроизводимых автопроверок, не заменяя человеческое ревью.
|
||||
|
||||
Связано: CLAUDE.md (ревью-процесс, конвенции), docs/conventions, «Словарь единого языка».
|
||||
@@ -0,0 +1,7 @@
|
||||
# Аниме с абсолютной нумерацией
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Релизы аниме часто нумеруют серии сквозным числом (#137) без сезонов, а Jellyfin ждёт SxxEyy. Нужен пересчёт абсолютной нумерации в сезон/серию — надёжнее всего через TVDB (там есть absolute order). Отдельный крайний случай распознавания; на стороне ревью — веб-хелпер «absolute → S·E».
|
||||
|
||||
Связано: specs/recognition.md (конвейер, сезон-паки), specs/jellyfin-layout.md (нумерация серий), specs/review-ux.md.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Авторизация веб-UI (на будущее)
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
Решено для v1: без авторизации в доверенной LAN, опц. allowlist подсетей (http.trusted_subnets) — как умеет qBittorrent. Если понадобится защита: токен/Basic в самом приложении или вынос за reverse-proxy с аутентификацией.
|
||||
|
||||
Связано: specs/architecture.md → «Транспорты» (доступ к веб-UI), пакет httpapi.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Бэкап SQLite
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
architecture.md требует «бекапить data-том», но как — не описано. Без понятной стратегии сбой или редеплой стирают всё in-flight состояние. Зафиксировать решение и реализовать: периодический VACUUM INTO в /data/backups по расписанию (с ротацией) либо потоковая репликация (litestream). Лучше сделать, пока БД маленькая.
|
||||
|
||||
Связано: specs/architecture.md → «Деплой» (data-том), пакет store.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Раздачи-копии диска (DVD/BluRay: VIDEO_TS/BDMV)
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Иногда для очень редких фильмов скачивается не один видеофайл, а полная копия диска — структура VIDEO_TS/ (DVD) или BDMV/ (BluRay). Сейчас распознавание и раскладка заточены под пофайловый разбор, а тут «фильм» — это каталог целиком. Jellyfin такие раскладки поддерживает (папка фильма с вложенным VIDEO_TS/BDMV). Нужно: распознать, что раздача — образ диска (по наличию VIDEO_TS/BDMV), не разбирать её по отдельным VOB/m2ts как серии, разложить весь каталог хардлинками в папку фильма (Название (Год)/VIDEO_TS/…). Крайний, но реальный случай; частота низкая.
|
||||
|
||||
Связано: specs/recognition.md (роли файлов), specs/jellyfin-layout.md (раскладка фильма), пакеты recognize, layout.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Добавление торрентов файлом/ссылкой — «единое окно»
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Поддержать источники помимо magnet: .torrent-файл и URL (отдаём их в qBittorrent, без исходящих запросов на пользовательский URL — SSRF исключён). Идеал — одно поле «единого окна»: кидаем туда текст или файл, а сервис сам разбирает, что это (magnet / ссылка / .torrent / сообщение бота), и заводит загрузку.
|
||||
|
||||
Связано: specs/architecture.md → «Транспорты» (source_type = magnet|torrent|url уже в схеме), пакет ingest (сейчас поддержан только magnet).
|
||||
@@ -0,0 +1,7 @@
|
||||
# Eval-харнес распознавания (корпус кейсов + метрика точности)
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
Распознавание — ядро продукта, но смена модели или правка промпта сейчас вслепую: регрессий не видно. Нужен корпус размеченных кейсов (русские релизы, аниме, сезон-паки, репаки, спецвыпуски) и прогон распознавания по нему с метрикой точности (тип/название/год/нумерация). Тогда можно сравнивать LLM-провайдеры и версии промпта по числам. Прогон — отдельной командой (jellybit eval или тестом), на фикстурах, без реального qBittorrent.
|
||||
|
||||
Связано: specs/recognition.md (конвейер, модель уверенности), пакет recognize.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Сделать фавиконку для jellybit
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
_Описание не заполнено._
|
||||
@@ -0,0 +1,7 @@
|
||||
# Гейт авто-раскладки по confidence: спека vs код
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Аудит спек↔код (2026-07-03) нашёл расхождение в модели уверенности. Спека recognition утверждает, что самооценка LLM confidence — вспомогательный сигнал, НЕ единственный гейт: при подтверждённом матче + чистой валидации + согласованности сигналов авто-раскладка допускается. Код же (internal/recognize/validate.go, confidence < AutoThreshold, дефолт 0.85) делает confidence жёстким блокирующим условием: план с матчем и чистой валидацией, но confidence 0.5 уйдёт в review вопреки спеке. Определиться: признать порог AutoThreshold в спеке как легитимный гейт (скорее так — код его осознанно ввёл конфигом) либо ослабить код. Заодно AutoThreshold как конфигурируемый гейт спекой не описан.
|
||||
|
||||
Связано: openspec/specs/recognition, ADR-2026-06-13-auto-link-requires-db-match, пакет recognize.
|
||||
@@ -0,0 +1,7 @@
|
||||
# [идея] guessit как сервис-спутник
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
ИДЕЯ. go-ptn слабее питоновского guessit. Если точности пред-парса не хватит — завернуть guessit в крошечный HTTP-сервис (один файл, поставляется рядом с бинарём jellybit) и спрашивать его на шаге пред-парса. Сохраняет «доставку копированием»: два файла вместо одного.
|
||||
|
||||
Связано: specs/recognition.md → «На будущее» (пред-парс).
|
||||
@@ -0,0 +1,7 @@
|
||||
# Глубокий healthcheck и статус зависимостей
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
/healthz проверяет только сам сервис. Если qBittorrent, LLM или метабаза недоступны — узнаёшь лишь по застрявшим задачам. Нужна readiness-проверка ключевых зависимостей и отражение их состояния в UI (бейдж «qBittorrent недоступен»), чтобы причина простоя была видна сразу.
|
||||
|
||||
Связано: specs/architecture.md → «Деплой» (healthcheck), пакеты qbt, llm, metadata, httpapi.
|
||||
@@ -0,0 +1,7 @@
|
||||
# История переходов загрузки
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто инициировал — воркер, человек, сверка), а не только текущее состояние. Сейчас по задаче виден лишь актуальный статус, разбор «как мы сюда попали» идёт по логам сервера. Отдельная таблица истории даёт лог переходов в карточке/расширенной информации и фундамент для метрик длительности стадий. Естественно ложится на собственный идентификатор загрузки и уже реализованный экран /download/{id}.
|
||||
|
||||
Связано: drafts/logical-title-model.md §5.4 (state_transition, actor worker|human|reconcile), specs/workflow.md, specs/database.md, пакеты worker, store.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Кэш метабаз (и опционально LLM)
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и тем же запросом. Кэш ответов с TTL экономит лимиты API и ускоряет «Распознать заново»/«Уточнить». При желании — кэш ответов LLM по хешу входа (но он менее полезен, т.к. вход меняется подсказками).
|
||||
|
||||
Связано: specs/recognition.md (сверка с базой), пакеты metadata, llm.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Машина состояний на go-библиотеке
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Сейчас FSM реализована вручную в worker. Выбрать подходящую go-библиотеку для описания воркфлоу/машины состояний и перевести переходы на неё — ради декларативности, проверяемости переходов и единого места правды. Кандидаты для оценки: looplab/fsm, qmuntal/stateless (и аналоги). Граф и переходы уже формализованы — переносим один в один.
|
||||
|
||||
Связано: specs/workflow.md (текущий граф состояний).
|
||||
@@ -0,0 +1,7 @@
|
||||
# НФТ: масштаб до 100 одновременных загрузок (потолок — 1000)
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
Потолок по нагрузке нигде не зафиксирован: воркер, поллинг qBittorrent, пул LLM-вызовов и запись в SQLite спроектированы «на глаз». Записать в НФТ целевой ориентир — архитектура держит до 100 одновременных загрузок в работе (приём → распознавание → раскладка), план-максимум — 1000. Сама запись требования дешева и высокоценна: задаёт рамку для решений ниже. Отдельно (дороже) — аудит узких мест: одиночное соединение SQLite и сериализация записи, конкурентность воркера и лимит параллельных распознаваний, частота/стоимость поллинга и дедуп при наплыве.
|
||||
|
||||
Связано: specs/architecture.md → «Отслеживание загрузки»/«Хранилище», пакеты worker, store, qbt, llm.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Раздачи с докачиванием (merge при повторном добавлении)
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
Свежий сериал раздают по мере выхода: торрент с 5 из 10 эпизодов позже перезаливают целиком, пользователь добавляет раздачу повторно. Новая загрузка приходит в ту же папку за счёт правила сходимости, а раскладка становится merge — доложить только недостающее. Существующие пути не трогаем (never-overwrite, владение у старой загрузки), новые кладём (владеет новая). Split-ownership сезона принят как норма per-path модели; обе раздачи сидируют независимо.
|
||||
|
||||
Шаги:
|
||||
- в плане раскладки отличать «путь занят живой ссылкой того же матча» (→ пропустить) от настоящей коллизии (→ review)
|
||||
- merge-раскладка: существующее пропустить, недостающее доложить
|
||||
- показать итог в карточке: сколько доложено, сколько уже было
|
||||
- решить «слияние загрузок» при перезаливе (одна строка download + новый infohash vs новая загрузка) — открытый вопрос черновика §10
|
||||
|
||||
Зависит от правила сходимости («Проблема второго сезона»), выигрывает от ULID-идентичности.
|
||||
Связано: drafts/logical-title-model.md §6.2, specs/jellyfin-layout.md, specs/workflow.md
|
||||
@@ -0,0 +1,7 @@
|
||||
# [идея] Многоступенчатая верификация привязки
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
ИДЕЯ (требует проработки). Несколько раз извлекать данные из раздачи и контекста разными промптами, искать в метабазах, затем сводить результаты в общий вердикт (голосование/консенсус) — выше точность ценой нескольких вызовов LLM и запросов к базам. Проработать: когда включать, как мерджить расхождения, стоимость/латентность.
|
||||
|
||||
Связано: specs/recognition.md (конвейер и модель уверенности).
|
||||
@@ -0,0 +1,7 @@
|
||||
# Обучение на правках человека (few-shot из прошлых ревью)
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Когда человек поправил матч, тип или нумерацию — сохранять это как пример и подмешивать похожие в будущие промпты. Системно повышает точность на «твоих» трекерах и форматах имён без смены модели. Развитие идеи многоступенчатой верификации, но дешевле: учимся на уже собранных hint/override.
|
||||
|
||||
Связано: specs/recognition.md (конвейер, промпт), «Многоступенчатая верификация», specs/architecture.md → «Хранилище» (hint, override).
|
||||
@@ -0,0 +1,7 @@
|
||||
# Панель действий ревью вне htmx-свопа блока источника
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
При выборе источника одним кликом обновляется только блок источника (#source-block) htmx-свопом, а нижняя панель действий (кнопка «Применить», завязанная на HasLinks) — вне блока и не обновляется до полной перезагрузки. Практически не мешает (хардлинки только по явному «Применить», Apply без плана вернёт ошибку), но в краевом случае (источник с пустым предпросмотром из-за коллизии) кнопка может остаться/пропасть не синхронно. Решение намечено в дизайне review-unified-source-block: обновлять панель hx-swap-oob из того же партиала.
|
||||
|
||||
Связано: openspec/specs/review, openspec/specs/web-ui, пакет httpapi.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Ретеншн и очистка БД
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
Терминальные задачи (done/cancelled/failed/reverted), их попытки recognition с сырыми ответами LLM и metadata_candidate копятся вечно — БД и список загрузок распухают и становятся нечитаемыми. Нужна авточистка старше N дней (настройка в [storage] или [worker]) и/или ручное удаление. Маленькая задача, но без неё интерфейс деградирует по мере эксплуатации.
|
||||
|
||||
Связано: specs/architecture.md → «Хранилище» (download/recognition/metadata_candidate/file_link), пакет store.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Гейт дозаписи хешей в dedup-ветке CreateDownloadIfNoActive (F1)
|
||||
|
||||
**Приоритет:** высокий · **Теги:** ingest, review-2026-07-08, invariant
|
||||
|
||||
Ревью Fable 2026-07-08 (приём). internal/store/download.go:271-290.
|
||||
|
||||
Проблема: dedup-ветка CreateDownloadIfNoActive безусловно дописывает ВСЕ хеши norm в найденную активную задачу (INSERT OR IGNORE) без пер-хеш гарда владения — в отличие от AddInfohashes (download.go:382-418), у которого гард есть. Единственная неохраняемая запись хешей — в авторитетном методе инварианта.
|
||||
|
||||
Сценарий: активная A владеет v1, активная B владеет v2 того же торрента (split-identity, см. F4) ИЛИ крафт-магнет (F5) → гибрид {v1,v2} дописывает v1 в B → две активные владеют v1. Инвариант «≤1 активная на infohash» нарушен. Спека ingest «Атомарность возврата в активное» это запрещает.
|
||||
|
||||
Фикс: применить пер-хеш гард как в AddInfohashes (исключить existing.ID, пропускать хеши чужой активной задачи). Tx уже открыта.
|
||||
|
||||
Вердикт: простой фикс (поведение уже обещано спекой).
|
||||
@@ -0,0 +1,13 @@
|
||||
# processCatched: promote-without-add если торрент уже в qBittorrent (F2)
|
||||
|
||||
**Приоритет:** средний · **Теги:** ingest, review-2026-07-08, lifecycle
|
||||
|
||||
Ревью Fable 2026-07-08 (приём). worker.go:361-391, sourceAddParts :407-432, qbt.go:243-246.
|
||||
|
||||
Сценарий: торрент уже в qBittorrent БЕЗ нашей категории/тега (юзер добавил вручную раньше → discover не усыновляет). Юзер грузит тот же .torrent в jellybit → catched → qbt.Add файлом; для file-add дубль → «Fails.» → Add ошибка → «will retry» каждый тик, вечно, до catch_timeout → failed/qbit_add, который reconcileRecovery НЕ воскрешает (worker.go:124-132). Торрент жив всё это время; юзер видит failed. Retry уже решает это alive-проверкой (worker.go:692-698 «повторный Add вреден»), а processCatched — нет, хотя live-снимок byHash того же тика доступен. Также лечит сценарий B (Add успех, PromoteCatched падает на транзиентной ошибке → снова Add дубля).
|
||||
|
||||
Замечание: поведение qBit на дубль file-add версионно-зависимо («Fails.» vs «Ok.») — проверить на целевой версии.
|
||||
|
||||
Фикс: перед Add проверить присутствие хешей в qBit; есть → promote без Add (зеркалит Retry).
|
||||
|
||||
Вердикт: change (малая спека-дельта download-tracking + код).
|
||||
@@ -0,0 +1,11 @@
|
||||
# Cancel во время add оставляет неуправляемый торрент в qBittorrent (F3/NIT-13)
|
||||
|
||||
**Приоритет:** средний · **Теги:** review-2026-07-08, lifecycle
|
||||
|
||||
Ревью Fable 2026-07-08 (оба ревьюера: F3 + NIT-13). worker.go:368-390, discover.go:43-50.
|
||||
|
||||
Сценарий: задача catched, worker вне w.mu выводит имя (LLM, секунды) + qbt.Add (успех). Параллельно user Cancel (catched→cancelled). PromoteCatched корректно пропускает (гард state='catched', спека соблюдена). НО торрент ДОБАВЛЕН в qBit под нашей категорией, будет качаться/сидировать вечно. Усыновить назад нельзя: adopt через ExistsByInfohash (любое состояние) → хеши cancelled-задачи существуют. Торрент ест диск без видимой задачи и владельца. Спека покрывает переход состояния, но не побочный эффект. Инвариант «источник неприкосновенен» — но этот торрент добавили МЫ после cancel-намерения.
|
||||
|
||||
Фикс-опции: (a) re-read state прямо перед Add (сужает окно); (b) при promote-skip из-за cancel — WARN «torrent left in qBittorrent»; (c) scoped delete/pause только что добавленного нами.
|
||||
|
||||
Вердикт: change (нужно решение по инварианту «источник неприкосновенен»).
|
||||
@@ -0,0 +1,11 @@
|
||||
# Идентичность инфохэшей: split v1/v2 одного торрента + крафт-магнет отравляет владение (F4, F5)
|
||||
|
||||
**Приоритет:** низкий · **Теги:** ingest, review-2026-07-08
|
||||
|
||||
Ревью Fable 2026-07-08 (приём). Две связанные находки о доверии к парам xt в magnet (предпосылки к F1).
|
||||
|
||||
F4 — split identity: v1-only magnet гибридного торрента T → задача A (catched). v2-only magnet того же T → дедуп не находит (строки хешей не связаны) → задача B. Обе активны (инвариант пер-хеш, не пер-торрент). A добавляется; qBit раскрывает infohash_v1+v2; captureInfohashes(A) пытается добавить v2 → ErrInfohashTaken (владеет B) → WARN КАЖДЫЙ тик. Add B — дубль → «Fails.» цикл → failed/qbit_add. Итог eventually-consistent, но: ложный failed, часы WARN, при худшем — обе reconcile против одного торрента → двойное распознавание/раскладка. captureInfohashes УЗНАЁТ факт (ErrInfohashTaken несёт владельца), но выбрасывает в WARN. Фикс-минимум: дедуп/дебаунс WARN; лучше — решение merge/supersede.
|
||||
|
||||
F5 — крафт-магнет: активная X владеет v1(X). Магнет с xt=btih:v1(X) + xt=btmh:v2(Y) где Y без активного владельца. Дедуп матчит X по v1; топ-ап пишет v2(Y) в X (гард отклоняет только хеши ЧУЖОЙ активной, у Y её нет). Теперь приём Y дедупит на X, Y никогда не качается до терминала X. CLAUDE.md трактует вывод LLM недоверенным, но ПАРУ полей magnet — доверяет. Транспорты semi-trusted (Telegram allowlist, LAN) → импакт низкий; но пересланное вредоносное сообщение трекер-бота — это ровно Telegram-поток. Фикс: топ-ап хешей на existing только когда qBit подтвердил пару (оставить топ-ап captureInfohashes, убрать из ingest attach).
|
||||
|
||||
Вердикт: change (решение по модели доверия/идентичности) либо задокументировать как ограничение. Связано с F1.
|
||||
@@ -0,0 +1,11 @@
|
||||
# .torrent поверх magnet при дедупе теряет байты — потерян upgrade-путь (F6)
|
||||
|
||||
**Приоритет:** средний · **Теги:** ingest, review-2026-07-08
|
||||
|
||||
Ревью Fable 2026-07-08 (приём). ingest.go:85-90, download.go:252-253, спека ingest «при дедупликации байты сохраняться SHALL NOT».
|
||||
|
||||
Сценарий: magnet с приватного трекера → catched, source_type=magnet. Юзер понимает, что magnet не докачает метаданные (нет DHT), грузит правильный .torrent. Ingest дедупит по infohash на magnet-задачу; по спеке блоб НЕ сохраняется, source_type остаётся magnet. Worker добавляет по magnet-URL → metaDL вечно → failed/magnet_timeout через 24ч. Юзер дал именно артефакт, который бы починил, — выброшен с «уже в работе». Retry снова по magnet. Рационал самой спеки (хранить байты, «иначе на закрытых трекерах не докачать») спорит с её же правилом дедупа здесь.
|
||||
|
||||
Фикс: при дедупе, где входящее — torrent-байты, а existing — catched с source_type=magnet: сохранить блоб и сменить source_type в той же tx.
|
||||
|
||||
Вердикт: change (противоречит текущему предложению спеки, нужна дельта).
|
||||
@@ -0,0 +1,17 @@
|
||||
# Приём/UI: мелкие фиксы границ и парсинга (F7, F8, F9, F10, N2)
|
||||
|
||||
**Приоритет:** низкий · **Теги:** ingest, review-2026-07-08
|
||||
|
||||
Ревью Fable 2026-07-08 (приём). Пачка независимых простых фиксов.
|
||||
|
||||
F7 — oversized .torrent через веб → 500 вместо 400. ingest.go:148-151 отдаёт plain fmt.Errorf «torrent too large», classifyErr (httpapi.go:753-770) → default → 500. Web MaxBytesReader пропускает MaxTorrentSize+1MiB. Фикс: sentinel-ошибка размера → 400. (Telegram ок — pre-check doc.FileSize.)
|
||||
|
||||
F8 — fast-path attach гонка с cancel. ingest.go:85-90,205-217: FindActiveByInfohash (без tx) вернул catched, параллельно cancel → attached() отдаёт Deduplicated=true (stale «уже в работе»), ничего не активно. Фикс: убрать ранний return, дедуп-решение только в CreateDownloadIfNoActive (уже re-check под BEGIN IMMEDIATE).
|
||||
|
||||
F9 — magnet parsing. magnet.go:46-51: HasPrefix «urn:btih:» регистрозависим → magnet:?xt=URN:BTIH:… отклоняется (RFC 2141: URN регистронезависим; scheme уже матчится EqualFold :91). tgbot/parse.go:11: magnet:\?[^\s]+ приклеивает хвостовую пунктуацию (точка/скобка) → xt последним → длина 41 → отказ. Фикс: case-insensitive префикс; trim хвостовых .,;:)]}>» в ParseMessage.
|
||||
|
||||
F10 — cap размера context из веб-формы. httpapi.go:405,412-414: multipart-бюджет на всё тело; без файла поле context ~9MiB → download.context в БД, рендер, LLM-промпты. REST 64KiB, Telegram лимит подписи — открыт только web. Фикс: cap Context в Ingest (одно место) ~16KiB с маркером.
|
||||
|
||||
N2 — shorten() режет по байтам не рунам (httpapi.go:713-718): кириллические source_ref-заголовки (частый случай) рвутся посреди руны → U+FFFD. Фикс: рунобезопасная обрезка.
|
||||
|
||||
Вердикт: все простые фиксы.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Нити приёма: NoName в контексте, устаревшие комментарии, лог без причины, bencode-аллокации (N1, N3, N4, N5)
|
||||
|
||||
**Приоритет:** низкий · **Теги:** ingest, review-2026-07-08
|
||||
|
||||
Ревью Fable 2026-07-08 (приём). Косметические нити.
|
||||
|
||||
N1 — torrent.go:114: Context() включает NoName-сентинел «-» как строку-название (name != "" проходит); ingest.parse фильтрует «-» только для source_ref (ingest.go:160-162). Одна грязная строка контекста для безымянных торрентов. Фикс: фильтровать «-» и в Context().
|
||||
|
||||
N3 — устаревшие комментарии. httpapi.go:574-577 и tgbot/bot.go:256-258 утверждают, что res.DownloadID может быть непуст при ошибке приёма («сбой после создания задачи, напр. qbit»). После fast-catch рефактора Ingest возвращает Result{} на КАЖДОМ пути ошибки (ingest.go:74-75,86,106-107) → корреляция всегда падает на request_id / без ключа. Фикс: поправить комментарии.
|
||||
|
||||
N4 — qbt.go:246 логирует «Fails.» со счётчиками, но qBittorrent не даёт причину; вместе с F2 оператор не отличит «дубль» от «битый файл». Идея: логировать хеши/первые байты для корреляции.
|
||||
|
||||
N5 — anacrolix bencode (v1.61.0, bencode/decode.go:17,250) аллоцирует до MaxStrLen (~128MiB) на объявленную строку до чтения — крафт-8MiB-торрент может форсить транзиентные ~128MiB аллокации при metainfo.Load. Ограничено и завершается ошибкой; на umbar приемлемо, но знать стоит. (files()-panic-guard torrent.go НЕ покрывает Load/UnmarshalInfo/HashBytes, но panic-путей там не найдено.)
|
||||
|
||||
Вердикт: простые фиксы/принять.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Жизненный цикл: мелкие находки (MINOR-8, MINOR-9, NIT-11, NIT-12)
|
||||
|
||||
**Приоритет:** низкий · **Теги:** review-2026-07-08, lifecycle
|
||||
|
||||
Ревью Fable 2026-07-08 (жизненный цикл). Мелкие находки.
|
||||
|
||||
MINOR-8 — recognition claim-token отсутствует. review.go:160-177: задача в recognizing, LLM в полёте (вне w.mu); user Cancel→Relink (recognizing→cancelled→recognizing revive). Старый вызов завершается, finishRecognition re-check d.State==recognizing — true, но это НОВЫЙ claim → устаревший результат коммитится (recognizing→review); свежий прогон отбрасывает себя. Импакт низкий (входы почти идентичны), но потерянный LLM-вызов + результат приписан не той попытке. Фикс: claim-token (updated_at на момент claim, или id строки recognition).
|
||||
|
||||
MINOR-9 — I/O под глобальным w.mu. worker.go:677-734 (Retry: torrentByInfohash + qbt.Add под mu), review.go: ensureSourcePresent (сеть) в Relink/Rerecognize/Refine/SetType, Apply (torrentByInfohash + layouter.Apply FS I/O), Undo (FS I/O) — держат w.mu целиком. Нарушает своё же правило «медленные вызовы вне блокировки» (processCatched/recognizeOne его соблюдают). Медленный/зависший qBit → каждый клик ревью = глобальный стопор поллинга и всех команд транспортов. Корректность ок (lock даёт race-free preflight), только liveness. Для one-user home-сервера — можно осознанно waive; худший — Retry с qbt.Add под mu при зависшем qBit. Фикс если делать: probe-вне-lock + re-validate-под-lock (как processCatched).
|
||||
|
||||
NIT-11 — byHash индексирует и усечённый 40-hex t.Hash v2-only торрентов (worker.go:444-452). Хранение усечённых хешей отклоняется (discover.go:98-111), но lookup-мапа всё ещё ключует t.Hash → v1-хеш одной задачи теоретически == усечённый-v2 t.Hash другого торрента → матч не тому. Требует 160-бит коллизию (космологические шансы). Фикс: исключить t.Hash из мапы, когда есть infohash_v1/v2 (зеркалить torrentHashes).
|
||||
|
||||
NIT-12 — Retry на failed/qbit_error (missingFiles) с живым errored-торрентом: alive=true → без re-Add → downloading → следующий тик classErrored → снова failed (+ дебаунс уведомления). Честно, но user-hostile: retry выглядит сломанным, реальное лекарство (recheck/fix в qBit) не подсказано. Фикс: отклонять retry (или форсить recheck) когда живой торрент classErrored. (Связано с задачей retry/stall семантики.)
|
||||
|
||||
Вердикт: MINOR-8/NIT-11/NIT-12 — простые фиксы; MINOR-9 — change или осознанный waive.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Retry/stall семантика: сброс базиса таймаута + простой от начала, а не от возраста торрента (MAJOR-1, MAJOR-2)
|
||||
|
||||
**Приоритет:** высокий · **Теги:** review-2026-07-08, lifecycle
|
||||
|
||||
Ревью Fable 2026-07-08 (жизненный цикл). Два связанных бага. worker.go:677-734 (Retry), :514-525 (checkTimeouts), :578-592 (torrentAge).
|
||||
|
||||
MAJOR-1: Retry с живым торрентом (alive=true) не переиздаёт Add, только ActivateIfNoOtherActive→downloading; базис age=now−added_on НЕ сбрасывается → следующий тик: stalledDL && age>StuckAfter → снова stuck (~5с). Спека state-reconciliation «Ручной повтор» требует: базис SHALL сбрасываться. Комментарий worker.go:690-691 верен лишь для re-Add ветки. Тест TestRetryReattaches не гоняет следующий тик.
|
||||
|
||||
MAJOR-2: stuck_after меряет ВОЗРАСТ торрента (от added_on), а не длительность простоя. Торрент, качавшийся 5ч, при мгновенном stalledDL на 1 тик → stuck с сообщением «stalled for 5h» (ложь) + EventFailed. Проход через stalledDL между пирами — норма → флап stuck↔downloading + до-часовые ложные уведомления. Спека сама противоречива («stalledDL дольше stuck_after» vs «возраст от added_on»).
|
||||
|
||||
Фикс: колонка retried_at и/или stalled_since (или qBit last_activity); базис = max(added_on, retried_at); простой мерить от stalled_since. Схема + миграция + сверка спеки. Покрывает также NIT-10 (фолбек added_on→created_at) и NIT-12 (retry на qbit_error мгновенно откатывается).
|
||||
|
||||
Вердикт: полноценный change (схема + спека). Бьёт по повседневным сценариям — retry выглядит сломанным, длинные загрузки спонтанно флапают в stuck.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Восстановление zombie downloading при пропаже источника из qBittorrent (MAJOR-3)
|
||||
|
||||
**Приоритет:** высокий · **Теги:** review-2026-07-08, lifecycle
|
||||
|
||||
Ревью Fable 2026-07-08 (жизненный цикл). worker.go:472-477. Подтверждено чтением кода.
|
||||
|
||||
Сценарий: торрент удалён из qBittorrent (юзером/другим клиентом), пока задача в downloading. Poll: torrentFor промах → Warn «active download not found in qbittorrent» → continue. Каждый тик, вечно. reconcileDesync покрывает только done/target_missing/orphaned; reconcileRecovery — failed/stuck; дебаунса для этого случая НЕТ, состояние не меняется, уведомления нет, checkTimeouts требует торрент. Задача — вечный зомби, активна в UI без телеметрии; выход только Cancel/Defer. Тот же зомби при провале отката Retry (worker.go:716-729). Спека намеренно исключает активные из матрицы source×target, но «источник исчез в downloading» не владеет НИКТО — дыра спеки (сравн.: та же пропажа в completed/recognizing деградирует штатно).
|
||||
|
||||
Фикс: расширить дебаунс пропажи источника (SourceMissCount) на downloading → после порога downloading→deleted (или failed с distinct error_code для re-Add) + уведомление. Нужно ребро графа.
|
||||
|
||||
Вердикт: полноценный change. Классический «застрявшее состояние, которое никто не двигает».
|
||||
@@ -0,0 +1,13 @@
|
||||
# Sweep застрявших linking при рестарте + фикс persist-failure (MAJOR-4)
|
||||
|
||||
**Приоритет:** средний · **Теги:** review-2026-07-08, lifecycle
|
||||
|
||||
Ревью Fable 2026-07-08 (жизненный цикл). review.go:284-287.
|
||||
|
||||
(A) Без краха: linkPlan создаёт хардлинки на FS, затем CreateFileLinks падает (транзиентная ошибка SQLite) → return без перехода → задача в linking, хардлинки на диске без file_link-строк (Undo нечего откатывать, targetPresent=false).
|
||||
|
||||
(B) Краш процесса между transition(StateLinking) (review.go:251) и финальным переходом → на рестарте linking не листит НИКТО (processCatched=catched, Poll=downloading, recognizePending=completed/recognizing, desync=done/tm/orphaned, recovery=failed/stuck). Задача сидит в linking вечно; выход только ручной Cancel/Defer (недискаверабельно). recognizing получил restart-healing (recognizePending), linking — нет — нарушен инвариант «у каждого нетерминального состояния есть владелец».
|
||||
|
||||
Фикс: на тике/старте sweep linking-задач (любая под w.mu — по построению устаревшая) → linking→review (ребро есть) с error_msg «прерванная раскладка, повтори»; при persist-failure переходить в review/failed, а не bare-return.
|
||||
|
||||
Вердикт: простой фикс (+ 1 спека-сценарий).
|
||||
@@ -0,0 +1,11 @@
|
||||
# Readiness-preflight: запрет авто-раскладки недокачанных файлов (MAJOR-5)
|
||||
|
||||
**Приоритет:** высокий · **Теги:** review-2026-07-08, lifecycle, data-integrity
|
||||
|
||||
Ревью Fable 2026-07-08 (жизненный цикл). САМАЯ ОПАСНАЯ — целостность библиотеки. internal/worker/review.go (ensureSourcePresent проверяет присутствие источника, НЕ classify(t.State)==classReady).
|
||||
|
||||
Сценарий: задача на 40% в downloading → user «Позже» (deferred, легально: граф допускает *→deferred) → «Распознать заново» (Rerecognize) → ensureSourcePresent проходит (торрент есть) → recognizing → LLM видит имена файлов (qBit отдаёт до завершения) → при уверенном матче res.Decision.Auto && !forceReview (Rerecognize НЕ ставит force_review, только Relink ставит) → авто linking → хардлинки на НЕДОКАЧАННЫЕ файлы → done → Jellyfin сканирует половину. Даже ручной review→Apply не имеет preflight завершённости. Обходит состояние completed, чей смысл (download-tracking «Готовность только когда файлы на месте») — финальность.
|
||||
|
||||
Фикс: ensureSourceReady требует classify(t.State)==classReady для Rerecognize/Refine/SetType/Apply; иначе ErrConflict «торрент ещё качается».
|
||||
|
||||
Вердикт: полноценный change (новое требование preflight на readiness).
|
||||
@@ -0,0 +1,11 @@
|
||||
# Defer из catched → лимбо → необратимый deleted (MAJOR-6)
|
||||
|
||||
**Приоритет:** средний · **Теги:** review-2026-07-08, lifecycle
|
||||
|
||||
Ревью Fable 2026-07-08 (жизненный цикл). review.go:465-479, worker.go:332-340, reconcile.go:251-265.
|
||||
|
||||
Сценарий: задача в catched (ещё не добавлена в qBit) → user Defer → deferred. processCatched больше её не видит (листит только catched) → торрент никогда не добавится. Из deferred: Apply→«нет плана», Rerecognize/Refine→ensureSourcePresent нет торрента→reconcileToReality(sourcePresent=false, targetPresent=false)→deriveState=deleted, а у deleted НОЛЬ исходящих рёбер (download.go:102) → задача необратима, хотя байты .torrent лежат в download_torrent. Также deleted семантически неверен (ничего не качалось/раскладывалось).
|
||||
|
||||
Фикс: исключить catched из Defer (пре-источниковое состояние, «позже» бессмысленно) ИЛИ processCatched резюмит deferred-без-recognition ИЛИ preflight «источника не было никогда» (source_added_at IS NULL и нет links) → failed/qbit_add (retriable), не deleted.
|
||||
|
||||
Вердикт: простой фикс (исключить catched из Defer).
|
||||
@@ -0,0 +1,11 @@
|
||||
# transition() глотает ошибки перед созданием хардлинков (MINOR-7)
|
||||
|
||||
**Приоритет:** средний · **Теги:** review-2026-07-08, lifecycle
|
||||
|
||||
Ревью Fable 2026-07-08 (жизненный цикл). worker.go:595-601 (ошибка логируется, не возвращается), review.go:251-252, :196-198.
|
||||
|
||||
Сценарий: в Apply w.transition(StateLinking) на транзиентной ошибке БД → залогировано, выполнение продолжается → linkPlan создаёт хардлинки, пока задача ещё в review. Финальная запись linking→done оценивается как review→done — НЕ в графе → отклонена → файлы на диске, задача застряла в review со stale-планом; file_link-строки есть (re-Apply увидит StatusExists, частично самолечится), но done не достигнут, скан/уведомление не сработали. Паттерн claim-then-side-effect корректен только если claim проверяется (везде ещё — PromoteCatched, finishRecognition — гейтят; тут нет).
|
||||
|
||||
Фикс: transition возвращает ошибку (или mustTransition); Apply/finishRecognition прерываются до linkPlan при провале claim.
|
||||
|
||||
Вердикт: простой фикс.
|
||||
@@ -0,0 +1,7 @@
|
||||
# [идея] Сила совпадения кандидата и пересмотр распознавания/матчинга
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
ИДЕЯ (сперва проработать). У кандидата метабазы нет метрики силы совпадения (metadata_candidate хранит provider/id/title/year/url), решение «авто vs review» — по правилу «единственный сильный матч + валидация», не по числу. Для ревью: список кандидатов нечем отсортировать/подсветить по уверенности. Идея — ввести на этапе матча силу совпадения кандидата (точное совпадение названия+года vs частичное) для сортировки и подсказки в UI. Шире — продумать сам процесс распознавания и матчинга: границы «разбор LLM / поиск в базе / сверка», что храним у кандидата, как считаем и показываем уверенность.
|
||||
|
||||
Связано: specs/recognition.md, ADR-2026-06-13-auto-link-requires-db-match, specs/review-ux.md.
|
||||
@@ -0,0 +1,7 @@
|
||||
# [идея] Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
ИДЕЯ (проработать крайние случаи). Обычный случай — один сезон (его номер видно глазами и сверяем на ревью — под это сделана сводка сезонов). Но в редких заказах раздача сложнее: все сезоны сериала разом, пак нескольких сезонов, смешанная нумерация, вложенные папки сезонов, разнобойные имена файлов. Сейчас PlanFile.Season задаётся на каждом файле (мультисезон в принципе выразим), но целостно эти сценарии не проработаны: как надёжно распознать, как показать на ревью, как разложить и как стыкуется со сходимостью папки и merge-докачиванием. Решить, что поддерживаем явно, а что уводим в ревью как «сложную раскладку».
|
||||
|
||||
Связано: specs/recognition.md, specs/jellyfin-layout.md, specs/review-ux.md, «Проблема второго сезона», «Раздачи с докачиванием».
|
||||
@@ -0,0 +1,7 @@
|
||||
# Мгновенные обновления через SSE
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
Живые обновления прогресса сейчас на htmx-поллинге (фаза 2 веб-UI) — просто и работает, но с задержкой в интервал опроса и холостыми запросами. Перевести динамический контент (прогресс загрузки, смена статуса, раздача) на Server-Sent Events, чтобы обновления приходили почти мгновенно и без лишнего поллинга. Поллинг работает, поэтому это улучшение, а не блокер; SSE — один долгоживущий ответ на соединение, ложится на server-rendered UI без тяжёлого фронтенда.
|
||||
|
||||
Связано: specs/architecture.md → «Транспорты», specs/review-ux.md, пакет httpapi.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Проверка свободного места перед copy-fallback
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
Когда хардлинк невозможен (EXDEV/ENOTSUP/…), layout копирует файл, дублируя место на диске. На забитом диске это упрётся в полку посреди раскладки. Перед копированием проверять доступное место и при нехватке внятно уходить в failed с понятной причиной, а не падать на полпути.
|
||||
|
||||
Связано: specs/architecture.md → «Раскладка файлов» (фолбэк-копирование), пакет layout.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Улучшения UI: показывать матч с записью метабазы в Telegram
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Web-сторона реализована: страница загрузки /download/{id} и экран ревью показывают, с какой именно записью метабазы (TMDB/TVDB/IMDb) сматчилась загрузка — провайдер, id и ссылку. Осталось довести то же в Telegram: в уведомлениях/подтверждениях показывать запись матча (название, год, провайдер-id, ссылку), чтобы ошибочную привязку было видно и из бота. Полный выбор источника в вебе уже реализован.
|
||||
|
||||
Связано: specs/review-ux.md, specs/recognition.md (матч в базе), specs/architecture.md → «Транспорты».
|
||||
@@ -0,0 +1,7 @@
|
||||
# Выбор из нескольких находок метабазы в Telegram
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
Когда распознавание даёт несколько подходящих кандидатов в метабазе, предлагать их в Telegram списком (кнопки) для ручного выбора, а не молча брать первый/лучший. Веб остаётся точкой точных правок (полный выбор источника уже реализован), бот — быстрый выбор из готового короткого списка.
|
||||
|
||||
Связано: specs/review-ux.md (боты — быстрые действия, веб — точные правки), specs/recognition.md (кандидаты матча).
|
||||
@@ -0,0 +1,7 @@
|
||||
# Словарь единого языка (ubiquitous language)
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Свести термины домена в один глоссарий, чтобы пользователь, документация, код и агент говорили на одном языке: загрузка, раздача, распознавание, матч, кандидат, раскладка, источник/цель, хардлинк, ревью, переход состояния и т.д. — русский термин, английский идентификатор в коде, краткое определение. Сейчас наименования расходятся между спеками, UI и кодом. Глоссарий — источник истины по именам; на нём же строится агент-ревьювер наименований.
|
||||
|
||||
Связано: docs/conventions, specs/architecture.md, новый файл-глоссарий.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Удаление средствами jellybit («единое окно», path 2)
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
Распознавание ручного удаления и пометка рассинхрона уже сделаны (state-reconciliation: target_missing/orphaned/deleted, безопасный undo с nlink-гардом, preflight). Осталось (path 2) — удалять просмотренное из самого jellybit, не идя руками в qBittorrent/Jellyfin. «Тайтл» — вычисляемая группа загрузок по (provider, provider_id)/общей папке, без новой сущности; удаление целиком — обход загрузок группы штатным undo; удаление раздачи из qBittorrent — осознанный выход за инвариант «источник неприкосновенен», только по явному подтверждению.
|
||||
|
||||
Шаги:
|
||||
- удаление одной загрузки: снять живые хардлинки (штатный undo, superseded пропускаем, nlink-гард) + опц. удалить раздачу из qBittorrent с файлами — с подтверждением
|
||||
- вычисляемая группа «тайтл» в UI: состав сериала/фильма одним экраном
|
||||
- удаление тайтла целиком: обход загрузок группы + опц. снос опустевшей папки
|
||||
- после полного удаления память о тайтле не остаётся
|
||||
|
||||
Связано: drafts/logical-title-model.md §5.3/§6.4, ADR-2026-06-13-hardlinks, specs/architecture.md, specs/workflow.md
|
||||
@@ -0,0 +1,7 @@
|
||||
# Привязка уведомлений к источнику в ботах (мульти-бот)
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Уведомления и запросы подтверждения должен получать тот, кто прислал загрузку: автор сообщения о новой раздаче — адресат пингов и ревью по ней. Транспортов-ботов может быть несколько (Telegram, в перспективе Matrix и др.); каждый адресует «своему» отправителю. Веб-интерфейс остаётся единым для всех и точкой правды по функциональности (боты — тонкие адаптеры над тем же ядром). Нужно: хранить у загрузки источник/транспорт и идентификатор отправителя, маршрутизировать пинги по нему.
|
||||
|
||||
Связано: specs/review-ux.md, specs/architecture.md → «Транспорты».
|
||||
@@ -0,0 +1,7 @@
|
||||
# Версии/качество одного тайтла (репаки, апгрейд 1080p → 2160p)
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
По калибровке болей (2026-07-02) — не боль, из приоритета выпало. Сосуществование версий доступно уже сейчас (Jellyfin multi-version, другой целевой путь), коллизия на тот же путь штатно уходит в review. Явный replace (undo старого хардлинка → lay нового → супересид владения путём) — отдельный change, если/когда станет болью.
|
||||
|
||||
Связано: «Раздачи с докачиванием», specs/jellyfin-layout.md (never-overwrite, коллизия).
|
||||
@@ -0,0 +1,7 @@
|
||||
# Привязка внешних субтитров к серии (сериалы)
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Аудит спек↔код (2026-07-03): спека recognition требует «внешние субтитры SHALL привязываться к соответствующему видео». Для фильма работает — раскладка именует субтитр по базе видеофайла. Для сериала связь субтитр→конкретная серия не выражена: в PlanFile нет поля привязки, и нет логики спаривания VobSub .idx+.sub. Смоделировать привязку субтитра к эпизоду (поле на PlanFile или роль с season/episode) и спаривание .idx+.sub, либо — если откладываем — сузить формулировку спеки до реального поведения.
|
||||
|
||||
Связано: openspec/specs/recognition, specs/jellyfin-layout.md, пакеты recognize, layout.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Проблема второго сезона (сходимость папки сериала)
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
Второй/третий сезон должен ложиться в ТУ ЖЕ папку сериала, а не заводить рядом почти одинаковую. Проблема не в группировке, а в сходимости папки: имя печатается заново из выхода LLM, совпадение provider_id не гарантирует совпадение строки («Fargo» vs «Фарго», год сезона vs год сериала). Отдельная сущность «тайтл» НЕ вводится. Решение — правило сходимости при построении плана: при подтверждённом матче наследовать базу папки (имя+год) от живых file_link загрузок с тем же (provider, provider_id), игнорируя LLM-выход; якоря нет → папка из распознавания, как сейчас.
|
||||
|
||||
Шаги:
|
||||
- lookup живых ссылок по (provider, provider_id) через current recognition
|
||||
- наследование базы папки (имя+год) при построении плана раскладки
|
||||
- рассинхрон (несколько живых папок с одним матчем) → review, не молча
|
||||
- тесты: сходимость, отсутствие якоря (свежая папка), смена провайдера
|
||||
|
||||
Связано: drafts/logical-title-model.md §5.2, specs/recognition.md, specs/jellyfin-layout.md
|
||||
@@ -0,0 +1,7 @@
|
||||
# Современный Web-UI как PWA
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое, отзывчивое, удобное с телефона). Текущий server-rendered UI функционален, поэтому это улучшение, а не блокер; большой объём работы.
|
||||
|
||||
Связано: specs/review-ux.md (веб = точные правки), пакет httpapi.
|
||||
@@ -0,0 +1,7 @@
|
||||
# [идея] Завершение загрузки через webhook
|
||||
|
||||
**Приоритет:** низкий
|
||||
|
||||
ИДЕЯ (решим по опыту эксплуатации). Сейчас завершение ловим поллингом qBittorrent раз в несколько секунд. Альтернатива: «Run external program on torrent completion» в qBittorrent дёргает эндпоинт jellybit. Реагирует быстрее, но связывает нас с конфигом qBittorrent.
|
||||
|
||||
Связано: specs/architecture.md → «Отслеживание загрузки», пакет worker.
|
||||
@@ -61,8 +61,8 @@ reject / defer / undo) — команды к `worker`:
|
||||
(server-rendered). В v1 **без авторизации** (доверенная LAN). Поле
|
||||
`http.trusted_subnets` зарезервировано, но **пока не применяется**:
|
||||
деплой только в локальную сеть без доступа из интернета, поэтому
|
||||
allowlist-middleware и авторизацию отложили — задача «Авторизация
|
||||
веб-UI» в беклоге (Tududi, проект «jellybit»).
|
||||
allowlist-middleware и авторизацию отложили — задача
|
||||
[«Авторизация веб-UI»](../backlog/avtorizaciya-web-ui.md) в беклоге.
|
||||
- **Telegram-бот** — переслать magnet/сообщение бота; текст становится
|
||||
контекстом. Доступ — по `telegram.allowed_user_ids` (пусто = запрет
|
||||
всем, fail-closed). Бот же шлёт **пинги** о входе в review/готовности.
|
||||
|
||||
@@ -99,4 +99,4 @@ inode общий — диск не дублируется.
|
||||
сезонам.
|
||||
- **Несколько аудиодорожек** — обычно внутри mkv, не наша забота.
|
||||
- **Аниме с абсолютной нумерацией** — пересчёт в S·E, отдельная проработка
|
||||
(задача в беклоге — Tududi, проект «jellybit»).
|
||||
([задача в беклоге](../backlog/anime-absolyutnaya-numeraciya.md)).
|
||||
|
||||
@@ -133,12 +133,12 @@ notes пояснения, неоднозначности
|
||||
- Сезон-паки разбираем по сериям; смешанные паки, спецвыпуски (`Season
|
||||
00`), двойные серии (`SxxEyy-Eyy`) — через per-file season/episode;
|
||||
любая неоднозначность → review.
|
||||
- Аниме с абсолютной нумерацией — отдельный крайний случай, задача
|
||||
в беклоге (Tududi, проект «jellybit»).
|
||||
- Аниме с абсолютной нумерацией — отдельный крайний случай,
|
||||
[задача в беклоге](../backlog/anime-absolyutnaya-numeraciya.md).
|
||||
|
||||
## На будущее
|
||||
|
||||
`go-ptn` слабее питоновского `guessit`. Если точности пред-парса не
|
||||
хватит — завернуть `guessit` лёгким сервисом-спутником (один файл рядом с
|
||||
бинарём). Задача «guessit как сервис-спутник» в беклоге (Tududi, проект
|
||||
«jellybit»).
|
||||
бинарём). Задача [«guessit как сервис-спутник»](../backlog/guessit-sputnik.md)
|
||||
в беклоге.
|
||||
|
||||
@@ -119,8 +119,8 @@ Telegram = одобрить / подсказать / выбрать кандид
|
||||
- **База неоднозначна** → выбор кандидата (часто чинит всё разом: пиннит
|
||||
provider-id и каноническое имя).
|
||||
- **База пустая (рус/аниме)** → «без базы» или ручной id/url. Аниме с
|
||||
абсолютной нумерацией → веб-хелпер «absolute → S·E» (задача
|
||||
«Аниме с абсолютной нумерацией» в беклоге — Tududi, проект «jellybit»).
|
||||
абсолютной нумерацией → веб-хелпер «absolute → S·E»
|
||||
([задача «Аниме с абсолютной нумерацией»](../backlog/anime-absolyutnaya-numeraciya.md)).
|
||||
- **Не тот тип (movie↔series)** → переключатель пересобирает форму плана.
|
||||
- **Мусор (sample/extra/дубли дорожек)** → роль «игнор».
|
||||
- **Полный провал** (LLM ничего не вытащил) → веб-«ручной режим»: выбрать
|
||||
|
||||
Reference in New Issue
Block a user