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
@@ -1,6 +1,6 @@
# Авто-раскладка только при подтверждённом матче в метабазе
- Дата: 2026-06-13
- **Дата:** 2026-06-13
## Контекст
@@ -21,7 +21,7 @@ jellybit распознаёт содержимое релиза через LLM
каноническое имя + `provider_id`. Но русские релизы и аниме часто в них
отсутствуют.
- Безопасность раскладки уже держится на валидации пути, не на промпте
(см. [recognition.md](../specs/recognition.md)); решение «авто vs review» —
(см. [recognition](../../openspec/specs/recognition/spec.md)); решение «авто vs review» —
второй слой защиты, на уровне доверия результату.
## Рассмотренные варианты
@@ -53,8 +53,8 @@ LLM не противоречат по типу/названию/году. Не
подтверждает) и убирает целый класс тихих ошибок «модель уверенно
ошиблась». Review здесь — не наказание, а штатный режим для всего, что
база не подтвердила (петля «догадка → подсказка → перераспознавание», см.
[review-ux.md](../specs/review-ux.md)). Полная модель уверенности — в
[recognition.md](../specs/recognition.md).
[review](../../openspec/specs/review/spec.md)). Полная модель уверенности — в
[recognition](../../openspec/specs/recognition/spec.md).
## Последствия
+2 -2
View File
@@ -1,7 +1,7 @@
# Docker как единица деплоя, образ собирается на сервере
- Дата: 2026-06-13
- Статус: заменено на ADR-2026-07-24-local-image-build
- **Дата:** 2026-06-13
- **Статус:** заменено на ADR-2026-07-24-local-image-build
## Контекст
+1 -1
View File
@@ -1,6 +1,6 @@
# Go и доставка одним бинарём
- Дата: 2026-06-13
- **Дата:** 2026-06-13
## Контекст
+1 -1
View File
@@ -1,6 +1,6 @@
# Хардлинки вместо копирования и симлинков
- Дата: 2026-06-13
- **Дата:** 2026-06-13
## Контекст
@@ -0,0 +1,81 @@
# Отдельную сущность «тайтл» не вводим
- **Дата:** 2026-07-02
- **Источник:** черновик `docs/drafts/logical-title-model.md` (разбор от
2026-07-01, переработан 2026-07-02; удалён при переводе проекта на канон,
полный текст — в истории git). Первый производный change —
[openspec/changes/archive/2026-07-02-ulid-identity/design.md](../../openspec/changes/archive/2026-07-02-ulid-identity/design.md).
## Решение
Логический тайтл (фильм или сериал, складывающийся из нескольких загрузок во
времени) остаётся **вычисляемой группой**, а не хранимой сущностью: доменная
идентичность — `download` (ULID + множество инфохэшей), связь с диском —
`file_link` с владением целевым путём, а «второй сезон в ту же папку» решается
**правилом сходимости папки** при построении плана раскладки.
## Почему
Разбор шёл от операций, и у тайтла их не нашлось:
> У `title` при разборе **не нашлось ни одной собственной операции**: сходимость
> папки — правило при построении плана; merge докачивания — per-path логика;
> удаление целиком — цикл по вычисляемой группе. Сущность без собственных
> операций — это линза, а линзу достаточно вычислять, не хранить.
Второй аргумент — у папки уже есть дом, и вычисляемый якорь **корректнее**
хранимого:
> «Папка — это title-уровневое состояние, ей нужен дом» разбивается о то, что
> дом у папки уже есть — файловая система и `dst_path` живых `file_link`'ов.
> Реестр дублировал бы то, что и так записано в БД в N экземплярах. Причём
> вычисляемый якорь корректнее хранимого: если все файлы сериала снесли, живых
> ссылок нет — и новая загрузка честно создаёт свежую папку; хранимый
> `title.folder` указывал бы в пустоту.
Третий — отказ **устраняет**, а не решает хвост развилок: жизненный цикл тайтла
(рождение, смерть, пустой тайтл), слияние тайтлов, ad-hoc тайтл без провайдера,
обратная миграция существующих строк, отдельный title-лог.
## Рассмотренные варианты
- **L2 — `title` с ключом `(provider, provider_id)`.** Отвергнут: привязывает
долгоживущую сущность к провайдеру, который может смениться.
- **L2-min — `title` со своим ULID + `title_external_id`** (провайдерные id
множеством-атрибутом, симметрично `download_infohash`). Схема красивая и
решает смену провайдера, ad-hoc тайтлы и слияние. Отвергнут именно по
аргументу выше: собственных операций нет, а сущность тянет жизненный цикл,
миграцию и четыре развилки.
- **L3 — title-центричная медиатека (модель sonarr).** Отвергнут осознанно: мы
не ходим в индексеры, не мониторим тайтлы и не ведём профили качества —
контент приносит пользователь. Это граница домена,
[passport.md](../passport.md) → «Что целью не является».
## Последствия
- `+` Идентичность осталась одноуровневой: `download` — мост между раздачей в
qBittorrent и файлами на диске, и каждая сущность цепочки отвечает на свои
операции.
- `+` Главная боль («второй сезон должен лечь в ту же папку») закрыта дешёвым
правилом при построении плана — реализовано change'ем
`2026-07-10-series-folder-convergence`, требования влиты в
[openspec/specs/file-layout](../../openspec/specs/file-layout/spec.md).
- `+` Устранён, а не отложен, хвост развилок вокруг жизненного цикла тайтла.
- `` Группировка тайтла в UI и «удалить тайтл целиком» придётся каждый раз
**вычислять** по `(provider, provider_id)` и общей папке; дешёвого хранимого
ключа для этого нет.
- `` Рассинхрон «несколько живых папок с одним `(provider, provider_id)`»
разрешается только уходом в review — in-app лечения нет, чинится руками на
диске.
- `` Слияние загрузок при перезаливе «той же вещи» осталось открытым: когда
несколько инфохэшей считать одной загрузкой, а когда разными, — вопрос
переехал в задачу про merge-раскладку.
## Триггер пересмотра
Записан отдельно, чтобы не гонять этот круг заново:
> Сущность `title` возвращается в обсуждение, только когда появится **операция
> или состояние, которому реально негде жить** в `download` + `file_link` —
> например, «переименовать сериал целиком с переносом ссылок» как регулярное
> действие или заметки уровня группы. До того — вычисляем.
@@ -1,6 +1,6 @@
# Конвейер ревью: гейт, generative-проходы и обязательный триаж
- Дата: 2026-07-23
- **Дата:** 2026-07-23
## Контекст
+1 -1
View File
@@ -1,6 +1,6 @@
# Образ собирается локально и едет на сервер через docker save/load
- Дата: 2026-07-24
- **Дата:** 2026-07-24
## Контекст
+34 -47
View File
@@ -1,64 +1,51 @@
# Architecture Decision Records (ADR)
# Журнал решений
Журнал значимых архитектурных решений по jellybit. Одна запись — одно
решение. ADR пишем **постфактум**, когда решение принято и зафиксировано
в коде/проекте: идеи и неподтверждённые планы живут в `docs/drafts`, а не
в ADR. Записи **неизменяемы**: передумали → не правим старую, заводим
новую и помечаем старую.
Одна запись — одно решение. **ADR это промоут поверх архивного `design.md`**,
а не второе сочинение: запись цитирует решение и ссылается на
`openspec/changes/archive/<id>/design.md`.
Главная ценность записи — сохранить **почему**: намерение и причинность.
Это важнее аккуратности оформления и полноты остальных секций.
Главная ценность записи — сохранить **почему**: намерение и причинность. Это
важнее аккуратности оформления и полноты остальных секций.
Формат и процесс унаследованы от соседнего проекта umbar.
## Когда заводить
## Когда заводить ADR
Верно одно из трёх:
- Выбор технологии или инструмента.
- Структурные решения (хранилище, организация компонентов, протоколы).
- Решения с долгосрочными последствиями или дорогим откатом.
- **Намеренный отказ** от очевидного подхода — чтобы потом не
переоткрывать «а почему мы не сделали X».
<!-- копия: adr-когда-заводить из av-dev-pm/skills/canon/references/canon.md -->
- **дорогой откат** — переделка стоит дороже переписывания одного файла;
- **намеренный отказ** от очевидного подхода;
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус
«заменено на».
<!-- /копия: adr-когда-заводить -->
Не заводить для рутины (бамп версии зависимости, добавление эндпоинта по
накатанной схеме) и того, что и так видно из кода и git.
Не заводить для рутины и для того, что видно из кода и `git log`.
## Соглашения
- **Имя файла = идентификатор:** `ADR-ГГГГ-ММ-ДД-kebab-slug.md`.
Идентификатор — имя без `.md`. Slug — латиницей.
- **Дата** — когда решение реально принято.
- Несколько ADR за один день различаются по slug.
- **Заголовок в файле:** `# Человеческий заголовок` (без даты и ID — они
в имени файла и в строке «Дата»).
- Секция **«Рассмотренные варианты» — опциональна**: оставляй её, только
если альтернативы реально рассматривались.
- Шаблон новой записи — [`template.md`](template.md).
- Имя файла `ADR-ГГГГ-ММ-ДД-slug.md`, слаг английский, дата — когда решение
реально принято. Идентификатор записи — имя файла без `.md`; несколько
записей за один день различаются слагом.
- **Заголовок в файле** — `# Человеческий заголовок`, без даты и id: они в
имени файла и в поле меты.
- Секция «Рассмотренные варианты» **опциональна**: оставляй, только если
альтернативы реально рассматривались.
- Записи неизменяемы: передумали — новая запись, старой ставится статус.
- Активная запись статуса не имеет. Значений два: `заменено на ADR-…` и
`устарело`; ставятся полем меты записи — `- **Статус:** …` рядом с датой и
источником, а не абзацем в теле.
- Замена: в новой записи — строка «Заменяет ADR-…», в старой — поле статуса,
тело не трогаем (это часть истории), в таблице ниже правится статус.
## Статусы
Активная запись статуса **не имеет**. Статус появляется, только когда
запись теряет силу, и значений всего два:
- `заменено на ADR-ГГГГ-ММ-ДД-slug` — решение пересмотрено новой ADR.
- `устарело` — решение потеряло смысл и замены нет.
## Замена и устаревание
1. Заводим новую ADR; в её «Контексте» — строка
«Заменяет ADR-ГГГГ-ММ-ДД-slug».
2. В старой ADR добавляем строку `- Статус: заменено на ADR-…` сразу под
датой. Тело не трогаем — это часть истории.
3. Обновляем статус старой записи в индексе ниже.
## Список записей
## Записи
Новые сверху.
| Дата | Запись | Статус |
| ---------- | ---------------------------------------------------------------- | ------ |
| Дата | Запись | Статус |
| --- | --- | --- |
| 2026-07-24 | [Локальная сборка образа + доставка docker save/load](ADR-2026-07-24-local-image-build.md) | — |
| 2026-07-23 | [Конвейер ревью: гейт, generative-проходы и триаж](ADR-2026-07-23-review-pipeline-generative.md) | — |
| 2026-07-02 | [Отдельную сущность «тайтл» не вводим](ADR-2026-07-02-no-title-entity.md) | — |
| 2026-06-13 | [Авто-раскладка только при матче в метабазе](ADR-2026-06-13-auto-link-requires-db-match.md) | — |
| 2026-06-13 | [Docker как единица деплоя](ADR-2026-06-13-docker-deploy.md) | заменено на ADR-2026-07-24-local-image-build |
| 2026-06-13 | [Docker как единица деплоя](ADR-2026-06-13-docker-deploy.md) | заменено на ADR-2026-07-24-local-image-build |
| 2026-06-13 | [Хардлинки вместо копирования и симлинков](ADR-2026-06-13-hardlinks.md) | — |
| 2026-06-13 | [Go и доставка одним бинарём](ADR-2026-06-13-go-single-binary.md) | — |
| 2026-06-13 | [Go и доставка одним бинарём](ADR-2026-06-13-go-single-binary.md) | — |
+20 -24
View File
@@ -1,36 +1,32 @@
# Краткий заголовок решения
- Дата: ГГГГ-ММ-ДД
<!-- Строку статуса добавляют позже, только если запись потеряла силу:
- Статус: заменено на ADR-ГГГГ-ММ-ДД-slug
- Статус: устарело
У активной записи строки статуса нет. -->
- **Дата:** ГГГГ-ММ-ДД
- **Источник:** openspec/changes/archive/<id>/design.md
## Контекст
<!-- Статус ставится тем же полем и только при пересмотре:
- **Статус:** заменено на ADR-ГГГГ-ММ-ДД-slug
- **Статус:** устарело
У активной записи поля нет. -->
Что вынудило принять решение: проблема, силы и ограничения (ресурсы,
стоимость, время на поддержку, существующая архитектура). Пиши так, чтобы
через год было понятно «почему это вообще делалось» без чтения переписки.
## Решение
Что именно решено — одной фразой.
## Почему
Намерение и причина. **Цитата из источника, а не пересказ.** Пиши так, чтобы
через год было понятно без чтения переписки.
## Рассмотренные варианты
<!-- Опциональная секция. Оставь, только если варианты реально
рассматривались. Если решение было единственным очевидным — удали
её, а причину объясни в «Решении». -->
рассматривались. Если решение было единственным очевидным — удали её,
а причину объясни в «Почему». -->
- **Вариант A** — суть, плюсы и минусы.
- **Вариант B** — суть, плюсы и минусы.
- **Вариант C** — если отвергнут сразу, коротко почему.
## Решение
Что именно сделано и — главное — **почему**: какое намерение и какая
причина за этим стоят. Если варианты рассматривались — почему выбран
этот, а не остальные.
- **Вариант A** — суть, почему отвергнут.
- **Вариант B** — суть, почему отвергнут.
## Последствия
- `+` что стало лучше, какие возможности открылись.
- `-` чем платим: новые ограничения, риски, регулярная нагрузка на
поддержку.
- Что нужно сделать как следствие (если есть).
- `+` что стало лучше.
- `` чем платим: ограничения, риски, нагрузка на поддержку.