Files
av 42d5b73a04 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.
2026-08-04 09:27:26 +03:00

16 KiB
Raw Permalink Blame History

Ревью: настройка и журнал

Проектная часть конвейера ревью: чем 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.

Операция с файловой системой (layout)

  • целевой путь проверяется после filepath.Clean, на принадлежность библиотеке;
  • существующая цель не перезаписывается ни при каком исходе;
  • под paths.downloads нет ни одной операции записи или удаления;
  • частичный сбой батча оставляет систему в состоянии, из которого повтор доводит начатое или откатывает целиком;
  • удаление снимает только свои ссылки своего батча и не снимает последнюю копию.

Парсер недоверенного входа (magnet, torrent, парсер сообщения бота, разбор ответа LLM)

  • вход враждебный по умолчанию: длина, вложенность, мусорные байты, пустота;
  • разбор не паникует и не аллоцирует по числу из самого входа;
  • невалидный вход даёт доменную ошибку, а не тихий дефолт;
  • результат нормализуется на границе (lowercase hex, trim, ident.Parse).

htmx-хендлер

  • один партиал обслуживает страницу и фрагмент, ветвление по isHTMX;
  • ошибка на htmx-пути — 200 плюс фрагмент, а не 4xx/5xx;
  • страница деградирует без JS;
  • поллинг самозавершается, когда наблюдать больше нечего.

Типовые ложноположительные

Находки, которые здесь выглядят убедительно и всегда неверны.

  • «Веб-UI и REST без авторизации». Принятое решение под сегодняшний периметр — security.md. Дефектом станет только вместе с путём снаружи LAN.
  • «Ошибка на htmx-пути возвращает 200». Так и задумано — conventions/web-ui.md.
  • «Решение auto/review должно опираться на confidence модели». Наоборот: авто только при подтверждённом матче в базе — ADR-2026-06-13-auto-link-requires-db-match. Самооценка LLM плохо откалибрована и поддаётся инъекции.
  • «Копировать надёжнее, чем хардлинк» / «взять симлинк». Хардлинк — осознанный выбор ради неприкосновенности источника и недублирования диска, ADR-2026-06-13-hardlinks; copy — только фолбэк.
  • «Целочисленный автоинкрементный ключ был бы проще». ULID — требование capability identity; AUTOINCREMENT вдобавок краснит гейт.
  • «Не хватает метрик, трейсинга, health-эндпоинтов по каждой зависимости». «Минимум компонентов» — принцип проекта; глубокий healthcheck заведён задачей и ждёт своей очереди, а не является упущением.
  • «Здесь нужен интерфейс, чтобы это можно было замокать». Единственная реализация за интерфейсом — обычно лишний слой; см. «Честный предел» ниже.
  • «Нет ретрая у вызова в фоновом цикле». Тик повторится сам через poll_interval; ретрай внутри тика чаще вреден.
  • «Оригинальное и локализованное названия дублируются — избыточность». original_title заполняется всегда и при неуверенности дублирует title — это контракт capability recognition, а не недосмотр.

Вопросы к проходам

Форма: <имя прохода>: <вопрос> (<провенанс>). Журнал дефектов пока пуст, поэтому провенанс у всех пунктов — инвариант или ADR, а не пойманный случай; по мере накопления журнала список должен смещаться в сторону реальных промахов.

  • adversary: можно ли, управляя только именами файлов в раздаче и текстом контекста, добиться целевого пути вне paths.movies/series — включая путь через юникод, длину сверх лимита ФС и коллизию после нормализации? (инвариант «целевой путь строго под библиотекой», security.md)
  • adversary: есть ли последовательность команд, после которой снимается последняя копия данных — с учётом superseded-ссылок и гонки со сверкой? (инвариант «источник неприкосновенен»)
  • adversary: что даёт крафт-магнет с чужим или подставным инфохэшем — присоединение к чужой активной загрузке, отравление владения? (открытая задача про идентичность инфохэшей)
  • ops: что делает эта ветка, когда qBittorrent недоступен несколько минут подряд — сколько ERROR-строк в секунду и меняется ли состояние задач? (задача про ERROR-шторм фоновых циклов)
  • ops: как это ведёт себя при сотне загрузок в базе и десятках тысяч file_link — есть ли запрос без индекса и полный проход по таблице? (задача про масштаб 100/1000, database.md → «Настройки»)
  • ops: что остаётся на диске и в базе, если процесс убит посреди раскладки батча? (состояние linking и его восстановление)
  • code: логирующий чекпоинт один на операцию — или ошибка залогирована и возвращена вверх, где залогирована снова? (conventions/logging.md)
  • code: новое поле конфига появилось в config.example.toml с описанием назначения, диапазона и единиц? (conventions/config.md)
  • specs: не завелось ли поведение, которого спека не заказывала — тихий дефолт, проглоченная ошибка, ретрай «на всякий случай», отброшенное поле?
  • architecture: не появился ли второй способ делать то, что уже делается — второе место, где генерится время или id, второй парсер источника, вторая логика целевых имён мимо naming? (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.

Журнал дефектов

Запись на каждый воспроизведённый дефект, сразу, а не ретроспективно: со временем теряется не факт, а причина непоймания. Проскочившие — эвал-сет для калибровки конвейера, выборка по пометке.

Форма:

ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман]

  • Где: путь:строка либо «конвейер, а не код»
  • Симптом: как обнаружилось, кем и когда
  • Причина: что на самом деле было не так
  • Чем воспроизведён: тест, команда, замер — с числами
  • Почему не поймали: только для проскочивших — какой проход обязан был найти и что ему помешало
  • Что меняем: правило прохода, шаг гейта, конвенция, факт в документе проекта — либо «ничего, цена поимки выше цены дефекта»

Записи

Пока пусто. Журнал заведён 2026-07-23 вместе с переработкой конвейера (ADR-2026-07-23-review-pipeline-generative); случаи до этой даты не восстанавливались — восстановленная постфактум причина непоймания недостоверна, а именно она и нужна.