docs: проект переведён на канон документов версии 4

- каталог задач: PLAN.md → ROADMAP.md с каноническими секциями, все 44
  записи получили тип, заголовки приведены к форме своего типа
- расхождения, найденные судьями канона: исключения инварианта «источник
  неприкосновенен», инвариант про один активный infohash, UTC в logging.md,
  поведение из architecture.md заменено ссылками на спеки
- триггеры профиля ревью переписаны под умолчание standard
This commit is contained in:
av
2026-08-06 13:32:06 +03:00
parent 905a1ee4d3
commit 66d39297c5
66 changed files with 334 additions and 204 deletions
+29 -7
View File
@@ -22,7 +22,9 @@ Driven Development** через OpenSpec — см. раздел ниже.
Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module path — Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module path —
`git.vakhrushev.me/av/jellybit`. SQLite через `modernc.org/sqlite` + `sqlx`, `git.vakhrushev.me/av/jellybit`. SQLite через `modernc.org/sqlite` + `sqlx`,
миграции `goose`, HTTP — `chi` + `html/template` + htmx, конфиг — миграции `goose`, HTTP — `chi` + `html/template` + htmx, конфиг —
`pelletier/go-toml/v2`, логи — `log/slog` (структурированный JSON). `pelletier/go-toml/v2`, разбор `.torrent` и инфохэшей — `anacrolix/torrent`,
пред-парс имени раздачи — `middelink/go-parse-torrent-name`, логи — `log/slog`
(структурированный JSON).
## Инварианты ## Инварианты
@@ -32,6 +34,15 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
- **Источник неприкосновенен** — под `paths.downloads` допустимы только чтение - **Источник неприкосновенен** — под `paths.downloads` допустимы только чтение
и `link(2)`; никаких `unlink`, `rename`, записи. Нарушение уничтожает и `link(2)`; никаких `unlink`, `rename`, записи. Нарушение уничтожает
невосстановимые данные пользователя. **Необратимо. `critical`.** невосстановимые данные пользователя. **Необратимо. `critical`.**
Исключения два, и оба — не наши операции с файловой системой, а вызов
`torrents/delete` qBittorrent с `deleteFiles=true`: (1) `Delete` из
`done`/`orphaned`/`target_missing` по явному подтверждению человека — гард
последней копии там выключен сознательно
([state-reconciliation](openspec/specs/state-reconciliation/spec.md));
(2) уборка воркером **собственного** торрента, добавленного этим же `add`
секундами ранее, когда задачу отменили в окне после `add`
([download-tracking](openspec/specs/download-tracking/spec.md)). Всё
остальное под `paths.downloads` — по-прежнему `critical`.
- **Последняя копия не снимается** — `Undo` отклоняется целиком, если у цели не - **Последняя копия не снимается** — `Undo` отклоняется целиком, если у цели не
осталось других жёстких ссылок (`nlink <= 1`) или исходного файла уже нет. осталось других жёстких ссылок (`nlink <= 1`) или исходного файла уже нет.
Частичный откат тоже стёр бы часть данных. **Необратимо. `critical`.** Частичный откат тоже стёр бы часть данных. **Необратимо. `critical`.**
@@ -47,14 +58,24 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
qBittorrent, ключи LLM и метабаз, токен Telegram, API-ключ Jellyfin. qBittorrent, ключи LLM и метабаз, токен Telegram, API-ключ Jellyfin.
Утёкший в лог секрет отзывается вручную. **`major`.** Утёкший в лог секрет отзывается вручную. **`major`.**
- **Авто-раскладка только при подтверждённом матче в метабазе** — самооценка - **Авто-раскладка только при подтверждённом матче в метабазе** — самооценка
LLM гейтом не является LLM **единственным** гейтом не является и матч не заменяет: порог
([ADR](docs/adr/ADR-2026-06-13-auto-link-requires-db-match.md)). Обратимо `[recognition].auto_confidence_threshold` стоит поверх матча дополнительным
условием
([ADR](docs/adr/ADR-2026-06-13-auto-link-requires-db-match.md),
[recognition](openspec/specs/recognition/spec.md)). Обратимо
через `Undo`. **`major`.** через `Undo`. **`major`.**
- **Не более одной активной загрузки на infohash** — проверка отсутствия другой
активной загрузки и вставка идут одной write-транзакцией (`_txlock=immediate`,
guarded-методы `store`); обход даёт две задачи, претендующие на одну раздачу и
один целевой путь. Обратимо (лишняя закрывается), но состояние расходится.
**`major`.** Поведение — [ingest](openspec/specs/ingest/spec.md).
- **Переходы состояний — только через `worker` под per-download блокировкой**, - **Переходы состояний — только через `worker` под per-download блокировкой**,
и только легальные по декларативному графу. Обход даёт гонку двух и только легальные по декларативному графу. Обход даёт гонку двух
транспортов. **`major`.** транспортов. **`major`.**
- **Время — только `store.Now()` (UTC), идентификаторы — только `ident`**; - **Время — только `store.Now()` (UTC), идентификаторы — только `ident`**;
`ident.Parse` на каждой входной границе. Механизировано линтером. **`minor`.** `ident.Parse` на каждой входной границе. Время механизировано линтером
(`forbidigo` на `time.Now`); правило про `ident` линтером не проверяется —
держится на ревью. **`minor`.**
## Команды ## Команды
@@ -117,9 +138,10 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
(`git merge-base HEAD master`), в неё вливает батч, от неё ветвятся задачи. (`git merge-base HEAD master`), в неё вливает батч, от неё ветвятся задачи.
- **Необратимое** (спрашивается у человека всегда): всё, что пишет в - **Необратимое** (спрашивается у человека всегда): всё, что пишет в
`paths.downloads` или удаляет оттуда; удаление раздачи из qBittorrent вместе `paths.downloads` или удаляет оттуда; удаление раздачи из qBittorrent вместе
с файлами (`Delete`); снятие последней копии данных; правка уже применённой с файлами (`Delete`) — кроме уборки собственного, только что добавленного
миграции; `git push --force`; удаление или перезапись файла в библиотеке торрента при отмене (см. исключения инварианта выше); снятие последней копии
Jellyfin, которого мы не создавали. данных; правка уже применённой миграции; `git push --force`; удаление или
перезапись файла в библиотеке Jellyfin, которого мы не создавали.
- **Общий станок** — покрасневший `task gate` на `master` врывается в - **Общий станок** — покрасневший `task gate` на `master` врывается в
замороженный спринт: пока он красный, ни одна задача не считается сделанной. замороженный спринт: пока он красный, ни одна задача не считается сделанной.
- **Ориентир по размеру спринта:** 5–8 задач. Ориентир, а не закон. - **Ориентир по размеру спринта:** 5–8 задач. Ориентир, а не закон.
+1 -1
View File
@@ -42,7 +42,7 @@ TMDB/TVDB/TVMaze) → раскладка в библиотеку хардлин
REST API, веб-UI, Telegram-бот и CLI (`jellybit add`). REST API, веб-UI, Telegram-бот и CLI (`jellybit add`).
Из источников поддержаны magnet и `.torrent`-файл; фетч `.torrent` по обычной Из источников поддержаны magnet и `.torrent`-файл; фетч `.torrent` по обычной
ссылке — в планах. Что дальше — [docs/tasks/PLAN.md](docs/tasks/PLAN.md). ссылке — в планах. Что дальше — [docs/tasks/ROADMAP.md](docs/tasks/ROADMAP.md).
## Документация ## Документация
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"canon": 2, "canon": 4,
"migrations": "internal/store/migrations" "migrations": "internal/store/migrations"
} }
@@ -1,6 +1,9 @@
# Авто-раскладка только при подтверждённом матче в метабазе # Авто-раскладка только при подтверждённом матче в метабазе
- **Дата:** 2026-06-13 - **Дата:** 2026-06-13
- **Источник:** архивного `design.md` нет — решение принято до перехода
проекта на OpenSpec (первый архивный change — `2026-06-28`); первичный
материал — история git
## Контекст ## Контекст
+3
View File
@@ -1,6 +1,9 @@
# Docker как единица деплоя, образ собирается на сервере # Docker как единица деплоя, образ собирается на сервере
- **Дата:** 2026-06-13 - **Дата:** 2026-06-13
- **Источник:** архивного `design.md` нет — решение принято до перехода
проекта на OpenSpec (первый архивный change — `2026-06-28`); первичный
материал — история git
- **Статус:** заменено на ADR-2026-07-24-local-image-build - **Статус:** заменено на ADR-2026-07-24-local-image-build
## Контекст ## Контекст
@@ -1,6 +1,9 @@
# Go и доставка одним бинарём # Go и доставка одним бинарём
- **Дата:** 2026-06-13 - **Дата:** 2026-06-13
- **Источник:** архивного `design.md` нет — решение принято до перехода
проекта на OpenSpec (первый архивный change — `2026-06-28`); первичный
материал — история git
## Контекст ## Контекст
+3
View File
@@ -1,6 +1,9 @@
# Хардлинки вместо копирования и симлинков # Хардлинки вместо копирования и симлинков
- **Дата:** 2026-06-13 - **Дата:** 2026-06-13
- **Источник:** архивного `design.md` нет — решение принято до перехода
проекта на OpenSpec (первый архивный change — `2026-06-28`); первичный
материал — история git
## Контекст ## Контекст
@@ -1,6 +1,8 @@
# Конвейер ревью: гейт, generative-проходы и обязательный триаж # Конвейер ревью: гейт, generative-проходы и обязательный триаж
- **Дата:** 2026-07-23 - **Дата:** 2026-07-23
- **Источник:** архивного `design.md` нет — решение процессное, change'ем jellybit
не велось
## Контекст ## Контекст
@@ -1,6 +1,8 @@
# Образ собирается локально и едет на сервер через docker save/load # Образ собирается локально и едет на сервер через docker save/load
- **Дата:** 2026-07-24 - **Дата:** 2026-07-24
- **Источник:** архивного `design.md` нет — решение деплойное, change'ем jellybit
не велось
## Контекст ## Контекст
+17 -12
View File
@@ -53,16 +53,20 @@ retry / delete / dismiss) идут командами к `worker`.
## Внешние границы и форматы ## Внешние границы и форматы
- **qBittorrent WebUI API** — единственный способ качать: источник (magnet, URL, - **qBittorrent WebUI API** — единственный способ качать: источник **отдаём
`.torrent`) **отдаём ему**, сами по пользовательскому URL не ходим (SSRF ему**, сами по пользовательскому URL не ходим (SSRF исключён). Какие виды
исключён). Пути берём из API (`save_path` + относительные имена из источника принимаются и как разбираются —
`/torrents/files`), не из константы. [ingest](../openspec/specs/ingest/spec.md); способ добавления по
- **LLM** — OpenAI-совместимый Chat Completions (`[llm].type = "openai-compat"`), `source_type` — [download-tracking](../openspec/specs/download-tracking/spec.md);
структурированный вывод через `response_format: json_object`; валидация ответа откуда берутся пути файлов —
своя, в Go. [file-layout](../openspec/specs/file-layout/spec.md).
- **LLM** — OpenAI-совместимый Chat Completions за интерфейсом (`[llm].type`);
контракт вызова, формат вывода и разбор ответа —
[recognition](../openspec/specs/recognition/spec.md).
- **Метабазы** — TMDB, TVDB, TVMaze (последняя без ключа, только сериалы). - **Метабазы** — TMDB, TVDB, TVMaze (последняя без ключа, только сериалы).
- **Jellyfin** — один вызов `POST /Library/Refresh`, авторизация заголовком - **Jellyfin** — HTTP-триггер пересканирования медиатеки, опционален; когда он
`X-Emby-Token`. дёргается и по каким переходам —
[file-layout](../openspec/specs/file-layout/spec.md).
- **Telegram Bot API** — приём сообщений и исходящие карточки/пинги. - **Telegram Bot API** — приём сообщений и исходящие карточки/пинги.
- **Сообщение торрент-бота** — чужой текстовый формат, разбирается парсером - **Сообщение торрент-бота** — чужой текстовый формат, разбирается парсером
`tgbot`; наблюдения по формату — в `tgbot`; наблюдения по формату — в
@@ -90,8 +94,9 @@ retry / delete / dismiss) идут командами к `worker`.
задаче, застрявшей в промежуточном состоянии; логи в stdout контейнера. задаче, застрявшей в промежуточном состоянии; логи в stdout контейнера.
Автоматического алертинга нет, метрик нет — только уведомления в Telegram о Автоматического алертинга нет, метрик нет — только уведомления в Telegram о
падении загрузки и о рассинхроне. падении загрузки и о рассинхроне.
- **Характер потока:** непрерывный фон (тик поллинга qBittorrent, по умолчанию - **Характер потока:** непрерывный фон (тик поллинга qBittorrent — период в
5 с, и периодическая сверка) плюс редкие события по запросу человека. Объём — [database.md](database.md) → «Настройки с числовым значением» — и
периодическая сверка) плюс редкие события по запросу человека. Объём —
единицы загрузок в день, десятки одновременно; ориентир масштаба и его аудит — единицы загрузок в день, десятки одновременно; ориентир масштаба и его аудит —
задача в беклоге. задача в беклоге.
@@ -101,7 +106,7 @@ retry / delete / dismiss) идут командами к `worker`.
| Что | Где | | Что | Где |
| --- | --- | | --- | --- |
| Время | `store.Now()` — единственный источник, всегда UTC; формат хранения — RFC 3339 | | Время | `store.Now()` — единственный источник меток времени в данных, всегда UTC; формат хранения — RFC 3339. Вторая санкционированная точка wall-clock — timestamp-часть ULID в `ident.NewID` (исключение в `.golangci.yml`) |
| Идентификаторы | `internal/ident` — генерация и нормализация ULID; `ident.Parse` на каждой входной границе | | Идентификаторы | `internal/ident` — генерация и нормализация ULID; `ident.Parse` на каждой входной границе |
| Целевые имена и превью раскладки | `internal/naming` — одна логика для превью в UI и для реального применения | | Целевые имена и превью раскладки | `internal/naming` — одна логика для превью в UI и для реального применения |
| Разбор источника | `internal/magnet` и `internal/torrent`; инфохэш извлекается только здесь | | Разбор источника | `internal/magnet` и `internal/torrent`; инфохэш извлекается только здесь |
+6 -4
View File
@@ -6,9 +6,6 @@
Краткая выжимка и инварианты — в [CLAUDE.md](../../CLAUDE.md), раздел Краткая выжимка и инварианты — в [CLAUDE.md](../../CLAUDE.md), раздел
«Конвенции кода». «Конвенции кода».
> Каркас. Загрузчик `internal/config/config.go` уже грузит TOML; валидация
> на старте — в работе (`TODO`), обкатывается на следующем шаге.
## Принципы ## Принципы
- **Конфигурация — только TOML.** Env-переменные для конфига **не - **Конфигурация — только TOML.** Env-переменные для конфига **не
@@ -41,7 +38,7 @@
```toml ```toml
[worker] [worker]
poll_interval = "5s" # как часто опрашивать qBittorrent; Go-duration (s/m/h) poll_interval = "5s" # как часто опрашивать qBittorrent; Go-duration (s/m/h)
magnet_timeout = "30m" # ждать метаданные magnet не дольше; Go-duration magnet_timeout = "24h" # ждать метаданные magnet не дольше; Go-duration
source_missing_threshold = 3 # тиков сверки без раздачи, чтобы счесть источник удалённым source_missing_threshold = 3 # тиков сверки без раздачи, чтобы счесть источник удалённым
[recognition] [recognition]
@@ -51,6 +48,11 @@ auto_confidence_threshold = 0.85 # порог авто-раскладки бе
max_retries = 3 # попыток получить валидный ответ LLM; целое ≥ 0 max_retries = 3 # попыток получить валидный ответ LLM; целое ≥ 0
``` ```
Значения в примере — иллюстрация формы комментария. Действующие умолчания и их
смысл живут одним домом — таблица «Настройки с числовым значением» в
[../database.md](../database.md); `config.example.toml` — источник истины по
составу полей.
Секретные поля оставляем пустыми — значение приходит из деплоя (см. Секретные поля оставляем пустыми — значение приходит из деплоя (см.
«Секреты»). «Секреты»).
+1 -1
View File
@@ -2,7 +2,7 @@
Как мы устраиваем таблицы и ключи в SQLite. Актуальная схема — Как мы устраиваем таблицы и ключи в SQLite. Актуальная схема —
[../database.md](../database.md); обоснование выбора ULID — [../database.md](../database.md); обоснование выбора ULID —
`openspec/changes/ulid-identity/design.md` (после архивации — в истории git). [архивный design.md change'а `ulid-identity`](../../openspec/changes/archive/2026-07-02-ulid-identity/design.md).
**Механизировано:** `AUTOINCREMENT` и `DEFAULT (datetime('now'))` в новых **Механизировано:** `AUTOINCREMENT` и `DEFAULT (datetime('now'))` в новых
миграциях (`internal/archrules`), время мимо `store.Now()` (`forbidigo`). миграциях (`internal/archrules`), время мимо `store.Now()` (`forbidigo`).
+1 -1
View File
@@ -97,7 +97,7 @@ jellybit — **приложение, а не библиотека**: внешн
в review/failed: коллизия, рассинхрон, сбой ФС) и `reasons` распознавания, в review/failed: коллизия, рассинхрон, сбой ФС) и `reasons` распознавания,
сохранённые в БД и показываемые на экране ревью и в Telegram-карточке. Это сохранённые в БД и показываемые на экране ревью и в Telegram-карточке. Это
**операторская поверхность владельца**: сервис однопользовательский в **операторская поверхность владельца**: сервис однопользовательский в
доверенной LAN (см. [architecture.md](../architecture.md)), эти поля — доверенном контуре (см. [security.md](../security.md) → «Периметр»), эти поля —
диагностический контекст для того, кто разбирает задачу. Здесь сырой текст диагностический контекст для того, кто разбирает задачу. Здесь сырой текст
ошибки (пути, фрагмент ответа LLM/qBittorrent) **допустим и полезен** — но: ошибки (пути, фрагмент ответа LLM/qBittorrent) **допустим и полезен** — но:
- **секреты запрещены** абсолютно (токены/ключи/пароли/`Authorization`) — так - **секреты запрещены** абсолютно (токены/ключи/пароли/`Authorization`) — так
+4 -3
View File
@@ -74,9 +74,10 @@ OpenSpec-спеках (`### Requirement` с `SHALL`).
- Поле — `time` (ключ по умолчанию `slog`). - Поле — `time` (ключ по умолчанию `slog`).
- UTC, RFC 3339 с долями секунды, суффикс `Z`: - UTC, RFC 3339 с долями секунды, суффикс `Z`:
`2026-06-28T11:23:45.123456Z`. `2026-06-28T11:23:45.123456Z`.
- Логи — **в UTC** (это явный TZ, не нарушает инвариант проекта): даёт - Логи — **в UTC**, как и хранение в БД: это даёт однозначный порядок событий и
однозначный порядок событий и лексикографическую сортировку. Бизнес-логика лексикографическую сортировку. Часовой пояс есть только у **отображения** в
по-прежнему работает в `Europe/Moscow` — UTC только в логах. веб-UI (`[general].timezone`, дефолт `UTC`) — см.
[database.md](../database.md); бизнес-логика в локальной зоне не работает.
## Поля: словарь имён ## Поля: словарь имён
+9 -3
View File
@@ -1,7 +1,7 @@
# Паспорт проекта # Паспорт проекта
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
устроено», [tasks/PLAN.md](tasks/PLAN.md) — «в каком порядке», паспорт — устроено», [tasks/ROADMAP.md](tasks/ROADMAP.md) — «что уже умеет», паспорт —
«зачем и для кого». «зачем и для кого».
## Цель ## Цель
@@ -44,8 +44,14 @@ Jellyfin, без ручного переименования и без ката
- **Не медиасервер.** Обложки, метаданные, учёт просмотренного и сам просмотр — - **Не медиасервер.** Обложки, метаданные, учёт просмотренного и сам просмотр —
забота Jellyfin. Мы отвечаем только за то, чтобы файл лежал там, где Jellyfin забота Jellyfin. Мы отвечаем только за то, чтобы файл лежал там, где Jellyfin
его правильно опознает. его правильно опознает.
- **Не хранилище медиа.** Данные живут в раздаче; мы создаём только хардлинки и - **Не хранилище медиа.** Данные живут в раздаче, мы создаём хардлинки и не
не владеем ни одним байтом контента. дублируем контент намеренно. Два исключения хранилищем нас не делают, но байты
у нас появляются: copy-fallback, когда хардлинк невозможен
([file-layout](../openspec/specs/file-layout/spec.md)), и состояние
`orphaned`, где библиотечная ссылка осталась последней копией
([state-reconciliation](../openspec/specs/state-reconciliation/spec.md)).
Сохранность мы и в этих случаях на себя не берём — резервных копий медиа у нас
нет.
- **Не мультипользовательский сервис.** Контур один, оператор один; разграничение - **Не мультипользовательский сервис.** Контур один, оператор один; разграничение
доступа сводится к allowlist Telegram (см. [security.md](security.md)). доступа сводится к allowlist Telegram (см. [security.md](security.md)).
+3 -3
View File
@@ -17,9 +17,9 @@ LLM-эндпоинта на живых раздачах. Автоматичес
это отдельная задача (eval-харнес распознавания), до неё числа здесь единичные это отдельная задача (eval-харнес распознавания), до неё числа здесь единичные
и приведены как условия, а не как статистика. и приведены как условия, а не как статистика.
Зафиксированные образцы чужих форматов лежат прямо в тестах пакета-разборщика Зафиксированные образцы чужих форматов лежат прямо в тестах пакета-разборщика
(`internal/tgbot/parse_test.go`, `internal/magnet`, `internal/torrent`) — там там они заодно и проверяются; где именно и почему без каталога `testdata/`, см.
они заодно и проверяются; каталог `testdata/` под них не заводился. [CLAUDE.md](../../CLAUDE.md) → «Запреты».
## Записи ## Записи
+3 -1
View File
@@ -72,4 +72,6 @@ cправка: /help, index (https://exfreedomist.com/stats/)
- Как выглядит сообщение для сериала-сезонника и для аниме — образцов не - Как выглядит сообщение для сериала-сезонника и для аниме — образцов не
снимали, а именно на них строится самый сложный случай раскладки. снимали, а именно на них строится самый сложный случай раскладки.
- Что бот присылает при неудачном поиске и при слишком длинном описании - Что бот присылает при неудачном поиске и при слишком длинном описании
(обрезка Telegram — 4096 символов на сообщение). (лимит Telegram на текст сообщения — 4096 символов UTF-16, по документации
Bot API `sendMessage`; сами обрезанные сообщения мы не наблюдали, поведение
бота на обрезке неизвестно).
+23 -15
View File
@@ -70,10 +70,12 @@ Go-сервиса и что здесь уже проскакивало. Устр
снаружи LAN. снаружи LAN.
- **«Ошибка на htmx-пути возвращает 200».** Так и задумано — - **«Ошибка на htmx-пути возвращает 200».** Так и задумано —
[conventions/web-ui.md](conventions/web-ui.md). [conventions/web-ui.md](conventions/web-ui.md).
- **«Решение auto/review должно опираться на `confidence` модели».** Наоборот: - **«Решение auto/review должно опираться на `confidence` модели».** Неверно в
авто только при подтверждённом матче в базе — форме «вместо матча»: авто только при подтверждённом матче в базе —
[ADR-2026-06-13-auto-link-requires-db-match](adr/ADR-2026-06-13-auto-link-requires-db-match.md). [ADR-2026-06-13-auto-link-requires-db-match](adr/ADR-2026-06-13-auto-link-requires-db-match.md).
Самооценка LLM плохо откалибрована и поддаётся инъекции. Самооценка LLM плохо откалибрована и поддаётся инъекции. Порог `confidence`
при этом существует **дополнительным** блокирующим условием поверх матча —
его наличие дефектом не является.
- **«Копировать надёжнее, чем хардлинк» / «взять симлинк».** Хардлинк — - **«Копировать надёжнее, чем хардлинк» / «взять симлинк».** Хардлинк —
осознанный выбор ради неприкосновенности источника и недублирования диска, осознанный выбор ради неприкосновенности источника и недублирования диска,
[ADR-2026-06-13-hardlinks](adr/ADR-2026-06-13-hardlinks.md); copy — только [ADR-2026-06-13-hardlinks](adr/ADR-2026-06-13-hardlinks.md); copy — только
@@ -129,19 +131,25 @@ Go-сервиса и что здесь уже проскакивало. Устр
### Триггеры профиля ### Триггеры профиля
Уточняет умолчания конвейера, не отменяет их. Уточняет умолчания конвейера, не отменяет их. Рабочее умолчание — `standard`;
миграция схемы и публичный контракт ступень **не** поднимают: их проверяют
проходы, которые в `standard` и так есть.
**Новым понятием или структурной единицей здесь считается** (→ `wide`): новый
пакет `internal/*`; новая capability в `openspec/specs/`; новый транспорт приёма
или уведомлений рядом с REST, веб-UI, ботом и CLI; новый провайдер метабазы за
существующим интерфейсом; новое состояние в графе переходов загрузки; перенос
ответственности между `worker`, `recognition`, `layout` и `store`.
**Правила идентичности, слияния и разбора живут здесь** (→ `deep`): владение
раздачей по инфохэшу и сверка с qBittorrent (`ident`, `internal/store`,
`state-reconciliation`); построение целевых путей и санитизация имён
(`internal/layout`, `naming`); разбор недоверенного входа — bencode, magnet,
текст контекста, ответ LLM (`internal/torrent`, `internal/magnet`,
`internal/tgbot/parse.go`, разбор ответа модели); выбор кандидата метабазы и
слияние его полей с догадкой LLM (`internal/metadata`, `metadata-match`);
merge-раскладка при повторном добавлении раздачи.
- **`deep`** — есть миграция в `internal/store/migrations/`; появляется новый
пакет `internal/*`; меняется сигнатура публичной команды воркера; трогается
раскладка файлов, построение целевых путей или удаление ссылок; трогается
разбор недоверенного входа.
- **`standard`** — меняется поведение, видимое снаружи: REST-эндпоинт,
htmx-путь, набор или семантика состояний загрузки, формат сообщения бота,
поле конфига.
- **`quick`** — всё остальное: локальный багфикс, документация, тесты.
- **Независимая реализация (`reimpl`)** запускается, когда узел одновременно
новый и имеет внешний оракул в виде спеки: новый парсер, новый провайдер за
существующим интерфейсом, новая стадия конвейера распознавания.
- «Поведение, видимое снаружи» здесь включает **тексты и карточки Telegram** - «Поведение, видимое снаружи» здесь включает **тексты и карточки Telegram**
для единственного пользователя это и есть интерфейс. для единственного пользователя это и есть интерфейс.
+42 -40
View File
@@ -1,8 +1,8 @@
# Беклог # Беклог
Что **можно взять**. Одна задача = один файл `items/<slug>.md` Что **можно взять**. Одна задача = один файл `items/<slug>.md`
+ строка здесь. Целей тут нет — они в [PLAN.md](PLAN.md): беклог — то, что берут, + строка здесь. Целей тут нет — они в [ROADMAP.md](ROADMAP.md): беклог — то, что
план — то, подо что берут. Порядка внутри секции нет: «что делать берут, роадмап — то, подо что берут. Порядка внутри секции нет: «что делать
дальше» отвечает набор спринта. Ведётся скиллом `tasks`. дальше» отвечает набор спринта. Ведётся скиллом `tasks`.
Секции «блокеры» здесь нет и не заводится: блокер — это состояние Секции «блокеры» здесь нет и не заводится: блокер — это состояние
@@ -10,43 +10,45 @@
человека, а его следы — вопросами в файлах задач. человека, а его следы — вопросами в файлах задач.
## Ядро продукта ## Ядро продукта
- [Аниме с абсолютной нумерацией](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)_ - [✨ Пересчитывать абсолютную нумерацию аниме в SxxEyy](items/anime-absolute-numbering.md) — аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации
- [Раздачи-копии диска (DVD/BluRay: VIDEO_TS/BDMV)](items/disc-image-releases.md) — раздача-образ диска (VIDEO_TS/BDMV) сейчас разбирается пофайлово вместо раскладки каталога целиком — редкий, но реальный случай - [🐞 Пересобирать `addReq` из свежего `source_type` перед `Add` (окно namer'а)](items/catched-source-type-refresh.md) — При апгрейде magnet→.torrent в окне namer'а добавится magnet из устаревшего снимка; самоисцеляется через magnet_timeout→failed→Retry _(аудит 2026-07-17)_
- [Веб-UI зовёт Cancel вместо Dismiss на не-терминальных → теряется `user_dismiss`](items/dismiss-marker-lost.md) — Функционально ок (Cancel даёт cancelled), но маркер user_dismiss в error_code теряется; расхождение с буквой спеки _(аудит 2026-07-17)_ - [✨ Раскладывать раздачу-копию диска (VIDEO_TS/BDMV) каталогом целиком](items/disc-image-releases.md) — раздача-образ диска (VIDEO_TS/BDMV) сейчас разбирается пофайлово вместо раскладки каталога целиком — редкий, но реальный случай
- [Фетч .torrent по URL — остаток «единого окна»](items/torrent-url-fetch.md) — magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард) - [🐞 Не терять маркер `user_dismiss` при закрытии не-терминальной загрузки из веб-UI](items/dismiss-marker-lost.md) — Функционально ок (Cancel даёт cancelled), но маркер user_dismiss в error_code теряется; расхождение с буквой спеки _(аудит 2026-07-17)_
- [Confidence-гейт авто-раскладки: узаконить в спеке + сделать выключаемым (дефолт 0.7)](items/auto-link-confidence-gate.md) — Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку - [✨ Принимать ссылку на .torrent и скачивать файл самим (нужен SSRF-гард)](items/torrent-url-fetch.md) — magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард)
- [[idea] guessit как сервис-спутник](items/guessit-sidecar.md) — go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём - [✨ Узаконить confidence-гейт авто-раскладки в спеке и сделать его выключаемым (дефолт 0.7)](items/auto-link-confidence-gate.md) — Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
- [Согласование канона нумерации серий с провайдером тега](items/episode-numbering-canon.md) — Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега - [✨ Докладывать недостающие эпизоды merge-раскладкой при повторной заливке](items/merge-incremental-redownload.md) — повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов
- [Раздачи с докачиванием (merge при повторном добавлении)](items/merge-incremental-redownload.md) — повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов - [✨ Подмешивать прошлые правки человека в промпт распознавания (few-shot)](items/learn-from-user-corrections.md) — правки человека (матч/тип/нумерация) не переиспользуются — few-shot из прошлых ревью поднял бы точность на «своих» трекерах без смены модели
- [[idea] Многоступенчатая верификация привязки](items/multi-pass-verification.md) — несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения - [🐞 Связать v1/v2-хеши одного торрента и не доверять паре xt из магнета (F4, F5)](items/infohash-identity-integrity.md) — split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_
- [Обучение на правках человека (few-shot из прошлых ревью)](items/learn-from-user-corrections.md) — правки человека (матч/тип/нумерация) не переиспользуются — few-shot из прошлых ревью поднял бы точность на «своих» трекерах без смены модели - [🧹 Закрыть мелочи приёма: NoName в контексте, устаревшие комментарии, лог без причины, bencode-аллокации (N1, N3, N4, N5)](items/ingest-nits.md) — косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_
- [Идентичность инфохэшей: split v1/v2 одного торрента + крафт-магнет отравляет владение (F4, F5)](items/infohash-identity-integrity.md) — split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_ - [✨ Слать живые обновления через SSE вместо htmx-поллинга](items/sse-live-updates.md) — живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер)
- [Нити приёма: NoName в контексте, устаревшие комментарии, лог без причины, bencode-аллокации (N1, N3, N4, N5)](items/ingest-nits.md) — косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_ - [✨ Проверять свободное место перед copy-fallback](items/free-space-check-copy-fallback.md) — copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования
- [[idea] Сила совпадения кандидата и пересмотр распознавания/матчинга](items/candidate-match-strength.md) — у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга) - [ Слать уведомления автору загрузки в его транспорт (мульти-бот)](items/notification-source-binding.md) — пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота)
- [[idea] Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки](items/complex-series-releases.md) — сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон - [✨ Заменять разложенную версию тайтла новой (репаки, апгрейд 1080p → 2160p)](items/title-versions-repacks.md) — По калибровке болей (2026-07-02) — не боль, из приоритета выпало
- [Мгновенные обновления через SSE](items/sse-live-updates.md) — живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер) - [✨ Спаривать VobSub .idx+.sub и проставлять языковой суффикс субтитра](items/external-subtitles.md) — Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags
- [Проверка свободного места перед copy-fallback](items/free-space-check-copy-fallback.md) — copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования - [✨ Переделать веб-UI в устанавливаемое PWA](items/web-ui-pwa.md) — текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
- [Ревью уведомлений в Telegram (аудит текстов и формата)](items/telegram-messages-audit.md) — зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи - [✨ Править на ревью маппинг «файл → серия» и раскладывать вручную при провале LLM](items/review-mapping-editor.md) — правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
- [Привязка уведомлений к источнику в ботах (мульти-бот)](items/notification-source-binding.md) — пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота) - [✨ Заказать спекой крайние случаи именования: многофайловый фильм, редакции, двойная серия](items/naming-edge-cases.md) — стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
- [Версии/качество одного тайтла (репаки, апгрейд 1080p → 2160p)](items/title-versions-repacks.md) — По калибровке болей (2026-07-02) — не боль, из приоритета выпало - [✨ Брать у TVDB название на языке настройки и оригинальное название](items/tvdb-title-locale.md) — [general].language правит только TMDB и промпт LLM — TVDB отдаёт primary name, и при language=ru в карточку ревью и имя папки попадает 哪吒之魔童降世 вместо «Нэчжа»
- [Внешние субтитры: пары VobSub и языковой суффикс](items/external-subtitles.md) — Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags - [🔬 Канон нумерации серий и порядок у провайдера тега](items/episode-numbering-canon.md) — Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
- [Современный Web-UI как PWA](items/web-ui-pwa.md) — текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер - [🔬 Тексты и формат уведомлений в Telegram](items/telegram-messages-audit.md) — зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
- [Полный редактор маппинга «файл → серия» и ручной режим ревью](items/review-mapping-editor.md) — правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом - [🔬 guessit как сервис-спутник](items/guessit-sidecar.md) — go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
- [Крайние случаи именования: многофайловый фильм, редакции, двойная серия](items/naming-edge-cases.md) — стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена - [🔬 Многоступенчатая верификация привязки](items/multi-pass-verification.md) — несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения
- [Локаль TVDB: перевод названия и оригинальное название](items/tvdb-title-locale.md) — [general].language правит только TMDB и промпт LLM — TVDB отдаёт primary name, и при language=ru в карточку ревью и имя папки попадает 哪吒之魔童降世 вместо «Нэчжа» - [🔬 Сила совпадения кандидата и пересмотр распознавания/матчинга](items/candidate-match-strength.md) — у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга)
- [🔬 Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки](items/complex-series-releases.md) — сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон
## Инфраструктура ## Инфраструктура
- [Агенты-ревьюверы качества (наименования, архитектура, конвенции, стиль)](items/quality-review-agents.md) — конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
- [Авторизация веб-UI (на будущее)](items/web-ui-auth.md) — для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита - [🧹 Откалибровать проходы ревью и завести ревьювер наименований](items/quality-review-agents.md) — конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
- [Бэкап SQLite](items/sqlite-backup.md) — architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая) - [✨ Закрыть веб-UI авторизацией, когда доверенной LAN станет мало](items/web-ui-auth.md) — для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита
- [Eval-харнес распознавания (корпус кейсов + метрика точности)](items/recognition-eval-harness.md) — смена модели или правка промпта распознавания сейчас вслепую — нет корпуса кейсов и метрики точности, регрессии не видно - [🧹 Бэкапить SQLite по расписанию с ротацией](items/sqlite-backup.md) — architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая)
- [Глубокий healthcheck и статус зависимостей](items/deep-healthcheck-dependencies.md) — /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI - [🧹 Завести eval-харнес распознавания: корпус кейсов и метрику точности](items/recognition-eval-harness.md) — смена модели или правка промпта распознавания сейчас вслепую — нет корпуса кейсов и метрики точности, регрессии не видно
- [История переходов загрузки](items/download-transition-history.md) — хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов - [✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI](items/deep-healthcheck-dependencies.md) — /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
- [Кэш метабаз (и опционально LLM)](items/metadata-cache.md) — повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново» - [✨ Хранить историю переходов загрузки отдельной таблицей](items/download-transition-history.md) — хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов
- [НФТ: масштаб до 100 одновременных загрузок (потолок — 1000)](items/scale-100-downloads.md) — Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг) - [🧹 Кэшировать ответы метабаз с TTL (и опционально LLM)](items/metadata-cache.md) — повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново»
- [Шум ERROR фоновых циклов при недоступной зависимости](items/background-error-noise.md) — Остаток задачи логирования: ext.* ERROR-шторм при недоступном qBittorrent + эскалация устойчивого сбоя тика _(ревью Fable)_ - [🐞 Не штормить ERROR при недоступном qBittorrent и эскалировать устойчивый сбой тика](items/background-error-noise.md) — Остаток задачи логирования: ext.* ERROR-шторм при недоступном qBittorrent + эскалация устойчивого сбоя тика _(ревью Fable)_
- [Ретеншн и очистка БД](items/db-retention-cleanup.md) — терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации - [✨ Чистить БД от терминальных задач и сырых ответов LLM старше срока хранения](items/db-retention-cleanup.md) — терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации
- [Словарь единого языка (ubiquitous language)](items/ubiquitous-language-glossary.md) — наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований) - [🧹 Свести термины домена в словарь единого языка](items/ubiquitous-language-glossary.md) — наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований)
- [[idea] Завершение загрузки через webhook](items/completion-webhook.md) — завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации) - [🔬 Потолок нагрузки: 100 одновременных загрузок, план-максимум 1000](items/scale-100-downloads.md) — Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
- [[idea] Кандидаты в конвенции кода](items/convention-candidates.md) — накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд - [🔬 Завершение загрузки через webhook](items/completion-webhook.md) — завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации)
- [🔬 Кандидаты в конвенции кода](items/convention-candidates.md) — накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд
-22
View File
@@ -1,22 +0,0 @@
# План
Оглавление целей. Цель — файл `[goal]` в `items/`; её задачи
здесь **не перечисляются** — перечень даёт `tasks.py list --goal <слаг>`.
В первой секции («порядок») очередь значима и обосновывается
прозой; в остальных порядка нет — это тематические цели.
## порядок
Пусто. Фазы Ф0–Ф6 прежней дорожной карты (каркас, приём и трекинг,
распознавание, раскладка и ревью, метаданные, Telegram и UX, деплой) закрыты —
сквозной путь работает и развёрнут; закрытый шаг планом больше не является.
Что было сделано и когда — по архиву `openspec/changes/archive/`.
Следующая упорядоченная очередь появится, когда она понадобится.
## темы
- [[goal] Точность распознавания](items/recognition-accuracy.md) — смена модели или правка промпта сегодня вслепую — нет ни метрики, ни способа переиспользовать уже сделанные человеком правки
- [[goal] Сложные раздачи](items/complex-releases.md) — типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
- [[goal] Эксплуатационная прочность](items/operational-resilience.md) — сервис работает, но не переживает роста: база копится вечно, бэкапа нет, отказ зависимости виден только по застрявшим задачам
- [[goal] Интерфейсы приёма и ревью](items/ingest-and-review-interfaces.md) — путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
- [[goal] Целостность состояния и приёма](items/state-integrity.md) — известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
- [[goal] Процесс и качество разработки](items/dev-process-quality.md) — наименования домена расходятся между спеками, UI и кодом, а конвейер ревью не откалиброван — растёт цена каждой следующей задачи
+34
View File
@@ -0,0 +1,34 @@
# Роадмап
Что приложение уже умеет и чего ещё не умеет. Цель — файл типа `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`
на первой достигнутой цели.
+3 -2
View File
@@ -1,6 +1,7 @@
# Аниме с абсолютной нумерацией # ✨ Пересчитывать абсолютную нумерацию аниме в SxxEyy
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации - **Зачем:** аниме со сквозной нумерацией (#137) не раскладывается в SxxEyy, который ждёт Jellyfin — нужен пересчёт абсолютной нумерации
- **Теги:** goal:complex-releases - **Теги:** goal:complex-releases
@@ -1,6 +1,7 @@
# Confidence-гейт авто-раскладки: узаконить в спеке + сделать выключаемым (дефолт 0.7) # ✨ Узаконить confidence-гейт авто-раскладки в спеке и сделать его выключаемым (дефолт 0.7)
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку - **Зачем:** Решено (B): гейт оставляем как доп. проверку на ревью — выключаемый порог, дефолт 0.85→0.7, записать в спеку
- **Теги:** goal:recognition-accuracy - **Теги:** goal:recognition-accuracy
@@ -35,7 +36,12 @@
вспомогательный сигнал». Добавить сценарий: матч + чистая валидация + вспомогательный сигнал». Добавить сценарий: матч + чистая валидация +
согласованность, но `confidence` ниже порога → review. согласованность, но `confidence` ниже порога → review.
4. **Конфиг-конвенция/дока:** описать ключ в `docs/conventions/config.md` (диапазон 4. **Конфиг-конвенция/дока:** описать ключ в `docs/conventions/config.md` (диапазон
[0,1], 0 = выкл, дефолт 0.7). [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); 5. **Тесты:** `decide` с `threshold=0` (гейт выключен, авто при чистых 1–3);
confidence ниже/выше порога при выполненных 1–3. confidence ниже/выше порога при выполненных 1–3.
6. Точное число порога откалибровать позже — для этого есть задача 6. Точное число порога откалибровать позже — для этого есть задача
+3 -2
View File
@@ -1,6 +1,7 @@
# Шум ERROR фоновых циклов при недоступной зависимости # 🐞 Не штормить ERROR при недоступном qBittorrent и эскалировать устойчивый сбой тика
- **Секция:** инфраструктура - **Тип:** fix
- **Категория:** Инфраструктура
- **Зачем:** Остаток задачи логирования: ext.* ERROR-шторм при недоступном qBittorrent + эскалация устойчивого сбоя тика _(ревью Fable)_ - **Зачем:** Остаток задачи логирования: ext.* ERROR-шторм при недоступном qBittorrent + эскалация устойчивого сбоя тика _(ревью Fable)_
- **Теги:** goal:operational-resilience - **Теги:** goal:operational-resilience
+3 -2
View File
@@ -1,6 +1,7 @@
# [idea] Сила совпадения кандидата и пересмотр распознавания/матчинга # 🔬 Сила совпадения кандидата и пересмотр распознавания/матчинга
- **Секция:** ядро продукта - **Тип:** research
- **Категория:** Ядро продукта
- **Зачем:** у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга) - **Зачем:** у кандидата метабазы нет метрики силы совпадения — список кандидатов на ревью нечем отсортировать по уверенности (сперва проработать процесс матчинга)
- **Теги:** goal:recognition-accuracy - **Теги:** goal:recognition-accuracy
@@ -1,6 +1,7 @@
# `addReq` не пересобирается из свежего `source_type` перед `Add` (окно namer'а) # 🐞 Пересобирать `addReq` из свежего `source_type` перед `Add` (окно namer'а)
- **Секция:** ядро продукта - **Тип:** fix
- **Категория:** Ядро продукта
- **Зачем:** При апгрейде magnet→.torrent в окне namer'а добавится magnet из устаревшего снимка; самоисцеляется через magnet_timeout→failed→Retry _(аудит 2026-07-17)_ - **Зачем:** При апгрейде magnet→.torrent в окне namer'а добавится magnet из устаревшего снимка; самоисцеляется через magnet_timeout→failed→Retry _(аудит 2026-07-17)_
- **Теги:** goal:state-integrity - **Теги:** goal:state-integrity
+3 -2
View File
@@ -1,6 +1,7 @@
# [idea] Завершение загрузки через webhook # 🔬 Завершение загрузки через webhook
- **Секция:** инфраструктура - **Тип:** research
- **Категория:** Инфраструктура
- **Зачем:** завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации) - **Зачем:** завершение сейчас ловим поллингом qBittorrent — webhook реагировал бы быстрее, но связывает нас с его конфигом (решим по опыту эксплуатации)
- **Теги:** goal:ingest-and-review-interfaces - **Теги:** goal:ingest-and-review-interfaces
+3 -2
View File
@@ -1,6 +1,7 @@
# [goal] Сложные раздачи # 🎯 Раскладывается не только типовая раздача
- **Секция:** темы - **Тип:** goal
- **Секция:** Направления
- **Зачем:** типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор - **Зачем:** типовая раздача раскладывается, а всё, что сложнее одного сезона одного тайтла, упирается в ручной разбор
- **Теги:** decomposed - **Теги:** decomposed
+3 -2
View File
@@ -1,6 +1,7 @@
# [idea] Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки # 🔬 Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки
- **Секция:** ядро продукта - **Тип:** research
- **Категория:** Ядро продукта
- **Зачем:** сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон - **Зачем:** сложные раздачи (все сезоны разом, паки, смешанная нумерация) целостно не проработаны — распознавание/ревью/раскладка заточены под один сезон
- **Теги:** goal:complex-releases - **Теги:** goal:complex-releases
+3 -2
View File
@@ -1,6 +1,7 @@
# [idea] Кандидаты в конвенции кода # 🔬 Кандидаты в конвенции кода
- **Секция:** Инфраструктура - **Тип:** research
- **Категория:** Инфраструктура
- **Зачем:** накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд - **Зачем:** накоплен список кандидатов (внешние клиенты, конкурентность, тесты, CLI, время) — надо решить, что из них стало реальным трением, а что выдумано вперёд
- **Теги:** goal:dev-process-quality - **Теги:** goal:dev-process-quality
+3 -2
View File
@@ -1,6 +1,7 @@
# Ретеншн и очистка БД # ✨ Чистить БД от терминальных задач и сырых ответов LLM старше срока хранения
- **Секция:** инфраструктура - **Тип:** feature
- **Категория:** Инфраструктура
- **Зачем:** терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации - **Зачем:** терминальные задачи и сырые ответы LLM копятся вечно — без авточистки список загрузок и БД деградируют по мере эксплуатации
- **Теги:** goal:operational-resilience - **Теги:** goal:operational-resilience
@@ -1,6 +1,7 @@
# Глубокий healthcheck и статус зависимостей # ✨ Проверять в healthcheck доступность qBittorrent, LLM и метабаз и показывать её в UI
- **Секция:** инфраструктура - **Тип:** feature
- **Категория:** Инфраструктура
- **Зачем:** /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI - **Зачем:** /healthz проверяет только сам сервис — недоступность qBittorrent/LLM/метабазы видна лишь по застрявшим задачам, нет readiness и бейджа в UI
- **Теги:** goal:operational-resilience - **Теги:** goal:operational-resilience
+3 -2
View File
@@ -1,6 +1,7 @@
# [goal] Процесс и качество разработки # 🎯 Домен называется одинаково везде, ревью откалибровано
- **Секция:** темы - **Тип:** goal
- **Секция:** Сопровождение
- **Зачем:** наименования домена расходятся между спеками, UI и кодом, а конвейер ревью не откалиброван — растёт цена каждой следующей задачи - **Зачем:** наименования домена расходятся между спеками, UI и кодом, а конвейер ревью не откалиброван — растёт цена каждой следующей задачи
- **Теги:** decomposed - **Теги:** decomposed
+3 -2
View File
@@ -1,6 +1,7 @@
# Раздачи-копии диска (DVD/BluRay: VIDEO_TS/BDMV) # ✨ Раскладывать раздачу-копию диска (VIDEO_TS/BDMV) каталогом целиком
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** раздача-образ диска (VIDEO_TS/BDMV) сейчас разбирается пофайлово вместо раскладки каталога целиком — редкий, но реальный случай - **Зачем:** раздача-образ диска (VIDEO_TS/BDMV) сейчас разбирается пофайлово вместо раскладки каталога целиком — редкий, но реальный случай
- **Теги:** goal:complex-releases - **Теги:** goal:complex-releases
+3 -2
View File
@@ -1,6 +1,7 @@
# Веб-UI зовёт Cancel вместо Dismiss на не-терминальных → теряется `user_dismiss` # 🐞 Не терять маркер `user_dismiss` при закрытии не-терминальной загрузки из веб-UI
- **Секция:** ядро продукта - **Тип:** fix
- **Категория:** Ядро продукта
- **Зачем:** Функционально ок (Cancel даёт cancelled), но маркер user_dismiss в error_code теряется; расхождение с буквой спеки _(аудит 2026-07-17)_ - **Зачем:** Функционально ок (Cancel даёт cancelled), но маркер user_dismiss в error_code теряется; расхождение с буквой спеки _(аудит 2026-07-17)_
- **Теги:** goal:state-integrity - **Теги:** goal:state-integrity
@@ -1,6 +1,7 @@
# История переходов загрузки # ✨ Хранить историю переходов загрузки отдельной таблицей
- **Секция:** инфраструктура - **Тип:** feature
- **Категория:** Инфраструктура
- **Зачем:** хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов - **Зачем:** хранится только текущий статус загрузки — разбор «как сюда попали» идёт по логам сервера, нет таблицы истории переходов
- **Теги:** goal:state-integrity - **Теги:** goal:state-integrity
+3 -2
View File
@@ -1,6 +1,7 @@
# Согласование канона нумерации серий с провайдером тега # 🔬 Канон нумерации серий и порядок у провайдера тега
- **Секция:** ядро продукта - **Тип:** research
- **Категория:** Ядро продукта
- **Зачем:** Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега - **Зачем:** Косметика/редкость: порядок просмотра ок, но у тайтлов со спорным порядком (Бибоп) Jellyfin подтягивает не те подписи серий, если канон файлов ≠ дефолтный порядок провайдера тега
- **Теги:** goal:complex-releases - **Теги:** goal:complex-releases
+3 -2
View File
@@ -1,6 +1,7 @@
# Внешние субтитры: пары VobSub и языковой суффикс # ✨ Спаривать VobSub .idx+.sub и проставлять языковой суффикс субтитра
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags - **Зачем:** Привязка субтитр→серия уже работает; остались пары VobSub .idx+.sub и потеря Lang/Flags
- **Теги:** goal:complex-releases - **Теги:** goal:complex-releases
@@ -1,6 +1,7 @@
# Проверка свободного места перед copy-fallback # Проверять свободное место перед copy-fallback
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования - **Зачем:** copy-fallback при невозможности хардлинка может упереться в переполненный диск посреди раскладки — нет проверки места до копирования
- **Теги:** goal:operational-resilience - **Теги:** goal:operational-resilience
+3 -2
View File
@@ -1,6 +1,7 @@
# [idea] guessit как сервис-спутник # 🔬 guessit как сервис-спутник
- **Секция:** ядро продукта - **Тип:** research
- **Категория:** Ядро продукта
- **Зачем:** go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём - **Зачем:** go-ptn слабее питоновского guessit — если точности пред-парса не хватит, завернуть guessit в сервис-спутник рядом с бинарём
- **Теги:** goal:recognition-accuracy - **Теги:** goal:recognition-accuracy
@@ -1,6 +1,7 @@
# Идентичность инфохэшей: split v1/v2 одного торрента + крафт-магнет отравляет владение (F4, F5) # 🐞 Связать v1/v2-хеши одного торрента и не доверять паре xt из магнета (F4, F5)
- **Секция:** ядро продукта - **Тип:** fix
- **Категория:** Ядро продукта
- **Зачем:** split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_ - **Зачем:** split v1/v2 идентичность и крафт-магнет отравляют владение инфохэшами _(ревью 2026-07-08)_
- **Теги:** goal:state-integrity - **Теги:** goal:state-integrity
@@ -1,6 +1,7 @@
# [goal] Интерфейсы приёма и ревью # 🎯 Раздача приносится и подтверждается из любого транспорта
- **Секция:** темы - **Тип:** goal
- **Секция:** Направления
- **Зачем:** путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику - **Зачем:** путь «принести раздачу и подтвердить догадку» упирается в незакрытые куски интерфейсов, а не в логику
- **Теги:** decomposed - **Теги:** decomposed
+3 -2
View File
@@ -1,6 +1,7 @@
# Нити приёма: NoName в контексте, устаревшие комментарии, лог без причины, bencode-аллокации (N1, N3, N4, N5) # 🧹 Закрыть мелочи приёма: NoName в контексте, устаревшие комментарии, лог без причины, bencode-аллокации (N1, N3, N4, N5)
- **Секция:** ядро продукта - **Тип:** chore
- **Категория:** Ядро продукта
- **Зачем:** косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_ - **Зачем:** косметика приёма: NoName в контексте, устаревшие комментарии, лог, bencode-аллокации _(ревью 2026-07-08)_
- **Теги:** goal:state-integrity - **Теги:** goal:state-integrity
@@ -1,6 +1,7 @@
# Обучение на правках человека (few-shot из прошлых ревью) # ✨ Подмешивать прошлые правки человека в промпт распознавания (few-shot)
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** правки человека (матч/тип/нумерация) не переиспользуются — few-shot из прошлых ревью поднял бы точность на «своих» трекерах без смены модели - **Зачем:** правки человека (матч/тип/нумерация) не переиспользуются — few-shot из прошлых ревью поднял бы точность на «своих» трекерах без смены модели
- **Теги:** goal:recognition-accuracy - **Теги:** goal:recognition-accuracy
@@ -1,6 +1,7 @@
# Раздачи с докачиванием (merge при повторном добавлении) # ✨ Докладывать недостающие эпизоды merge-раскладкой при повторной заливке
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов - **Зачем:** повторная заливка сериала целиком должна доложить недостающие эпизоды merge-раскладкой, не трогая существующие ссылки — блокирует типовой сценарий свежих сериалов
- **Теги:** goal:complex-releases - **Теги:** goal:complex-releases
+3 -2
View File
@@ -1,6 +1,7 @@
# Кэш метабаз (и опционально LLM) # 🧹 Кэшировать ответы метабаз с TTL (и опционально LLM)
- **Секция:** инфраструктура - **Тип:** chore
- **Категория:** Инфраструктура
- **Зачем:** повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново» - **Зачем:** повторные и ретраящиеся прогоны бьют TMDB/TVDB/TVMaze одним запросом — кэш с TTL сэкономил бы лимиты и ускорил «Распознать заново»
- **Теги:** goal:operational-resilience - **Теги:** goal:operational-resilience
+3 -2
View File
@@ -1,6 +1,7 @@
# [idea] Многоступенчатая верификация привязки # 🔬 Многоступенчатая верификация привязки
- **Секция:** ядро продукта - **Тип:** research
- **Категория:** Ядро продукта
- **Зачем:** несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения - **Зачем:** несколько проходов распознавания с консенсусом подняли бы точность ценой стоимости/латентности — проработать, когда включать и как мерджить расхождения
- **Теги:** goal:recognition-accuracy - **Теги:** goal:recognition-accuracy
+3 -2
View File
@@ -1,6 +1,7 @@
# Крайние случаи именования: многофайловый фильм, редакции, двойная серия # ✨ Заказать спекой крайние случаи именования: многофайловый фильм, редакции, двойная серия
- **Секция:** Ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена - **Зачем:** стэкинг частей (part1/cd1), редакции [edition-…] и двойная серия SxxEyy-Eyy описаны нарративом, но в file-layout не заказаны — раскладка таких раздач не определена
- **Теги:** goal:complex-releases - **Теги:** goal:complex-releases
@@ -1,6 +1,7 @@
# Привязка уведомлений к источнику в ботах (мульти-бот) # ✨ Слать уведомления автору загрузки в его транспорт (мульти-бот)
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота) - **Зачем:** пинги и ревью должен получать автор загрузки в своём транспорте — нет привязки загрузки к источнику/отправителю (нужно для мульти-бота)
- **Теги:** goal:ingest-and-review-interfaces - **Теги:** goal:ingest-and-review-interfaces
+3 -2
View File
@@ -1,6 +1,7 @@
# [goal] Эксплуатационная прочность # 🎯 Сервис переживает рост и потерю тома
- **Секция:** темы - **Тип:** goal
- **Секция:** Сопровождение
- **Зачем:** сервис работает, но не переживает роста: база копится вечно, бэкапа нет, отказ зависимости виден только по застрявшим задачам - **Зачем:** сервис работает, но не переживает роста: база копится вечно, бэкапа нет, отказ зависимости виден только по застрявшим задачам
- **Теги:** decomposed - **Теги:** decomposed
+3 -2
View File
@@ -1,6 +1,7 @@
# Агенты-ревьюверы качества (наименования, архитектура, конвенции, стиль) # 🧹 Откалибровать проходы ревью и завести ревьювер наименований
- **Секция:** инфраструктура - **Тип:** chore
- **Категория:** Инфраструктура
- **Зачем:** конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка) - **Зачем:** конвейер ревью переехал в плагин `av-dev-pipeline`; осталась калибровка проходов на этом проекте и ревьювер наименований (ждёт словарь единого языка)
- **Теги:** goal:dev-process-quality - **Теги:** goal:dev-process-quality
+3 -2
View File
@@ -1,6 +1,7 @@
# [goal] Точность распознавания # 🎯 Раздача узнаётся верно, и точность этого измерима
- **Секция:** темы - **Тип:** goal
- **Секция:** Направления
- **Зачем:** смена модели или правка промпта сегодня вслепую — нет ни метрики, ни способа переиспользовать уже сделанные человеком правки - **Зачем:** смена модели или правка промпта сегодня вслепую — нет ни метрики, ни способа переиспользовать уже сделанные человеком правки
- **Теги:** decomposed - **Теги:** decomposed
+3 -2
View File
@@ -1,6 +1,7 @@
# Eval-харнес распознавания (корпус кейсов + метрика точности) # 🧹 Завести eval-харнес распознавания: корпус кейсов и метрику точности
- **Секция:** инфраструктура - **Тип:** chore
- **Категория:** Инфраструктура
- **Зачем:** смена модели или правка промпта распознавания сейчас вслепую — нет корпуса кейсов и метрики точности, регрессии не видно - **Зачем:** смена модели или правка промпта распознавания сейчас вслепую — нет корпуса кейсов и метрики точности, регрессии не видно
- **Теги:** goal:recognition-accuracy - **Теги:** goal:recognition-accuracy
+3 -2
View File
@@ -1,6 +1,7 @@
# Полный редактор маппинга «файл → серия» и ручной режим ревью # ✨ Править на ревью маппинг «файл → серия» и раскладывать вручную при провале LLM
- **Секция:** Ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом - **Зачем:** правка S·E, «нумеровать подряд» и ручной режим при полном провале LLM были запланированы объёмом Ф5 и не заведены задачей — в ревью сегодня можно только подсказать текстом
- **Теги:** goal:ingest-and-review-interfaces - **Теги:** goal:ingest-and-review-interfaces
+3 -2
View File
@@ -1,6 +1,7 @@
# НФТ: масштаб до 100 одновременных загрузок (потолок — 1000) # 🔬 Потолок нагрузки: 100 одновременных загрузок, план-максимум 1000
- **Секция:** инфраструктура - **Тип:** research
- **Категория:** Инфраструктура
- **Зачем:** Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг) - **Зачем:** Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
- **Теги:** goal:operational-resilience - **Теги:** goal:operational-resilience
+3 -2
View File
@@ -1,6 +1,7 @@
# Бэкап SQLite # 🧹 Бэкапить SQLite по расписанию с ротацией
- **Секция:** инфраструктура - **Тип:** chore
- **Категория:** Инфраструктура
- **Зачем:** architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая) - **Зачем:** architecture требует бекапить data-том, но стратегия не описана — сбой или редеплой стирают всё in-flight состояние (проще, пока БД маленькая)
- **Теги:** goal:operational-resilience - **Теги:** goal:operational-resilience
+3 -2
View File
@@ -1,6 +1,7 @@
# Мгновенные обновления через SSE # ✨ Слать живые обновления через SSE вместо htmx-поллинга
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер) - **Зачем:** живые обновления на htmx-поллинге дают задержку и холостые запросы — SSE убрал бы то и другое (поллинг работает, поэтому улучшение, не блокер)
- **Теги:** goal:ingest-and-review-interfaces - **Теги:** goal:ingest-and-review-interfaces
+3 -2
View File
@@ -1,6 +1,7 @@
# [goal] Целостность состояния и приёма # 🎯 По записи загрузки видно, как она сюда попала
- **Секция:** темы - **Тип:** goal
- **Секция:** Направления
- **Зачем:** известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний - **Зачем:** известные окна рассинхрона и потери маркеров: каждое по отдельности самоисцеляется, вместе — источник необъяснимых состояний
- **Теги:** decomposed - **Теги:** decomposed
+3 -2
View File
@@ -1,6 +1,7 @@
# Ревью уведомлений в Telegram (аудит текстов и формата) # 🔬 Тексты и формат уведомлений в Telegram
- **Секция:** ядро продукта - **Тип:** research
- **Категория:** Ядро продукта
- **Зачем:** зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи - **Зачем:** зонтичный проход по всем текстам бота: полнота карточек, единый язык, оформление; порождает под-задачи
- **Теги:** goal:ingest-and-review-interfaces - **Теги:** goal:ingest-and-review-interfaces
+3 -2
View File
@@ -1,6 +1,7 @@
# Версии/качество одного тайтла (репаки, апгрейд 1080p → 2160p) # ✨ Заменять разложенную версию тайтла новой (репаки, апгрейд 1080p → 2160p)
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** По калибровке болей (2026-07-02) — не боль, из приоритета выпало - **Зачем:** По калибровке болей (2026-07-02) — не боль, из приоритета выпало
- **Теги:** goal:complex-releases - **Теги:** goal:complex-releases
+3 -2
View File
@@ -1,6 +1,7 @@
# Фетч .torrent по URL — остаток «единого окна» # ✨ Принимать ссылку на .torrent и скачивать файл самим (нужен SSRF-гард)
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард) - **Зачем:** magnet и .torrent-файл приняты; остался фетч .torrent по URL (нужен SSRF-гард)
- **Теги:** goal:ingest-and-review-interfaces - **Теги:** goal:ingest-and-review-interfaces
+3 -2
View File
@@ -1,6 +1,7 @@
# Локаль TVDB: перевод названия и оригинальное название # ✨ Брать у TVDB название на языке настройки и оригинальное название
- **Секция:** Ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** [general].language правит только TMDB и промпт LLM — TVDB отдаёт primary name, и при language=ru в карточку ревью и имя папки попадает 哪吒之魔童降世 вместо «Нэчжа» - **Зачем:** [general].language правит только TMDB и промпт LLM — TVDB отдаёт primary name, и при language=ru в карточку ревью и имя папки попадает 哪吒之魔童降世 вместо «Нэчжа»
- **Теги:** goal:recognition-accuracy - **Теги:** goal:recognition-accuracy
@@ -1,6 +1,7 @@
# Словарь единого языка (ubiquitous language) # 🧹 Свести термины домена в словарь единого языка
- **Секция:** инфраструктура - **Тип:** chore
- **Категория:** Инфраструктура
- **Зачем:** наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований) - **Зачем:** наименования домена расходятся между спеками, UI и кодом — нет единого глоссария (на нём же стоит агент-ревьювер наименований)
- **Теги:** goal:dev-process-quality - **Теги:** goal:dev-process-quality
+3 -2
View File
@@ -1,6 +1,7 @@
# Авторизация веб-UI (на будущее) # ✨ Закрыть веб-UI авторизацией, когда доверенной LAN станет мало
- **Секция:** инфраструктура - **Тип:** feature
- **Категория:** Инфраструктура
- **Зачем:** для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита - **Зачем:** для v1 решено без авторизации (доверенная LAN, опц. allowlist подсетей) — задел на случай, если понадобится защита
- **Теги:** goal:ingest-and-review-interfaces - **Теги:** goal:ingest-and-review-interfaces
+3 -2
View File
@@ -1,6 +1,7 @@
# Современный Web-UI как PWA # ✨ Переделать веб-UI в устанавливаемое PWA
- **Секция:** ядро продукта - **Тип:** feature
- **Категория:** Ядро продукта
- **Зачем:** текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер - **Зачем:** текущий server-rendered UI функционален — PWA (устанавливаемое, удобное с телефона) это улучшение большого объёма, не блокер
- **Теги:** goal:ingest-and-review-interfaces - **Теги:** goal:ingest-and-review-interfaces
+6 -1
View File
@@ -1,7 +1,12 @@
# live-status Specification # live-status Specification
## Purpose ## Purpose
TBD - created by archiving change live-status. Update Purpose after archive. Живая телеметрия раздач: единственный сэмплер — воркер, снимок держится в
памяти на тике поллинга и в БД не персистится. Состав телеметрии (прогресс,
скорость, ETA, размер; для сидирующих — рейтинг, сиды и пиры, отдано), узкий
контракт чтения транспортом и живое обновление интерфейса поллингом фрагментов.
Что именно рисуется на странице и в карточке — `web-ui`; откуда берутся сами
состояния загрузки — `download-tracking`.
## Requirements ## Requirements
### Requirement: Снимок живой телеметрии ### Requirement: Снимок живой телеметрии