docs: канон поднят с версии 7 до 12
- каталог задач переехал в tasks/ в корне, спринт упразднён — приоритет теперь порядок строк в BACKLOG.md, четыре задачи набора вернулись в беклог - гейт: путь docs.py переведён на av-dev-docs вместо снесённого av-dev-pm, добавлены шаги tasks.py check и openspec.py check - относительные ссылки внутри задач и ссылки из docs/ на задачи починены
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"canon": 7,
|
||||
"canon": 12,
|
||||
"migrations": "internal/store/migrations"
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
- Критерий приёмки задачи «запрос поиска содержит параметр языка» выполнен быть
|
||||
не может и отменён этим решением. Расхождение вынесено вопросом человеку —
|
||||
разведка [tvdb-search-response-live-check](../tasks/items/tvdb-search-response-live-check.md).
|
||||
разведка [tvdb-search-response-live-check](../../tasks/items/tvdb-search-response-live-check.md).
|
||||
- **Решение опирается на документацию, а не на замер.** Семантика параметра и
|
||||
форма блока переводов живым API не подтверждены —
|
||||
[research/tvdb-search-translations.md](../research/tvdb-search-translations.md).
|
||||
|
||||
@@ -172,7 +172,7 @@ Jellyfin указывают на `movies`/`series`, а не на корень
|
||||
|
||||
Места, где устройство знаемо тонкое: не дефекты, а принятые пока пробелы. Здесь
|
||||
только адрес и одна фраза — что именно не сделано; работа под каждым живёт
|
||||
задачей в [tasks/BACKLOG.md](tasks/BACKLOG.md). Список нужен ревью: правка,
|
||||
задачей в [tasks/BACKLOG.md](../tasks/BACKLOG.md). Список нужен ревью: правка,
|
||||
попавшая в такую область, стоит дороже, чем выглядит.
|
||||
|
||||
| Область | Чего нет сегодня | Задача |
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# Паспорт проекта
|
||||
|
||||
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
|
||||
устроено», [tasks/ROADMAP.md](tasks/ROADMAP.md) — «что уже умеет», паспорт —
|
||||
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «что уже умеет», паспорт —
|
||||
«зачем и для кого».
|
||||
|
||||
## Цель
|
||||
|
||||
+2
-2
@@ -252,8 +252,8 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
**Ориентир частоты.** `medium` закрывает большинство задач, `large` рассчитана
|
||||
на 5–10% и приходится на крупную функциональность, а не на уборку: задача типа
|
||||
`chore` или `fix`, собранная из нитей прошлого ревью, идёт в `small` или
|
||||
`medium`, даже когда трогает файл из перечней выше. `large` чаще одной задачи на
|
||||
спринт означает ошибку в критерии, а не спринт из сложных задач.
|
||||
`medium`, даже когда трогает файл из перечней выше. `large` чаще одной задачи из
|
||||
десяти означает ошибку в критерии, а не полосу сложных задач подряд.
|
||||
|
||||
### Недоступно проверке
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Беклог
|
||||
|
||||
Что **можно взять**. Одна задача = один файл `items/<slug>.md`
|
||||
+ строка здесь. Целей тут нет — они в [ROADMAP.md](ROADMAP.md): беклог — то, что
|
||||
берут, роадмап — то, подо что берут. Порядка внутри секции нет: «что делать
|
||||
дальше» отвечает набор спринта. Ведётся скиллом `tasks`.
|
||||
|
||||
Секции «блокеры» здесь нет и не заводится: блокер — это состояние
|
||||
(спринт не может продолжаться ни одной задачей), оно живёт до ответа
|
||||
человека, а его следы — вопросами в файлах задач.
|
||||
|
||||
## Ядро продукта
|
||||
|
||||
- [✨ Пересчитывать абсолютную нумерацию аниме в SxxEyy](items/anime-absolute-numbering.md) — аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации
|
||||
- [✨ Раскладывать раздачу-копию диска (VIDEO_TS/BDMV) каталогом целиком](items/disc-image-releases.md) — раздача-образ диска (VIDEO_TS/BDMV) сейчас разбирается пофайлово вместо раскладки каталога целиком — редкий, но реальный случай
|
||||
- [✨ Принимать ссылку на .torrent и скачивать файл самим (нужен SSRF-гард)](items/torrent-url-fetch.md) — magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард)
|
||||
- [✨ Докладывать недостающие эпизоды merge-раскладкой при повторной заливке](items/merge-incremental-redownload.md) — повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов
|
||||
- [🐞 Связать v1/v2-хеши одного торрента и не доверять паре xt из магнета (F4, F5)](items/infohash-identity-integrity.md) — split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_
|
||||
- [✨ Слать живые обновления через SSE вместо htmx-поллинга](items/sse-live-updates.md) — живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер)
|
||||
- [✨ Проверять свободное место перед copy-fallback](items/free-space-check-copy-fallback.md) — copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования
|
||||
- [✨ Слать уведомления автору загрузки в его транспорт (мульти-бот)](items/notification-source-binding.md) — пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота)
|
||||
- [✨ Заменять разложенную версию тайтла новой (репаки, апгрейд 1080p → 2160p)](items/title-versions-repacks.md) — По калибровке болей (2026-07-02) — не боль, из приоритета выпало
|
||||
- [✨ Спаривать VobSub .idx+.sub и проставлять языковой суффикс субтитра](items/external-subtitles.md) — Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags
|
||||
- [✨ Переделать веб-UI в устанавливаемое PWA](items/web-ui-pwa.md) — текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
|
||||
- [✨ Править на ревью маппинг «файл → серия» и раскладывать вручную при провале LLM](items/review-mapping-editor.md) — правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
|
||||
- [✨ Заказать спекой крайние случаи именования: многофайловый фильм, редакции, двойная серия](items/naming-edge-cases.md) — стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
|
||||
- [🔬 Форма ответа поиска TheTVDB и семантика параметра language](items/tvdb-search-response-live-check.md) — форма ответа поиска TheTVDB принята по swagger 4.7.10 и живым прогоном не подтверждена — при иной форме разбор молча уходит в фолбэк, гейт зелёный, локализованное название не работает
|
||||
- [🔬 Канон нумерации серий и порядок у провайдера тега](items/episode-numbering-canon.md) — Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
|
||||
- [🔬 Тексты и формат уведомлений в Telegram](items/telegram-messages-audit.md) — зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
|
||||
- [🔬 guessit как сервис-спутник](items/guessit-sidecar.md) — go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
|
||||
- [🔬 Многоступенчатая верификация привязки](items/multi-pass-verification.md) — несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения
|
||||
- [🔬 Сила совпадения кандидата и пересмотр распознавания/матчинга](items/candidate-match-strength.md) — у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга)
|
||||
- [🔬 Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки](items/complex-series-releases.md) — сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон
|
||||
|
||||
## Инфраструктура
|
||||
|
||||
- [🧹 Откалибровать проходы ревью и завести ревьювер наименований](items/quality-review-agents.md) — конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
|
||||
- [✨ Закрыть веб-UI авторизацией, когда доверенной LAN станет мало](items/web-ui-auth.md) — для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита
|
||||
- [🧹 Бэкапить SQLite по расписанию с ротацией](items/sqlite-backup.md) — architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая)
|
||||
- [✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI](items/deep-healthcheck-dependencies.md) — /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
|
||||
- [✨ Хранить историю переходов загрузки отдельной таблицей](items/download-transition-history.md) — хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов
|
||||
- [🧹 Кэшировать ответы метабаз с TTL (и опционально LLM)](items/metadata-cache.md) — повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново»
|
||||
- [✨ Чистить БД от терминальных задач и сырых ответов LLM старше срока хранения](items/db-retention-cleanup.md) — терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации
|
||||
- [🧹 Свести термины домена в словарь единого языка](items/ubiquitous-language-glossary.md) — наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований)
|
||||
- [🧹 Разобрать кандидатов по тестам и записать конвенцию](items/tests-convention.md) — как пишем тесты, не записано нигде: пункт «Тесты» в convention-candidates не пересматривали, он обещает фикстуры в testdata/, которых в проекте нет — а трение накопилось (четыре внешних клиента, fakeStore с инъекцией ошибок, env-гейты, флаки-прогон, diff-coverage)
|
||||
- [🔬 Потолок нагрузки: 100 одновременных загрузок, план-максимум 1000](items/scale-100-downloads.md) — Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
|
||||
- [🔬 Завершение загрузки через webhook](items/completion-webhook.md) — завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации)
|
||||
- [🔬 Кандидаты в конвенции кода](items/convention-candidates.md) — накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд
|
||||
@@ -1,9 +0,0 @@
|
||||
# Ушедшее без реализации
|
||||
|
||||
Задачи, покинувшие беклог **без реализации**, с причиной и датой.
|
||||
Пишется `tasks.py close --reason`. Реализованные сюда не идут — у них
|
||||
есть коммит. Это первое место, куда смотрит дедупликация при заведении.
|
||||
|
||||
<!-- - ГГГГ-ММ-ДД `slug` — Заголовок. Причина: … Была секция: … -->
|
||||
- 2026-08-06 `learn-from-user-corrections` — ✨ Подмешивать прошлые правки человека в промпт распознавания (few-shot). Причина: решено не собирать базу правок человека: направление — тюнинг автоматического распознавания без участия человека. Была секция: ядро продукта.
|
||||
- 2026-08-06 `recognition-eval-harness` — 🧹 Завести eval-харнес распознавания: корпус кейсов и метрику точности. Причина: решено не собирать размеченный корпус руками: направление — тюнинг автоматического распознавания без участия человека. Была секция: инфраструктура.
|
||||
@@ -1,34 +0,0 @@
|
||||
# Роадмап
|
||||
|
||||
Что приложение уже умеет и чего ещё не умеет. Цель — файл типа `goal` в
|
||||
`items/`; её задачи здесь **не перечисляются** — перечень даёт
|
||||
`tasks.py list --goal <слаг>`. В `Запланировано` очередь значима и
|
||||
обосновывается прозой; в `Направлениях` порядка нет; `Сопровождение` — то, чем
|
||||
держат проект, а не возможности приложения; в `Готово` строку с датой пишет
|
||||
`close --implemented`.
|
||||
|
||||
## Запланировано
|
||||
|
||||
Пусто. Фазы Ф0–Ф6 прежней дорожной карты (каркас, приём и трекинг,
|
||||
распознавание, раскладка и ревью, метаданные, Telegram и UX, деплой) закрыты —
|
||||
сквозной путь работает и развёрнут; закрытый шаг планом больше не является.
|
||||
Следующая упорядоченная очередь появится, когда она понадобится.
|
||||
|
||||
## Направления
|
||||
|
||||
- [🎯 Раздача узнаётся верно без подсказок человека](items/recognition-accuracy.md) — распознавание ошибается молча и правдоподобно, а смена модели или правка промпта идёт вслепую — сдвига точности не видно ни до, ни после
|
||||
- [🎯 Раскладывается не только типовая раздача](items/complex-releases.md) — типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
|
||||
- [🎯 Раздача приносится и подтверждается из любого транспорта](items/ingest-and-review-interfaces.md) — путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
|
||||
- [🎯 По записи загрузки видно, как она сюда попала](items/state-integrity.md) — известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
|
||||
|
||||
## Сопровождение
|
||||
|
||||
- [🎯 Сервис переживает рост и потерю тома](items/operational-resilience.md) — сервис работает, но не переживает роста: база копится вечно, бэкапа нет, отказ зависимости виден только по застрявшим задачам
|
||||
- [🎯 Домен называется одинаково везде, ревью откалибровано](items/dev-process-quality.md) — наименования домена расходятся между спеками, UI и кодом, а конвейер ревью не откалиброван — растёт цена каждой следующей задачи
|
||||
|
||||
## Готово
|
||||
|
||||
Пусто. Фазы Ф0–Ф6 велись прозой и целями в роадмапе не числились, поэтому
|
||||
строк с датами за ними нет; что было сделано и когда — по архиву
|
||||
`openspec/changes/archive/`. Первую строку сюда впишет `close --implemented`
|
||||
на первой достигнутой цели.
|
||||
@@ -1,14 +0,0 @@
|
||||
# Спринт
|
||||
|
||||
- **Цель:** [🎯 Раздача узнаётся верно без подсказок человека](items/recognition-accuracy.md)
|
||||
- **Начат:** 2026-08-06
|
||||
- **Спринт:** `2026-08-06`
|
||||
|
||||
Урожай спринта поднимается `tasks.py list --tag sprint:2026-08-06` — это первая порция переоценки на сессии.
|
||||
|
||||
## Набор
|
||||
|
||||
- [✨ Узаконить confidence-гейт авто-раскладки в спеке и сделать его выключаемым (дефолт 0.7)](items/auto-link-confidence-gate.md) — Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
|
||||
- [🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой](items/background-error-noise.md) — недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации
|
||||
- [🐞 Санитизировать название из метабазы перед подстановкой в план](items/metadata-title-sanitize.md) — plan.Title = match.Title подставляется ПОСЛЕ sanitizePlan — название из TMDB/TVDB уезжает в имя каталога Jellyfin дословно, с невидимыми символами и гомоглифами, и авто-раскладка это пропускает
|
||||
- [🐞 Отправлять на ревью раздачу с непомещающимся именем вместо отказа](items/long-title-to-review.md) — название длиннее ~237 байт роняет раскладку в failed с текстом системной ошибки: пользователь видит «file name too long» вместо карточки ревью, где это чинится подсказкой
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Пересчитывать абсолютную нумерацию аниме в SxxEyy
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Релизы аниме часто нумеруют серии сквозным числом (#137) без сезонов, а Jellyfin ждёт SxxEyy. Нужен пересчёт абсолютной нумерации в сезон/серию — надёжнее всего через TVDB (там есть absolute order). Отдельный крайний случай распознавания; на стороне ревью — веб-хелпер «absolute → S·E».
|
||||
|
||||
Связано: specs/recognition.md (конвейер, сезон-паки), specs/jellyfin-layout.md (нумерация серий), specs/review-ux.md.
|
||||
@@ -1,81 +0,0 @@
|
||||
# ✨ Узаконить confidence-гейт авто-раскладки в спеке и сделать его выключаемым (дефолт 0.7)
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
Аудит спек↔код (2026-07-03) нашёл расхождение: спека recognition считает
|
||||
`confidence` вспомогательным сигналом (условия авто — только матч в базе +
|
||||
чистая валидация + согласованность), а код (`internal/recognize/validate.go:102`)
|
||||
добавляет `confidence < threshold` (дефолт 0.85) четвёртым **блокирующим**
|
||||
условием.
|
||||
|
||||
**Решение (2026-07-08): оставляем гейт (вариант B).** Низкий confidence — это
|
||||
полезная доп. проверка на ревью: LLM могла ошибиться так, что под ошибочные
|
||||
данные в базе нашёлся «такой же» фильм/сериал (ложный, но самосогласованный
|
||||
матч — матч и валидация чисты, а модель при этом не уверена). Такой случай ловит
|
||||
именно порог, уводя задачу в review, а не в авто. Значит confidence остаётся
|
||||
законным условием — но его надо честно оформить.
|
||||
|
||||
Что сделать:
|
||||
|
||||
1. **Сделать гейт реально выключаемым.** Сейчас `recognize.go:173-175`
|
||||
(`if threshold <= 0 { threshold = defaultAutoThreshold }`) не даёт выключить
|
||||
порог: `0` в конфиге молча возвращается к дефолту. Убрать этот фолбэк — дефолт
|
||||
задаётся один раз при загрузке конфига; значение `0` → гейт пропускается
|
||||
(в `decide` уже `p.Confidence < 0` никогда не истинно, достаточно снять
|
||||
пере-применение дефолта).
|
||||
2. **Понизить дефолт** `recognition.auto_confidence_threshold` 0.85 → **0.7**
|
||||
(`config.go:218`, `recognize.go:144 defaultAutoThreshold`). Условия 1–3 несут
|
||||
корректность; порогу остаётся ловить только по-настоящему неуверенные планы.
|
||||
3. **Записать в спеку.** В `openspec/specs/recognition/spec.md` (Requirement
|
||||
«Модель уверенности и решение auto/review») переформулировать: confidence —
|
||||
явное **четвёртое, конфигурируемое** блокирующее условие (порог
|
||||
`recognition.auto_confidence_threshold`, `0` = выкл, дефолт 0.7), а не «лишь
|
||||
вспомогательный сигнал». Добавить сценарий: матч + чистая валидация +
|
||||
согласованность, но `confidence` ниже порога → review.
|
||||
4. **Конфиг-конвенция/дока:** описать ключ в `docs/conventions/config.md` (диапазон
|
||||
[0,1], 0 = выкл, дефолт 0.7) и поправить значение в таблице «Настройки с
|
||||
числовым значением» [docs/database.md](../../database.md) — дом числа там, и
|
||||
при смене дефолта оно разъедется первым. Формулировки инварианта в
|
||||
`CLAUDE.md` и ложноположительного в `docs/review.md` уже приведены к
|
||||
«единственным гейтом не является» (канон 4, 2026-08-06) — переписывать их
|
||||
задача не должна.
|
||||
5. **Тесты:** `decide` с `threshold=0` (гейт выключен, авто при чистых 1–3);
|
||||
confidence ниже/выше порога при выполненных 1–3.
|
||||
6. Точное число порога откалибровать позже по рабочему потоку — сколько раздач
|
||||
ушло в ревью и сколько из них пришлось поправить. Размеченный корпус, по
|
||||
которому порог можно было бы подобрать заранее, решено не собирать
|
||||
(`../REJECTED.md`, 2026-08-06), так что первое значение остаётся оценкой.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/recognize` — `decide`/`validate.go` (четвёртое условие) и
|
||||
`recognize.go` (пере-применение дефолта, из-за которого `0` не выключает
|
||||
гейт);
|
||||
- секция `[recognition]` конфига, `config.example.toml` и значение по умолчанию
|
||||
в `internal/config`;
|
||||
- `openspec/specs/recognition/spec.md` — требование «Модель уверенности и
|
||||
решение auto/review»;
|
||||
- `docs/database.md` (дом числа) и `docs/conventions/config.md` (описание ключа).
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- `auto_confidence_threshold = 0` выключает гейт: при чистых условиях 1–3
|
||||
раздача уходит в авто независимо от `confidence` (оракул: тест `decide` с
|
||||
нулевым порогом).
|
||||
- При `confidence` ниже порога и выполненных условиях 1–3 раздача уходит в
|
||||
review (оракул: табличный тест на границе порога — ниже, равно, выше).
|
||||
- Умолчание равно 0.7 в одном месте — загрузке конфига; `recognize.go` своего
|
||||
дефолта не применяет (оракул: тест, что пустой конфиг даёт 0.7, плюс
|
||||
отсутствие `defaultAutoThreshold` в диффе).
|
||||
- Спека называет `confidence` четвёртым конфигурируемым блокирующим условием и
|
||||
несёт сценарий «матч чист, но уверенность ниже порога → review» (оракул:
|
||||
`openspec validate --strict`).
|
||||
|
||||
Оформить как OpenSpec-change (дельта `recognition` + правки
|
||||
`validate.go`/`recognize.go`/`config`).
|
||||
|
||||
Связано: openspec/specs/recognition, ADR-2026-06-13-auto-link-requires-db-match,
|
||||
пакет recognize.
|
||||
@@ -1,90 +0,0 @@
|
||||
# 🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации
|
||||
|
||||
Остаток от задачи «классификация доменных ошибок + конвенции логирования»
|
||||
(основное реализовано, см. ниже) плюс бэкофф опроса, заказанный 2026-08-06.
|
||||
Речь о поведении фонового цикла, пока зависимость лежит: с какой частотой он её
|
||||
дёргает и каким уровнем об этом пишет.
|
||||
|
||||
## Что уже сделано (не переоткрывать)
|
||||
|
||||
Коммит `f8fb4fa` (Tier A) + коммит этой задачи закрыли:
|
||||
|
||||
- **Классификация доменных ошибок:** sentinel `worker.ErrInvalidInput`→400;
|
||||
обёртки `ErrConflict` в Cancel/Retry/Defer/Undo; `layout.ErrCollision`→409 в
|
||||
`classifyErr` и ветка в tgbot; `logCmd` относит новые классы в DEBUG.
|
||||
- **Конвенции:** `logging.md` — команды воркера = доменная граница, таблица
|
||||
уровней доменных отказов (граница команды vs асинхронная стадия), правило про
|
||||
`*url.Error`/секреты в URL, канон категории `state transition` (унифицированы
|
||||
cancel/retry/relink/recovery). `errors.md` — таблица маппинга ошибка→статус,
|
||||
развилка «транзиентный ответ vs персистентная диагностика» решена как (а):
|
||||
`error_msg`/`reasons` — операторская поверхность владельца (сырой текст ок,
|
||||
секреты запрещены; аудит показал, что секреты туда не текут).
|
||||
- **Мелочи:** reason-коды const-блок; лог-поля `id`→`download_id`; preview
|
||||
WARN; комментарий у `parseIgnored`.
|
||||
|
||||
## Остаток
|
||||
|
||||
**Шум `ext.*` ERROR решено оставить как есть (2026-08-06).** Запись
|
||||
«зависимость недоступна» на каждом тике — легитимный сигнал транспортного слоя,
|
||||
и гасится он уровнем сбора логов, а не кодом. Варианты с пониженным уровнем у
|
||||
`logging.ExtCall` и с дедупом отклонены: первый заводит второе правило уровня
|
||||
для того же класса вызовов, второй даёт транспортному логгеру память о
|
||||
состоянии.
|
||||
|
||||
Остаются две вещи, и обе стоят на одном счётчике подряд-идущих сбоев тика.
|
||||
|
||||
**Бэкофф опроса (решение 2026-08-06).** Пока qBittorrent недоступен, цикл
|
||||
продолжает дёргать его каждые `poll_interval` (5 с) — недоступную зависимость
|
||||
незачем опрашивать с рабочей частотой. Интервал растёт экспоненциально от
|
||||
`poll_interval` до потолка порядка минуты; первый успешный ответ возвращает
|
||||
рабочий интервал сразу, без ступенчатого спуска. Бэкофф заодно снимает и остроту
|
||||
шума: записей становится столько же на событие, но событий — единицы в минуту.
|
||||
|
||||
**Эскалация уровня.** Сейчас сбой тика — **всегда WARN**, сколько бы тиков
|
||||
подряд он ни падал. `docs/conventions/logging.md` требует иного: устойчивый сбой
|
||||
N тиков подряд — это реальная деградация, и она пишется ERROR.
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
1. Остановить qBittorrent (локально, не на umbar).
|
||||
2. Смотреть лог воркера в течение нескольких минут поллинга.
|
||||
3. Наблюдается: запрос к qBittorrent уходит каждые 5 секунд всё время
|
||||
недоступности, а доменная запись `poll failed` идёт WARN на каждом тике и
|
||||
остаётся WARN бесконечно.
|
||||
4. Ожидается: интервал опроса растёт до минутного потолка, а после N
|
||||
подряд-идущих неудачных тиков уровень поднимается до ERROR — деградация
|
||||
отличается от разового промаха.
|
||||
5. Поднять qBittorrent обратно: опрос возвращается к `poll_interval` с первого
|
||||
успешного ответа.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- цикл поллинга воркера (`internal/worker`) — счётчик подряд-идущих сбоев,
|
||||
текущий интервал тика и его сброс по успеху;
|
||||
- секция `[worker]` конфига и `config.example.toml` — потолок бэкоффа и порог
|
||||
эскалации;
|
||||
- `docs/database.md`, таблица «Настройки с числовым значением» — дом обоих
|
||||
чисел;
|
||||
- `docs/architecture.md`, «Характер потока» — там сказано, что фон непрерывный с
|
||||
периодом поллинга; переменный интервал это уточняет;
|
||||
- `docs/conventions/logging.md` — правило эскалации уже записано, меняться не
|
||||
должно; задача приводит код к нему.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- При подряд-идущих сбоях интервал опроса растёт экспоненциально от
|
||||
`poll_interval` и упирается в потолок из конфига, дальше не растёт (оракул:
|
||||
тест цикла с подставным клиентом и управляемыми часами — проверяет
|
||||
последовательность интервалов).
|
||||
- Первый успешный ответ возвращает `poll_interval` немедленно (оракул: тот же
|
||||
тест, сценарий «серия сбоев, успех, сбой» — после успеха интервал рабочий).
|
||||
- Сбой тика ниже порога пишется WARN, начиная с N-го подряд — ERROR, а успешный
|
||||
тик сбрасывает счётчик (оракул: тест, считающий уровни записей на сценарии
|
||||
«сбой, сбой, успех, сбой»).
|
||||
- Потолок бэкоффа и порог эскалации читаются из конфига и описаны в
|
||||
`config.example.toml` с единицами и диапазоном (оракул: `task gate`, шаг
|
||||
канона — сверка с `database.md`).
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🔬 Сила совпадения кандидата и пересмотр распознавания/матчинга
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга)
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
У кандидата метабазы нет метрики силы совпадения (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.
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🔬 Завершение загрузки через webhook
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации)
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Сейчас завершение ловим поллингом qBittorrent раз в несколько секунд. Альтернатива: «Run external program on torrent completion» в qBittorrent дёргает эндпоинт jellybit. Реагирует быстрее, но связывает нас с конфигом qBittorrent.
|
||||
|
||||
Связано: specs/architecture.md → «Отслеживание загрузки», пакет worker.
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 Раскладывается не только типовая раздача
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: сериальные паки, докачивание, аниме со сквозной нумерацией, образы дисков и внешние субтитры — это ровно тот контент, ради которого проект и заводился вместо arr-стека.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда сериальный пак, докачивание недостающих серий, аниме со сквозной нумерацией, образ диска и внешние субтитры раскладываются без ручного вмешательства в файлы на диске — либо честно уходят в ревью с названной причиной, а не молча кладутся неверно.
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🔬 Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Обычный случай — один сезон (его номер видно глазами и сверяем на ревью — под это сделана сводка сезонов). Но в редких заказах раздача сложнее: все сезоны сериала разом, пак нескольких сезонов, смешанная нумерация, вложенные папки сезонов, разнобойные имена файлов. Сейчас PlanFile.Season задаётся на каждом файле (мультисезон в принципе выразим), но целостно эти сценарии не проработаны: как надёжно распознать, как показать на ревью, как разложить и как стыкуется со сходимостью папки и merge-докачиванием. Решить, что поддерживаем явно, а что уводим в ревью как «сложную раскладку».
|
||||
|
||||
Связано: specs/recognition.md, specs/jellyfin-layout.md, specs/review-ux.md, «Проблема второго сезона», «Раздачи с докачиванием».
|
||||
@@ -1,59 +0,0 @@
|
||||
# 🔬 Кандидаты в конвенции кода
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд
|
||||
- **Теги:** goal:dev-process-quality
|
||||
|
||||
Список копился в черновике `docs/drafts/conventions-backlog.md` (удалён при
|
||||
переводе на канон, текст в истории git) под правилом «пишем по мере реального
|
||||
трения, а не вперёд». Правило соблюдено — но список с тех пор не пересматривали,
|
||||
а часть пунктов за это время либо реализовалась, либо механизировалась правилом
|
||||
и должна из кандидатов выпасть, а не переехать в прозу.
|
||||
|
||||
Разобрать по одному: стало реальным трением → в `docs/conventions/`; выражается
|
||||
правилом → в `.golangci.yml` или `internal/archrules` и в таблицу
|
||||
«Механизировано»; выдумано вперёд → выбросить.
|
||||
|
||||
**Кандидаты в отдельный документ**
|
||||
|
||||
- **Раскладка пакетов и направление зависимостей.** `cmd/<bin>` +
|
||||
`internal/<компонент>` по доменам, домен не импортирует транспорт, без свалок
|
||||
`util`/`common`/`helpers`. *Частично уже механизировано* тестами
|
||||
`internal/archrules` — проверить, что осталось прозой.
|
||||
- **`context.Context`.** Первый параметр, не хранить в структурах, в `Value`
|
||||
только request-scoped данные (не зависимости), дедлайны и отмена тянутся
|
||||
сквозь стадии. Протяжка логгера уже сделана (`internal/logctx`).
|
||||
- **Внешние клиенты.** Таймаут на **каждый** исходящий вызов, не
|
||||
`http.DefaultClient`, ретраи с backoff и потолком, HTTP-прокси из конфига.
|
||||
Кандидат на общий конструктор клиента вместо копипасты в
|
||||
`qbt`/`llm`/`jellyfin`/`metadata`. Самый живой пункт: клиентов уже четыре.
|
||||
- ~~**Тесты.**~~ Вынесены отдельной задачей —
|
||||
[tests-convention](tests-convention.md): кандидат разошёлся с проектом
|
||||
(обещал фикстуры в `testdata/`, которого нет), и разбирать его надо вместе с
|
||||
тем, что накопилось в самих тестах.
|
||||
|
||||
**Кандидаты в строку-инвариант, а не в документ**
|
||||
|
||||
- **БД и миграции.** Forward-only, только параметризованные запросы, явные
|
||||
транзакции для многошаговых изменений, context-aware запросы. Сильно
|
||||
стек-специфично.
|
||||
- **Конкурентность.** Каждая горутина знает, **как** останавливается
|
||||
(ctx/закрытие канала); `errgroup` для связанных задач; фоновые процессы
|
||||
гасятся при shutdown. Актуально для воркера, не для всего проекта.
|
||||
- **CLI.** Данные в `stdout`, логи и диагностика в `stderr`, осмысленные коды
|
||||
возврата. Для диагностических команд `add`/`recognize`/`healthcheck`.
|
||||
- **Время.** Явный TZ всегда, хранение и логи в UTC. Уже частично в `CLAUDE.md`
|
||||
и `conventions/logging.md`, а `time.Now` вне `store` запрещён линтером — этот
|
||||
пункт, вероятно, закрыт и подлежит вычёркиванию.
|
||||
- **Язык вывода связан с мапперами.** Провенанс — ревью `tvdb-title-locale`
|
||||
(2026-08-07,
|
||||
[отчёт триажа](../../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md),
|
||||
находка R2 прохода `architecture`). Язык вывода живёт в пяти местах четырёх
|
||||
пакетов, и теста, связывающего множество кодов языка с мапперами, нет:
|
||||
забытая ветка молча даст английский вывод вместо ошибки. Кандидат в правило
|
||||
`internal/archrules`, а не в прозу. **Дешёвый паллиатив, если правило окажется
|
||||
дорогим:** строка «язык вывода» в `docs/architecture.md` → «Единые точки
|
||||
проекта» — тогда расхождение хотя бы спросит проход по теме `architecture`.
|
||||
Число мест перед правкой перепроверить: оно снято ревью одной задачи и с тех
|
||||
пор могло измениться.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Чистить БД от терминальных задач и сырых ответов LLM старше срока хранения
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
Терминальные задачи (done/cancelled/failed/reverted), их попытки recognition с сырыми ответами LLM и metadata_candidate копятся вечно — БД и список загрузок распухают и становятся нечитаемыми. Нужна авточистка старше N дней (настройка в [storage] или [worker]) и/или ручное удаление. Маленькая задача, но без неё интерфейс деградирует по мере эксплуатации.
|
||||
|
||||
Связано: specs/architecture.md → «Хранилище» (download/recognition/metadata_candidate/file_link), пакет store.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
/healthz проверяет только сам сервис. Если qBittorrent, LLM или метабаза недоступны — узнаёшь лишь по застрявшим задачам. Нужна readiness-проверка ключевых зависимостей и отражение их состояния в UI (бейдж «qBittorrent недоступен»), чтобы причина простоя была видна сразу.
|
||||
|
||||
Связано: specs/architecture.md → «Деплой» (healthcheck), пакеты qbt, llm, metadata, httpapi.
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 Домен называется одинаково везде, ревью откалибровано
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Сопровождение
|
||||
- **Зачем:** наименования домена расходятся между спеками, UI и кодом, а конвейер ревью не откалиброван — растёт цена каждой следующей задачи
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: это не поведение продукта, а то, чем он делается. Единый словарь, калибровка проходов ревью и разбор накопленных кандидатов в конвенции — вложение в скорость всех остальных целей.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда домен называется одинаково в спеках, коде и интерфейсе, а конвейер ревью откалиброван на журнале реальных дефектов, а не на догадках о том, что он ловит.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Раскладывать раздачу-копию диска (VIDEO_TS/BDMV) каталогом целиком
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** раздача-образ диска (VIDEO_TS/BDMV) сейчас разбирается пофайлово вместо раскладки каталога целиком — редкий, но реальный случай
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Иногда для очень редких фильмов скачивается не один видеофайл, а полная копия диска — структура 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.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Хранить историю переходов загрузки отдельной таблицей
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов
|
||||
- **Теги:** goal:state-integrity
|
||||
|
||||
Сохранять полную историю переходов состояний загрузки (что/когда/почему/кто инициировал — воркер, человек, сверка), а не только текущее состояние. Сейчас по задаче виден лишь актуальный статус, разбор «как мы сюда попали» идёт по логам сервера. Отдельная таблица истории даёт лог переходов в карточке/расширенной информации и фундамент для метрик длительности стадий. Естественно ложится на собственный идентификатор загрузки и уже реализованный экран /download/{id}.
|
||||
|
||||
Связано: drafts/logical-title-model.md §5.4 (state_transition, actor worker|human|reconcile), specs/workflow.md, specs/database.md, пакеты worker, store.
|
||||
@@ -1,75 +0,0 @@
|
||||
# 🔬 Канон нумерации серий и порядок у провайдера тега
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Косметика и редкий случай: порядок просмотра не страдает (файлы уже
|
||||
пронумерованы канонически и лежат по порядку), разъезжаются только подписи серий
|
||||
в Jellyfin — не то название/описание у части эпизодов. Задевает лишь тайтлы с
|
||||
исторически спорным порядком, таких мало.
|
||||
|
||||
## Проблема
|
||||
|
||||
У некоторых сериалов есть несколько *легитимных* порядков серий, и разные
|
||||
метабазы придерживаются разных. Каноничный пример — «Ковбой Бибоп»: в титрах и
|
||||
на дисках/IMDb/TVDB порядок «сессий» (Session #1 «Asteroid Blues» … #26), а TMDB
|
||||
по своей политике нумерует по **самой ранней дате эфира**. Часть серий вышла
|
||||
раньше на TV Tokyo вразнобой (2, 3, 7–15, 18) — при сортировке по дате они
|
||||
всплывают вперёд, и диапазон ~1–18 перемешивается. Это не баг одной базы: оба
|
||||
порядка «правильные», просто разные каноны. У TMDB канон отдаётся отдельной
|
||||
episode group (тип DVD/production), у TVDB — отдельными order-типами
|
||||
(Aired/DVD/Absolute).
|
||||
|
||||
## Где это бьёт по jellybit (и где нет)
|
||||
|
||||
jellybit **не** матчит серии по `(season, episode)` между провайдерами —
|
||||
описанного класса бага у нас нет. Номер эпизода рождается из имён файлов через
|
||||
LLM (`recognize.PlanFile.Episode`), проходит без изменений в раскладку и
|
||||
печатается в `SxxEyy`; метабаза даёт лишь `SeasonEpisodeCounts` для гейта
|
||||
полноты пака. То есть мы **доверяем нумерации релиз-группы** и про порядок вообще
|
||||
не знаем.
|
||||
|
||||
Настоящий риск — тихий и уже существует:
|
||||
|
||||
> jellybit пишет `SxxEyy` **и** тег папки `[tmdbid-…]`/`[tvdbid-…]`. Дальше
|
||||
> Jellyfin по этому тегу заново скрейпит серии у *того же* провайдера. Номер в
|
||||
> имени файла и порядок, который ждёт скрейпер, обязаны быть **из одного
|
||||
> канона** — иначе метаданные разъедутся на именно тех сериях.
|
||||
|
||||
Для Бибопа: релиз почти всегда пронумерован канонически (session order). Если
|
||||
матч ушёл на TVDB и написан `[tvdbid-…]` — Jellyfin скрейпит aired order TVDB,
|
||||
для Бибопа = канон, всё сходится. Если матч ушёл на **TMDB** и написан
|
||||
`[tmdbid-…]` — Jellyfin ждёт airing order TMDB (перемешанные 1–18), а файлы
|
||||
канонические → метаданные поедут. Инвариант «канон нумерации файлов ↔ провайдер
|
||||
тега» сейчас нигде не проверяется.
|
||||
|
||||
## Что можно сделать (варианты, не решение)
|
||||
|
||||
- **Минимум (дёшево, ценно):** осознать инвариант и эскалировать в review, когда
|
||||
у распознанного тайтла провайдер матча — из тех, где порядок известно спорный
|
||||
(episode groups у TMDB, absolute order у TVDB), а нумерация файлов может не
|
||||
совпадать с дефолтным скрейп-порядком этого провайдера. Лучше явный вопрос
|
||||
человеку, чем тихий разъезд.
|
||||
- **Предпочтение провайдера тега:** для сериалов с известным расхождением тегать
|
||||
папку провайдером, чей дефолтный порядок совпадает с каноном файлов (обычно
|
||||
TVDB), даже если матч найден в TMDB.
|
||||
- **Максимум:** знать про порядок явно — тянуть episode group (TMDB) / order-тип
|
||||
(TVDB) и сверять нумерацию файлов с выбранным каноном. Требует, чтобы у нас
|
||||
появилось понятие «канон эпизода», которого сейчас в модели нет (эпизодов как
|
||||
сущностей в БД нет, план — JSON-блоб).
|
||||
|
||||
## Связи
|
||||
|
||||
- Тот же класс «у тайтла несколько легитимных порядков», что и
|
||||
[Аниме с абсолютной нумерацией](anime-absolute-numbering.md) (absolute
|
||||
order через TVDB) — стоит проработать совместно, возможно как одну тему.
|
||||
- [Сложные сериальные раздачи](complex-series-releases.md) — соседний пласт
|
||||
крайних случаев раскладки.
|
||||
- Схема «локальная сущность каноническая, provider id — опциональный внешний
|
||||
ключ» уже заложена (draft `logical-title-model.md`, сущность `title` осознанно
|
||||
отвергнута) — эту же логику надо дотянуть до эпизодов/порядка, если пойдём в
|
||||
«максимум».
|
||||
- specs/recognition.md (гейт полноты пака, крайние случаи), specs/jellyfin-layout.md
|
||||
(нумерация серий, тег провайдера), specs/review-ux.md (эскалация в review).
|
||||
@@ -1,24 +0,0 @@
|
||||
# ✨ Спаривать VobSub .idx+.sub и проставлять языковой суффикс субтитра
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Базовая привязка субтитр→серия для сериала уже работает: `layout.PlanFile` несёт
|
||||
`Season/Episode`, а `seriesDst` именует субтитр по стему эпизода
|
||||
(`internal/layout/layout.go`, код старше аудита 2026-07-03). Для фильма тоже —
|
||||
субтитр именуется по базе видеофайла.
|
||||
|
||||
Реальные пробелы, оставшиеся от аудита:
|
||||
|
||||
- **Пары VobSub** `.idx`+`.sub` не спариваются — их надо переносить вместе как одну
|
||||
дорожку.
|
||||
- **Языковой суффикс теряется:** у `recognize.PlanFile` нет полей `Lang/Flags`, и
|
||||
`toLayoutPlan` (`internal/httpapi/review.go`) не проставляет язык — субтитр ляжет
|
||||
без `*.ru.srt`-суффикса, который ждёт Jellyfin.
|
||||
|
||||
Смоделировать язык/флаги субтитра сквозь recognize→layout и спаривание `.idx`+`.sub`;
|
||||
спека recognition уже требует «внешние субтитры SHALL привязываться к видео».
|
||||
|
||||
Связано: openspec/specs/recognition, specs/jellyfin-layout.md, пакеты recognize, layout.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Проверять свободное место перед copy-fallback
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
Когда хардлинк невозможен (EXDEV/ENOTSUP/…), layout копирует файл, дублируя место на диске. На забитом диске это упрётся в полку посреди раскладки. Перед копированием проверять доступное место и при нехватке внятно уходить в failed с понятной причиной, а не падать на полпути.
|
||||
|
||||
Связано: specs/architecture.md → «Раскладка файлов» (фолбэк-копирование), пакет layout.
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🔬 guessit как сервис-спутник
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
go-ptn слабее питоновского guessit. Если точности пред-парса не хватит — завернуть guessit в крошечный HTTP-сервис (один файл, поставляется рядом с бинарём jellybit) и спрашивать его на шаге пред-парса. Сохраняет «доставку копированием»: два файла вместо одного.
|
||||
|
||||
Связано: specs/recognition.md → «На будущее» (пред-парс).
|
||||
@@ -1,14 +0,0 @@
|
||||
# 🐞 Связать v1/v2-хеши одного торрента и не доверять паре xt из магнета (F4, F5)
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_
|
||||
- **Теги:** goal:state-integrity
|
||||
|
||||
Ревью 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.
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 Раздача приносится и подтверждается из любого транспорта
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: приём и ревью — единственные места, где система встречается с человеком. Здесь копятся незакрытые куски: фетч по URL, редактор маппинга, привязка уведомлений к автору, латентность обновлений.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда любой из поддержанных источников принимается одним действием из любого транспорта, а ревью позволяет довести план до применимого состояния без ухода в другой инструмент.
|
||||
@@ -1,73 +0,0 @@
|
||||
# 🐞 Отправлять на ревью раздачу с непомещающимся именем вместо отказа
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** название длиннее ~237 байт роняет раскладку в failed с текстом системной ошибки: пользователь видит «file name too long» вместо карточки ревью, где это чинится подсказкой
|
||||
- **Теги:** sprint:2026-08-06
|
||||
|
||||
Длина компонента пути ограничена файловой системой (255 байт на имя, минус
|
||||
расширение и суффиксы — практический потолок около 237). Название такой длины
|
||||
приходит из метабазы или из распознавания штатно: длинные оригинальные заголовки
|
||||
и сериалы с подзаголовком в имя укладываются не всегда.
|
||||
|
||||
Сегодня это не проверяется нигде: раскладка доходит до `link(2)`, получает от ядра
|
||||
`file name too long`, и задача уходит в `failed` с текстом системной ошибки в
|
||||
поле ошибки. Для владельца это тупик — состояние терминальное, а причина написана
|
||||
языком ядра.
|
||||
|
||||
Правильный исход — **review**: там человек правит название подсказкой, ровно как
|
||||
при коллизии цели. Коллизия уже так и обрабатывается, то есть путь в системе
|
||||
есть и его надо переиспользовать, а не изобретать.
|
||||
|
||||
Провенанс: ревью изменения `tvdb-title-locale`, находка AD4 —
|
||||
[отчёт триажа](../../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md).
|
||||
Понижена там не по доказательности, а по принадлежности к диффу: путь этим
|
||||
изменением не тронут.
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
Оракул из отчёта: подать раздачу, у которой имя целевого файла складывается в
|
||||
250 байт.
|
||||
|
||||
```
|
||||
n=250 → Apply err=… file name too long, results=0
|
||||
```
|
||||
|
||||
Наблюдаемо: задача в состоянии `failed`, в поле ошибки — текст системной ошибки,
|
||||
ни одной ссылки не создано, карточки ревью нет.
|
||||
|
||||
Ожидаемо: задача в `review` с названной причиной «имя не помещается», раскладка
|
||||
не начата, подсказка человека чинит случай.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/layout` — проверка длины компонента до `link(2)` и доменная ошибка
|
||||
вместо системной;
|
||||
- `internal/worker` — ветка перевода задачи в `review` по этой ошибке, рядом с
|
||||
существующей веткой коллизии цели;
|
||||
- `openspec/specs/file-layout/spec.md` — требование о непомещающемся имени;
|
||||
`state-reconciliation` — если переход в `review` заказывается там;
|
||||
- текст причины в карточке ревью: веб-UI и Telegram показывают её человеку.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Раздача с именем в 250 байт уходит в `review` с доменной причиной, а не в
|
||||
`failed` с текстом ядра. **Оракул:** тест на временном каталоге прогона,
|
||||
воспроизводящий случай из «Воспроизведения», — проверяет состояние задачи и
|
||||
код причины.
|
||||
- Ни одна ссылка не создана до отказа: частичной раскладки батча не остаётся.
|
||||
**Оракул:** тот же тест — проверяет, что каталог цели пуст.
|
||||
- Причина, показанная человеку, не содержит текста системной ошибки. **Оракул:**
|
||||
утверждение теста на текст причины плюс конвенция
|
||||
[errors.md](../../conventions/errors.md) — перевод доменной ошибки на внешней
|
||||
границе.
|
||||
- Проверка длины стоит **до** первой операции с файловой системой. **Оракул:**
|
||||
чтение диффа на ревью; тест на пустоту каталога цели его подтверждает.
|
||||
|
||||
## Рамки
|
||||
|
||||
Обрезать или переименовывать название самостоятельно нельзя — это решение
|
||||
человека, у него для этого есть подсказка на ревью. Предел длины зависит от
|
||||
файловой системы; зашивать 255 как универсальную константу не стоит, но и
|
||||
выяснять предел у ядра в рантайме ради этой задачи не требуется — достаточно
|
||||
консервативного значения с объяснением, откуда оно.
|
||||
@@ -1,17 +0,0 @@
|
||||
# ✨ Докладывать недостающие эпизоды merge-раскладкой при повторной заливке
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Свежий сериал раздают по мере выхода: торрент с 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
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🧹 Кэшировать ответы метабаз с TTL (и опционально LLM)
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново»
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
Повторные и ретраящиеся прогоны распознавания бьют TMDB/TVDB/TVMaze одним и тем же запросом. Кэш ответов с TTL экономит лимиты API и ускоряет «Распознать заново»/«Уточнить». При желании — кэш ответов LLM по хешу входа (но он менее полезен, т.к. вход меняется подсказками).
|
||||
|
||||
Связано: specs/recognition.md (сверка с базой), пакеты metadata, llm.
|
||||
@@ -1,73 +0,0 @@
|
||||
# 🐞 Санитизировать название из метабазы перед подстановкой в план
|
||||
|
||||
- **Тип:** fix
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** plan.Title = match.Title подставляется ПОСЛЕ sanitizePlan — название из TMDB/TVDB уезжает в имя каталога Jellyfin дословно, с невидимыми символами и гомоглифами, и авто-раскладка это пропускает
|
||||
- **Теги:** sprint:2026-08-06
|
||||
|
||||
Название, пришедшее из метабазы, подставляется в план **после** того, как план
|
||||
прошёл санитизацию: `plan.Title = match.Title` стоит ниже `sanitizePlan`.
|
||||
Значение уезжает в имя каталога библиотеки, не сверенное ни с чем — гейт
|
||||
авто-раскладки сверяет `normalize(c.Title) || normalize(c.OriginalTitle)` и
|
||||
проходит по второму полю, а в путь идёт первое.
|
||||
|
||||
`layout.sanitizeComponent` ниже по потоку снимает `/\:*?"<>|` и байты `< 0x20`,
|
||||
но категорию Cf (невидимые управляющие) не трогает и гомоглифы не сворачивает.
|
||||
Итог — два визуально неотличимых каталога в медиатеке.
|
||||
|
||||
**Класс пре-существующий, и это важно для рамок:** тот же путь у TMDB, задача
|
||||
`tvdb-title-locale` его только распространила на второго провайдера. Инвариант
|
||||
«целевой путь строго под библиотекой» **держится** — проверено на `Dune/../../etc`,
|
||||
`" .. "`, `"..."` и на 400 символах, выхода из песочницы нет. Поэтому `major`,
|
||||
а не `critical`.
|
||||
|
||||
Провенанс: ревью изменения `tvdb-title-locale`, находка 2 —
|
||||
[отчёт триажа](../../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md).
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
Падающий тест на реальном `Recognizer` (оракул добыт триажом, не рассуждением).
|
||||
Подать кандидата метабазы с названием и посмотреть на `plan.Title` и решение
|
||||
гейта:
|
||||
|
||||
| Вход | Что происходит |
|
||||
| --- | --- |
|
||||
| три символа ZWSP (`U+200B`) | `auto=true`, в план уезжают невидимки; `sanitizeTitle` дал бы пустую строку |
|
||||
| `Dune<RLO>gnp.mkv` (`U+202E`) | `auto=true`, имя каталога переворачивается при отображении |
|
||||
| `Dune\nHACK` | `auto=true`, перевод строки доезжает до плана |
|
||||
| `Dunа` с кириллической `а` | `auto=true`, каталог визуально неотличим от латинского `Dune` |
|
||||
|
||||
Во всех четырёх случаях значение в плане отличается от того, что дал бы
|
||||
санитайзер, и ни одно не остановлено гейтом.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `internal/recognize/recognize.go` — порядок подстановки `match.Title` и
|
||||
`match.OriginalTitle` относительно `sanitizePlan`;
|
||||
- `internal/layout` — состав `sanitizeComponent`, если решим закрывать категорию
|
||||
Cf и гомоглифы здесь, а не на подстановке (выбор места — часть задачи);
|
||||
- поведение **обоих** провайдеров, TMDB и TVDB: правка меняет уже работающий
|
||||
TMDB, и это её главный риск;
|
||||
- `openspec/specs/metadata-match/spec.md` либо `file-layout` — чьим требованием
|
||||
станет «название из метабазы санитизируется перед попаданием в путь».
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Все четыре входа из «Воспроизведения» дают либо санитизированный `plan.Title`,
|
||||
либо уход в review — но не авто-раскладку с исходным значением. **Оракул:**
|
||||
тот самый падающий тест из отчёта триажа, перенесённый в дерево.
|
||||
- Название, схлопывающееся санитизацией в пустую строку, не порождает каталог с
|
||||
пустым именем и не роняет раскладку. **Оракул:** табличный тест на границе,
|
||||
случай «три ZWSP».
|
||||
- Поведение TMDB на нормальных названиях не изменилось. **Оракул:** существующие
|
||||
тесты `internal/recognize` и `internal/metadata` зелёные без правок ожиданий.
|
||||
- Место санитизации названо требованием спеки, а не только кодом. **Оракул:**
|
||||
`openspec validate --strict` на дельте.
|
||||
|
||||
## Рамки
|
||||
|
||||
Гомоглифы сворачивать не обязательно — достаточно сделать значение в пути
|
||||
предсказуемым и сверяемым; полноценная нормализация Unicode это отдельный
|
||||
разговор. Инвариант «целевой путь строго под библиотекой» уже держится, ломать
|
||||
его правкой нельзя. Правка задевает работающий TMDB — регресс на нём дороже
|
||||
самого дефекта.
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🔬 Многоступенчатая верификация привязки
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения
|
||||
- **Теги:** goal:recognition-accuracy
|
||||
|
||||
Несколько раз извлекать данные из раздачи и контекста разными промптами, искать в метабазах, затем сводить результаты в общий вердикт (голосование/консенсус) — выше точность ценой нескольких вызовов LLM и запросов к базам. Проработать: когда включать, как мерджить расхождения, стоимость/латентность.
|
||||
|
||||
Связано: specs/recognition.md (конвейер и модель уверенности).
|
||||
@@ -1,35 +0,0 @@
|
||||
# ✨ Заказать спекой крайние случаи именования: многофайловый фильм, редакции, двойная серия
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
Целевые имена для типового фильма и типового сезона заказаны
|
||||
[file-layout](../../../openspec/specs/file-layout/spec.md). Крайние случаи там
|
||||
не заказаны: до перевода на канон они жили разделом «Крайние случаи» нарратива
|
||||
`docs/specs/jellyfin-layout.md` (удалён, текст в истории git) как намерение, а
|
||||
не как требование. Значит, что делает код в этих случаях, без чтения кода
|
||||
неизвестно, и проверить это нечем.
|
||||
|
||||
Что надо определить и заказать спекой:
|
||||
|
||||
- **Многофайловый фильм** (фильм, разрезанный на части) — стэкинг по точному
|
||||
токену Jellyfin: `Имя (Год) - part1.mkv` либо `cd1`. Точный формат уточняется
|
||||
по документации Jellyfin: в нарративе он стоял с пометкой «уточнить при
|
||||
реализации».
|
||||
- **Редакции** — `Имя (Год) [edition-Director's Cut]` либо отдельные версии
|
||||
внутри папки фильма. Смежно с задачей про репаки и версии одного тайтла, но
|
||||
это про именование, а не про выбор версии.
|
||||
- **Двойная серия в одном файле** — `… SxxEyy-Eyy`.
|
||||
- **Спецвыпуски** — `Season 00`. Сперва проверить, не покрыты ли уже
|
||||
требованием «Роли файлов на краях раздачи» в
|
||||
[recognition](../../../openspec/specs/recognition/spec.md).
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
<!-- 2–5 проверяемых утверждений списком, у каждого назван оракул -->
|
||||
|
||||
## Рамки
|
||||
|
||||
<!-- одна строка: чего касаться нельзя, что перезапускается, что считается необратимым -->
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Слать уведомления автору загрузки в его транспорт (мульти-бот)
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота)
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Уведомления и запросы подтверждения должен получать тот, кто прислал загрузку: автор сообщения о новой раздаче — адресат пингов и ревью по ней. Транспортов-ботов может быть несколько (Telegram, в перспективе Matrix и др.); каждый адресует «своему» отправителю. Веб-интерфейс остаётся единым для всех и точкой правды по функциональности (боты — тонкие адаптеры над тем же ядром). Нужно: хранить у загрузки источник/транспорт и идентификатор отправителя, маршрутизировать пинги по нему.
|
||||
|
||||
Связано: specs/review-ux.md, specs/architecture.md → «Транспорты».
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 Сервис переживает рост и потерю тома
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Сопровождение
|
||||
- **Зачем:** сервис работает, но не переживает роста: база копится вечно, бэкапа нет, отказ зависимости виден только по застрявшим задачам
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: сегодня всё держится на том, что загрузок мало и всё рядом работает. Ретеншена нет, бэкапа нет, глубокого healthcheck нет, поведение под сотней загрузок не мерялось.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда база не растёт бесконечно, состояние переживает потерю тома, отказ любой зависимости виден владельцу раньше, чем по застрявшим задачам, и поведение под сотней одновременных загрузок измерено, а не предположено.
|
||||
@@ -1,67 +0,0 @@
|
||||
# 🧹 Откалибровать проходы ревью и завести ревьювер наименований
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
|
||||
- **Теги:** goal:dev-process-quality
|
||||
|
||||
Набор проходов ревью поверх ревью-процесса из CLAUDE.md. Развивает ревью-процесс
|
||||
OpenSpec в сторону воспроизводимых автопроверок, не заменяя человеческое ревью.
|
||||
|
||||
## Сделано (2026-07-10)
|
||||
|
||||
Заведены два кастомных ревьювера: оптика спек/требований и оптика кода
|
||||
(архитектура, инварианты, конвенции, стиль, дублирование); оба подключены
|
||||
чекпоинтами в пайплайн задачи.
|
||||
|
||||
## Сделано (2026-07-23) — переработка конвейера
|
||||
|
||||
Конвейер пересобран по типу проходов, а не по ролям: гейт → сверка со спекой в
|
||||
обе стороны → generative-проходы → архитектура → враждебные постановки → триаж,
|
||||
профили `quick`/`standard`/`deep`/`design`, контракт находок, границы покрытия,
|
||||
храповик «находка → конвенция → правило → удаление», журнал проскочивших
|
||||
дефектов и процедура калибровки. Подробности — ADR
|
||||
[ADR-2026-07-23-review-pipeline-generative](../../adr/ADR-2026-07-23-review-pipeline-generative.md).
|
||||
|
||||
Открытый вопрос «дробить ли проход по конвенциям на узкие оптики» закрыт:
|
||||
**не дробим** — декорреляция внимания без декорреляции суждения почти не
|
||||
добавляет recall, но линейно удорожает триаж.
|
||||
|
||||
## Сделано (2026-08-04) — переезд в плагин
|
||||
|
||||
Проектные копии агентов (`.claude/agents/jellybit-review-*`) и скиллов
|
||||
(`review-pipeline`, `task-pipeline`, `task-batch`) удалены в пользу плагина
|
||||
`av-dev-pipeline`. Проектная специфика теперь приходит из документов канона —
|
||||
[docs/review.md](../../review.md): типовые узлы, ложноположительные, вопросы по
|
||||
темам, триггеры метки, недоступное проверке.
|
||||
|
||||
Два прохода плагин при этом **упразднил**, и это надо помнить:
|
||||
|
||||
- `idiom` — поимённая сверка со стайлгайдами языка не задаётся теперь ни одним
|
||||
проходом; куда переселены способные части и почему класс признан обратимым —
|
||||
[docs/review.md](../../review.md) → «Перестали проверять сознательно».
|
||||
- `negative` — вопрос «что опытный человек отсюда удалил бы» вошёл в
|
||||
`architecture` вторым обязательным.
|
||||
|
||||
## Осталось
|
||||
|
||||
- **Ревьювер наименований** (соответствие словарю единого языка) — отдельной
|
||||
оптикой не выделен: зависит от задачи «Словарь единого языка», без глоссария
|
||||
проверять не по чему. Завести после неё.
|
||||
- **Калибровка проходов** по процедуре `references/calibration.md` скилла
|
||||
`av-dev-pipeline:review-pipeline` — ни один проход ещё не замерен инъекцией.
|
||||
До замера ничего не удаляем и промпты не правим.
|
||||
- **Проходы не сообщают свой потолок находок.** На прогоне `tvdb-title-locale`
|
||||
(2026-08-07) о потолке промолчали четверо из шести — `autotests`, `specs`,
|
||||
`architecture`, `ops`; триаж свой назвал («6 из 7, потолок не выбран»).
|
||||
Устав требует объявлять сработавший потолок строкой, и без неё «находок
|
||||
больше нет» неотличимо от «больше не поместилось» — то есть ровно тот сигнал,
|
||||
ради которого потолок и заведён. Первый замер калибровки стоит начать с этого:
|
||||
дефект прогона виден без инъекции.
|
||||
- **Заполнить журнал дефектов** в [docs/review.md](../../review.md) случаями,
|
||||
которые уже проскочили ревью, — они станут первыми пробами калибровки.
|
||||
- **Решить судьбу упразднённых проходов:** нужен ли проекту свой `idiom` поверх
|
||||
плагина, или записи в «Недоступно проверке» достаточно.
|
||||
|
||||
Связано: CLAUDE.md (ревью-процесс, конвенции),
|
||||
[docs/conventions/](../../conventions/README.md), «Словарь единого языка».
|
||||
@@ -1,14 +0,0 @@
|
||||
# 🎯 Раздача узнаётся верно без подсказок человека
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** распознавание ошибается молча и правдоподобно, а смена модели или правка промпта идёт вслепую — сдвига точности не видно ни до, ни после
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: распознавание — единственное место, где система может ошибиться молча и правдоподобно. Сегодня о его точности судят по впечатлению, и сдвиг от смены модели или правки промпта заметен только задним числом.
|
||||
|
||||
Размеченный корпус и обучение на правках человека из этой цели исключены сознательно (`REJECTED.md`, 2026-08-06): базу руками не собираем, точность поднимаем тюнингом автоматического распознавания.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда решение auto/review опирается на измеримую силу совпадения с записью метабазы, а не на самооценку модели, и доля раздач, ушедших в ревью или поправленных после авто-раскладки, видна по рабочему потоку и не растёт от версии к версии.
|
||||
@@ -1,34 +0,0 @@
|
||||
# ✨ Править на ревью маппинг «файл → серия» и раскладывать вручную при провале LLM
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Когда распознавание разложило файлы по сериям неверно, единственный путь —
|
||||
подсказать текстом и перераспознать. Точечно поправить номер серии у одного
|
||||
файла нельзя, а при полном провале LLM (ничего не вытащил) выхода нет вообще.
|
||||
|
||||
Материал, из которого задача выведена, — раздел «Объём по версиям» удалённого
|
||||
нарратива `docs/specs/review-ux.md` (полный текст в истории git). Заявленный там
|
||||
объём Ф5:
|
||||
|
||||
- **таблица «файл → серия»** с живой валидацией дыр и дублей нумерации и
|
||||
кнопкой «нумеровать подряд» — частый случай, когда файлы идут по порядку, но
|
||||
подписаны криво;
|
||||
- **ручной режим при полном провале LLM** — выбрать тип, ввести название и год,
|
||||
разложить файлы руками;
|
||||
- **выбор кандидата метабазы и ввод id прямо в Telegram** — сегодня это только
|
||||
в вебе, из бота идёт эскалация по deep-link.
|
||||
|
||||
Смежное: превью раскладки и единый список источников совпадения уже есть
|
||||
([review](../../../openspec/specs/review/spec.md)), так что задача про
|
||||
редактирование плана, а не про его показ.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
<!-- 2–5 проверяемых утверждений списком, у каждого назван оракул -->
|
||||
|
||||
## Рамки
|
||||
|
||||
<!-- одна строка: чего касаться нельзя, что перезапускается, что считается необратимым -->
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🔬 Потолок нагрузки: 100 одновременных загрузок, план-максимум 1000
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
Потолок по нагрузке нигде не зафиксирован: воркер, поллинг qBittorrent, пул LLM-вызовов и запись в SQLite спроектированы «на глаз». Записать в НФТ целевой ориентир — архитектура держит до 100 одновременных загрузок в работе (приём → распознавание → раскладка), план-максимум — 1000. Сама запись требования дешева и высокоценна: задаёт рамку для решений ниже. Отдельно (дороже) — аудит узких мест: одиночное соединение SQLite и сериализация записи, конкурентность воркера и лимит параллельных распознаваний, частота/стоимость поллинга и дедуп при наплыве.
|
||||
|
||||
Связано: specs/architecture.md → «Отслеживание загрузки»/«Хранилище», пакеты worker, store, qbt, llm.
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🧹 Бэкапить SQLite по расписанию с ротацией
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая)
|
||||
- **Теги:** goal:operational-resilience
|
||||
|
||||
architecture.md требует «бекапить data-том», но как — не описано. Без понятной стратегии сбой или редеплой стирают всё in-flight состояние. Зафиксировать решение и реализовать: периодический VACUUM INTO в /data/backups по расписанию (с ротацией) либо потоковая репликация (litestream). Лучше сделать, пока БД маленькая.
|
||||
|
||||
Связано: specs/architecture.md → «Деплой» (data-том), пакет store.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Слать живые обновления через SSE вместо htmx-поллинга
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер)
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Живые обновления прогресса сейчас на htmx-поллинге (фаза 2 веб-UI) — просто и работает, но с задержкой в интервал опроса и холостыми запросами. Перевести динамический контент (прогресс загрузки, смена статуса, раздача) на Server-Sent Events, чтобы обновления приходили почти мгновенно и без лишнего поллинга. Поллинг работает, поэтому это улучшение, а не блокер; SSE — один долгоживущий ответ на соединение, ложится на server-rendered UI без тяжёлого фронтенда.
|
||||
|
||||
Связано: specs/architecture.md → «Транспорты», specs/review-ux.md, пакет httpapi.
|
||||
@@ -1,12 +0,0 @@
|
||||
# 🎯 По записи загрузки видно, как она сюда попала
|
||||
|
||||
- **Тип:** goal
|
||||
- **Секция:** Направления
|
||||
- **Зачем:** известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
|
||||
- **Теги:** decomposed
|
||||
|
||||
Ради чего: состояние загрузки — то, по чему судят обо всём остальном. Накопились известные щели: окно namer'а, идентичность split v1/v2, потеря маркера dismiss, отсутствие истории переходов.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда по записи загрузки можно ответить «как она сюда попала», ни один известный сегодня путь не оставляет состояние, которое не объясняется историей переходов, и идентичность раздачи не подделывается входом.
|
||||
@@ -1,29 +0,0 @@
|
||||
# 🔬 Тексты и формат уведомлений в Telegram
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Зонтичная задача: пройтись по всем исходящим уведомлениям и запросам подтверждения
|
||||
бота, выправить формулировки, состав данных и оформление. Тексты формируются в
|
||||
`internal/tgbot/render.go`, отправка — `internal/tgbot/bot.go`; parse mode не задан
|
||||
(plain text), моноширинных/жирных акцентов нет.
|
||||
|
||||
На что смотреть при аудите:
|
||||
|
||||
- **Полнота карточек:** показываем ли нужное — название, тип (фильм/сериал), год,
|
||||
запись матча в метабазе, download id, причину `failed`.
|
||||
- **Единый язык:** в текстах бота вперемешку «задача»/«раздача»/«загрузка» —
|
||||
свести к доменному `Download` (см. [[ubiquitous-language-glossary]]).
|
||||
- **Оформление:** моноширинный download id — уже сделано (HTML parse mode +
|
||||
escape всех текстов, capability `notifications`); осталось при желании добавить
|
||||
акценты поверх включённого parse mode.
|
||||
- **Не дублировать** уже заведённое: матч метабазы в боте
|
||||
[[telegram-match-metabazy]], мульти-бот адресация уведомлений
|
||||
[[notification-source-binding]], выбор из нескольких находок [[telegram-vybor-nahodok]].
|
||||
|
||||
Итог аудита — конкретные под-задачи (эта их порождает). Проход дешёвый, при желании
|
||||
приоритет можно поднять.
|
||||
|
||||
Связано: `internal/tgbot`, [review](../../../openspec/specs/review/spec.md).
|
||||
@@ -1,69 +0,0 @@
|
||||
# 🧹 Разобрать кандидатов по тестам и записать конвенцию
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** как пишем тесты, не записано нигде: пункт «Тесты» в convention-candidates не пересматривали, он обещает фикстуры в testdata/, которых в проекте нет — а трение накопилось (четыре внешних клиента, fakeStore с инъекцией ошибок, env-гейты, флаки-прогон, diff-coverage)
|
||||
- **Теги:** sprint:2026-08-06
|
||||
|
||||
В `docs/conventions/` пять записей, и ни одна не про тесты. Знание о том, как
|
||||
они здесь устроены, живёт в головах и в самих файлах: фикстуры чужих форматов
|
||||
константами (`testdata` не заводился сознательно), интеграционные за env-гейтами
|
||||
в `*_integration_test.go`, инъекция ошибок хранилища через `fakeStore`,
|
||||
архитектурные правила тестом-сканером `internal/archrules` с русскими именами,
|
||||
покрытие изменённых строк меряет гейт.
|
||||
|
||||
Пункт «Тесты» в [convention-candidates](convention-candidates.md) с тех пор не
|
||||
пересматривали, и он уже расходится с проектом: обещает фикстуры в `testdata/`,
|
||||
которого нет и который запрещён `CLAUDE.md`. Часть остальных кандидатов
|
||||
проверяется замером, а не мнением: table-driven используется в 18 пакетах из 19,
|
||||
`t.Parallel()` — ни разу, `testify` в `go.mod` не подключён.
|
||||
|
||||
Правило каталога конвенций — «пишем по мере реального трения, а не вперёд».
|
||||
Трение здесь и есть: тема `autotests` идёт первым проходом на любой метке ревью,
|
||||
а дома у неё нет — источником ей служит только семантика гейта из `CLAUDE.md`,
|
||||
то есть «что краснеет», а не «что стоит проверять».
|
||||
|
||||
**Два кандидата пришли из ревью `tvdb-title-locale`** (2026-08-07,
|
||||
[отчёт триажа](../../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md)
|
||||
→ «Promote candidates»), оба с провенансом прохода, а не из головы:
|
||||
|
||||
- **один стенд чужого API на пакет.** В `tvdb_test.go` завелись два фейка одного
|
||||
и того же API. Когда форма ответа поменяется по факту разведки, забытый
|
||||
`fakeTVDB` останется зелёным и продолжит подтверждать опровергнутое
|
||||
предположение;
|
||||
- **интеграционный тест обязан утверждать, а не печатать.** `TestIntegration_TVDB`
|
||||
печатает результат и ничего не проверяет: `PASS` с пустым выводом неотличим от
|
||||
подтверждения. Тест, который нельзя провалить, оракулом не является.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- `docs/conventions/tests.md` — новый файл записи конвенции;
|
||||
- `docs/conventions/README.md` — строка в индексе «Записи» и, возможно, строки в
|
||||
таблице «Механизировано»;
|
||||
- `docs/tasks/items/convention-candidates.md` — пункт «Тесты» уходит из списка
|
||||
кандидатов;
|
||||
- `.golangci.yml` и `internal/archrules` — только если что-то из решённого
|
||||
выражается правилом, а не прозой.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- Каждый кандидат пункта «Тесты» получил исход поимённо: в прозу, в правило или
|
||||
выброшен как выдуманный вперёд. **Оракул:** в `convention-candidates.md`
|
||||
пункта «Тесты» больше нет, а судьба каждого его подпункта названа — в новой
|
||||
конвенции, в таблице «Механизировано» или в `REJECTED`-строке причины.
|
||||
- `docs/conventions/tests.md` заведён и назван в индексе. **Оракул:** `task
|
||||
gate`, шаг `canon` — файл вне канона и битая ссылка краснят безусловно.
|
||||
- Ни одно утверждение конвенции не расходится с тем, как код устроен сегодня:
|
||||
фикстуры описаны как лежат, `testdata` не обещан, число пакетов и приёмов не
|
||||
выдумано. **Оракул:** `grep -r testdata` по репозиторию пуст, и агент
|
||||
`doc-code-drift` на ближайшей сессии не даёт находки по этому файлу.
|
||||
- Выражаемое правилом не осталось прозой. **Оракул:** каждое утверждение новой
|
||||
конвенции либо отсутствует в таблице «Механизировано», либо стоит там с
|
||||
адресом механизации — конфигом линтера или тестом `internal/archrules`.
|
||||
|
||||
## Рамки
|
||||
|
||||
Существующие тесты под новую конвенцию не переписываются — она применяется к
|
||||
тому, что пишется дальше. `testdata/` не заводится: отказ от него записан в
|
||||
`CLAUDE.md`, и его пересмотр — отдельное решение, а не побочный эффект этой
|
||||
задачи.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Заменять разложенную версию тайтла новой (репаки, апгрейд 1080p → 2160p)
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** По калибровке болей (2026-07-02) — не боль, из приоритета выпало
|
||||
- **Теги:** goal:complex-releases
|
||||
|
||||
По калибровке болей (2026-07-02) — не боль, из приоритета выпало. Сосуществование версий доступно уже сейчас (Jellyfin multi-version, другой целевой путь), коллизия на тот же путь штатно уходит в review. Явный replace (undo старого хардлинка → lay нового → супересид владения путём) — отдельный change, если/когда станет болью.
|
||||
|
||||
Связано: «Раздачи с докачиванием», specs/jellyfin-layout.md (never-overwrite, коллизия).
|
||||
@@ -1,22 +0,0 @@
|
||||
# ✨ Принимать ссылку на .torrent и скачивать файл самим (нужен SSRF-гард)
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард)
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Приём magnet и `.torrent`-файла уже реализован: ветка `TorrentData → torrent.Parse`
|
||||
(`internal/ingest/ingest.go`), файл-пикер в веб-форме (`web/templates/index.html`),
|
||||
приём документа в Telegram (`internal/tgbot`). Осталась одна ветка «единого окна» —
|
||||
**URL на `.torrent`**: сервис сам скачивает файл по ссылке и заводит загрузку. Была
|
||||
осознанно вынесена из change `torrent-file-ingest`, потому что требует исходящего
|
||||
запроса на пользовательский URL → нужен SSRF-гард (allowlist схем/хостов, запрет
|
||||
приватных сетей, лимит размера/редиректов).
|
||||
|
||||
Идеал по-прежнему — одно поле, куда кидают текст или файл, а сервис разбирает, что
|
||||
это (magnet / ссылка на .torrent / .torrent-файл / сообщение бота). Сейчас текстовое
|
||||
поле идёт только через `magnet.Parse`.
|
||||
|
||||
Связано: [architecture.md](../../architecture.md) → «Внешние границы и форматы»
|
||||
(`source_type = magnet|torrent|url` уже в схеме), пакет `ingest`, архив change
|
||||
`torrent-file-ingest`.
|
||||
@@ -1,72 +0,0 @@
|
||||
# 🔬 Форма ответа поиска TheTVDB и семантика параметра language
|
||||
|
||||
- **Тип:** research
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** форма ответа поиска TheTVDB принята по swagger 4.7.10 и живым прогоном не подтверждена — при иной форме разбор молча уходит в фолбэк, гейт зелёный, локализованное название не работает
|
||||
- **Теги:** goal:recognition-accuracy, sprint:2026-08-06
|
||||
|
||||
Задача `tvdb-title-locale` научила клиент TVDB брать локализованное название из
|
||||
блока переводов ответа `/search` и заполнять `OriginalTitle` primary name'ом. Но
|
||||
живым прогоном форма ответа не сверялась: `CLAUDE.md` → «Запреты» запрещает
|
||||
ходить в боевые метабазы из отладочных прогонов и расходовать лимиты ключа.
|
||||
Форма взята из публичной документации (swagger TheTVDB v4, версия `4.7.10`) и
|
||||
записана в [docs/research/tvdb-search-translations.md](../../research/tvdb-search-translations.md)
|
||||
как **условие, а не замер**.
|
||||
|
||||
Разведка нужна потому, что ошибка предположения **не наблюдаема**: разбор уйдёт в
|
||||
тотальный фолбэк, `Title` станет равен primary name, то есть исход побайтно
|
||||
совпадёт с поведением до задачи. Гейт зелёный, карточка ревью прежняя, фича не
|
||||
работает. Единственный след — DEBUG-строка «в выдаче не разобрался ни один блок
|
||||
переводов».
|
||||
|
||||
## Вопрос
|
||||
|
||||
Какова реальная форма блока переводов в ответе `/search` TheTVDB v4 — карта
|
||||
«трёхбуквенный код языка → название» или иная, — и правда ли параметр `language`
|
||||
этого эндпоинта сужает выдачу по основному языку записи, а не выбирает перевод?
|
||||
|
||||
## Куда ляжет ответ
|
||||
|
||||
- [docs/research/tvdb-search-translations.md](../../research/tvdb-search-translations.md):
|
||||
предположения заменяются наблюдениями с датой прогона, а строка «живым
|
||||
прогоном не подтверждено» — результатом. Условие пересмотра там уже записано.
|
||||
- Решение по трём развилкам, оставшимся от `tvdb-title-locale`:
|
||||
1. **Критерий приёмки про параметр языка в запросе.** Задача требовала, чтобы
|
||||
запрос поиска содержал параметр языка из `[general].language`. При
|
||||
реализации критерий отменён: по документации параметр — фильтр («Restrict
|
||||
results to a specific primary language»), и его передача отсекла бы ровно
|
||||
иноязычные записи, ради которых задача заводилась. Тест сейчас проверяет
|
||||
обратное — что параметра нет. Критерий менял исполнитель, а не приёмщик;
|
||||
нужно решение человека: переписать критерий под факт, отвергнуть решение
|
||||
или подтвердить семантику прогоном и решить по факту. Рекомендация —
|
||||
подтвердить прогоном, затем переписать критерий.
|
||||
2. **Форма блока `translations`.** Подтвердить карту и трёхбуквенность ключей
|
||||
либо починить разбор под реальную форму.
|
||||
3. **Граница авто-раскладки сдвинулась в обе стороны.** Заполнение
|
||||
`OriginalTitle` даёт кандидату TVDB два названия вместо одного. Логика
|
||||
гейта не менялась, вход изменился: запись, которую отсекал иероглифический
|
||||
primary name, теперь может пройти по переводу (review → авто), а две разные
|
||||
записи могут совпасть с планом разными названиями (авто → review —
|
||||
франшиза с одним русским названием и годами в пределах ±1). Рекомендация —
|
||||
принять как есть: движение вниз ведёт к человеку, движение вверх и есть
|
||||
польза задачи, и это ровно то, как уже работает TMDB. Альтернатива —
|
||||
сравнивать у TVDB только по `OriginalTitle`, но тогда пропадает польза от
|
||||
совпадения по переводу.
|
||||
|
||||
## Рамки
|
||||
|
||||
Оракул уже написан и лежит в репозитории — прогоняется вручную, человеком, под
|
||||
своим ключом:
|
||||
|
||||
```
|
||||
TVDB_API_KEY=… go test ./internal/metadata/ -run Integration -v
|
||||
```
|
||||
|
||||
Он печатает `Title` и `OriginalTitle` для `Fargo` и для иноязычной записи
|
||||
`Ne Zha` (movie 131155, primary name `哪吒之魔童降世`, русский перевод «Нэчжа»).
|
||||
Расхождение `Title` и `OriginalTitle` у второй записи подтверждает форму;
|
||||
совпадение означает, что перевод не доехал.
|
||||
|
||||
Автоматическим прогоном разведка не делается: лимиты ключа беречь, в гейт этот
|
||||
тест не заводить. Кода менять не требуется — исход разведки это запись; правка
|
||||
разбора, если форма окажется иной, заводится отдельной задачей.
|
||||
@@ -1,10 +0,0 @@
|
||||
# 🧹 Свести термины домена в словарь единого языка
|
||||
|
||||
- **Тип:** chore
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований)
|
||||
- **Теги:** goal:dev-process-quality
|
||||
|
||||
Свести термины домена в один глоссарий, чтобы пользователь, документация, код и агент говорили на одном языке: загрузка, раздача, распознавание, матч, кандидат, раскладка, источник/цель, хардлинк, ревью, переход состояния и т.д. — русский термин, английский идентификатор в коде, краткое определение. Сейчас наименования расходятся между спеками, UI и кодом. Глоссарий — источник истины по именам; на нём же строится агент-ревьювер наименований.
|
||||
|
||||
Связано: docs/conventions, specs/architecture.md, новый файл-глоссарий.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Закрыть веб-UI авторизацией, когда доверенной LAN станет мало
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Инфраструктура
|
||||
- **Зачем:** для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Решено для v1: без авторизации в доверенной LAN, опц. allowlist подсетей (http.trusted_subnets) — как умеет qBittorrent. Если понадобится защита: токен/Basic в самом приложении или вынос за reverse-proxy с аутентификацией.
|
||||
|
||||
Связано: specs/architecture.md → «Транспорты» (доступ к веб-UI), пакет httpapi.
|
||||
@@ -1,10 +0,0 @@
|
||||
# ✨ Переделать веб-UI в устанавливаемое PWA
|
||||
|
||||
- **Тип:** feature
|
||||
- **Категория:** Ядро продукта
|
||||
- **Зачем:** текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
|
||||
- **Теги:** goal:ingest-and-review-interfaces
|
||||
|
||||
Переделать веб-интерфейс в современное PWA-приложение (устанавливаемое, отзывчивое, удобное с телефона). Текущий server-rendered UI функционален, поэтому это улучшение, а не блокер; большой объём работы.
|
||||
|
||||
Связано: specs/review-ux.md (веб = точные правки), пакет httpapi.
|
||||
Reference in New Issue
Block a user