профили ревью: тяжёлые проходы в верхнюю ступень, на умолчании — один базовый

Тема 33 сняла самую большую разовую статью расхода, но не тронула главную —
частоту. Меряющая пара стояла в standard, то есть на большинстве задач, и именно
она делала прогон долгим: два прохода держат машину, идут цепочкой и доказывают
находки запуском. Цель разбора названа прямо: лучше поправить в следующей задаче,
чем держать одну два часа.

adversary и ops переехали в wide. Стадия осталась самой урожайной за всю историю
замеров — пять из семи выживших находок дозапуска и единственная находка про
молчаливый старт отката, — но её ценность оплачивается на каждой задаче, а
получается на немногих. Решение по цене, не по ценности.

Заведён review-basics: мелкая осадка двух тяжёлых проходов, без единого запуска.
Стоит только в standard. Восемь вопросов, на которые отвечают чтением: таймаут и
отказ соседа, идемпотентность и одновременная запись, остановка на середине,
частичный откат при двух версиях, наблюдаемость и тишина, очевидный рост объёма,
второй способ мимо единой точки (грепом, не картой), что отсюда удалить. Потолок
4 находки, машину не держит, ничего не меряет.

Вопрос про частичный откат — не для полноты списка. Без него правило «миграция
схемы не поднимает ступень» рассыпалось бы: раньше миграцию разбирал ops, а он
теперь наверху. Проход заведён затем, чтобы у standard остался хоть один взгляд
на ось времени.

Модель у него верхняя, opus, и это не спорит со словом «средний»: усилие режется
входом и потолком, а не моделью. Дешёвая модель на опиниативном проходе платит
триажем — это записанный замер, отменять его без нового замера нечем.

Лестница вышла 4/5/7. Главный выигрыш не в числе проходов, а в том, что из
standard ушла цепочка: теперь там гейт, три прохода одним сообщением и триаж —
граф плоский, ждать некому.

Правило выбора ступени переписано на два вопроса, и объём изменения вошёл в него
впервые. Крупное или незнакомое — трогает несколько узлов, переносит
ответственность, форму решения нащупывают по ходу — это wide, и он рассчитан на
5-10% задач. Мелкое — один узел, форма очевидна заранее, откат сводится к
обратной правке — quick. Всё остальное standard, рабочее умолчание. Раньше
ступень выбиралась только по классу изменения и на размер смотреть запрещала;
теперь признаков два: класс отвечает за обратимость, объём — за цену
разбирательства.

Отрицательный тест сохранил прежнюю мудрость в новой рамке: что после мерджа не
откатывается обратной правкой — не quick, каким бы маленьким ни был дифф. Три
строки миграции идут в standard.

Спорный случай решается вниз, и асимметрия объяснена ценой: ошибка в сторону
standard стоит находки на следующей задаче, ошибка в обратную — трёх тяжёлых
проходов на каждой задаче, выбранной неверно.

Сделка записана вместе с обратной связью, иначе это тихая потеря качества. На
quick и standard не проверяется ничего, что требует запуска: построенный путь,
эксперимент против драйвера, любое число. Это самая крупная граница покрытия
конвейера, и она идёт строкой в каждом таком прогоне поимённо. Сигналов о том,
что ступень занижена, два: журнал дефектов в docs/review.md и сам basics —
он единственный, кто смотрит на дифф целиком на нижних ступенях, и обязан
сказать строкой, если задача выглядит крупнее профиля.

Побочно: условие профиля design то же самое, так что rubric и architecture на
предложении тоже упали до 5-10% задач.

