раскладка av-dev повышена с канона 12 до версии 5
- три плагина слились в один `av-dev`: служебные `docs/.docs.json` и `tasks/.tasks.json` заменены на `.av-dev.toml` в корне, в гейте переехали пути трёх скриптов, вызовы скиллов переименованы по всему репозиторию - тип задачи `goal` и `ROADMAP.md` упразднены: семь целей закрыты с причинами, теги сняты, объявлена стадия `support` - метка `small`/`medium`/`large` снята из процесса — вместо «Триггеров метки» в review.md подраздел «Когда звать глубокое ревью»; следом разобран урожай doc-consistency: девять фактов сведены к одному дому
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"canon": 12,
|
||||
"migrations": "internal/store/migrations"
|
||||
}
|
||||
+11
-4
@@ -1,17 +1,24 @@
|
||||
# Журнал решений
|
||||
|
||||
Одна запись — одно решение. **ADR это промоут поверх архивного `design.md`**,
|
||||
а не второе сочинение: запись цитирует решение и ссылается на
|
||||
`openspec/changes/archive/<id>/design.md`.
|
||||
Одна запись — одно решение. **ADR продвигает уже написанное решение, а не
|
||||
сочиняет его заново**: запись цитирует решение и ссылается на источник —
|
||||
`openspec/changes/archive/<id>/design.md`, а у решения, принятого разведкой без
|
||||
изменения, на её записку.
|
||||
|
||||
Главная ценность записи — сохранить **почему**: намерение и причинность. Это
|
||||
важнее аккуратности оформления и полноты остальных секций.
|
||||
|
||||
Решение старше OpenSpec либо принятое вне этого репозитория архивного
|
||||
`design.md` не имеет по построению. Тогда поле **Источник** называет то, что
|
||||
есть, — историю git, черновик, записку соседнего репозитория — и прямо говорит,
|
||||
что архивного `design.md` нет. Запись без поля «Источник» вовсе — дефект; запись,
|
||||
честно назвавшая отсутствие источника, — нет.
|
||||
|
||||
## Когда заводить
|
||||
|
||||
Верно одно из трёх:
|
||||
|
||||
<!-- копия: adr-когда-заводить из av-dev-docs/skills/canon/references/canon.md -->
|
||||
<!-- копия: adr-когда-заводить из av-dev/skills/canon/references/canon.md -->
|
||||
- **дорогой откат** — переделка стоит дороже переписывания одного файла;
|
||||
- **намеренный отказ** от очевидного подхода;
|
||||
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Краткий заголовок решения
|
||||
|
||||
- **Дата:** ГГГГ-ММ-ДД
|
||||
- **Источник:** openspec/changes/archive/<id>/design.md
|
||||
- **Источник:** openspec/changes/archive/<id>/design.md — либо записка разведки,
|
||||
если решение принято без изменения
|
||||
|
||||
<!-- Статус ставится тем же полем и только при пересмотре:
|
||||
- **Статус:** заменено на ADR-ГГГГ-ММ-ДД-slug
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
|
||||
| Что | Где |
|
||||
| --- | --- |
|
||||
| Время | `store.Now()` — единственный источник меток времени в данных, всегда UTC; формат хранения — RFC 3339. Вторая санкционированная точка wall-clock — timestamp-часть ULID в `ident.NewID` (исключение `^internal/(ident\|store)/` в `.golangci.yml`). Отдельно от меток в данных стоят замеры длительности: `cmd/jellybit` исключён из `forbidigo` целиком (правило `^cmd/`), плюс точечные `//nolint:forbidigo` в `internal/logging/ext.go` и `internal/httpapi/httpapi.go` |
|
||||
| Время | `store.Now()` — единственный источник меток времени в данных, всегда UTC; формат хранения — [database.md](database.md). Вторая санкционированная точка wall-clock — timestamp-часть ULID в `ident.NewID` (исключение `^internal/(ident\|store)/` в `.golangci.yml`). Отдельно от меток в данных стоят замеры длительности: `cmd/jellybit` исключён из `forbidigo` целиком (правило `^cmd/`), плюс точечные `//nolint:forbidigo` в `internal/logging/ext.go` и `internal/httpapi/httpapi.go` |
|
||||
| Идентификаторы | `internal/ident` — генерация и нормализация ULID; `ident.Parse` на каждой входной границе |
|
||||
| Целевые имена и превью раскладки | `internal/naming` — одна логика для превью в UI и для реального применения |
|
||||
| Чистка человекочитаемых значений | три санитайзера с разным предметом, сводить их в один нельзя: `recognize.SanitizeTitle` — значение (недоверенный вход: LLM и метабазы), `layout.sanitizeComponent` — компонент пути под требования ФС, `naming.sanitize` — отображаемый ярлык. Значение метабазы чистится **на каждой** точке входа в план: сборка матча, копия кандидата для ревью, набор закреплённых значений источника и его чтение — [ADR-2026-08-10-sanitize-at-every-entry](adr/ADR-2026-08-10-sanitize-at-every-entry.md) |
|
||||
@@ -116,7 +116,7 @@
|
||||
| Приём | use-case `ingest` — общий путь для HTTP, веб-UI, Telegram и CLI |
|
||||
| Переходы состояний | `worker` под per-download блокировкой; легальность перехода задаётся декларативным графом |
|
||||
| Хардлинки и удаление своих ссылок | `internal/layout` — единственное место, которое пишет в файловую систему библиотеки |
|
||||
| Построение и проверка целевого пути | `layout.BuildLinks` — единственная сборка пути; там же обе проверки, и порядок значим: нахождение под корнем библиотеки, затем длина компонента. Отсюда же строятся оба предпросмотра ревью, поэтому показанное и применённое совпадают устройством, а не договорённостью |
|
||||
| Построение и проверка целевого пути | `layout.BuildLinks` — единственная сборка пути; там же обе проверки, порядок нормирован [file-layout](../openspec/specs/file-layout/spec.md). Отсюда же строятся оба предпросмотра ревью, поэтому показанное и применённое совпадают устройством, а не договорённостью |
|
||||
| Причина, по которой человек не видит плана | считается **на показе** (`worker.ReviewData.PreviewError`) и предпочитается записанной в состоянии: записанной может не быть вовсе, а после смены источника она уже про другой план — [ADR-2026-08-10-reason-computed-on-read](adr/ADR-2026-08-10-reason-computed-on-read.md) |
|
||||
| Условие допуска полного удаления | `store.State.CanDelete()` — «из этого состояния удаление с файлами разрешено»; своего перечня состояний не заводит ни один транспорт (веб-UI, Telegram, страница группового удаления), а проверку в ядре предикат не заменяет: допуск держится без транспорта |
|
||||
| Условие самообновления веб-UI | `store.State.IsObservable()` — «состояние ещё может измениться без человека»; транспорт своего перечня состояний не заводит, а поверхность (карточка списка, страница загрузки) держит **ровно один** поллер на обновляемый корень — [ADR-2026-08-10-observability-is-not-terminality](adr/ADR-2026-08-10-observability-is-not-terminality.md), правило разметки — [conventions/web-ui.md](conventions/web-ui.md) |
|
||||
@@ -185,7 +185,7 @@ Jellyfin указывают на `movies`/`series`, а не на корень
|
||||
| Область | Чего нет сегодня | Задача |
|
||||
| --- | --- | --- |
|
||||
| Масштаб | ориентир 100/1000 загрузок не зафиксирован, узкие места SQLite, воркера и поллинга не измерены | `scale-100-downloads` |
|
||||
| Ретеншен | терминальные задачи и сырые ответы LLM копятся вечно, авточистки нет | `db-retention-cleanup` |
|
||||
| Срок хранения | терминальные задачи и сырые ответы LLM копятся вечно, авточистки нет | `db-retention-cleanup` |
|
||||
| Бекап | бекапить `/data` требуется, а стратегия и ротация не описаны | `sqlite-backup` |
|
||||
| Метрики и алертинг | healthcheck проверяет только сам сервис; метрик и алертинга нет, отказ виден по застрявшей задаче | `deep-healthcheck-dependencies` |
|
||||
| Идентичность раздачи | split v1/v2-хеши не связаны, паре `xt` из магнета доверяем | `infohash-identity-integrity` |
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
перечень «Механизировано» ниже. Причина: файл на несколько сотен строк
|
||||
размазывает внимание по тривиальному — и модель, и человек добросовестно
|
||||
проверят именование и не дойдут до формы решения. Процедура промоута —
|
||||
`references/promote.md` скилла `av-dev-pipeline:review-pipeline`.
|
||||
`references/promote.md` скилла `av-dev:code-review`.
|
||||
|
||||
Обоснование «почему именно так» живёт в [../adr/](../adr/README.md); инварианты
|
||||
с severity — в [CLAUDE.md](../../CLAUDE.md).
|
||||
|
||||
@@ -39,16 +39,16 @@
|
||||
|
||||
## Прочее
|
||||
|
||||
- Enum-поля (`state`, `kind`, …) — обычный `TEXT` без `CHECK`; допустимые
|
||||
значения держит код (`internal/store`).
|
||||
- Временные метки — `TEXT` в **RFC 3339, UTC (суффикс `Z`)**, напр.
|
||||
`2006-01-02T15:04:05Z` (секундная точность). Фиксированная ширина сохраняет
|
||||
лексикографическую сортировку TEXT = хронологию (`ORDER BY created_at`).
|
||||
Единая точка генерации — приложение: `store.Now()` + `store.FormatTime`/
|
||||
- Enum-поля — обычный `TEXT`/`INTEGER` без `CHECK`; допустимые значения держит
|
||||
код (`internal/store`). Какие именно поля — [../database.md](../database.md) →
|
||||
«Индексы и ограничения».
|
||||
- Временные метки пишет приложение: `store.Now()` + `store.FormatTime`/
|
||||
`ParseTime` (аналогично `ident.NewID` для id), а не дефолт в схеме — так
|
||||
забытая вставка падает громко. Измерение длительности — не метка времени: у
|
||||
внешних вызовов его засекает `logging.StartCall`. Таймзона отображения в
|
||||
UI — конфиг `[general].timezone`.
|
||||
забытая вставка падает громко. Формат и точность хранения —
|
||||
[../database.md](../database.md); фиксированная ширина там выбрана затем, чтобы
|
||||
лексикографическая сортировка TEXT совпадала с хронологией. Измерение
|
||||
длительности — не метка времени: у внешних вызовов его засекает
|
||||
`logging.StartCall`. Таймзона отображения в UI — конфиг `[general].timezone`.
|
||||
- Миграции — goose (`internal/store/migrations`): SQL-файлы для DDL;
|
||||
Go-миграции (`goose.AddMigrationContext`) — когда нужен код (генерация
|
||||
id, backfill). При изменении структуры обновляем ER-схему
|
||||
|
||||
@@ -219,12 +219,16 @@ Go-ошибки логируем как атрибут, не как текст
|
||||
|
||||
## Безопасность: что не логируем
|
||||
|
||||
Никаких секретов в полях и сообщениях. Под запретом:
|
||||
Никаких секретов в полях и сообщениях. Состав секретов —
|
||||
[CLAUDE.md](../../CLAUDE.md) → «Инварианты», строка «Секреты не попадают в логи,
|
||||
диагностику и ответы API»; здесь он не дублируется.
|
||||
|
||||
- учётные данные qBittorrent (логин/пароль, cookie сессии);
|
||||
- API-ключ и токен LLM-провайдера, `Authorization`-заголовки;
|
||||
- ключи TMDB/TVDB и прочих метабаз;
|
||||
- содержимое аутентификационных параметров magnet/трекеров.
|
||||
Специфичные для логов **носители** секрета, которых в инварианте нет:
|
||||
|
||||
- cookie сессии qBittorrent;
|
||||
- заголовки `Authorization`;
|
||||
- аутентификационные параметры magnet и трекеров;
|
||||
- URL внутри `*url.Error` (см. ниже).
|
||||
|
||||
Дополнительно:
|
||||
|
||||
@@ -246,8 +250,9 @@ Go-ошибки логируем как атрибут, не как текст
|
||||
|
||||
- Пишем JSON в `stdout` одним потоком; сбор и ротацию делает окружение
|
||||
(docker/journald). Не маршрутизируем по файлам.
|
||||
- Базовый уровень в проде — `INFO`; `DEBUG` включается через конфиг/env при
|
||||
необходимости. dev — `DEBUG`.
|
||||
- Базовый уровень в проде — `INFO`; `DEBUG` включается полем `level` секции
|
||||
`[log]` в `config.toml` — env для конфига не используется
|
||||
([config.md](config.md)). dev — `DEBUG`.
|
||||
|
||||
## Анализ
|
||||
|
||||
|
||||
+7
-5
@@ -176,7 +176,7 @@ erDiagram
|
||||
внутри JSON нет, SQL по содержимому этих полей не делается.
|
||||
- **`recognition.raw_llm`** — сырой ответ модели как есть, **несжатый**. Это
|
||||
самое крупное поле в базе и главный кандидат на рост: у каждой попытки
|
||||
распознавания свой ответ, попытки не вытесняются, ретеншена нет
|
||||
распознавания свой ответ, попытки не вытесняются, срока хранения нет
|
||||
(задача в беклоге).
|
||||
- **`download_torrent.data`** — единственный BLOB: исходные байты `.torrent`
|
||||
(обычно десятки КБ, у больших раздач — сотни). Читается целиком при
|
||||
@@ -224,8 +224,10 @@ erDiagram
|
||||
| `httpapi.maxBulkDelete` | `20` загрузок | предел размера одной пачки группового удаления. Подтверждение, перечисляющее больше, человек не читает — то есть перестаёт быть подтверждением; плюс один синхронный запрос упирается в столько же последовательных вызовов qBittorrent. Предел называет сама страница выбора; отказ по пределу возвращает выбор с сохранёнными отметками |
|
||||
| `httpapi.bulkFailThreshold` | `3` отказа подряд | сколько подряд идущих отказов внешнего сервиса прекращают проход группового удаления. Удаление снимает библиотечные ссылки раньше, чем сносит раздачу: при недоступном qBittorrent каждая единица успевает выполнить необратимый локальный шаг и упасть на внешнем. Счётчик сбрасывается на успехе; конфликт состояния системным отказом не считается |
|
||||
| `httpapi.bulkBudget` | `2` минуты | потолок времени на один проход группового удаления. Удаление держит общий замок воркера на всё время обращения к qBittorrent, поэтому медленно, но успешно отвечающий сосед остановил бы фоновую работу целиком, а порог отказов такого не ловит. Проверяется между единицами: начатое удаление не обрывается, иначе оно встанет между снятием ссылок и сносом раздачи |
|
||||
| `layout.maxComponentBytes` | `255` байт | предел длины компонента целевого пути (`NAME_MAX` у ext4/xfs/btrfs); меряется в байтах UTF-8, проверяется **до** первой операции с ФС, отказ уводит задачу в `review` с кодом `name_too_long`. У ядра не выясняется; на ФС с меньшим пределом остаётся отказ ядра — лечение правкой константы, а не настройкой |
|
||||
| `layout.maxComponentBytes` | `255` байт | предел длины компонента целевого пути (`NAME_MAX` у ext4/xfs/btrfs); меряется в байтах UTF-8. Что происходит при превышении — [file-layout](../openspec/specs/file-layout/spec.md); величину у ядра не выясняем, и на ФС с меньшим пределом остаётся отказ ядра — чинится правкой константы, а не настройкой |
|
||||
| `llm.maxResponseBody` | `8 MiB` | предел размера тела ответа LLM-эндпоинта: ответ читается через `io.LimitReader`, а не целиком |
|
||||
| `metadata.maxBody` | `4 MiB` | то же для ответов TMDB, TVDB и TVMaze |
|
||||
|
||||
**Ретеншена нет ни у одной таблицы**, лимита на размер тела ответа LLM нет,
|
||||
кэша метабаз нет; состояние по каждому пробелу и заведённые под них задачи —
|
||||
[architecture.md](architecture.md) → «Открытые вопросы».
|
||||
**Срока хранения нет ни у одной таблицы**, кэша метабаз нет; состояние по каждому
|
||||
пробелу и заведённые под них задачи — [architecture.md](architecture.md) →
|
||||
«Открытые вопросы».
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# Паспорт проекта
|
||||
|
||||
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
|
||||
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «что уже умеет», паспорт —
|
||||
устроено», [openspec/specs/](../openspec/specs/) — «что уже умеет», паспорт —
|
||||
«зачем и для кого».
|
||||
|
||||
## Цель
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
не от смены формата.
|
||||
|
||||
**Каждый вывод — с числами и командой или условиями, которыми получен**, чтобы
|
||||
его можно было перепроверить. Число без провенанса проход обязан читать как
|
||||
его можно было перепроверить. Число без происхождения проход обязан читать как
|
||||
условие, а не как замер. Число, чей источник по ссылке не подтвердился, не
|
||||
выбрасывается и не переписывается по догадке — остаётся с пометкой «расходится
|
||||
с источником: там <что нашли>».
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
сообщения берутся **источник** (magnet) и **контекст** для распознавания. Формат
|
||||
чужой, ничем не документирован и может измениться без предупреждения.
|
||||
|
||||
**Провенанс.** Образец снят вручную из личного чата Telegram (сообщение
|
||||
**Происхождение.** Образец снят вручную из личного чата Telegram (сообщение
|
||||
датировано 2026-03-21) и зафиксирован в [BRIEF.md] проекта; второй образец,
|
||||
меньшего размера, живёт константой `botMessage` в
|
||||
`internal/tgbot/parse_test.go` и проверяется тестами разборщика. Статистики по
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
из ответа `/search`. Здесь записано, откуда взята форма этого ответа и чего в ней
|
||||
не подтверждено.
|
||||
|
||||
**Провенанс — и он слабый.** Всё ниже сверено **по публичной документации**
|
||||
**Происхождение — и оно слабое.** Всё ниже сверено **по публичной документации**
|
||||
TheTVDB API v4, файл `docs/swagger.yml` репозитория `thetvdb/v4-api`, поле
|
||||
`info.version` = `4.7.10` (прочитано 2026-08-07). **Живым прогоном не
|
||||
подтверждено ни одно наблюдение**: `CLAUDE.md` → «Запреты» запрещает ходить в
|
||||
|
||||
+56
-75
@@ -1,8 +1,8 @@
|
||||
# Ревью: настройка и журнал
|
||||
|
||||
Проектная часть конвейера ревью: чем jellybit отличается от абстрактного
|
||||
Go-сервиса и что здесь уже проскакивало. Устройство самого конвейера (метки,
|
||||
стадии, контракт находок) живёт в скилле, а не здесь.
|
||||
Go-сервиса и что здесь уже проскакивало. Устройство самого конвейера (состав
|
||||
проходов, стадии, контракт находок) живёт в скилле, а не здесь.
|
||||
|
||||
## Как настроен конвейер
|
||||
|
||||
@@ -135,8 +135,8 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
|
||||
### Вопросы по темам
|
||||
|
||||
Форма: `<тема>: <вопрос> (<провенанс>)`. Адресуется теме, а не имени прохода:
|
||||
проход переезжает между метками и упраздняется, тема переезд переживает. Задаёт
|
||||
Форма: `<тема>: <вопрос> (<откуда>)`. Адресуется теме, а не имени прохода:
|
||||
проход переезжает между скиллами и упраздняется, тема переезд переживает. Задаёт
|
||||
вопрос тот, кто закрывает тему на текущем прогоне.
|
||||
|
||||
- `security`: можно ли, управляя только именами файлов в раздаче и текстом
|
||||
@@ -179,8 +179,8 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
- `requirements`: не завелось ли поведение, которого спека не заказывала — тихий
|
||||
дефолт, проглоченная ошибка, ретрай «на всякий случай», отброшенное поле?
|
||||
- `security`: читается ли тело ответа внешнего сервиса целиком без предела —
|
||||
у LLM (8 MiB) и метабаз (4 MiB) предел стоит, и новый исходящий вызов обязан
|
||||
заводить свой ([security.md](security.md) → «Что вне модели»)
|
||||
у LLM и метабаз предел стоит ([database.md](database.md) → «Пределы, зашитые
|
||||
константой кода»), и новый исходящий вызов обязан заводить свой?
|
||||
- `operations`: гарантия, которую вводит изменение, поставлена на запись или на
|
||||
чтение — и что будет с данными, записанными до деплоя, которые обычный путь
|
||||
не перезаписывает? (журнал, 2026-08-10: чистка названия стояла на записи, и
|
||||
@@ -201,75 +201,50 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
логика целевых имён мимо `naming`?
|
||||
([architecture.md](architecture.md) → «Единые точки проекта»)
|
||||
|
||||
### Триггеры метки
|
||||
### Когда звать глубокое ревью
|
||||
|
||||
Уточняет умолчания конвейера, не отменяет их. Рабочее умолчание — `medium`;
|
||||
миграция схемы и публичный контракт метку **не** поднимают: их проверяют
|
||||
проходы, которые в `medium` и так есть. Списка три: два поднимают до `large`,
|
||||
по одному на ось, третий опускает до `small`.
|
||||
Цикл задачи проверяет корректность и механику одним и тем же составом на всякой
|
||||
задаче. Глубину даёт только отдельный прогон по области —
|
||||
`av-dev:code-deep-review`, — и **зовёт его человек**. Списки ниже уточняют
|
||||
признаки, по которым его здесь зовут, а не заводят расписание.
|
||||
|
||||
**Крупное здесь** — про объём, сколько узлов и слоёв трогает изменение:
|
||||
**Области, которые смотрят целиком:**
|
||||
|
||||
- перенос ответственности между `worker`, `recognition`, `layout` и `store`;
|
||||
- новое состояние в графе переходов загрузки: оно тянет за собой воркер, спеку,
|
||||
отображение в веб-UI и боте и восстановление после рестарта;
|
||||
- правка, идущая насквозь по цепочке приём → распознавание → раскладка;
|
||||
- новый провайдер метабазы за существующим интерфейсом: клиент, поле конфига с
|
||||
образцом, слияние полей кандидата, ветка «провайдера нет».
|
||||
- **правила идентичности, слияния и разбора** — ключ владения раздачей и сверка
|
||||
с qBittorrent (`ident`, `internal/store`, `state-reconciliation`); построение
|
||||
целевых путей и санитизация имён (`internal/layout`, `naming`); разбор
|
||||
недоверенного входа — bencode, magnet, текст контекста, ответ LLM
|
||||
(`internal/torrent`, `internal/magnet`, `internal/tgbot/parse.go`); слияние
|
||||
кандидата метабазы и победитель при конфликте (`internal/metadata`,
|
||||
`metadata-match`). Сюда задачи возвращаются чаще всего: у одного правила здесь
|
||||
несколько домов, и правка одного из них молча расходится с остальными.
|
||||
- **удаление раздачи с файлами** — история инцидентов самая длинная: уборка
|
||||
своего торрента после отмены сносила чужие файлы (журнал, 2026-08-06), три
|
||||
дефекта поштучного удаления жили незамеченными до появления пачки и ещё
|
||||
четыре нашлись на групповой поверхности (журнал, 2026-08-10).
|
||||
- **граф состояний загрузки и восстановление после рестарта** — новое состояние
|
||||
тянет за собой воркер, спеку, отображение в веб-UI и боте; расхождение видно
|
||||
не на диффе, а на живом сервере.
|
||||
- **код под дорогое решение** — то, на что обопрётся ADR или новая capability:
|
||||
форму здесь нащупывают по ходу, и цена ошибки не в правке, а в переписывании
|
||||
решения.
|
||||
|
||||
**Незнакомое здесь** — про форму решения, которую предстоит нащупать по ходу:
|
||||
**Необратимое здесь** — **места кода**, которые после мерджа не откатываются
|
||||
обратной правкой. Находка в таком месте уходит человеку развилкой, а не чинится
|
||||
молча. Перечень **операций**, которые спрашиваются у человека всегда, — другой
|
||||
вопрос и другой дом: [CLAUDE.md](../CLAUDE.md) → «Работа» → «Необратимое».
|
||||
|
||||
- новый пакет `internal/*` или новая capability в `openspec/specs/`;
|
||||
- новый транспорт приёма или уведомлений рядом с REST, веб-UI, ботом и CLI;
|
||||
- заводится или меняется **правило идентичности, слияния или разбора**: ключ
|
||||
владения раздачей и сверка с qBittorrent (`ident`, `internal/store`,
|
||||
`state-reconciliation`); построение целевых путей и санитизация имён
|
||||
(`internal/layout`, `naming`); новый вид входа или новая ветка неоднозначности
|
||||
у разбора недоверенного — bencode, magnet, текст контекста, ответ LLM
|
||||
(`internal/torrent`, `internal/magnet`, `internal/tgbot/parse.go`); новый
|
||||
источник или новый победитель при конфликте в слиянии кандидата метабазы
|
||||
(`internal/metadata`, `metadata-match`); merge-раскладка при повторном
|
||||
добавлении раздачи.
|
||||
|
||||
- **новая поверхность поверх необратимой операции** — вторая точка входа в
|
||||
команду, которая удаляет файлы или снимает последнюю копию. Форма решения
|
||||
здесь нащупывается по ходу: подтверждение, порядок отказов, остаток,
|
||||
наблюдаемость. Выведено по факту на `bulk-delete-page` (журнал, 2026-08-10):
|
||||
метка `medium` не дала ни враждебного прохода, ни замера, а именно они нашли
|
||||
четыре дефекта класса «необратимо».
|
||||
|
||||
- «Поведение, видимое снаружи» здесь включает **тексты и карточки Telegram** —
|
||||
для единственного пользователя это и есть интерфейс.
|
||||
|
||||
**Место из перечня метку не поднимает — поднимает правило.** Перечни выше
|
||||
отвечают «здесь такие правила водятся», а не «любая правка здесь идёт в
|
||||
`large`». Метку поднимает то, что даёт работу новому проходу: заводится ключ
|
||||
сравнения или меняется его состав; у разбора появляется новый вид входа или
|
||||
новая ветка неоднозначности; в слияние добавляется источник или меняется
|
||||
победитель при конфликте.
|
||||
|
||||
**Мелкое здесь** — опускает до `small`. Перечень закрытый, каждый пункт
|
||||
проверяется взглядом на дифф и дельта-спеку, любое сработавшее держит метку
|
||||
внизу:
|
||||
|
||||
- **дельта-спека называет исход поимённо** — сценарий уже говорит, что даёт
|
||||
вырожденный вход, и решать в коде нечего;
|
||||
- **новых сценариев в дельта-спеке нет** — изменение уточняет уже описанное
|
||||
поведение, а не заказывает новое;
|
||||
- **правка сообщения, комментария, записи журнала, имени или теста** в узле из
|
||||
перечней выше;
|
||||
- **сужение уже существующей нормализации** без нового вида входа: вход остался
|
||||
тот же, изменился исход на одном его значении.
|
||||
|
||||
Отрицательный тест поверх перечня: что после мерджа не откатывается обратной
|
||||
правкой — миграция, формат на диске, публичный контракт, имя, — **не** `small`,
|
||||
каким бы маленьким ни был дифф.
|
||||
|
||||
**Ориентир частоты.** `medium` закрывает большинство задач, `large` рассчитана
|
||||
на 5–10% и приходится на крупную функциональность, а не на уборку: задача типа
|
||||
`chore` или `fix`, собранная из нитей прошлого ревью, идёт в `small` или
|
||||
`medium`, даже когда трогает файл из перечней выше. `large` чаще одной задачи из
|
||||
десяти означает ошибку в критерии, а не полосу сложных задач подряд.
|
||||
- **миграция схемы**, уже накатанная на боевую БД umbar;
|
||||
- **операции, снимающие данные** — `torrents/delete` с `deleteFiles=true` и
|
||||
снятие последней жёсткой ссылки: обе исключены из гарда сознательно, и второй
|
||||
точки входа в них не бывает дешёвой (журнал, 2026-08-10);
|
||||
- **раскладка в библиотеке Jellyfin** — переименование или перезапись файла,
|
||||
на который уже смотрят клиенты и закладки;
|
||||
- **публичный контракт** — форма ответа REST, имена полей конфига,
|
||||
идентификаторы в ссылках: их держат чужие потребители, и переименование
|
||||
расходится по базе;
|
||||
- **формат на диске** — раскладка `paths.movies`/`paths.series` и имена, по
|
||||
которым Jellyfin узнаёт тайтл.
|
||||
|
||||
### Недоступно проверке
|
||||
|
||||
@@ -283,7 +258,7 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
ссылки) на текущее поведение.
|
||||
- `architecture`: суждение «этой функциональности не должно существовать».
|
||||
- `requirements`: качество распознавания как таковое — правильно ли LLM
|
||||
определил фильм. Это вопрос тюнинга модели и промпта, а не ревью кода;
|
||||
определил фильм. Это вопрос настройки модели и промпта, а не ревью кода;
|
||||
размеченный корпус, по которому это можно было бы судить числом, решено не
|
||||
собирать (`tasks/REJECTED.md`, 2026-08-06).
|
||||
|
||||
@@ -299,10 +274,10 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
что уже есть в библиотеке»). **Различение «идиоматично против
|
||||
распространено» теперь не спрашивает никто.** Класс обратимый: портит форму
|
||||
кода, не данные. Пересмотр — задача `quality-review-agents`.
|
||||
- `security`, `operations`, `architecture`: на метках `small` и `medium` не
|
||||
проверяется ничто, требующее запуска, — построенных путей атаки, замеров и
|
||||
- `security`, `operations`, `architecture`: в цикле задачи не проверяется
|
||||
ничто, требующее запуска, — построенных путей атаки, замеров и
|
||||
эксплуатационного постмортема там нет по устройству конвейера. Их даёт только
|
||||
`large`, а она приходится на 5–10% задач.
|
||||
отдельный прогон `av-dev:code-deep-review`, который зовёт человек.
|
||||
|
||||
## Журнал дефектов
|
||||
|
||||
@@ -312,7 +287,7 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
|
||||
Форма:
|
||||
|
||||
<!-- копия: журнал-дефектов-форма из av-dev-pipeline/skills/review-pipeline/references/review-journal.md -->
|
||||
<!-- копия: журнал-дефектов-форма из av-dev/skills/code-review/references/review-journal.md -->
|
||||
## ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман]
|
||||
|
||||
- **Где:** путь:строка либо «конвейер, а не код»
|
||||
@@ -351,6 +326,9 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
удержания общего замка воркера (`280.450631ms` при задержке соседа `300ms`)
|
||||
- **Что меняем:** в «Триггеры метки», ось «незнакомое», добавлен пункт про новую
|
||||
поверхность поверх необратимой операции
|
||||
- **Отменено 2026-09-02:** метка упразднена вместе с переходом на постоянный
|
||||
состав прогона. Признак живёт в «Когда звать глубокое ревью» → «Области,
|
||||
которые смотрят целиком»
|
||||
|
||||
## 2026-08-10 — три дефекта поштучного удаления жили незамеченными, пока рядом не появилась пачка [проскочил]
|
||||
|
||||
@@ -374,6 +352,9 @@ Go-сервиса и что здесь уже проскакивало. Устр
|
||||
- **Что меняем:** три записи в беклоге со ссылкой на оракулы; триггер метки
|
||||
дополнен (см. запись выше), чтобы следующая поверхность над необратимой
|
||||
операцией шла сразу с доказательными проходами
|
||||
- **Отменено 2026-09-02:** метки нет; вывод перенесён в «Когда звать глубокое
|
||||
ревью» — удаление раздачи с файлами названо там областью, которую смотрят
|
||||
целиком
|
||||
|
||||
## 2026-08-10 — тест остался зелёным навсегда, потому что проверял снятый атрибут [пойман]
|
||||
|
||||
|
||||
+6
-6
@@ -50,10 +50,10 @@ REST API работают **без авторизации** осознанно;
|
||||
- **Правило:** компоненты санитизируются (убираются разделители пути, `..`,
|
||||
управляющие символы), финальный `filepath.Clean`-путь обязан быть **строго
|
||||
под** соответствующей библиотекой, иначе операция отклоняется. Проверка на
|
||||
результате, а не на входе. Следом — длина: каждый компонент обязан помещаться в
|
||||
255 байт UTF-8, иначе задача уходит в `review`. Порядок значим: путь, вышедший
|
||||
за песочницу, отклоняется как выход за библиотеку, а не как длинное имя, иначе
|
||||
находка безопасности спряталась бы за косметической причиной.
|
||||
результате, а не на входе. Длина компонента проверяется следом и только после
|
||||
проверки принадлежности — порядок и исход нормированы
|
||||
[file-layout](../openspec/specs/file-layout/spec.md), величина предела —
|
||||
[database.md](database.md).
|
||||
- **Исходный путь** = `save_path` из qBittorrent + относительное имя файла из
|
||||
`/torrents/files`. Тоже недоверенный, но по нему мы только **читаем и
|
||||
линкуем**; писать в `paths.downloads` нельзя вообще.
|
||||
@@ -104,8 +104,8 @@ REST API работают **без авторизации** осознанно;
|
||||
- **Отказ в обслуживании изнутри контура.** Огромная раздача, тысяча файлов,
|
||||
бесконечный ответ LLM — это вопросы устойчивости и ресурсов
|
||||
([architecture.md](architecture.md) → «Эксплуатация»), а не безопасности.
|
||||
Тело ответа внешнего сервиса при этом читается с пределом: LLM — 8 MiB
|
||||
(`internal/llm`), метабазы — 4 MiB (`internal/metadata`).
|
||||
Тело ответа внешнего сервиса при этом читается с пределом — величины в
|
||||
[database.md](database.md) → «Пределы, зашитые константой кода».
|
||||
- **Целостность содержимого медиафайлов.** Что в контейнере mkv — не наша забота.
|
||||
- **Цепочка поставки** — модули Go, базовый образ distroless, плагины тулинга.
|
||||
- **Приватность запросов к внешним сервисам.** Названия раздач уезжают в LLM и
|
||||
|
||||
Reference in New Issue
Block a user