ревью: добавлен последовательный режим запуска проходов

- профиль отвечает «какие проходы», режим — «как их запускать»
- явная просьба владельца — достаточное основание, без обоснований
  и переспрашивания; перекрывает эвристики в обе стороны
- главный собственный повод — ожидаемые замеры: adversary и ops меряют
  одно железо (блокировка SQLite, куча, рост -wal) и портят числа друг
  другу, а находка с испорченным оракулом дороже сэкономленных минут
- декорреляция не меняется ни в каком режиме: проход не видит чужих
  находок, «последовательно» ≠ «читает предыдущего»
- ранний выход только при переделке формы изменения, с перезапуском
  с нулевой стадии; триаж — только на полном прогоне
This commit is contained in:
av
2026-08-02 20:57:20 +03:00
parent 9ad1deeb01
commit 58cf5c07d8
2 changed files with 86 additions and 7 deletions
@@ -1,6 +1,6 @@
--- ---
name: healthlog-review-pipeline name: healthlog-review-pipeline
description: Конвейер ревью изменений healthlog — детерминированный гейт, сверка с дельта-спеками OpenSpec в обе стороны, враждебные постановки и эксплуатационный постмортем, независимая реализация по триггеру, архитектура и обязательный триаж. Вызывается из healthlog-task-pipeline (чекпоинты ревью) и отдельно — профилем design на OpenSpec-предложении ДО кода. description: Конвейер ревью изменений healthlog — детерминированный гейт, сверка с дельта-спеками OpenSpec в обе стороны, враждебные постановки и эксплуатационный постмортем, независимая реализация по триггеру, архитектура и обязательный триаж. Проходы гонятся параллельно или последовательно (второе — когда ожидаются замеры или занята машина). Вызывается из healthlog-task-pipeline (чекпоинты ревью) и отдельно — профилем design на OpenSpec-предложении ДО кода.
--- ---
# Конвейер ревью (healthlog) # Конвейер ревью (healthlog)
@@ -119,7 +119,7 @@ description: Конвейер ревью изменений healthlog — дет
измерена: семь находок и отдельная задача на их дозакрытие измерена: семь находок и отдельная задача на их дозакрытие
(`docs/review-journal.md`, 2026-08-02). (`docs/review-journal.md`, 2026-08-02).
Правило выбора — по факту изменения, не по ощущению важности: Правило выбора профиля — по факту изменения, не по ощущению важности:
- есть миграция в `internal/store/migrations/`, новый пакет `internal/*`, - есть миграция в `internal/store/migrations/`, новый пакет `internal/*`,
изменение контракта Read API или MCP, трогается правило слияния точек или изменение контракта Read API или MCP, трогается правило слияния точек или
@@ -131,6 +131,69 @@ description: Конвейер ревью изменений healthlog — дет
Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно
попадает в границы покрытия строкой «профиль понижен до X, потому что …». попадает в границы покрытия строкой «профиль понижен до X, потому что …».
## Режим запуска: параллельно или последовательно
Профиль отвечает «какие проходы», режим — «как их запускать». Стадии всегда идут
по порядку номеров; выбор касается только проходов **внутри** стадии.
| Режим | Как | Когда |
|---|---|---|
| **параллельно** (умолчание) | все проходы стадии — одним сообщением | обычный случай: проходы только читают код |
| **последовательно** | по одному, следующий стартует после отчёта предыдущего | причины ниже, любой одной достаточно |
Последовательный режим выбирается, когда верно хоть что-то из:
0. **Его попросили.** Владелец сказал «гони последовательно» — этого достаточно,
обоснования не требуется и переспрашивать не надо. Просьба перекрывает любую
эвристику ниже, в том числе когда по ним вышло бы «параллельно». В отчёте —
строкой «режим: последовательный, по просьбе». Симметрично: явная просьба
гнать параллельно перекрывает пункты 1–4, и тогда в границы покрытия идёт
оговорка, что замеры сняты под конкурентной нагрузкой и как оракул слабее.
1. **Ожидаются замеры.** Проход измеряет время удержания блокировки, пик кучи,
рост `-wal`, длительность транзакции, пропускную способность. Два таких
прохода, запущенных разом на одной машине, соревнуются за диск, CPU и за саму
SQLite — и выдают числа, которые не воспроизведутся. Это не гипотеза:
находки сессии опираются ровно на такие замеры (5.019 с удержания блокировки
при `busy_timeout` 5000, пик 768 МиБ на теле 40 МиБ, 7 МБ/с роста `-wal`,
1492 тика из 5502). Число, снятое под конкурентную нагрузку от соседнего
прохода, — это находка с испорченным оракулом, а её опровержение стоит
дороже, чем весь выигрыш от параллельности.
2. **Машина занята.** Идёт другая задача, поднят сервис, гоняется
`task verify:archive` (минута) или `task gate` (несколько минут).
3. **Нужен ранний выход.** См. ниже.
4. **Разбирается сам конвейер.** Когда выясняется, почему проход что-то не
нашёл, порядок и изоляция важнее скорости.
**Чего режим не меняет — и это не подлежит обсуждению.** Проход **не видит**
находок других проходов ни в каком режиме. «Последовательно» значит «по
очереди», а не «следующий читает предыдущего». Вся ценность конвейера держится
на декорреляции: под всеми ролями одна модель с одними априорными, и стоит
показать ей чужой вывод — она согласится. Согласие нескольких проходов и так не
повышает `confidence` (см. «Честный предел»); согласие **наведённое** ещё и
маскируется под независимое подтверждение. Единственный, кто видит всё, —
триаж, и это его работа.
**Ранний выход.** В последовательном режиме допустимо остановить прогон, не
докатив остаток, ровно в одном случае: находка требует **переделки формы**
изменения, и остальные проходы будут смотреть на код, которого через час не
станет. Тогда:
- прогон останавливается, находка чинится, конвейер запускается **заново с
нулевой стадии** — а не «доезжает» остатком по старому коду;
- незапущенные проходы идут в границы покрытия строкой «не запускался: прогон
остановлен на <проход> из-за <находка>», поимённо;
- триаж запускается только на полном прогоне. Отчёт триажа по половине проходов
— ровно тот случай, который уже стоил семи находок: он выглядит полным,
потому что агрегирует всё, что ему подали.
Ранний выход по находке, которая чинится в пределах существующей формы
(`Действие: инлайн`), **не делается**: дешевле дособрать все находки и починить
пачкой, чем гонять конвейер дважды.
Режим объявляется в отчёте наравне с профилем, и если он последовательный — с
причиной. Строка «режим: последовательный, ожидались замеры удержания
блокировки» стоит ничего и объясняет, почему прогон занял втрое дольше.
## Стадия 0 — Gate (обязательна во всех профилях) ## Стадия 0 — Gate (обязательна во всех профилях)
Агент `healthlog-review-gate`. Запускает `task gate` и интерпретирует вывод. Агент `healthlog-review-gate`. Запускает `task gate` и интерпретирует вывод.
@@ -155,8 +218,9 @@ description: Конвейер ревью изменений healthlog — дет
## Стадия 1 — Conformance (обязательна во всех профилях) ## Стадия 1 — Conformance (обязательна во всех профилях)
Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые, Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые.
запускаются **одним сообщением параллельно**. По умолчанию — одним сообщением параллельно; замеров они не делают, так что
последовательный режим им нужен только из-за занятой машины.
- `healthlog-review-specs` — критерий взят из **дельта-спек change в - `healthlog-review-specs` — критерий взят из **дельта-спек change в
`openspec/changes/<id>/specs/`**, а не из proposal, сообщения коммита или `openspec/changes/<id>/specs/`**, а не из proposal, сообщения коммита или
@@ -173,13 +237,19 @@ Recall обоих равен длине их источника — это и е
## Стадия 2 — Adversarial и operational (`standard`, `deep`) ## Стадия 2 — Adversarial и operational (`standard`, `deep`)
Два прохода, запускаются **одним сообщением параллельно**: Два прохода:
- `healthlog-review-adversary` — находка есть **построенный путь**, а не - `healthlog-review-adversary` — находка есть **построенный путь**, а не
свойство; свойство;
- `healthlog-review-ops` — постмортем от симптома у владельца сервиса к строке - `healthlog-review-ops` — постмортем от симптома у владельца сервиса к строке
кода. кода.
**Это главные кандидаты на последовательный режим.** Оба доказывают находки
замером, и оба меряют одно и то же железо: удержание блокировки SQLite, пик
кучи, рост `-wal`, длительность транзакции. Запущенные разом, они портят числа
друг другу. Если ждёшь от прогона хоть один такой замер — гони их по очереди,
а не одним сообщением.
**Эта стадия зарабатывает больше всех остальных вместе, и потому стоит в **Эта стадия зарабатывает больше всех остальных вместе, и потому стоит в
`standard`, а не только в `deep`.** Измерено на пяти задачах: враждебный проход `standard`, а не только в `deep`.** Измерено на пяти задачах: враждебный проход
дал пять из семи выживших находок дозапуска на `f8200f7` (включая обе верхние) и дал пять из семи выживших находок дозапуска на `f8200f7` (включая обе верхние) и
@@ -151,14 +151,23 @@ healthlog — хранилище данных о здоровье, у котор
### 7. Ревью кода — Skill `healthlog-review-pipeline` ### 7. Ревью кода — Skill `healthlog-review-pipeline`
Второй чекпоинт. Вызови Skill **`healthlog-review-pipeline`**, дав ссылку на change Второй чекпоинт. Вызови Skill **`healthlog-review-pipeline`**, дав ссылку на change
`<id>`, базу диффа и профиль. Профиль выбирается по факту изменения, а не по `<id>`, базу диффа, профиль **и режим запуска**. Профиль выбирается по факту
ощущению важности (правило — в самом скилле): изменения, а не по ощущению важности (правило — в самом скилле):
- миграция, новый пакет, контракт Read API или MCP, правило слияния точек или - миграция, новый пакет, контракт Read API или MCP, правило слияния точек или
вывод слоя → `deep`; вывод слоя → `deep`;
- иначе меняется поведение, видимое снаружи → `standard`; - иначе меняется поведение, видимое снаружи → `standard`;
- иначе (багфикс, локальная правка, доки) → `quick`. - иначе (багфикс, локальная правка, доки) → `quick`.
Режим по умолчанию параллельный. **Попросили последовательный — значит
последовательный**, без обоснований и переспрашивания. Сам проси
**последовательный**, если ждёшь от
прохода замеров — времени удержания блокировки, пика кучи, роста `-wal`,
длительности транзакции: два меряющих прохода на одной машине портят числа друг
другу, а находка с испорченным оракулом дороже сэкономленных минут. Второй
повод — машина занята (поднят сервис, идёт `task gate` или
`task verify:archive`). Правило и его оговорки — в самом скилле.
Скилл сам гоняет гейт, нужные проходы и обязательный триаж. Возвращает отчёт с Скилл сам гоняет гейт, нужные проходы и обязательный триаж. Возвращает отчёт с
потолком 7 пунктов, разметкой `Действие: инлайн | развилка` и секцией границ потолком 7 пунктов, разметкой `Действие: инлайн | развилка` и секцией границ
покрытия. покрытия.