sprint: набран спринт 2026-08-06 под цель распознавания
- в наборе шесть задач: локаль TVDB и confidence-гейт под цель, плюс баги и техдолг помимо неё - взятым дописаны разделы своего типа: «Затрагивает», критерии с оракулами, воспроизведение - решены развилки: оба расхождения код↔спека правятся спекой (и потому стали chore), опрос qBittorrent тормозится бэкоффом до минутного потолка
This commit is contained in:
@@ -12,14 +12,10 @@
|
|||||||
## Ядро продукта
|
## Ядро продукта
|
||||||
|
|
||||||
- [✨ Пересчитывать абсолютную нумерацию аниме в SxxEyy](items/anime-absolute-numbering.md) — аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации
|
- [✨ Пересчитывать абсолютную нумерацию аниме в SxxEyy](items/anime-absolute-numbering.md) — аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации
|
||||||
- [🐞 Пересобирать `addReq` из свежего `source_type` перед `Add` (окно namer'а)](items/catched-source-type-refresh.md) — При апгрейде magnet→.torrent в окне namer'а добавится magnet из устаревшего снимка; самоисцеляется через magnet_timeout→failed→Retry _(аудит 2026-07-17)_
|
|
||||||
- [✨ Раскладывать раздачу-копию диска (VIDEO_TS/BDMV) каталогом целиком](items/disc-image-releases.md) — раздача-образ диска (VIDEO_TS/BDMV) сейчас разбирается пофайлово вместо раскладки каталога целиком — редкий, но реальный случай
|
- [✨ Раскладывать раздачу-копию диска (VIDEO_TS/BDMV) каталогом целиком](items/disc-image-releases.md) — раздача-образ диска (VIDEO_TS/BDMV) сейчас разбирается пофайлово вместо раскладки каталога целиком — редкий, но реальный случай
|
||||||
- [🐞 Не терять маркер `user_dismiss` при закрытии не-терминальной загрузки из веб-UI](items/dismiss-marker-lost.md) — Функционально ок (Cancel даёт cancelled), но маркер user_dismiss в error_code теряется; расхождение с буквой спеки _(аудит 2026-07-17)_
|
|
||||||
- [✨ Принимать ссылку на .torrent и скачивать файл самим (нужен SSRF-гард)](items/torrent-url-fetch.md) — magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард)
|
- [✨ Принимать ссылку на .torrent и скачивать файл самим (нужен SSRF-гард)](items/torrent-url-fetch.md) — magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард)
|
||||||
- [✨ Узаконить confidence-гейт авто-раскладки в спеке и сделать его выключаемым (дефолт 0.7)](items/auto-link-confidence-gate.md) — Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
|
|
||||||
- [✨ Докладывать недостающие эпизоды merge-раскладкой при повторной заливке](items/merge-incremental-redownload.md) — повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов
|
- [✨ Докладывать недостающие эпизоды merge-раскладкой при повторной заливке](items/merge-incremental-redownload.md) — повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов
|
||||||
- [🐞 Связать v1/v2-хеши одного торрента и не доверять паре xt из магнета (F4, F5)](items/infohash-identity-integrity.md) — split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_
|
- [🐞 Связать v1/v2-хеши одного торрента и не доверять паре xt из магнета (F4, F5)](items/infohash-identity-integrity.md) — split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_
|
||||||
- [🧹 Закрыть мелочи приёма: NoName в контексте, устаревшие комментарии, лог без причины, bencode-аллокации (N1, N3, N4, N5)](items/ingest-nits.md) — косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_
|
|
||||||
- [✨ Слать живые обновления через SSE вместо htmx-поллинга](items/sse-live-updates.md) — живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер)
|
- [✨ Слать живые обновления через SSE вместо htmx-поллинга](items/sse-live-updates.md) — живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер)
|
||||||
- [✨ Проверять свободное место перед copy-fallback](items/free-space-check-copy-fallback.md) — copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования
|
- [✨ Проверять свободное место перед copy-fallback](items/free-space-check-copy-fallback.md) — copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования
|
||||||
- [✨ Слать уведомления автору загрузки в его транспорт (мульти-бот)](items/notification-source-binding.md) — пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота)
|
- [✨ Слать уведомления автору загрузки в его транспорт (мульти-бот)](items/notification-source-binding.md) — пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота)
|
||||||
@@ -28,7 +24,6 @@
|
|||||||
- [✨ Переделать веб-UI в устанавливаемое PWA](items/web-ui-pwa.md) — текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
|
- [✨ Переделать веб-UI в устанавливаемое PWA](items/web-ui-pwa.md) — текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
|
||||||
- [✨ Править на ревью маппинг «файл → серия» и раскладывать вручную при провале LLM](items/review-mapping-editor.md) — правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
|
- [✨ Править на ревью маппинг «файл → серия» и раскладывать вручную при провале LLM](items/review-mapping-editor.md) — правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
|
||||||
- [✨ Заказать спекой крайние случаи именования: многофайловый фильм, редакции, двойная серия](items/naming-edge-cases.md) — стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
|
- [✨ Заказать спекой крайние случаи именования: многофайловый фильм, редакции, двойная серия](items/naming-edge-cases.md) — стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
|
||||||
- [✨ Брать у TVDB название на языке настройки и оригинальное название](items/tvdb-title-locale.md) — [general].language правит только TMDB и промпт LLM — TVDB отдаёт primary name, и при language=ru в карточку ревью и имя папки попадает 哪吒之魔童降世 вместо «Нэчжа»
|
|
||||||
- [🔬 Канон нумерации серий и порядок у провайдера тега](items/episode-numbering-canon.md) — Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
|
- [🔬 Канон нумерации серий и порядок у провайдера тега](items/episode-numbering-canon.md) — Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
|
||||||
- [🔬 Тексты и формат уведомлений в Telegram](items/telegram-messages-audit.md) — зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
|
- [🔬 Тексты и формат уведомлений в Telegram](items/telegram-messages-audit.md) — зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
|
||||||
- [🔬 guessit как сервис-спутник](items/guessit-sidecar.md) — go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
|
- [🔬 guessit как сервис-спутник](items/guessit-sidecar.md) — go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
|
||||||
@@ -44,7 +39,6 @@
|
|||||||
- [✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI](items/deep-healthcheck-dependencies.md) — /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
|
- [✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI](items/deep-healthcheck-dependencies.md) — /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
|
||||||
- [✨ Хранить историю переходов загрузки отдельной таблицей](items/download-transition-history.md) — хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов
|
- [✨ Хранить историю переходов загрузки отдельной таблицей](items/download-transition-history.md) — хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов
|
||||||
- [🧹 Кэшировать ответы метабаз с TTL (и опционально LLM)](items/metadata-cache.md) — повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново»
|
- [🧹 Кэшировать ответы метабаз с TTL (и опционально LLM)](items/metadata-cache.md) — повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново»
|
||||||
- [🐞 Не штормить ERROR при недоступном qBittorrent и эскалировать устойчивый сбой тика](items/background-error-noise.md) — Остаток задачи логирования: ext.* ERROR-шторм при недоступном qBittorrent + эскалация устойчивого сбоя тика _(ревью Fable)_
|
|
||||||
- [✨ Чистить БД от терминальных задач и сырых ответов LLM старше срока хранения](items/db-retention-cleanup.md) — терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации
|
- [✨ Чистить БД от терминальных задач и сырых ответов LLM старше срока хранения](items/db-retention-cleanup.md) — терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации
|
||||||
- [🧹 Свести термины домена в словарь единого языка](items/ubiquitous-language-glossary.md) — наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований)
|
- [🧹 Свести термины домена в словарь единого языка](items/ubiquitous-language-glossary.md) — наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований)
|
||||||
- [🔬 Потолок нагрузки: 100 одновременных загрузок, план-максимум 1000](items/scale-100-downloads.md) — Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
|
- [🔬 Потолок нагрузки: 100 одновременных загрузок, план-максимум 1000](items/scale-100-downloads.md) — Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
|
||||||
|
|||||||
+12
-2
@@ -1,6 +1,16 @@
|
|||||||
# Спринт
|
# Спринт
|
||||||
|
|
||||||
Спринта нет. Цель называет человек, набор собирает агент:
|
- **Цель:** [🎯 Раздача узнаётся верно без подсказок человека](items/recognition-accuracy.md)
|
||||||
`tasks.py sprint start --goal <слаг>`.
|
- **Начат:** 2026-08-06
|
||||||
|
- **Спринт:** `2026-08-06`
|
||||||
|
|
||||||
|
Урожай спринта поднимается `tasks.py list --tag sprint:2026-08-06` — это первая порция переоценки на сессии.
|
||||||
|
|
||||||
## Набор
|
## Набор
|
||||||
|
|
||||||
|
- [✨ Брать у TVDB название на языке настройки и оригинальное название](items/tvdb-title-locale.md) — [general].language правит только TMDB и промпт LLM — TVDB отдаёт primary name, и при language=ru в карточку ревью и имя папки попадает 哪吒之魔童降世 вместо «Нэчжа»
|
||||||
|
- [✨ Узаконить confidence-гейт авто-раскладки в спеке и сделать его выключаемым (дефолт 0.7)](items/auto-link-confidence-gate.md) — Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
|
||||||
|
- [🧹 Уточнить в спеке требование о re-read `source_type` перед `Add`](items/catched-source-type-refresh.md) — спека требует перечитывать source_type непосредственно перед добавлением, код перечитывает после тик-снимка — узкое namer-окно самоисцеляется через Retry и признано допустимым
|
||||||
|
- [🧹 Зафиксировать в спеке разделение Cancel и Dismiss по состояниям](items/dismiss-marker-lost.md) — спека обещает dismiss из любого состояния, код осознанно даёт Cancel для активных и Dismiss для терминальных — расходится буква, а не поведение
|
||||||
|
- [🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой](items/background-error-noise.md) — недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации
|
||||||
|
- [🧹 Закрыть мелочи приёма: NoName в контексте, устаревшие комментарии, лог без причины, bencode-аллокации (N1, N3, N4, N5)](items/ingest-nits.md) — косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_
|
||||||
|
|||||||
@@ -49,6 +49,31 @@
|
|||||||
которому порог можно было бы подобрать заранее, решено не собирать
|
которому порог можно было бы подобрать заранее, решено не собирать
|
||||||
(`../REJECTED.md`, 2026-08-06), так что первое значение остаётся оценкой.
|
(`../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` + правки
|
Оформить как OpenSpec-change (дельта `recognition` + правки
|
||||||
`validate.go`/`recognize.go`/`config`).
|
`validate.go`/`recognize.go`/`config`).
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
# 🐞 Не штормить ERROR при недоступном qBittorrent и эскалировать устойчивый сбой тика
|
# 🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой
|
||||||
|
|
||||||
- **Тип:** fix
|
- **Тип:** fix
|
||||||
- **Категория:** Инфраструктура
|
- **Категория:** Инфраструктура
|
||||||
- **Зачем:** Остаток задачи логирования: ext.* ERROR-шторм при недоступном qBittorrent + эскалация устойчивого сбоя тика _(ревью Fable)_
|
- **Зачем:** недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации
|
||||||
- **Теги:** goal:operational-resilience
|
|
||||||
|
|
||||||
Остаток от задачи «классификация доменных ошибок + конвенции логирования»
|
Остаток от задачи «классификация доменных ошибок + конвенции логирования»
|
||||||
(основное реализовано, см. ниже). Здесь — два смежных пункта про уровень
|
(основное реализовано, см. ниже) плюс бэкофф опроса, заказанный 2026-08-06.
|
||||||
повторяющихся сбоев фоновых циклов, каждый требует небольшого решения, а не
|
Речь о поведении фонового цикла, пока зависимость лежит: с какой частотой он её
|
||||||
только правки.
|
дёргает и каким уровнем об этом пишет.
|
||||||
|
|
||||||
## Что уже сделано (не переоткрывать)
|
## Что уже сделано (не переоткрывать)
|
||||||
|
|
||||||
@@ -29,29 +28,63 @@
|
|||||||
|
|
||||||
## Остаток
|
## Остаток
|
||||||
|
|
||||||
### ERROR-шторм при недоступном qBittorrent
|
**Шум `ext.*` ERROR решено оставить как есть (2026-08-06).** Запись
|
||||||
|
«зависимость недоступна» на каждом тике — легитимный сигнал транспортного слоя,
|
||||||
|
и гасится он уровнем сбора логов, а не кодом. Варианты с пониженным уровнем у
|
||||||
|
`logging.ExtCall` и с дедупом отклонены: первый заводит второе правило уровня
|
||||||
|
для того же класса вызовов, второй даёт транспортному логгеру память о
|
||||||
|
состоянии.
|
||||||
|
|
||||||
Клиент `qbt` логирует `ext.*` `Failure` → **ERROR** на каждом тике поллинга
|
Остаются две вещи, и обе стоят на одном счётчике подряд-идущих сбоев тика.
|
||||||
(`torrents/info`, `internal/qbt/qbt.go`), пока qBittorrent недоступен (рестарт
|
|
||||||
демона, сеть). Домен уже пишет `poll failed` = WARN (по новой конвенции), но
|
|
||||||
транспортная `ext.*`-запись остаётся ERROR по правилу ext-конвенции («сервис
|
|
||||||
недоступен → ERROR»). При частом поллинге это шумит.
|
|
||||||
|
|
||||||
Развилка (решить до правки):
|
**Бэкофф опроса (решение 2026-08-06).** Пока qBittorrent недоступен, цикл
|
||||||
|
продолжает дёргать его каждые `poll_interval` (5 с) — недоступную зависимость
|
||||||
|
незачем опрашивать с рабочей частотой. Интервал растёт экспоненциально от
|
||||||
|
`poll_interval` до потолка порядка минуты; первый успешный ответ возвращает
|
||||||
|
рабочий интервал сразу, без ступенчатого спуска. Бэкофф заодно снимает и остроту
|
||||||
|
шума: записей становится столько же на событие, но событий — единицы в минуту.
|
||||||
|
|
||||||
- (а) Ввести у `logging.ExtCall` вариант с пониженным уровнем для рутинно-частых
|
**Эскалация уровня.** Сейчас сбой тика — **всегда WARN**, сколько бы тиков
|
||||||
вызовов (симметрично `SuccessDebug`) — поллинг-вызовы (`torrents/info`) на
|
подряд он ни падал. `docs/conventions/logging.md` требует иного: устойчивый сбой
|
||||||
транзиентном сбое пишут WARN, не ERROR;
|
N тиков подряд — это реальная деградация, и она пишется ERROR.
|
||||||
- (б) Дедуп/circuit-breaker: первый ERROR, дальше тишина до восстановления;
|
|
||||||
- (в) Оставить как есть, признав `ext.*` ERROR легитимным сигналом «зависимость
|
|
||||||
лежит» (тогда шум гасить уровнем сбора, а не кодом).
|
|
||||||
|
|
||||||
### Эскалация устойчивого сбоя тика
|
## Воспроизведение
|
||||||
|
|
||||||
Сейчас транзиентный сбой тика = WARN всегда. Договорённость на будущее
|
1. Остановить qBittorrent (локально, не на umbar).
|
||||||
(`logging.md`): устойчивый сбой N тиков подряд эскалировать в ERROR (реальная
|
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,9 +1,8 @@
|
|||||||
# 🐞 Пересобирать `addReq` из свежего `source_type` перед `Add` (окно namer'а)
|
# 🧹 Уточнить в спеке требование о re-read `source_type` перед `Add`
|
||||||
|
|
||||||
- **Тип:** fix
|
- **Тип:** chore
|
||||||
- **Категория:** Ядро продукта
|
- **Категория:** Ядро продукта
|
||||||
- **Зачем:** При апгрейде magnet→.torrent в окне namer'а добавится magnet из устаревшего снимка; самоисцеляется через magnet_timeout→failed→Retry _(аудит 2026-07-17)_
|
- **Зачем:** спека требует перечитывать source_type непосредственно перед добавлением, код перечитывает после тик-снимка — узкое namer-окно самоисцеляется через Retry и признано допустимым
|
||||||
- **Теги:** goal:state-integrity
|
|
||||||
|
|
||||||
Найдено аудитом capability **download-tracking** (сверка код↔спека после пачки
|
Найдено аудитом capability **download-tracking** (сверка код↔спека после пачки
|
||||||
lifecycle-задач). Пред-существующее, вне scope задачи F3/cancel-cleanup — T4
|
lifecycle-задач). Пред-существующее, вне scope задачи F3/cancel-cleanup — T4
|
||||||
@@ -35,22 +34,42 @@ lifecycle-задач). Пред-существующее, вне scope зада
|
|||||||
«источник неприкосновенен» не задет. Окно узкое (апгрейд должен лечь ровно в
|
«источник неприкосновенен» не задет. Окно узкое (апгрейд должен лечь ровно в
|
||||||
LLM-вызов по тому же infohash). Поэтому средний, не высокий.
|
LLM-вызов по тому же infohash). Поэтому средний, не высокий.
|
||||||
|
|
||||||
## Развилка (решить до кода)
|
## Решение (2026-08-06): B — привести спеку к коду
|
||||||
|
|
||||||
- **A — ужесточить код (соответствие букве спеки, закрыть окно):** после re-read
|
Рациональ требования — «не полагаться на снимок, снятый ранее вне блокировки» —
|
||||||
`before` под замком (`:484`) пересобирать `addReq`/`hint` из `before`, если
|
уже выполнен первым re-read под замком на `:447`. Требование переформулируется
|
||||||
`source_type` изменился. Нюанс: `sourceAddParts` читает байты `.torrent` — это
|
как «перечитывать `source_type` под блокировкой после тик-снимка», а узкое
|
||||||
тяжёлый вызов, держать под замком нельзя (спека: тяжёлое — вне блокировки), плюс
|
namer-окно принимается явно: оно самоисцеляется через `magnet_timeout` →
|
||||||
подсказка имени для `.torrent` иная (метаданные раздачи vs имя из magnet), т.е.
|
`failed` → `Retry`.
|
||||||
при апгрейде корректно был бы и повторный namer. Не однострочник.
|
|
||||||
- **B — смягчить спеку (принять реальность):** признать, что рациональ («не
|
|
||||||
полагаться на снимок, снятый ранее вне блокировки») уже выполнен первым re-read
|
|
||||||
под замком на `:447`, и переформулировать требование как «перечитывать
|
|
||||||
`source_type` под блокировкой после тик-снимка», явно приняв узкое namer-окно
|
|
||||||
как самоисцеляемое через `Retry`.
|
|
||||||
|
|
||||||
Рекомендация — начать с B (дёшево, отражает фактическое осознанное поведение), A
|
Вариант A (пересобирать `addReq` из `before` под замком) отклонён: не
|
||||||
завести только если узкое окно окажется реальной болью в эксплуатации.
|
однострочник — `sourceAddParts` читает байты `.torrent`, держать это под
|
||||||
|
блокировкой нельзя, а при апгрейде корректно был бы и повторный вызов namer'а.
|
||||||
|
Заводить его отдельной задачей, только если узкое окно окажется реальной болью в
|
||||||
|
эксплуатации.
|
||||||
|
|
||||||
|
Кода задача не трогает: наблюдаемое поведение остаётся прежним, меняется
|
||||||
|
заявленное.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `openspec/specs/download-tracking/spec.md` — требование про re-read
|
||||||
|
`source_type` перед добавлением, его формулировка и сценарии;
|
||||||
|
- дельта-спека change'а — новых сценариев с namer-окном может потребоваться два
|
||||||
|
(апгрейд до тик-снимка и апгрейд в окне namer'а);
|
||||||
|
- `internal/worker/worker.go` — только чтение, правок не предполагается.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Требование спеки описывает фактическое поведение: re-read `source_type` под
|
||||||
|
блокировкой после тик-снимка, апгрейд в окне namer'а назван допустимым и
|
||||||
|
самоисцеляемым (оракул: `openspec validate --strict`).
|
||||||
|
- В спеке есть сценарий, покрывающий апгрейд в окне namer'а с исходом «magnet из
|
||||||
|
снимка, дальше `magnet_timeout` → `failed` → `Retry`» (оракул: тот же прогон
|
||||||
|
плюс существующий `TestProcessCatchedReReadsSourceTypeUnderLock` продолжает
|
||||||
|
проходить без правок).
|
||||||
|
- Ни один файл под `internal/` в диффе не изменён (оракул: `git diff --stat`
|
||||||
|
в отчёте ревью).
|
||||||
|
|
||||||
## Ссылки
|
## Ссылки
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# 🐞 Не терять маркер `user_dismiss` при закрытии не-терминальной загрузки из веб-UI
|
# 🧹 Зафиксировать в спеке разделение Cancel и Dismiss по состояниям
|
||||||
|
|
||||||
- **Тип:** fix
|
- **Тип:** chore
|
||||||
- **Категория:** Ядро продукта
|
- **Категория:** Ядро продукта
|
||||||
- **Зачем:** Функционально ок (Cancel даёт cancelled), но маркер user_dismiss в error_code теряется; расхождение с буквой спеки _(аудит 2026-07-17)_
|
- **Зачем:** спека обещает dismiss из любого состояния, код осознанно даёт Cancel для активных и Dismiss для терминальных — расходится буква, а не поведение
|
||||||
- **Теги:** goal:state-integrity
|
|
||||||
|
|
||||||
Найдено аудитом capability **state-reconciliation** (сверка код↔спека).
|
Найдено аудитом capability **state-reconciliation** (сверка код↔спека).
|
||||||
Пред-существующее, вне scope пачки lifecycle-задач.
|
Пред-существующее, вне scope пачки lifecycle-задач.
|
||||||
@@ -31,16 +30,36 @@
|
|||||||
наблюдаемости (в аналитике/логах не отличить «пользователь закрыл активную» от
|
наблюдаемости (в аналитике/логах не отличить «пользователь закрыл активную» от
|
||||||
«пользователь отменил»). Отсюда низкий приоритет.
|
«пользователь отменил»). Отсюда низкий приоритет.
|
||||||
|
|
||||||
## Развилка (решить до кода)
|
## Решение (2026-08-06): B — привести спеку к коду
|
||||||
|
|
||||||
- **A — привести код к спеке:** веб-UI на не-терминальных тоже зовёт `Dismiss`
|
Разделение осознанное: `Cancel` — стоп-кран для активных состояний, `Dismiss` —
|
||||||
ради единого маркера `user_dismiss`; либо `Cancel` пишет `user_dismiss`.
|
закрытие терминальных. Требование «Ручное закрытие» уточняется: на
|
||||||
- **B — привести спеку к коду:** зафиксировать осознанное разделение (`Cancel`
|
не-терминальных состояниях закрытие из интерфейса реализуется `Cancel`'ом, и
|
||||||
для активных, `Dismiss` для терминальных) — уточнить требование, что стоп-кран
|
называется, какой `error_code` при этом ожидается.
|
||||||
на не-терминальных реализуется `Cancel`'ом, и определить, какой `error_code`
|
|
||||||
ожидается.
|
|
||||||
|
|
||||||
Сначала решить, осознанно ли разделение Cancel/Dismiss; если да — вероятно B.
|
Вариант A (звать `Dismiss` из веб-UI на не-терминальных ради единого маркера)
|
||||||
|
отклонён: он меняет рабочее поведение ради маркера в диагностике.
|
||||||
|
|
||||||
|
Кода задача не трогает: наблюдаемое поведение остаётся прежним, меняется
|
||||||
|
заявленное.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `openspec/specs/state-reconciliation/spec.md` — требование «Ручное закрытие»:
|
||||||
|
доступность `dismiss` по состояниям и ожидаемый `error_code`;
|
||||||
|
- дельта-спека change'а — сценарий закрытия не-терминальной загрузки из веб-UI;
|
||||||
|
- `internal/httpapi/download.go`, `web/templates/partials/download_main.html` —
|
||||||
|
только чтение, правок не предполагается.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Требование спеки различает `Cancel` и `Dismiss` по состояниям и называет
|
||||||
|
`error_code` для каждого пути закрытия (оракул: `openspec validate --strict`).
|
||||||
|
- В спеке есть сценарий «пользователь закрывает не-терминальную загрузку из
|
||||||
|
веб-UI» с исходом `cancelled` и названным `error_code` (оракул: тот же
|
||||||
|
прогон).
|
||||||
|
- Гейт `Dismissable` в коде и danger-zone шаблона остаются как есть (оракул:
|
||||||
|
`git diff --stat` в отчёте ревью — файлов под `internal/` и `web/` нет).
|
||||||
|
|
||||||
## Ссылки
|
## Ссылки
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
- **Тип:** chore
|
- **Тип:** chore
|
||||||
- **Категория:** Ядро продукта
|
- **Категория:** Ядро продукта
|
||||||
- **Зачем:** косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_
|
- **Зачем:** косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_
|
||||||
- **Теги:** goal:state-integrity
|
|
||||||
|
|
||||||
Ревью Fable 2026-07-08 (приём). Косметические нити.
|
Ревью Fable 2026-07-08 (приём). Косметические нити.
|
||||||
|
|
||||||
@@ -16,3 +15,26 @@ N4 — qbt.go:246 логирует «Fails.» со счётчиками, но qB
|
|||||||
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-путей там не найдено.)
|
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-путей там не найдено.)
|
||||||
|
|
||||||
Вердикт: простые фиксы/принять.
|
Вердикт: простые фиксы/принять.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/torrent/torrent.go` — `Context()` и фильтр NoName-сентинела «-»;
|
||||||
|
- `internal/httpapi/httpapi.go` и `internal/tgbot/bot.go` — устаревшие
|
||||||
|
комментарии про непустой `DownloadID` на пути ошибки;
|
||||||
|
- `internal/qbt/qbt.go` — лог `Fails.` без причины;
|
||||||
|
- N5 (аллокации bencode в `anacrolix/torrent`) — граница чужой библиотеки,
|
||||||
|
правке не подлежит: исход пункта — запись наблюдения, а не код.
|
||||||
|
|
||||||
|
## Критерии приёмки
|
||||||
|
|
||||||
|
- Для безымянного торрента `Context()` не отдаёт «-» как название — поле пустое
|
||||||
|
(оракул: тест разбора на фикстуре безымянного торрента в
|
||||||
|
`internal/torrent`).
|
||||||
|
- Комментарии в `httpapi` и `tgbot` описывают фактическое поведение `Ingest`:
|
||||||
|
на любом пути ошибки возвращается пустой `Result`, корреляция идёт по
|
||||||
|
`request_id` (оракул: чтение диффа на ревью — механического оракула нет).
|
||||||
|
- Лог неудачного добавления в qBittorrent несёт инфохэш для корреляции (оракул:
|
||||||
|
тест клиента с подставным сервером, проверяющий поля записи).
|
||||||
|
- Наблюдение про аллокации bencode до `MaxStrLen` записано в
|
||||||
|
`docs/research/` с провенансом либо явно отклонено строкой в теле задачи
|
||||||
|
(оракул: `task gate`, шаг канона).
|
||||||
|
|||||||
@@ -32,6 +32,16 @@
|
|||||||
([metadata-match](../../../openspec/specs/metadata-match/spec.md)) написано
|
([metadata-match](../../../openspec/specs/metadata-match/spec.md)) написано
|
||||||
только под TMDB — либо обобщается на провайдеров, либо получает соседа.
|
только под TMDB — либо обобщается на провайдеров, либо получает соседа.
|
||||||
|
|
||||||
|
## Затрагивает
|
||||||
|
|
||||||
|
- `internal/metadata/tvdb.go` — `TVDBConfig` (поле языка), строка запроса
|
||||||
|
`Search`, разбор переводов и `OriginalTitle` в кандидате;
|
||||||
|
- `cmd/jellybit/serve.go` — сборка провайдера TVDB из конфига;
|
||||||
|
- `openspec/specs/metadata-match/spec.md` — требование «Локаль запроса к TMDB»:
|
||||||
|
обобщается на провайдеров либо получает соседа под TVDB;
|
||||||
|
- внешний контракт: поиск TVDB (`/search`) и его блок переводов — формат
|
||||||
|
сверяется живым прогоном под `TVDB_API_KEY`, наугад не пишется.
|
||||||
|
|
||||||
## Критерии приёмки
|
## Критерии приёмки
|
||||||
|
|
||||||
- Запрос поиска TVDB содержит параметр языка, выведенный из `[general].language`
|
- Запрос поиска TVDB содержит параметр языка, выведенный из `[general].language`
|
||||||
|
|||||||
Reference in New Issue
Block a user