docs: перевод документации на канон av-dev

- Раскладка docs/ приведена к канону 2: заведены passport/architecture/
  database/security/review и research; docs/specs, drafts, backlog, review/
  и BRIEF.md разобраны и удалены, беклог переехал в docs/tasks (34 задачи,
  6 целей, слаги на английский).
- Нарративы specs удалены как дубли openspec-спек после поимённой сверки;
  остаток заведён задачами (редактор маппинга ревью, крайние случаи
  именования), отказ от сущности title промоутнут в ADR.
- Проектные копии агентов и скиллов ревью/пайплайна удалены в пользу
  плагинов av-dev-pm и av-dev-pipeline; в task gate добавлен шаг canon
  вместо er-schema.
This commit is contained in:
av
2026-08-04 09:27:26 +03:00
parent 08bef2cac0
commit 42d5b73a04
128 changed files with 1606 additions and 4889 deletions
+201
View File
@@ -0,0 +1,201 @@
# Ревью: настройка и журнал
Проектная часть конвейера ревью: чем jellybit отличается от абстрактного
Go-сервиса и что здесь уже проскакивало. Устройство самого конвейера (профили,
стадии, контракт находок) живёт в скилле, а не здесь.
## Как настроен конвейер
### Типовые узлы
Рода узлов проекта и проверяемые свойства к каждому. Род, а не инвентарь
пакетов: узел, которого ещё нет, но который проект заведёт, включён намеренно.
**Клиент внешнего HTTP-сервиса** (`qbt`, `llm`, `metadata`, `jellyfin`, `tgbot`)
- у каждого исходящего вызова свой таймаут из конфига, не дефолт транспорта;
- `context` доходит до запроса и отменяет его, а не игнорируется;
- ошибка зависимости отличима от ошибки нашей логики на приёме результата;
- секреты (пароль, ключ, токен) не попадают ни в лог, ни в текст ошибки;
- недоступность **опциональной** зависимости (метабаза, Jellyfin, Telegram) не
двигает состояние загрузки и не краснеет ERROR-ом в фоновом цикле.
**Тик воркера и переход состояния**
- переход легален по декларативному графу, а не «просто присвоили `state`»;
- работа идёт под per-download блокировкой; два транспорта не гонятся;
- тик идемпотентен: повтор на том же состоянии не порождает второго эффекта;
- отмена контекста на середине не оставляет полуприменённого состояния;
- новое промежуточное состояние имеет выход **и** предохранитель по времени.
**Репозиторий `store`**
- запрос параметризован, время только через `store.Now()`, id через `ident`;
- многошаговое изменение — в одной write-транзакции (`_txlock=immediate`);
- инвариант, не выражаемый схемой («одна активная загрузка на infohash»),
держится guarded-методом, а не проверкой в вызывающем коде;
- миграция forward-only и сопровождается правкой [database.md](database.md).
**Операция с файловой системой** (`layout`)
- целевой путь проверяется **после** `filepath.Clean`, на принадлежность
библиотеке;
- существующая цель не перезаписывается ни при каком исходе;
- под `paths.downloads` нет ни одной операции записи или удаления;
- частичный сбой батча оставляет систему в состоянии, из которого повтор
доводит начатое или откатывает целиком;
- удаление снимает только свои ссылки своего батча и не снимает последнюю копию.
**Парсер недоверенного входа** (`magnet`, `torrent`, парсер сообщения бота,
разбор ответа LLM)
- вход враждебный по умолчанию: длина, вложенность, мусорные байты, пустота;
- разбор не паникует и не аллоцирует по числу из самого входа;
- невалидный вход даёт доменную ошибку, а не тихий дефолт;
- результат нормализуется на границе (lowercase hex, trim, `ident.Parse`).
**htmx-хендлер**
- один партиал обслуживает страницу и фрагмент, ветвление по `isHTMX`;
- ошибка на htmx-пути — 200 плюс фрагмент, а не 4xx/5xx;
- страница деградирует без JS;
- поллинг самозавершается, когда наблюдать больше нечего.
### Типовые ложноположительные
Находки, которые здесь выглядят убедительно и всегда неверны.
- **«Веб-UI и REST без авторизации».** Принятое решение под сегодняшний
периметр — [security.md](security.md). Дефектом станет только вместе с путём
снаружи LAN.
- **«Ошибка на htmx-пути возвращает 200».** Так и задумано —
[conventions/web-ui.md](conventions/web-ui.md).
- **«Решение auto/review должно опираться на `confidence` модели».** Наоборот:
авто только при подтверждённом матче в базе —
[ADR-2026-06-13-auto-link-requires-db-match](adr/ADR-2026-06-13-auto-link-requires-db-match.md).
Самооценка LLM плохо откалибрована и поддаётся инъекции.
- **«Копировать надёжнее, чем хардлинк» / «взять симлинк».** Хардлинк —
осознанный выбор ради неприкосновенности источника и недублирования диска,
[ADR-2026-06-13-hardlinks](adr/ADR-2026-06-13-hardlinks.md); copy — только
фолбэк.
- **«Целочисленный автоинкрементный ключ был бы проще».** ULID — требование
capability `identity`; `AUTOINCREMENT` вдобавок краснит гейт.
- **«Не хватает метрик, трейсинга, health-эндпоинтов по каждой зависимости».**
«Минимум компонентов» — принцип проекта; глубокий healthcheck заведён задачей
и ждёт своей очереди, а не является упущением.
- **«Здесь нужен интерфейс, чтобы это можно было замокать».** Единственная
реализация за интерфейсом — обычно лишний слой; см. «Честный предел» ниже.
- **«Нет ретрая у вызова в фоновом цикле».** Тик повторится сам через
`poll_interval`; ретрай внутри тика чаще вреден.
- **«Оригинальное и локализованное названия дублируются — избыточность».**
`original_title` заполняется всегда и при неуверенности дублирует `title`
это контракт capability `recognition`, а не недосмотр.
### Вопросы к проходам
Форма: `<имя прохода>: <вопрос> (<провенанс>)`. Журнал дефектов пока пуст,
поэтому провенанс у всех пунктов — инвариант или ADR, а не пойманный случай;
по мере накопления журнала список должен смещаться в сторону реальных промахов.
- `adversary`: можно ли, управляя только именами файлов в раздаче и текстом
контекста, добиться целевого пути вне `paths.movies`/`series` — включая путь
через юникод, длину сверх лимита ФС и коллизию после нормализации?
(инвариант «целевой путь строго под библиотекой», [security.md](security.md))
- `adversary`: есть ли последовательность команд, после которой снимается
**последняя** копия данных — с учётом `superseded`-ссылок и гонки со сверкой?
(инвариант «источник неприкосновенен»)
- `adversary`: что даёт крафт-магнет с чужим или подставным инфохэшем —
присоединение к чужой активной загрузке, отравление владения?
(открытая задача про идентичность инфохэшей)
- `ops`: что делает эта ветка, когда qBittorrent недоступен несколько минут
подряд — сколько ERROR-строк в секунду и меняется ли состояние задач?
(задача про ERROR-шторм фоновых циклов)
- `ops`: как это ведёт себя при сотне загрузок в базе и десятках тысяч
`file_link` — есть ли запрос без индекса и полный проход по таблице?
(задача про масштаб 100/1000, [database.md](database.md) → «Настройки»)
- `ops`: что остаётся на диске и в базе, если процесс убит посреди раскладки
батча? (состояние `linking` и его восстановление)
- `code`: логирующий чекпоинт один на операцию — или ошибка залогирована и
возвращена вверх, где залогирована снова?
([conventions/logging.md](conventions/logging.md))
- `code`: новое поле конфига появилось в `config.example.toml` с описанием
назначения, диапазона и единиц? ([conventions/config.md](conventions/config.md))
- `specs`: не завелось ли поведение, которого спека не заказывала — тихий
дефолт, проглоченная ошибка, ретрай «на всякий случай», отброшенное поле?
- `architecture`: не появился ли второй способ делать то, что уже делается —
второе место, где генерится время или id, второй парсер источника, вторая
логика целевых имён мимо `naming`?
([architecture.md](architecture.md) → «Единые точки проекта»)
### Триггеры профиля
Уточняет умолчания конвейера, не отменяет их.
- **`deep`** — есть миграция в `internal/store/migrations/`; появляется новый
пакет `internal/*`; меняется сигнатура публичной команды воркера; трогается
раскладка файлов, построение целевых путей или удаление ссылок; трогается
разбор недоверенного входа.
- **`standard`** — меняется поведение, видимое снаружи: REST-эндпоинт,
htmx-путь, набор или семантика состояний загрузки, формат сообщения бота,
поле конфига.
- **`quick`** — всё остальное: локальный багфикс, документация, тесты.
- **Независимая реализация (`reimpl`)** запускается, когда узел одновременно
новый и имеет внешний оракул в виде спеки: новый парсер, новый провайдер за
существующим интерфейсом, новая стадия конвейера распознавания.
- «Поведение, видимое снаружи» здесь включает **тексты и карточки Telegram**
для единственного пользователя это и есть интерфейс.
### Недоступно проверке
**Не проверит ни один проход** — принципиальная граница, по факту промаха не
пересматривается.
- История инцидентов на umbar и то, что уже ломалось в проде.
- Поведение таблицы SQLite под реальным объёмом и профилем нагрузки: реального
профиля нет ни у кого, кроме сервера.
- Завязка внешних потребителей (Jellyfin, закладки, чужие ссылки) на текущее
поведение.
- Качество распознавания как таковое: правильно ли LLM определил фильм — вопрос
eval-харнеса и корпуса кейсов, а не ревью кода.
- Суждение «этой функциональности не должно существовать».
**Перестали проверять сознательно** — пересматривается первым, как только
что-то проскочило.
- **Идиоматичность Go — с 2026-08-04.** Проектный проход `idiom` (поимённая
сверка с положениями Effective Go, Go Code Review Comments, стайлгайдов Uber
и Google) удалён вместе с проектными копиями агентов при переезде на плагин
`av-dev-pipeline`, который этот проход упразднил. Способные части переселены:
эксперимент против поведения библиотеки и драйвера — в `ops`, «не изобретаем
ли то, что уже есть в библиотеке» — в `architecture`. **Различение
«идиоматично против распространено» теперь не спрашивает никто.** Класс
обратимый: портит форму кода, не данные. Пересмотр — задача
`quality-review-agents`.
## Журнал дефектов
Запись на каждый воспроизведённый дефект, **сразу**, а не ретроспективно: со
временем теряется не факт, а причина непоймания. Проскочившие — эвал-сет для
калибровки конвейера, выборка по пометке.
Форма:
<!-- копия: журнал-дефектов-форма из av-dev-pipeline/skills/review-pipeline/references/review-journal.md -->
## ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман]
- **Где:** путь:строка либо «конвейер, а не код»
- **Симптом:** как обнаружилось, кем и когда
- **Причина:** что на самом деле было не так
- **Чем воспроизведён:** тест, команда, замер — с числами
- **Почему не поймали:** только для проскочивших — какой проход обязан был найти
и что ему помешало
- **Что меняем:** правило прохода, шаг гейта, конвенция, факт в документе
проекта — либо «ничего, цена поимки выше цены дефекта»
<!-- /копия: журнал-дефектов-форма -->
### Записи
Пока пусто. Журнал заведён 2026-07-23 вместе с переработкой конвейера
([ADR-2026-07-23-review-pipeline-generative](adr/ADR-2026-07-23-review-pipeline-generative.md));
случаи до этой даты не восстанавливались — восстановленная постфактум причина
непоймания недостоверна, а именно она и нужна.