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:
+201
@@ -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));
|
||||
случаи до этой даты не восстанавливались — восстановленная постфактум причина
|
||||
непоймания недостоверна, а именно она и нужна.
|
||||
Reference in New Issue
Block a user