Тема 34 в DECISIONS.md, следствия 130-133. Версия канона не поднята; инструкция
проекту дописана в пункт 8 записи «Версия 4».

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-06 19:29:07 +03:00
co-authored by Claude Opus 5
parent ea84a4fbb3
commit 21b840a8e4
17 changed files with 553 additions and 141 deletions
+84
View File
@@ -2328,3 +2328,87 @@ JJJ): у профиля обязан быть один правильный от
стоимости, тест выбора верхней ступени и проектный перечень мест держались стоимости, тест выбора верхней ступени и проектный перечень мест держались
только на `reimpl`. Оставшись, они выглядели бы работающими правилами и только на `reimpl`. Оставшись, они выглядели бы работающими правилами и
тратили бы внимание на каждом прогоне. тратили бы внимание на каждом прогоне.
## 34. Пропускная способность против глубины: тяжёлые проходы уехали в верхнюю ступень (2026-08-06)
Тема 33 сняла самую большую разовую статью расхода, но не тронула главную —
**частоту**. Меряющая пара стояла в `standard`, то есть на большинстве задач, и
именно она делала прогон долгим: два прохода держат машину, идут цепочкой и
доказывают находки запуском. Разбор шёл от цели, названной прямо: **лучше
поправить в следующей задаче, чем держать одну два часа.**
**ААББСС. `adversary` и `ops` переехали в `wide`, и это решение по цене, а не по
ценности.** Стадия осталась самой урожайной за всю историю замеров — пять из семи
выживших находок дозапуска и единственная находка про молчаливый старт отката. Но
её ценность оплачивается на **каждой** задаче, а получается на немногих: оракул
добывается запуском, запуск — это машина, цепочка и часы. Ступень, которая раньше
была умолчанием, стала исключением на 5–10% задач.
**ААББТТ. Заведён `review-basics` — мелкая осадка двух тяжёлых проходов, без
единого запуска.** Он стоит только в `standard` и берёт ту половину вопросов, на
которые отвечают **чтением**: таймаут и отказ соседа, идемпотентность и
одновременная запись, остановка на середине, частичный откат при двух версиях,
наблюдаемость и тишина, очевидный рост объёма — плюс два вопроса архитектурного:
второй способ мимо единой точки (грепом, не картой) и что отсюда удалить. Потолок
4 находки, машину не держит, ничего не меряет.
Отдельная его обязанность — **вопрос 4, частичный откат**. Без него правило
«миграция схемы не поднимает ступень» рассыпалось бы: раньше миграцию разбирал
`ops`, а он теперь в `wide`. Проход заведён не «до кучи», а затем, чтобы у
`standard` остался хоть один взгляд на ось времени.
Модель у него верхняя, `opus`, и это не противоречит слову «средний»: усилие
режется **входом и потолком**, а не моделью. Дешёвая модель на опиниативном
проходе платит триажем — это записанный замер, и отменять его без нового замера
нельзя.
**ААББУУ. Объём и незнакомость изменения вошли в правило выбора ступени.** Раньше
ступень выбиралась только по классу («вводит ли новое понятие»), и правило прямо
запрещало смотреть на размер. Теперь вопросов два: крупное или незнакомое (трогает
несколько узлов, переносит ответственность, форму решения нащупывают по ходу) →
`wide`; мелкое (один узел, форма очевидна заранее, откат — обратная правка) →
`quick`; всё остальное → `standard`. Причина смены: цена разбирательства растёт
именно с объёмом и неизвестностью, а не с классом правила.
Отрицательный тест `quick` сохранил прежнюю мудрость в новой рамке: **что после
мерджа не откатывается обратной правкой — не `quick`, каким бы маленьким ни был
дифф.** Три строки миграции идут в `standard`.
**ААББФФ. Спорный случай решается вниз, и асимметрия объяснена ценой.** Между
`standard` и `wide` — в пользу `standard`: ошибка сюда стоит находки на следующей
задаче, ошибка обратно стоит трёх тяжёлых проходов на каждой задаче, выбранной
неверно. Между `quick` и `standard` — тоже в пользу `standard`, но по другой
причине: там разница в один дешёвый проход, зато единственный, кто на нижних
ступенях смотрит на отказы.
Доля `wide` 5–10% записана как **проверка правила, а не пожелание**: если ступень
уходит каждой третьей задаче, её выбирают по ощущению важности.
**ААББХХ. Сделка записана вместе с механизмом обратной связи, иначе это тихая
потеря качества.** На `quick` и `standard` не проверяется ничего, что требует
запуска: построенный путь, эксперимент против драйвера, любое число. Это самая
крупная граница покрытия конвейера, и она обязана идти строкой в каждом таком
прогоне поимённо. Обратная связь — журнал дефектов `docs/review.md`: класс,
который ловят только меряющие проходы, начал всплывать после мерджа — значит
ступень выбирают слишком низко. Плюс сам `basics` обязан сигналить строкой, если
видит, что ступень занижена: он единственный, кто смотрит на дифф целиком на
нижних ступенях.
### Что из этого следует
130. **Стоимость прохода — это его цена, умноженная на частоту, и вторая
переменная важнее.** Тема 33 убрала самый дорогой проход, тема 34 —
самый частый. Второе дало больше, хотя снятый проход был дешевле каждого
отдельного `reimpl`.
131. **Урожайность прохода не отвечает на вопрос, где ему стоять.** Меряющая пара
осталась самой ценной и всё равно уехала вверх: ценность оправдывает
существование прохода, но не его частоту.
132. **Замена тяжёлого прохода лёгким записывается как сужение, а не как
эквивалент.** `basics` задаёт те же вопросы чтением, и его ответы поэтому
слабее — условия вместо оракулов. Назвать это «покрыли то же дешевле» значит
соврать себе на первом же прогоне.
133. **Ступень, выбираемая по классу изменения, слепа к объёму.** Правило,
запрещавшее смотреть на размер, защищало от выбора по ощущению важности — и
заодно отправляло трёхстрочную правку и переборку пяти узлов в один профиль.
Признаков нужно два: класс отвечает за обратимость, объём — за цену
разбирательства.
+5 -3
View File
@@ -28,9 +28,11 @@
- **av-dev-pipeline** — исполнение. **Требует OpenSpec.** - **av-dev-pipeline** — исполнение. **Требует OpenSpec.**
- `task-pipeline` — задача через полный цикл SDD, от постановки до коммита; - `task-pipeline` — задача через полный цикл SDD, от постановки до коммита;
- `task-batch` — несколько задач разом, каждая в своём worktree; - `task-batch` — несколько задач разом, каждая в своём worktree;
- `review-pipeline` — конвейер ревью: гейт, сверка со спеками, враждебные - `review-pipeline` — конвейер ревью: гейт, сверка со спеками, базовый проход
постановки, эксплуатационный постмортем, архитектура, обязательный триаж. на отказы и лишнее, а в верхней ступени — враждебные постановки,
Восемь агентов-проходов, три ступени стоимости: `quick`, `standard`, `wide`. эксплуатационный постмортем и архитектура; триаж обязателен всегда. Девять
агентов-проходов, три ступени стоимости: `quick` (4 прохода), `standard` (5,
умолчание), `wide` (7, только крупное или незнакомое — 5–10% задач).
- **av-dev-git** — `commit`: сообщения в личном стиле. - **av-dev-git** — `commit`: сообщения в личном стиле.
Соглашение об именах: имя **плагина** длинное с префиксом `av-dev-`, имена Соглашение об именах: имя **плагина** длинное с префиксом `av-dev-`, имена
+6 -4
View File
@@ -208,7 +208,9 @@ jellybit 43. Шаги повышения — [changelog.md](av-dev-pm/skills/can
каждого `fix` и `Вопрос` + `Куда ляжет ответ` у каждого `research` пишутся каждого `fix` и `Вопрос` + `Куда ляжет ответ` у каждого `research` пишутся
по мере того, как задача идёт в набор (`sprint take` без них откажет). по мере того, как задача идёт в набор (`sprint take` без них откажет).
Сколько записей готово к взятию, печатает блок здоровья `check` Сколько записей готово к взятию, печатает блок здоровья `check`
- [ ] `docs/review.md`, «Триггеры профиля»: снести перечень мест для `deep` - [ ] `docs/review.md`, «Триггеры профиля» — переписать целиком: снести перечень
профиль упразднён вместе с проходом независимой реализации; перечень для мест для `deep` (профиль упразднён), а оставшийся перевести на новое
`wide` остаётся. Там же: класс «форма решения, где спека выбора не сделала» правило — `wide` это крупное или незнакомое изменение, 510% задач, плюс
— в подраздел «перестали проверять сознательно» отдельный список мелкого для `quick`. Там же две честные строки в
«перестали проверять сознательно»: форма решения (снят проход независимой
реализации) и всё, что требует запуска (меряющие проходы только в `wide`)
@@ -22,6 +22,14 @@ color: yellow
падающий тест, которым ты доказываешь путь, воспроизводим — и ссылка на него падающий тест, которым ты доказываешь путь, воспроизводим — и ссылка на него
законный оракул. законный оракул.
**Тебя запускают только в профиле `wide`** — на изменении крупном или незнакомом,
и это 5–10% задач. Причина в цене прогона, а не в ценности находок: ты держишь
машину и идёшь цепочкой, то есть стоишь часов на каждой задаче, где запущен. На
нижних ступенях твою половину, отвечаемую **чтением**, задаёт `review-basics`, а
построенные пути там не строит никто — и так и написано в границах покрытия
каждого такого прогона. Значит, раз тебя позвали, стройте путь до конца: сокращать
себя «ради скорости» тебе нечем, скорость уже оплачена выбором ступени.
## Модель угроз — из `docs/security.md`, и не расширяй её самовольно ## Модель угроз — из `docs/security.md`, и не расширяй её самовольно
**Первая строка `docs/security.md` — периметр,** и она задаёт смысл всему **Первая строка `docs/security.md` — периметр,** и она задаёт смысл всему
+13 -6
View File
@@ -10,12 +10,19 @@ color: yellow
судить об архитектуре: он не знает, какие понятия в проекте уже есть и как они судить об архитектуре: он не знает, какие понятия в проекте уже есть и как они
называются. Поэтому твой вход шире, и первое, что ты делаешь, — его собираешь. называются. Поэтому твой вход шире, и первое, что ты делаешь, — его собираешь.
**Тебя запускают не на каждой задаче.** Условие одно: изменение вводит **новое **Тебя запускают не на каждой задаче, а в профиле `wide` — это 5–10% задач.**
понятие или структурную единицу** — новый пакет или слой, новую точку входа, Условие ступени: изменение **крупное или незнакомое**трогает несколько узлов
второй способ делать то, что уже делается, перенос ответственности между узлами. или слоёв разом, переносит ответственность между ними, перекладывает существующий
Ни миграция схемы, ни изменение публичного контракта тебя не зовут: там работы код в новую форму, либо вводит функциональность, форму решения которой нащупывали
для тебя нет, её делают `gate`, `ops` и `specs`. Если тебя позвали — в проекте по ходу. Ни миграция схемы, ни изменение публичного контракта сами по себе тебя не
стало больше сущностей, чем было, и оба твоих главных вопроса осмысленны. зовут: там работы для тебя нет, её делают `gate`, `basics` и `specs`. Если тебя
позвали — в проекте либо стало больше сущностей, чем было, либо старые
перекладывались, и оба твоих главных вопроса осмысленны.
Мелкую осадку твоих вопросов 2 и 5 — второй способ рядом с диффом и что отсюда
удалить — на ступени `standard` задаёт `review-basics`, грепом против единых точек
проекта и без карты. Твоё отличие не в вопросах, а во входе: карта, граница домена
и граф зависимостей есть только у тебя.
Находки — по контракту Находки — по контракту
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md` `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
+187
View File
@@ -0,0 +1,187 @@
---
name: review-basics
description: "Базовый проход ревью для профиля standard — мелкая осадка эксплуатационного и архитектурного проходов, без единого запуска. Восемь вопросов, на которые отвечают чтением: таймаут и отказ соседа, идемпотентность и одновременная запись, остановка на середине, частичный откат при двух версиях, наблюдаемость и тишина, очевидный рост объёма, второй способ мимо единой точки проекта, что отсюда удалить. Ничего не запускает, не меряет, машину не держит: замеры, построенные пути и карта проекта — это профиль wide. Формулирует условиями, потолок 4 находки плюс «дешевле переделать до мерджа». Обязан сигналить, если ступень выбрана слишком низко. Только чтение."
tools: Read, Grep, Glob, Bash
model: opus
color: yellow
---
Ты — **базовый проход** ревью. Ты существуешь не потому, что у тебя своя оптика, а
потому, что у конвейера есть ступень, на которой тяжёлые проходы не окупаются.
Враждебный и эксплуатационный проходы держат машину, строят пути и снимают числа —
это часы на каждую задачу. Ты берёшь из них ту часть, на которую отвечают
**чтением**, и отвечаешь за неё на большинстве задач проекта.
Отсюда твоя главная обязанность и главный запрет: **ты не запускаешь ничего.** Ни
тестов, ни сервиса, ни запросов к хранилищу, ни замеров. Проход, который начал
мерить, превращается в тот самый дорогой проход, вместо которого его позвали.
Находки — по контракту
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
(точный путь конвейер передаёт в задании).
## Когда тебя запускают
**Только в профиле `standard`** — рабочем умолчании конвейера. В `quick` тебя нет:
там дифф мелкий, форма решения очевидна, и платить за тебя не за что. В `wide`
тебя тоже нет, и по обратной причине: там идут `review-adversary`, `review-ops` и
`review-architecture` целиком, а ты — их мелкая осадка, и дублировать их значит
удорожать триаж на ровном месте.
Из этого следует, как читать твой отчёт: **ты не «облегчённая версия ревью», ты
нижняя граница.** Всё, что требует запуска, на этой ступени не проверено вовсе, и
сказать об этом в границах покрытия — твоя работа, а не чужая.
## Что читаешь до диффа
Немного и целенаправленно — широкий вход это `wide`, не ты.
- **`CLAUDE.md`** — инварианты с severity и что в проекте необратимо. Это
единственное твоё основание для `critical`: без запуска другого у тебя нет.
- **`docs/architecture.md`** — **единые точки проекта** (генерация
идентификаторов, время, разбор формата, маппинг доменной ошибки в код ответа,
путь приёма) и **внешние зависимости поимённо**. Первое нужно вопросу 7, второе
— вопросу 1.
- **`docs/review.md`** — журнал: что в этом проекте уже ломалось; и блок `basics`
в «Вопросах к проходам», если он есть, — эти вопросы задаются дополнительно к
обязательным, и ответы на них выводятся явно.
- дельта-спеки change — чтобы отличить заказанное поведение от появившегося само.
Карта «что нужно проходу → где лежит» —
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`.
**Деградация поразрядная, каждый пробел — своей строкой.** Нет единых точек в
`docs/architecture.md` — вопрос 7 задавай грепом по коду и скажи, что перечня
единых точек в проекте нет. Нет инвариантов в `CLAUDE.md` — не присваивай
`critical` и скажи об этом отдельной строкой.
## Обязательные вопросы (по каждому — ответ или явное «неприменимо»)
Первые шесть — от эксплуатационного прохода, последние два — от архитектурного.
1. **Отказ соседа.** Внешняя зависимость отвечает **медленно** (не падает —
именно медленно), молчит или отдаёт мусор; диск заполнился; хранилище отвечает
«занято». Есть ли таймаут вообще? Заблокируется ли обработка навсегда? Отличит
ли «медленно» от «упало» **отправитель**, который просто перестанет слать?
2. **Повтор и одновременность.** Повторы бывают штатными: расписание, пересборка,
дубль апдейта. Операция идемпотентна или удваивает эффект? Отдельно и
обязательно: если запись устроена как **read-modify-write**, две операции над
одним ключом теряют данные друг друга, и потеря молчаливая. Есть ли транзакция,
блокировка или сериализация — и покрыта ли она тестом?
3. **Остановка на середине.** Процесс останавливают между шагами: тело записано,
строки нет; строка есть, обработка не начиналась; запись прочитана и слита, но
не сохранена. Что останется? Кто подберёт это при следующем старте — и
подберёт ли вообще, или чинится только руками?
4. **Частичный откат при двух версиях.** Бинарь откатили, а миграция уже
накатилась (или наоборот). Читает ли старый код новую схему? Что с записями,
созданными новой версией? Обратима ли миграция сама по себе? **Этот вопрос —
причина, по которой миграция схемы не поднимает ступень:** на `standard` его
задаёшь только ты.
5. **Наблюдаемость и тишина.** Увидит ли человек, что поток оборвался ночью, — не
залезая в БД и не читая логи построчно? Отличим ли штатный отказ от поломки по
уровню? Виден ли факт **тишины** — что событий не стало, а не что их просто
нет? И зеркально: не утекают ли в лог тело, значения или токен.
6. **Очевидный рост объёма.** Только то, что видно по коду без чисел: чтение
всего тела в память, распаковка ради одной проверки, растущий без границ буфер,
`N+1` к хранилищу, проход по всему архиву, ответ, собираемый целиком перед
отправкой. **Чисел не придумывай** — их знает `docs/research/`, а замеры делает
профиль `wide`.
7. **Второй способ рядом с диффом.** Не появилась ли вторая точка того, что в
проекте делается единой: второй способ получить время, вторая генерация
идентификатора, второй парсер того же формата, второй маппинг доменной ошибки,
второй путь приёма мимо общего. Проверяется грепом против перечня единых точек,
а не ощущением. Второй способ дороже плохого первого: плохой стоит своей
плохости, второй — вечного вопроса «а как здесь принято» на каждом следующем
изменении.
8. **Что отсюда удалить.** Слой с единственной реализацией; интерфейс, заведённый
ради мока; конфигурируемость, которую никто не просил; параметр, у которого во
всей кодовой базе одно значение; подстраховка поверх подстраховки; счётчик,
который никто не читает. Формулируй **удалением** («у этих трёх методов нет
второго вызывающего»), а не вкусом. Лишнее — такая же находка, как
недостающее, и стоит она дешевле: удалить проще, чем дописать.
## Правило формулировки
**Условиями, а не утверждениями** — реального профиля нагрузки ты не знаешь и
проверить его не можешь.
- Годится: «если внешний сервис отвечает дольше 30 секунд, обработка встаёт
навсегда: таймаута у клиента нет — `client.go:41`».
- Не годится: «этот запрос тормозит».
Если находке нужен замер или прогон — **не делай их**, а положи предлагаемую
команду в поле `Оракул` и оставь находку гипотезой, назвав прямо: «проверяется
профилем `wide`, проходом `ops`». Это честный исход, а не полумера: неснятое
число хуже отсутствующего только тогда, когда его выдают за снятое.
## Потолок
**Не больше 4 находок.** Сверх потолка — короткая секция **«Дешевле переделать до
мерджа»**: то, что после мерджа фиксируется надолго — форма ответа, схема
хранилища, раскладка файлов, поле конфига, имя, которое разойдётся по кодовой
базе. Секция может быть непустой, даже когда находок нет.
## Сигнал о заниженной ступени
Ты единственный, кто видит дифф целиком на нижних ступенях, — значит ты и
замечаешь, что ступень выбрана не та. Скажи об этом **отдельной строкой в начале
вывода**, если видишь хоть одно:
- дифф трогает несколько узлов или слоёв разом;
- решение выглядит нащупанным по ходу: две попытки одного и того же, брошенный
первый подход, закомментированное;
- изменение вводит новое понятие: новый пакет, новая точка входа, новая сущность;
- ты вынужден отвечать «проверяется профилем `wide`» больше чем на два вопроса.
Формулировка: «ступень, вероятно, занижена: <признак> — прогон профилем `wide`
дал бы <что именно>». Решение о перезапуске принимает оркестратор, не ты.
## Чем ты НЕ занимаешься
Не дублируй чужие проходы — совпадающие находки удорожают триаж и ничего не
добавляют:
- механизируемое (форматирование, запрещённые вызовы, импорты) — это
`review-gate`;
- конвенции проекта и их нарушения — `review-code`;
- соответствие дельта-спекам — `review-specs`;
- **построенный путь атаки** (его надо прогнать), **эксперимент против драйвера и
библиотеки** в вырожденном случае, **любое число** — это `review-adversary` и
`review-ops`, и они живут в профиле `wide`;
- **граница домена, направление зависимостей, стоимость следующего изменения,
инвентарь понятий проекта** — это `review-architecture`, там же.
Видишь такое — не выводи находкой; строкой в границы покрытия, чей это проход и
какой профиль его запускает.
## Чего этот проход принципиально не может поймать
- Всё, что доказывается запуском: пути отказа, поведение библиотеки в вырожденном
случае, числа.
- Дефекты, видимые только на карте проекта целиком.
- Реальный профиль нагрузки и то, что на самом деле лежит в данных.
## Формат вывода
1. Строка о ступени — только если сработал «Сигнал о заниженной ступени».
2. `## Ответы на обязательные вопросы` — таблица `Вопрос | Ответ | Где смотрел`.
Ответ «неприменимо» допустим, но с обоснованием.
3. Находки по контракту, **не больше четырёх**.
4. `## Дешевле переделать до мерджа`.
5. Обязательный блок:
```
## Coverage of this pass
- проверено: <какие вопросы прослежены, по каким файлам>
- не проверялось и почему: ...
- не проверяется на этой ступени вовсе: построенные пути, эксперименты против библиотеки и драйвера, любые замеры, карта проекта — это профиль wide
```
Последняя строка обязательна **дословно по смыслу** и на каждом прогоне: она и
есть та граница покрытия, которой платит ступень `standard`.
## Ограничения
Только чтение. `Bash` — для читающих команд: `git diff`, `grep`, перечисление
файлов. Не запускай тесты, не поднимай сервис, не обращайся к хранилищу и внешним
сервисам, ничего не меряй. Код и спеки не редактируй.
+5 -3
View File
@@ -145,9 +145,11 @@ color: green
- механизируемое (форматирование, запрещённые вызовы, сравнение ошибок, импорты) - механизируемое (форматирование, запрещённые вызовы, сравнение ошибок, импорты)
— это `review-gate`; — это `review-gate`;
- архитектурные границы и второй способ делать то же самое — - архитектурные границы и второй способ делать то же самое —
`review-architecture`; `review-architecture` в профиле `wide`, `review-basics` в `standard`;
- стиль, дублирование, лишние слои, «я бы написал иначе» — `review-architecture` - стиль, дублирование, лишние слои, «я бы написал иначе» — те же двое (лишнее и
(лишнее и второй способ); второй способ);
- отказы, таймауты, наблюдаемость, откат — `review-basics` в `standard`,
`review-ops` в `wide`;
- соответствие дельта-спекам — `review-specs`. - соответствие дельта-спекам — `review-specs`.
Видишь такое — не выводи находкой; максимум упомяни строкой в границах покрытия, Видишь такое — не выводи находкой; максимум упомяни строкой в границах покрытия,
+9
View File
@@ -20,6 +20,15 @@ color: green
её надо назвать, а не списать на соседа. Задание, объявившее прогон линейным или её надо назвать, а не списать на соседа. Задание, объявившее прогон линейным или
сказавшее, что цепочку слили, — повод оговорить это в границах покрытия. сказавшее, что цепочку слили, — повод оговорить это в границах покрытия.
**Тебя запускают только в профиле `wide`** — на изменении крупном или незнакомом,
и это 5–10% задач. На нижних ступенях шесть твоих вопросов, на которые отвечают
чтением (отказ соседа, повтор и одновременность, остановка на середине, частичный
откат, наблюдаемость, очевидный рост), задаёт `review-basics` — **без замеров и
без запуска**. Тебя же зовут ровно за тем, чего он не может: **число и
эксперимент**. Раз ты позван, вопрос 8 (поведение библиотеки и драйвера в
вырожденном случае) обязателен — это единственное место конвейера, где он
задаётся вообще.
## Что такое «прод» здесь — из документов проекта ## Что такое «прод» здесь — из документов проекта
**`docs/architecture.md`, раздел эксплуатации:** где это работает и что рядом; **`docs/architecture.md`, раздел эксплуатации:** где это работает и что рядом;
+186 -101
View File
@@ -1,6 +1,6 @@
--- ---
name: review-pipeline name: review-pipeline
description: "Конвейер ревью изменения — детерминированный гейт, сверка с дельта-спеками в обе стороны, враждебные постановки и эксплуатационный постмортем, архитектурный проход и обязательный триаж. Три ступени стоимости: quick, standard, wide. Порядок прогона — граф зависимостей, а не очередь: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, триаж — единственный сток. Линейный прогон — по слову оператора или на занятой машине. Проектная специфика приходит из документов канона av-dev-pm. Вызывается из task-pipeline (чекпоинты ревью), из task-batch (финальная сверка) и отдельно — профилем design на предложении ДО кода." description: "Конвейер ревью изменения — детерминированный гейт, сверка с дельта-спеками в обе стороны, базовый проход на отказы и лишнее, а в верхнем профиле враждебные постановки, эксплуатационный постмортем и архитектурный проход; триаж обязателен всегда. Три ступени стоимости: quick (4 прохода), standard (5, рабочее умолчание), wide (7, только крупное или незнакомое — 5-10% задач). Ступень выбирается по объёму и незнакомости изменения, спорный случай решается вниз. Порядок прогона — граф зависимостей, а не очередь: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, триаж — единственный сток. Линейный прогон — по слову оператора или на занятой машине. Проектная специфика приходит из документов канона av-dev-pm. Вызывается из task-pipeline (чекпоинты ревью), из task-batch (финальная сверка) и отдельно — профилем design на предложении ДО кода."
--- ---
# Конвейер ревью # Конвейер ревью
@@ -83,7 +83,7 @@ description: "Конвейер ревью изменения — детерми
- **его блок вопросов** из «Вопросы к проходам» в `docs/review.md`, если он там - **его блок вопросов** из «Вопросы к проходам» в `docs/review.md`, если он там
есть, — **дословно**. Блок адресован проходу поимённо и выведен из промаха есть, — **дословно**. Блок адресован проходу поимённо и выведен из промаха
этого проекта; заставлять восемь charter'ов самим ходить за ним значит этого проекта; заставлять девять charter'ов самим ходить за ним значит
получить, что за ним ходят двое. Проход отвечает на такие вопросы явно, получить, что за ним ходят двое. Проход отвечает на такие вопросы явно,
дополнительно к обязательным; дополнительно к обязательным;
- **контракт находок** — путь к - **контракт находок** — путь к
@@ -106,7 +106,7 @@ description: "Конвейер ревью изменения — детерми
| Модель | Цвет | Проходы | Почему | | Модель | Цвет | Проходы | Почему |
|---|---|---|---| |---|---|---|---|
| `sonnet` | green | gate, code, ops | вход структурный, критерий записан заранее | | `sonnet` | green | gate, code, ops | вход структурный, критерий записан заранее |
| `opus` | yellow | specs, adversary, rubric, architecture, triage | суждение без опоры на инструмент | | `opus` | yellow | specs, adversary, rubric, basics, architecture, triage | суждение без опоры на инструмент |
**Цвет charter'а кодирует модель, а не роль прохода.** Это единственное **Цвет charter'а кодирует модель, а не роль прохода.** Это единственное
назначение цвета: список агентов читается взглядом, и по нему сразу видно, чем назначение цвета: список агентов читается взглядом, и по нему сразу видно, чем
@@ -122,16 +122,16 @@ charter'а, а модель потом двигает калибровка, и
модели **дороже** `opus` не обнаружилось ни на одном проходе, а прогон на ней модели **дороже** `opus` не обнаружилось ни на одном проходе, а прогон на ней
стоил заметно дольше и дороже — значит платить за неё не за что. стоил заметно дольше и дороже — значит платить за неё не за что.
Двое из пяти держатся на `opus` по признаку, отдельному от суждения: **их ошибка Двое из шести держатся на `opus` по признаку, отдельному от суждения: **их ошибка
распространяется дальше собственной находки.** Понижать их до `sonnet` вместе с распространяется дальше собственной находки.** Понижать их до `sonnet` вместе с
остальными дешёвыми проходами нельзя. остальными дешёвыми проходами нельзя.
- `triage` — через него проходит всё, что оркестратор реализует **молча**: - `triage` — через него проходит всё, что оркестратор реализует **молча**:
ложноположительная находка становится кодом, потерянный `critical` — дефектом. ложноположительная находка становится кодом, потерянный `critical` — дефектом.
Ошибка триажа дороже ошибки любого отдельного прохода. Ошибка триажа дороже ошибки любого отдельного прохода.
- `architecture` — запускается только там, где изменение вводит новое понятие, - `architecture` — запускается только в верхней ступени, на 5–10% задач, потолок
потолок в 3 находки делает его дешёвым по выходу, а находка на предложении в 3 находки делает его дешёвым по выходу, а находка на предложении стоит абзаца
стоит абзаца против переписывания на готовом коде. Дёшево × высокое плечо. против переписывания на готовом коде. Дёшево × высокое плечо.
**Самая дешёвая модель не используется ни на одном проходе, и это не экономия **Самая дешёвая модель не используется ни на одном проходе, и это не экономия
наоборот.** Дешёвая модель на опиниативном проходе даёт правдоподобные находки, наоборот.** Дешёвая модель на опиниативном проходе даёт правдоподобные находки,
@@ -141,23 +141,36 @@ charter'а, а модель потом двигает калибровка, и
Дешёвому проходу просто не осталось работы. Дешёвому проходу просто не осталось работы.
Экономия достигается не понижением модели, а **непуском прохода**: `quick` Экономия достигается не понижением модели, а **непуском прохода**: `quick`
четыре прохода, `wide` — семь. Правило выбора профиля и есть главный четыре прохода, `standard` — пять, `wide` — семь. Правило выбора профиля и есть
рычаг стоимости, и ступеней у него три именно поэтому. главный рычаг стоимости, и ступеней у него три именно поэтому.
Второй рычаг, помимо непуска, — **вход и потолок прохода**, и он же объясняет
`basics` на `opus`. «Проход среднего усилия» тут значит не дешёвую модель, а
узкий вход (дифф и его окрестности, без карты проекта) и жёсткий потолок находок.
Прогон он ускоряет тем, чего **не** делает: ничего не запускает, ничего не меряет,
машину не держит — а именно замеры и цепочка меряющих проходов и составляли те
самые долгие часы.
## Профили ## Профили
| Профиль | Когда | Стадии | Проходов | | Профиль | Когда | Стадии | Проходов | Доля задач |
|---|---|---|---| |---|---|---|---|---|
| `quick` | багфикс, локальная правка, доки | 0, 1, 4 | 4 | | `quick` | мелкое: багфикс, мелкая фича, локальная правка, доки | 0, 1, 4 | 4 | много |
| `standard` | **рабочее умолчание**: поведение, миграция схемы, публичный контракт, инвариант | 0, 1, 2, 4 | 6 | | `standard` | **рабочее умолчание**: всё, что не мелкое и не крупное | 0, 1, 2, 4 | 5 | большинство |
| `wide` | изменение вводит новое понятие или структурную единицу | 0, 1, 2, 3, 4 | 7 | | `wide` | крупное или незнакомое: большой рефакторинг, функциональность, форму которой ещё предстоит нащупать | 0, 1, 3, 4 | 7 | **510%** |
| `design` | **до кода**, на предложении | specs, плюс rubric и architecture по условию `wide` | 13 | | `design` | **до кода**, на предложении | specs, плюс rubric и architecture по условию `wide` | 13 | — |
**`wide` назван по тому, что он добавляет: вход шире диффа.** Единственное его **`wide` назван по тому, что он добавляет: вход шире диффа.** Он единственный, где
отличие от `standard` — архитектурный проход, а тот и получает дерево пакетов, живут тяжёлые проходы — враждебный, эксплуатационный и архитектурный, — и
граф зависимостей и инвентарь понятий вместо одного диффа. Это и **верхняя единственный, где что-то **запускается и меряется**. Отсюда и его доля: три прохода
ступень**: выше неё конвейер не поднимается, и добавить к семи проходам ему на стадии 3, два из них держат машину и потому идут цепочкой, а не разом. Это и
нечего. есть те самые долгие часы, и платить их каждой задаче не за что.
**Доля 5–10% — не пожелание, а проверка правила.** Она не считается механически, но
читается по журналу: если `wide` уходит каждая третья задача, ступень выбирают по
ощущению важности, а не по факту изменения. Обратный перекос виден иначе — по
журналу проскочивших дефектов в `docs/review.md`: класс, который ловят только
меряющие проходы, начинает всплывать после мерджа.
**Состав сверяется по этой таблице до коммита.** Реестр из трёх-семи проходов **Состав сверяется по этой таблице до коммита.** Реестр из трёх-семи проходов
проверяется взглядом — и это единственная защита от промаха, который уже проверяется взглядом — и это единственная защита от промаха, который уже
@@ -168,49 +181,70 @@ charter'а, а модель потом двигает калибровка, и
границы покрытия, а не отсутствует. Цена молчащего пропуска измерена: семь границы покрытия, а не отсутствует. Цена молчащего пропуска измерена: семь
находок и отдельная задача на их дозакрытие. находок и отдельная задача на их дозакрытие.
Правило выбора профиля **по факту изменения, не по ощущению важности**: Правило выбора — **два вопроса по факту изменения, не по ощущению важности**.
Отвечать по порядку, первый подошедший ответ и есть профиль:
- изменение вводит **новое понятие или структурную единицу**: новый пакет 1. **Изменение крупное или незнакомое?**`wide`. Крупное — трогает несколько
или слой, новая точка входа, второй способ делать то, что уже делается, перенос узлов или слоёв разом, переносит ответственность между ними, перекладывает
ответственности между узлами → `wide`; существующий код в новую форму (большой рефакторинг). Незнакомое —
- иначе меняется поведение, видимое снаружи, трогается схема, публичный контракт функциональность, которой в проекте ещё не было, и **форму решения предстоит
или инвариант проекта → `standard`; нащупать по ходу**, а не выбрать до начала. Признак незнакомого простой: перед
- иначе → `quick`. работой нельзя назвать, какие узлы будут тронуты.
2. **Изменение мелкое?**`quick`. Мелкое — помещается в один узел, форма
решения очевидна до начала работы, а откат сводится к обратной правке. Сюда
идут мелкий багфикс, мелкая фича, правка текста и документации.
3. **Всё остальное**`standard`. Это рабочее умолчание, и оно должно набирать
большинство задач.
**Ступень поднимает то, что даёт работу новому проходу, а не то, что кажется **Два признака смотрят на разное, и в этом весь смысл двух вопросов.** Первый —
рискованным.** Это правило вывода, по которому спорные случаи решаются без нового про **объём и неизвестность**: сколько мест трогается и знаем ли мы форму решения
списка: спроси, какому проходу изменение даёт работу, которой у него не было заранее. Второй — про **обратимость**: во что обойдётся ошибка, если она уедет в
ступенью ниже. мердж. Раньше ступень выбиралась только по классу изменения («вводит ли новое
понятие»), и объём в правило не входил вовсе; теперь входит, потому что цена
разбирательства растёт именно с ним.
Оно же объясняет, почему миграция схемы и публичный контракт **не** поднимают **Отрицательный тест `quick`, и он важнее положительного:** изменение, которое
ступень, хотя выглядят опаснее прочего. Они не добавляют ни одного прохода: после мерджа **не откатывается обратной правкой**, — не `quick`, каким бы
миграцию гоняет `gate` шагом миграций и разбирает `ops` («миграция под живым маленьким ни был дифф. Сюда попадают миграция схемы и данных, формат на диске,
потоком», «частичный откат при двух версиях»), контракт сверяет `specs` публичный контракт, имя, которое разойдётся по кодовой базе. Три строки миграции
направлением `code → spec`, инвариант даёт основание для `critical` любому — это `standard`, а не `quick`: размер диффа и цена ошибки здесь расходятся.
проходу. Все трое уже в `standard`. Раньше эти три факта стояли триггерами
верхних ступеней, и на проекте с базой и эндпоинтами верхняя ступень оказывалась
не исключением, а умолчанием — то есть правило объявляло исключением то, что
происходит всегда. `architecture` же получает работу **не** от того, что контракт
изменился, а от того, что появилось новое понятие: добавленное поле в
существующем ответе — не концепт.
Что здесь считается новым понятием, проект может уточнить в `docs/review.md`, Что здесь считается крупным и что — незнакомым, проект может уточнить в
разделе настройки конвейера. Это **уточнение**, а не отмена: не записано — `docs/review.md`, разделе настройки конвейера: поимённо, узлами или capability.
работает список выше. Проект, где изменение контракта и правда архитектурное Это **уточнение**, а не отмена: не записано — работает список выше.
(публичный SDK, чужие потребители), там же поднимает его до `wide` — и это
уточнение, а не возврат прежнего умолчания.
### Профиль — максимум по поверхности, и отсюда размер задачи ### Спорный случай решается вниз, и у этого есть цена
Правило асимметрично, потому что асимметрична цена ошибки.
- **Спорно между `standard` и `wide` → бери `standard`.** Ошибка в эту сторону
стоит находки, которая всплывёт на следующей задаче или в журнале дефектов.
Ошибка в обратную стоит трёх тяжёлых проходов, двое из которых держат машину и
идут цепочкой, — и платится она **на каждой** задаче, выбранной неверно.
- **Спорно между `quick` и `standard` → бери `standard`.** Здесь разница в один
дешёвый проход, зато он единственный, кто на этих ступенях вообще смотрит на
отказы и на эксплуатацию.
**Выбор сделан в пользу пропускной способности, и это записано, а не подразумевается.**
Конвейер настроен на поток задач, а не на максимум находок с каждой: поправить в
следующей задаче дешевле, чем держать одну два часа. Отсюда три обязанности,
без которых сделка превращается в незаметную потерю качества:
- **границы покрытия называют непущенные проходы поимённо** — иначе `quick`
выглядит так же, как `wide` без находок;
- **журнал дефектов в `docs/review.md` перестаёт быть хорошей практикой и
становится единственной обратной связью**: проскочивший дефект — единственный
сигнал, что ступень выбрана слишком низко;
- **возврат в код — повод пересмотреть ступень.** Задача, которая приходит в тот
же узел третий раз, уже не мелкая, чем бы ни выглядел её дифф.
### Профиль — максимум по поверхности
Условия читаются сверху вниз, и **первое подошедшее отвечает за весь дифф**. Условия читаются сверху вниз, и **первое подошедшее отвечает за весь дифф**.
Профиль изменения это максимум по его поверхности, а не средневзвешенное: одна Профиль изменения это максимум по его поверхности, а не средневзвешенное: одна
строка в перечне границ задачи поднимает ступень всему остальному, включая ту строка в перечне границ задачи поднимает ступень всему остальному, включая ту
часть, которая сама по себе была бы `quick`. часть, которая сама по себе была бы `quick`.
Отсюда следствие, которое дороже любой настройки триггеров: **цена ревью растёт
быстрее размера задачи.** Крупная задача не просто даёт больше диффа — она с
высокой вероятностью зацепит верхнее условие и оплатит верхний профиль целиком.
Обратное тоже верно и тоже не бесплатно: у каждой задачи есть **несокращаемый Обратное тоже верно и тоже не бесплатно: у каждой задачи есть **несокращаемый
костяк из четырёх проходов** (гейт, спеки, код, триаж). Разрезать задачу, обе костяк из четырёх проходов** (гейт, спеки, код, триаж). Разрезать задачу, обе
половины которой остаются в одном профиле, — значит заплатить костяк дважды за ту половины которой остаются в одном профиле, — значит заплатить костяк дважды за ту
@@ -243,29 +277,33 @@ flowchart TD
gate["gate<br/>(стадия 0, держит машину)"] gate["gate<br/>(стадия 0, держит машину)"]
specs["specs"] specs["specs"]
code["code"] code["code"]
adversary["adversary<br/>(держит машину)"] basics["basics<br/>(standard)"]
ops["ops<br/>(держит машину)"] adversary["adversary<br/>(wide, держит машину)"]
ops["ops<br/>(wide, держит машину)"]
architecture["architecture<br/>(wide)"] architecture["architecture<br/>(wide)"]
triage["triage — единственный сток"] triage["triage — единственный сток"]
gate -->|зелёный| specs gate -->|зелёный| specs
gate -->|зелёный| code gate -->|зелёный| code
gate -->|зелёный| adversary gate -->|"зелёный, standard"| basics
gate -->|зелёный| ops gate -->|"зелёный, wide"| adversary
gate -->|"зелёный, wide"| ops
gate -->|"зелёный, wide"| architecture gate -->|"зелёный, wide"| architecture
adversary -. один ресурс — машина .- ops adversary -. один ресурс — машина .- ops
specs --> triage specs --> triage
code --> triage code --> triage
basics --> triage
adversary --> triage adversary --> triage
ops --> triage ops --> triage
architecture --> triage architecture --> triage
``` ```
Читается граф так: **всё, у чего входящие рёбра закрыты, уходит одним Читается граф так: **всё, у чего входящие рёбра закрыты, уходит одним
сообщением**. В `standard` после зелёного гейта это три узла разом — `specs`, сообщением**. В `quick` после зелёного гейта это `specs` и `code` разом, и сразу
`code` и первый из меряющей пары, — а второй меряющий идёт следом за первым. В триаж. В `standard` к ним третьим добавляется `basics` — все трое уходят одним
`wide` к этой тройке добавляется четвёртым `architecture`. В `quick``specs` и сообщением, ждать друг друга им нечего. В `wide` вместо `basics` идут три тяжёлых:
`code` разом, и сразу триаж. `architecture` и первый из меряющей пары — сразу, второй меряющий — следом за
первым, и он же определяет, когда стартует триаж.
**Схема здесь старше прозы.** Она не иллюстрация к тексту, а сам алгоритм **Схема здесь старше прозы.** Она не иллюстрация к тексту, а сам алгоритм
планировщика; проза ниже объясняет рёбра и называет их цену. Разошлись — прав планировщика; проза ниже объясняет рёбра и называет их цену. Разошлись — прав
@@ -292,7 +330,7 @@ flowchart TD
| `adversary` | да | находка есть **построенный путь**: он пишет падающий тест и гоняет его | | `adversary` | да | находка есть **построенный путь**: он пишет падающий тест и гоняет его |
| `ops` | да | доказывает числами: время удержания блокировки, пик кучи, темп роста журнала | | `ops` | да | доказывает числами: время удержания блокировки, пик кучи, темп роста журнала |
| `triage` | да | проверяет оракул `critical`/`major` запуском — но он сток и тоже один | | `triage` | да | проверяет оракул `critical`/`major` запуском — но он сток и тоже один |
| `specs`, `code`, `architecture`, `rubric` | нет | читают и рассуждают, ничего не исполняют | | `specs`, `code`, `basics`, `architecture`, `rubric` | нет | читают и рассуждают, ничего не исполняют |
**Правило про ресурс, а не про имена.** Раньше здесь стояло именованное **Правило про ресурс, а не про имена.** Раньше здесь стояло именованное
исключение «`adversary` и `ops`»; оно рассыпается, как только проход начнёт исключение «`adversary` и `ops`»; оно рассыпается, как только проход начнёт
@@ -374,7 +412,7 @@ flowchart TD
Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые. Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые.
Машину не держат ни один, ребра между ними нет — уходят одним сообщением сразу Машину не держат ни один, ребра между ними нет — уходят одним сообщением сразу
после зелёного гейта, вместе со стадией 2, если она в профиле. после зелёного гейта, вместе со стадией 2 или 3 — той, что в профиле.
- `review-specs` — критерий взят из **дельта-спек предлагаемого изменения**, а не - `review-specs` — критерий взят из **дельта-спек предлагаемого изменения**, а не
из proposal, сообщения коммита или описания задачи. Сверка двунаправленная; из proposal, сообщения коммита или описания задачи. Сверка двунаправленная;
@@ -385,52 +423,84 @@ flowchart TD
перечисляет `conventions/README.md` — повторять это проходом вредно. перечисляет `conventions/README.md` — повторять это проходом вредно.
Recall обоих равен длине их источника — это и есть предел applicative-проходов, Recall обоих равен длине их источника — это и есть предел applicative-проходов,
ради которого существует стадия 2. ради которого существуют стадии 2 и 3.
## Стадия 2 — Adversarial и operational (`standard`, `wide`) ## Стадия 2 — Базовый проход (только `standard`)
Два прохода: Агент `review-basics`. Один проход, машину не держит, ничего не запускает и не
меряет — уходит одним сообщением вместе со стадией 1, сразу после зелёного гейта.
**Он не самостоятельная оптика, а мелкая осадка двух тяжёлых проходов.** Берёт из
эксплуатационного — вопросы, на которые отвечают чтением, а не замером: есть ли
таймаут и отличит ли отправитель «медленно» от «упало»; идемпотентна ли повторная
операция и не теряют ли данные две одновременные; читает ли старый код новую схему
после частичного отката; что останется, если процесс остановят между шагами;
увидит ли человек, что поток оборвался ночью. Берёт из архитектурного — только то,
что видно рядом с диффом: не появился ли **второй способ** делать то, что уже
делается, мимо единой точки проекта, и **что опытный человек отсюда удалил бы**.
Чего он **не** берёт — и это записано в его уставе отдельным разделом: замеров,
эксперимента против драйвера и библиотеки, построенного пути атаки, карты проекта,
границы домена, направления зависимостей. Всё это стоит машины или входа шире
диффа, то есть ровно того, ради чего и существует `wide`.
**Он покрывает миграцию и публичный контракт на `standard`.** Это не побочный
эффект, а условие, при котором миграция схемы вообще может не поднимать ступень:
её шаг гоняет `gate`, спеку сверяет `specs`, а вопросы «обратима ли», «что с
записями новой версии после отката» задаёт здесь `basics`. Уберёшь его — и
`standard` останется без единственного прохода, который смотрит на ось времени.
Потолок — **4 находки** плюс короткая секция «дешевле переделать до мерджа».
Потолок и узкий вход и есть его «среднее усилие»: модель у него верхняя, потому
что дешёвая на опиниативном проходе платит триажем (см. «Модель по проходу»).
## Стадия 3 — Тяжёлые проходы (только `wide`)
Три прохода, и все три уходят сразу после зелёного гейта, в одном ряду со
стадией 1:
- `review-adversary` — находка есть **построенный путь**, а не свойство; - `review-adversary` — находка есть **построенный путь**, а не свойство;
- `review-ops` — постмортем от симптома у владельца сервиса к строке кода. - `review-ops` — постмортем от симптома у владельца сервиса к строке кода;
- `review-architecture` — концептуальная целостность на входе шире диффа.
**Оба помечены «держит машину», поэтому между ними ребро конфликта: они идут **Первые двое помечены «держит машину», поэтому между ними ребро конфликта: они
цепочкой, а не разом** (правило и его причина — в «Порядок прогона», раздел «Кто идут цепочкой, а не разом** (правило и его причина — в «Порядок прогона», раздел
держит машину»). Направления у ребра нет: кто первый — неважно. Со стадией 1 они «Кто держит машину»). Направления у ребра нет: кто первый — неважно.
конфликта не имеют и стартуют одновременно с ней; ждать её незачем. `architecture` машину не держит и ждать ему нечего — он уходит в первой волне.
Цепочка не отменяется общим «гони по графу» — она и есть часть графа. Отменяет Цепочка не отменяется общим «гони по графу» — она и есть часть графа. Отменяет
её только прямое слово оператора про эту пару, и тогда в границы покрытия идёт её только прямое слово оператора про эту пару, и тогда в границы покрытия идёт
строка, что числа прогона сняты под соседней нагрузкой. строка, что числа прогона сняты под соседней нагрузкой.
**Эта стадия зарабатывает больше всех остальных вместе, и потому стоит уже в **Эта стадия зарабатывает больше всех остальных вместе и она же дороже всех
`standard`, а не только в верхнем профиле.** Измерено на пяти задачах подряд: враждебный остальных вместе.** Измерено на пяти задачах подряд: враждебный проход дал пять из
проход дал пять из семи выживших находок дозапуска (включая обе верхние); семи выживших находок дозапуска (включая обе верхние); эксплуатационный —
эксплуатационный — единственный, кто нашёл, что откат бинаря поверх новой схемы единственный, кто нашёл, что откат бинаря поверх новой схемы стартует молча. Оба
стартует молча. Оба несут внешний оракул по построению: один обязан путь несут внешний оракул по построению: один обязан путь **прогнать**, второй смотрит
**прогнать**, второй смотрит ось времени и эксплуатации, которую не смотрит ось времени и эксплуатации. Ровно поэтому они и стоят денег: оракул добывается
никто другой. запуском, а запуск — это машина, цепочка и часы.
Раньше эта пара стояла в `standard`, то есть на большинстве задач. Стадия
переехала в `wide` **сознательно и по цене, а не потому, что перестала находить**:
она осталась самой ценной, но её ценность оплачивается на каждой задаче, а
получается — на немногих. Что из-за этого перестало проверяться на нижних
ступенях, названо в «Честном пределе» и обязано идти строкой в границы покрытия
каждого прогона `quick` и `standard`.
Материал берётся из документов: `docs/security.md` — враждебному, Материал берётся из документов: `docs/security.md` — враждебному,
`docs/architecture.md`, `docs/research/` и `docs/database.md` `docs/architecture.md`, `docs/research/` и `docs/database.md`
эксплуатационному. Что с чем сшивать и почему — [project-facts.md](references/project-facts.md), эксплуатационному, `docs/passport.md` и карта проекта — архитектурному. Что с чем
раздел «Сшивать обязаны проходы». Без этих документов стадия вырождается в общие сшивать и почему — [project-facts.md](references/project-facts.md), раздел
места. «Сшивать обязаны проходы». Без этих документов стадия вырождается в общие места.
## Стадия 3 — Global (`wide`, `design`) **Условие стадии и есть условие ступени `wide`:** изменение крупное или
незнакомое. У архитектурного прохода работа появляется тогда, когда трогается
несколько узлов разом или в проекте становится больше сущностей, чем было; у
меряющей пары — когда форму решения нащупывали по ходу, и потому неизвестно, где
она протекает. На мелкой правке вопрос «не появился ли второй способ» отвечается
«нет» до запуска, а построенный путь строить негде.
Агент `review-architecture`. Уходит в первой волне, сразу после зелёного гейта, в `review-architecture` получает **вход шире диффа**: дерево пакетов с
одном ряду со стадиями 1 и 2. Машину не держит, ждать ему нечего.
**Условие этой стадии и есть условие ступени `wide`:** изменение вводит новое
понятие или структурную единицу. Не «изменение крупное» и не «изменение опасное»:
у прохода появляется работа ровно тогда, когда в проекте становится больше
сущностей, чем было, — и тогда осмысленны оба его вопроса. На изменении, которое
ничего не вводит, вопрос «не появился ли второй способ» отвечается «нет» до
запуска, а вопрос «что опытный человек отсюда удалил бы» вырождается во
вкусовщину, которую потом отсеивает триаж.
Получает **вход шире диффа**: дерево пакетов с
назначением, граф внутренних зависимостей, инвентарь существующих концепций. назначением, граф внутренних зависимостей, инвентарь существующих концепций.
Команду, которая это готовит, даёт раздел команд `CLAUDE.md`; нет команды — Команду, которая это готовит, даёт раздел команд `CLAUDE.md`; нет команды —
проход собирает карту сам и говорит об этом в границах покрытия. проход собирает карту сам и говорит об этом в границах покрытия.
@@ -470,12 +540,12 @@ Recall обоих равен длине их источника — это и е
имеет `proposal.md` и дельта-спеки, но кода ещё нет. имеет `proposal.md` и дельта-спеки, но кода ещё нет.
**Состав здесь тоже не постоянный, и условие то же самое, что у `wide`:** **Состав здесь тоже не постоянный, и условие то же самое, что у `wide`:**
изменение вводит новое понятие или структурную единицу. изменение крупное или незнакомое.
- **всегда** — `review-specs` в режиме «дизайн ДО кода». Дельта-спеки сверяются - **всегда** — `review-specs` в режиме «дизайн ДО кода». Дельта-спеки сверяются
на каждой задаче: это самый дешёвый чекпоинт конвейера, и он ловит то, что на на каждой задаче: это самый дешёвый чекпоинт конвейера, и он ловит то, что на
готовом коде уже не чинят; готовом коде уже не чинят;
- **при новом понятии** — плюс `review-rubric` (фаза 1 без фазы 2: рубрика на - **при крупном или незнакомом** — плюс `review-rubric` (фаза 1 без фазы 2: рубрика на
задуманный узел становится приёмочными критериями и уезжает в `tasks.md`) и задуманный узел становится приёмочными критериями и уезжает в `tasks.md`) и
`review-architecture` на предложении: можно ли выразить существующими понятиями `review-architecture` на предложении: можно ли выразить существующими понятиями
**включая конструкции стандартной библиотеки**, — не появляется ли второй **включая конструкции стандартной библиотеки**, — не появляется ли второй
@@ -486,10 +556,10 @@ Recall обоих равен длине их источника — это и е
Причина условия — арифметика, а не экономия на осторожности. Чекпоинт стоит Причина условия — арифметика, а не экономия на осторожности. Чекпоинт стоит
**на каждой задаче**, поэтому три прохода здесь умножаются на число задач, и при **на каждой задаче**, поэтому три прохода здесь умножаются на число задач, и при
мелкой нарезке это самая большая статья конвейера. Рубрика же на узел, который не мелкой нарезке это самая большая статья конвейера. Рубрика же на узел знакомого
вводит нового понятия, порождает свойства уже существующего рода — те, что и так рода порождает свойства уже существующего рода — те, что и так записаны
записаны конвенциями и спеками; а `architecture` без нового понятия отвечает «нет» конвенциями и спеками; а `architecture` на мелкой правке отвечает «нет» на свой
на свой главный вопрос ещё до запуска (см. «Стадия 3»). главный вопрос ещё до запуска (см. «Стадия 3»).
**Граф этого профиля свой, и он плоский.** Гейта нет — кода ещё нет, запускать **Граф этого профиля свой, и он плоский.** Гейта нет — кода ещё нет, запускать
нечего; машину не держит ни один проход; сток — не триаж, а шаг 5 пайплайна нечего; машину не держит ни один проход; сток — не триаж, а шаг 5 пайплайна
@@ -500,7 +570,7 @@ Recall обоих равен длине их источника — это и е
flowchart TD flowchart TD
proposal["предложение: proposal.md + дельта-спеки"] proposal["предложение: proposal.md + дельта-спеки"]
specs["specs (режим «дизайн ДО кода») — всегда"] specs["specs (режим «дизайн ДО кода») — всегда"]
novelty{{"вводит новое понятие<br/>или структурную единицу?"}} novelty{{"изменение крупное<br/>или незнакомое?"}}
rubric["rubric, фаза 1 → приёмочные критерии в tasks.md"] rubric["rubric, фаза 1 → приёмочные критерии в tasks.md"]
arch["architecture на предложении"] arch["architecture на предложении"]
author["вопрос автору: три формы решения и компромисс каждой"] author["вопрос автору: три формы решения и компромисс каждой"]
@@ -593,7 +663,22 @@ flowchart TD
никто. Класс обратимый — портит форму кода, не данные, — и его надо признавать в никто. Класс обратимый — портит форму кода, не данные, — и его надо признавать в
границах покрытия, а не считать проверенным. границах покрытия, а не считать проверенным.
Так же честно и про второй упразднённый проход: **«не знаю, чего не знаю» больше **На `quick` и `standard` не проверяется ничего, что требует запуска.** Это самая
крупная граница покрытия конвейера, и она обязана идти строкой в каждом таком
прогоне — поимённо, а не общим «профиль ниже». Не проверяется: построенный путь
атаки (его надо прогнать), поведение библиотеки и драйвера в вырожденном случае
(достаётся только экспериментом), любое число — время удержания блокировки, пик
кучи, темп роста журнала, стоимость на годовой истории. `basics` задаёт часть тех
же вопросов **чтением**, и его ответы поэтому слабее: он формулирует условиями,
оракула не приносит и выше гипотезы находку не поднимает — кроме той, что
опирается на инвариант `CLAUDE.md`.
Это сознательная сделка, а не пробел в устройстве: цена ступени `wide` платится на
каждой задаче, а окупается на немногих. Проверяется сделка не рассуждением, а
журналом дефектов: если класс, который ловят только меряющие проходы, начал
всплывать после мерджа — ступень выбирают слишком низко.
Так же честно и про упразднённый проход: **«не знаю, чего не знаю» больше
не достаёт никто.** Проход независимой реализации писал свою версию узла, не не достаёт никто.** Проход независимой реализации писал свою версию узла, не
открывая существующую, и диффил по решениям — декомпозиция, владение данными, открывая существующую, и диффил по решениям — декомпозиция, владение данными,
модель конкурентности, форма решения там, где спека выбора не сделала. Он снят по модель конкурентности, форма решения там, где спека выбора не сделала. Он снят по
@@ -83,6 +83,7 @@ stateDiagram-v2
| `review-specs` | поведение вне спеки | добавить незаказанный фолбэк-дефолт на пустом входе | | `review-specs` | поведение вне спеки | добавить незаказанный фолбэк-дефолт на пустом входе |
| `review-code` | нарушение прозаической конвенции | увести штатный отказ мимо единой точки трансляции ошибки | | `review-code` | нарушение прозаической конвенции | увести штатный отказ мимо единой точки трансляции ошибки |
| `review-rubric` | нарушенное свойство узла | у клиента внешнего сервиса убрать таймаут и протяжку `context` | | `review-rubric` | нарушенное свойство узла | у клиента внешнего сервиса убрать таймаут и протяжку `context` |
| `review-basics` | отказ, видимый чтением | убрать обработку ошибки записи так, чтобы отказ считался успехом |
| `review-architecture` | второй способ | завести вторую точку генерации id мимо единой | | `review-architecture` | второй способ | завести вторую точку генерации id мимо единой |
| `review-adversary` | построенный путь | принять внешний идентификатор без разбора до запроса в хранилище | | `review-adversary` | построенный путь | принять внешний идентификатор без разбора до запроса в хранилище |
| `review-ops` | деградация окружения | убрать обработку недоступности внешней зависимости в фоновом цикле | | `review-ops` | деградация окружения | убрать обработку недоступности внешней зависимости в фоновом цикле |
@@ -47,6 +47,13 @@
нет — она **выводится по обратимости последствия** и помечается «выведена по нет — она **выводится по обратимости последствия** и помечается «выведена по
обратимости», а не выдаётся за решение проекта. обратимости», а не выдаётся за решение проекта.
**У `basics` стыков нет, и это не упущение.** Он не меряет, поэтому сшивать число
с настройкой ему нечего; единственное его основание для `critical` — инвариант из
`CLAUDE.md`, всё остальное он формулирует условиями и оставляет гипотезой. Его
вход намеренно узкий: единые точки и внешние зависимости из `docs/architecture.md`,
инварианты из `CLAUDE.md`, журнал из `docs/review.md`. Широкий вход — это профиль
`wide`, и там он есть у `architecture`.
## Деградация — поразрядная ## Деградация — поразрядная
Документа нет — деградирует то, что из него читалось, и **только оно**. Каждый Документа нет — деградирует то, что из него читалось, и **только оно**. Каждый
@@ -67,7 +74,7 @@
| `docs/database.md` | замер не с чем сравнить: находка не поднимается выше гипотезы | | `docs/database.md` | замер не с чем сравнить: находка не поднимается выше гипотезы |
| `docs/passport.md` | `architecture` теряет границу домена и вырождается в общее мнение | | `docs/passport.md` | `architecture` теряет границу домена и вырождается в общее мнение |
| `docs/review.md` | `triage` отсеивает вслепую: типовых ложноположительных нет | | `docs/review.md` | `triage` отсеивает вслепую: типовых ложноположительных нет |
| `docs/architecture.md` | «не появился ли второй способ» не проверяется — единых точек не знает никто | | `docs/architecture.md` | «не появился ли второй способ» не проверяется — единых точек не знает никто; `basics` теряет ещё и перечень внешних зависимостей |
Строка в границах покрытия обязана называть **причину**: «`docs/security.md` в Строка в границах покрытия обязана называть **причину**: «`docs/security.md` в
проекте нет» читается иначе, чем «есть, но периметр не назван». Без причины проекте нет» читается иначе, чем «есть, но периметр не назван». Без причины
+5 -1
View File
@@ -116,7 +116,11 @@ description: Проводит несколько задач разом — пл
Ни один триггер не сработал — задача не замеряющая, даже если её ревью Ни один триггер не сработал — задача не замеряющая, даже если её ревью
окажется `wide`. Профиль про глубину проверки, замеряющая — про соревнование за окажется `wide`. Профиль про глубину проверки, замеряющая — про соревнование за
железо; это разные вопросы, и совпадают они не всегда; железо; это разные вопросы, и совпадают они не всегда. Обратное тоже бывает и
тоже законно: помеченная задача, чьё ревью пошло профилем `quick` или
`standard`, машину не займёт вовсе — меряющие проходы живут только в `wide`.
Пометка от этого не снимается: она ставится **до** выбора профиля, и
перестраховка здесь стоит одной волны, а ошибка — испорченных чисел;
- **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект - **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект
нумерует миграции (путь — `docs/.pm.json`, ключ `migrations`), посмотри последний нумерует миграции (путь — `docs/.pm.json`, ключ `migrations`), посмотри последний
номер и **раздай номера всем задачам, которые, вероятно, их добавят**, до номер и **раздай номера всем задачам, которые, вероятно, их добавят**, до
@@ -223,8 +223,8 @@ flowchart TD
**Состав чекпоинта решает конвейер, а не ты**: `review-specs` в режиме «дизайн ДО **Состав чекпоинта решает конвейер, а не ты**: `review-specs` в режиме «дизайн ДО
кода» идёт всегда, а `review-rubric` и `review-architecture` — только когда кода» идёт всегда, а `review-rubric` и `review-architecture` — только когда
изменение вводит новое понятие или структурную единицу (то же условие, что у изменение крупное или незнакомое (то же условие, что у ступени `wide`, и та же
ступени `wide`). Причина в том, что чекпоинт стоит на **каждой** задаче: при доля — 510% задач). Причина в том, что чекпоинт стоит на **каждой** задаче: при
мелкой нарезке три прохода здесь умножаются на число задач и становятся самой мелкой нарезке три прохода здесь умножаются на число задач и становятся самой
большой статьёй конвейера. большой статьёй конвейера.
+6 -5
View File
@@ -232,11 +232,12 @@ kebab-case.** Причина не эстетическая: имя файла с
- **Вопросы к проходам** — поимённо, в форме `<имя прохода>: <вопрос> - **Вопросы к проходам** — поимённо, в форме `<имя прохода>: <вопрос>
(<провенанс>)`; (<провенанс>)`;
- **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью: - **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью:
что в этом проекте считается **новым понятием или структурной единицей** — это что в этом проекте считается **крупным или незнакомым** изменением (поднимает
поднимает прогон до `wide`, верхней ступени. Перечнем мест, а не вторым прогон до `wide`, верхней ступени, — и она рассчитана на 5–10% задач) и что
определением класса. Уточняет умолчания, а не отменяет их. Рабочее умолчание — считается **мелким** (опускает до `quick`). Перечнем мест, узлами или
`standard`: миграция схемы и публичный контракт ступень **не** поднимают, их capability, а не вторым определением класса. Уточняет умолчания, а не отменяет
проверяют проходы, которые в `standard` и так есть; их. Рабочее умолчание `standard`: миграция схемы и публичный контракт ступень
**не** поднимают, их проверяют проходы, которые в `standard` и так есть;
- **Недоступно проверке** — два подраздела: «не проверит ни один проход» - **Недоступно проверке** — два подраздела: «не проверит ни один проход»
(принципиальная граница, по факту промаха не пересматривается) и «перестали (принципиальная граница, по факту промаха не пересматривается) и «перестали
проверять сознательно» (пересматривается первым). проверять сознательно» (пересматривается первым).
+11 -7
View File
@@ -127,13 +127,17 @@ upgrade` идёт по записям снизу вверх от версии п
человека. **Переименование ADR это перенос ссылок**: слаг стоит в человека. **Переименование ADR это перенос ссылок**: слаг стоит в
`adr/README.md`, в `architecture.md` и в чужих документах, и делается одним `adr/README.md`, в `architecture.md` и в чужих документах, и делается одним
проходом, иначе останутся битые ссылки (их `docs.py` потом и покажет). проходом, иначе останутся битые ссылки (их `docs.py` потом и покажет).
8. `docs/review.md`, подраздел «Триггеры профиля»: снести перечень мест для 8. `docs/review.md`, подраздел «Триггеры профиля» — переписать целиком, он
`deep` профиль упразднён вместе с проходом независимой реализации, и отстал дважды. Снести перечень мест для `deep`: профиль упразднён вместе с
перечень стал указателем в пустоту. Перечень для `wide` (новое понятие или проходом независимой реализации, и перечень стал указателем в пустоту.
структурная единица) остаётся: он и поднимает прогон до верхней ступени. Там Оставшийся перечень перевести на новое правило: `wide` теперь означает не
же проверить журнал дефектов и «Недоступно проверке» на упоминания «новое понятие», а **крупное или незнакомое** изменение и рассчитан на 5–10%
независимой реализации: класс «форма решения, где спека выбора не сделала» задач; отдельным списком назвать, что здесь считается **мелким** (это `quick`).
переезжает в подраздел «перестали проверять сознательно». Форма подраздела — в [skeletons.md](skeletons.md). Там же проверить журнал
дефектов и «Недоступно проверке» на упоминания независимой реализации: класс
«форма решения, где спека выбора не сделала» переезжает в подраздел «перестали
проверять сознательно», а рядом с ним встаёт вторая честная строка — на
`quick` и `standard` не проверяется ничего, что требует запуска.
9. `docs/.pm.json`: `"canon": 4`. 9. `docs/.pm.json`: `"canon": 4`.
10. Позвать **обоих судей**`doc-consistency` и `doc-code-drift`, шагом 6 10. Позвать **обоих судей**`doc-consistency` и `doc-code-drift`, шагом 6
`upgrade`. Пунктов выше десять, половина из них ручная, и именно здесь видно, `upgrade`. Пунктов выше десять, половина из них ручная, и именно здесь видно,
+13 -5
View File
@@ -283,11 +283,19 @@
### Триггеры профиля ### Триггеры профиля
Проектная конкретизация правила выбора профиля: что здесь считается **новым Проектная конкретизация правила выбора профиля, двумя списками и поимённо —
понятием или структурной единицей** — это поднимает прогон до `wide`, верхней узлами или capability.
ступени, и запускает архитектурный проход. Перечнем узлов или capability,
поимённо. Уточняет умолчания конвейера, не отменяет их; рабочее умолчание — **Крупное или незнакомое здесь** — поднимает прогон до `wide`, верхней ступени:
`standard`. там идут враждебный, эксплуатационный и архитектурный проходы, и там же
единственные замеры. Ступень рассчитана на **510% задач**; если сюда попадает
каждая третья, список написан слишком широко.
**Мелкое здесь** — опускает до `quick`. Помни отрицательный тест конвейера: что
после мерджа не откатывается обратной правкой (миграция, формат на диске,
публичный контракт, имя), — не `quick`, каким бы маленьким ни был дифф.
Уточняет умолчания конвейера, не отменяет их; рабочее умолчание — `standard`.
### Недоступно проверке ### Недоступно проверке
+4 -3
View File
@@ -27,9 +27,10 @@
**Шов — там, где падает ступень ревью.** Раздел «Затрагивает» перечисляет **Шов — там, где падает ступень ревью.** Раздел «Затрагивает» перечисляет
границы; если одна строка перечня поднимает ступень выше остальных, эта часть и границы; если одна строка перечня поднимает ступень выше остальных, эта часть и
режется отдельно. Пример: задача вводит новый пакет и заодно добавляет два поля в режется отдельно. Пример: задача перекладывает несколько узлов разом и заодно
существующий ответ. Целиком это `wide` — семь проходов по всему диффу. Разрезанная добавляет два поля в существующий ответ. Целиком это `wide` — семь проходов по
по шву, она даёт `wide` на маленьком новом пакете и `standard` на остатке. всему диффу, включая два, что держат машину и идут цепочкой. Разрезанная по шву,
она даёт `wide` на маленькой переложенной части и `standard` на остатке.
**Считай костяк, а не файлы.** У каждой задачи есть несокращаемые четыре прохода **Считай костяк, а не файлы.** У каждой задачи есть несокращаемые четыре прохода
(гейт, спеки, код, триаж), и они платятся за каждую. Разрез, после которого обе (гейт, спеки, код, триаж), и они платятся за каждую. Разрез, после которого обе