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

- параллельно гоняем только по явной просьбе И с явно названным набором
  проходов или стадии; просьба без набора основанием не считается
- обосновывается отступление, а не умолчание
- пару adversary + ops не параллелим даже по просьбе без переспроса:
  оба меряют одно железо, испорченный оракул хуже отсутствующего
- параллельный прогон объявляется в отчёте с составом, а замеры из него
  идут в границы покрытия как ослабленные
This commit is contained in:
av
2026-08-02 20:59:11 +03:00
parent 58cf5c07d8
commit 7e6d63415e
2 changed files with 60 additions and 44 deletions
@@ -1,6 +1,6 @@
---
name: healthlog-review-pipeline
description: Конвейер ревью изменений healthlog — детерминированный гейт, сверка с дельта-спеками OpenSpec в обе стороны, враждебные постановки и эксплуатационный постмортем, независимая реализация по триггеру, архитектура и обязательный триаж. Проходы гонятся параллельно или последовательно (второе — когда ожидаются замеры или занята машина). Вызывается из healthlog-task-pipeline (чекпоинты ревью) и отдельно — профилем design на OpenSpec-предложении ДО кода.
description: Конвейер ревью изменений healthlog — детерминированный гейт, сверка с дельта-спеками OpenSpec в обе стороны, враждебные постановки и эксплуатационный постмортем, независимая реализация по триггеру, архитектура и обязательный триаж. Проходы гонятся последовательно; параллельно — только по явной просьбе и с явно названным набором. Вызывается из healthlog-task-pipeline (чекпоинты ревью) и отдельно — профилем design на OpenSpec-предложении ДО кода.
---
# Конвейер ревью (healthlog)
@@ -138,31 +138,44 @@ description: Конвейер ревью изменений healthlog — дет
| Режим | Как | Когда |
|---|---|---|
| **параллельно** (умолчание) | все проходы стадии — одним сообщением | обычный случай: проходы только читают код |
| **последовательно** | по одному, следующий стартует после отчёта предыдущего | причины ниже, любой одной достаточно |
| **последовательно** (умолчание) | по одному, следующий стартует после отчёта предыдущего | всегда, пока не попросили иначе |
| **параллельно** | названные проходы — одним сообщением | только по явной просьбе **и** с явно названным набором |
Последовательный режим выбирается, когда верно хоть что-то из:
**Умолчание — последовательно, и его не надо обосновывать.** Обосновывается
отступление.
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. **Разбирается сам конвейер.** Когда выясняется, почему проход что-то не
нашёл, порядок и изоляция важнее скорости.
**Параллельный режим включается при двух условиях сразу**, и второе так же
обязательно, как первое:
1. **о нём попросили явно** — «гони параллельно», а не «сделай побыстрее»;
2. **названо, что именно гнать параллельно** — поимённый набор проходов
`specs` и `code` параллельно») или стадия целиком («стадию 1 параллельно»).
Просьба без набора — **не основание**: гоним последовательно и одной строкой
говорим, что набор не был назван. Это не придирка к формулировке. Параллелить
можно ровно то, что не мешает друг другу, а знание об этом лежит у того, кто
просит: он видит, занята ли машина, и ждёт ли он от прогона замеров. Домысливать
набор за него — значит принять решение, которое он оставил себе.
Почему умолчание именно такое:
- **Замеры.** Проходы `adversary` и `ops` доказывают находки числами: время
удержания блокировки, пик кучи, рост `-wal`, длительность транзакции. Два
меряющих прохода на одной машине соревнуются за диск, CPU и за саму SQLite и
выдают числа, которые не воспроизведутся. Это не гипотеза: находки сессии
опираются ровно на такие замеры (5.019 с удержания блокировки при
`busy_timeout` 5000, пик 768 МиБ на теле 40 МиБ, 7 МБ/с роста `-wal`, 1492
тика из 5502). Число, снятое под конкурентную нагрузку от соседнего прохода, —
это находка с испорченным оракулом, а её опровержение стоит дороже всего
выигрыша от параллельности.
- **Машина одна.** Рядом идёт задача, поднят сервис, гоняется `task gate` или
`task verify:archive`.
- **Ранний выход** возможен только при последовательном прогоне (см. ниже).
- **Разбор самого конвейера.** Когда выясняется, почему проход чего-то не нашёл,
порядок и изоляция важнее скорости.
Если параллельный режим всё же включён, в границы покрытия идёт строка: какие
проходы шли разом и что замеры, снятые в этом прогоне, как оракул слабее.
**Чего режим не меняет — и это не подлежит обсуждению.** Проход **не видит**
находок других проходов ни в каком режиме. «Последовательно» значит «по
@@ -173,8 +186,9 @@ description: Конвейер ревью изменений healthlog — дет
маскируется под независимое подтверждение. Единственный, кто видит всё, —
триаж, и это его работа.
**Ранний выход.** В последовательном режиме допустимо остановить прогон, не
докатив остаток, ровно в одном случае: находка требует **переделки формы**
**Ранний выход** (последовательный режим делает его возможным — это его побочная
выгода, а не повод его выбирать). Допустимо остановить прогон, не докатив
остаток, ровно в одном случае: находка требует **переделки формы**
изменения, и остальные проходы будут смотреть на код, которого через час не
станет. Тогда:
@@ -190,9 +204,10 @@ description: Конвейер ревью изменений healthlog — дет
(`Действие: инлайн`), **не делается**: дешевле дособрать все находки и починить
пачкой, чем гонять конвейер дважды.
Режим объявляется в отчёте наравне с профилем, и если он последовательный — с
причиной. Строка «режим: последовательный, ожидались замеры удержания
блокировки» стоит ничего и объясняет, почему прогон занял втрое дольше.
Режим объявляется в отчёте наравне с профилем, и если он **параллельный**с
причиной и составом: «режим: параллельный по просьбе, одним сообщением шли
`specs` и `code`». Последовательный режим объявляется одним словом:
обосновывается отступление, а не умолчание.
## Стадия 0 — Gate (обязательна во всех профилях)
@@ -219,8 +234,8 @@ description: Конвейер ревью изменений healthlog — дет
## Стадия 1 — Conformance (обязательна во всех профилях)
Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые.
По умолчанию — одним сообщением параллельно; замеров они не делают, так что
последовательный режим им нужен только из-за занятой машины.
Замеров они не делают и потому безобиднее прочих, если параллельный режим
попросят с их именами; сами по себе идут по очереди, как и все.
- `healthlog-review-specs` — критерий взят из **дельта-спек change в
`openspec/changes/<id>/specs/`**, а не из proposal, сообщения коммита или
@@ -244,11 +259,12 @@ Recall обоих равен длине их источника — это и е
- `healthlog-review-ops` — постмортем от симптома у владельца сервиса к строке
кода.
**Это главные кандидаты на последовательный режим.** Оба доказывают находки
замером, и оба меряют одно и то же железо: удержание блокировки SQLite, пик
кучи, рост `-wal`, длительность транзакции. Запущенные разом, они портят числа
друг другу. Если ждёшь от прогона хоть один такой замер — гони их по очереди,
а не одним сообщением.
**Эту пару параллелить не стоит даже по просьбе — переспроси.** Оба доказывают
находки замером, и оба меряют одно и то же железо: удержание блокировки SQLite,
пик кучи, рост `-wal`, длительность транзакции. Запущенные разом, они портят
числа друг другу, а испорченный оракул хуже отсутствующего: находка выглядит
доказанной. Если их всё же назвали в параллельном наборе — выполняй, но скажи в
границах покрытия, что числа этого прогона сняты под соседней нагрузкой.
**Эта стадия зарабатывает больше всех остальных вместе, и потому стоит в
`standard`, а не только в `deep`.** Измерено на пяти задачах: враждебный проход