верхняя ступень ревью задана тестом, а не списком

«Идентичность, слияние, разбор» пришли из одного проекта, и в общем виде
формулировка не читалась: вопрос «как применить это к моему проекту» не имел
ответа в тексте. Теперь класс задан тремя условиями, не зависящими ни от
домена, ни от языка: вариантов несколько и оба защитимы; спека между ними не
выбирает; неверный выбор не падает, а даёт правдоподобный результат и молча
меняет смысл данных.

Отрицательный тест сильнее трёх положительных: то, что красит гейт, роняет
запрос или ломает тест, в класс не входит — это ловят проходы дешевле. Отсюда
же и причина, по которой класс достался самому дорогому проходу: независимая
реализация выберет другой вариант, и дифф между вариантами и есть находка; там,
где вариант один, она совпадёт с существующей.

Три слова остались как три места, где такие правила водятся — граница, где
данные входят или встречаются: состав ключа и нормализация перед сравнением;
победитель конфликта и тай-брейк при равенстве; границы токенов и неоднозначный
вход. Проект перечисляет свои места в docs/review.md, и перечень производен от
теста, а не заменяет его.

Две оговорки, без которых правило вырождается:
- триггер — новое или изменённое по существу правило, а не код рядом с ним;
  иначе проект, чей домен и состоит из таких правил, всегда в deep;
- проект, где такого класса нет вовсе, deep не запускает никогда, и это
  законное состояние, а не недонастройка.

review-reimpl получил тот же тест и право сказать первой строкой, что позвали
не на его класс, — строкой в границы покрытия, а не отказом работать.
DECISIONS 18, XXX и следствия 76–77.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-04 17:12:40 +03:00
co-authored by Claude Opus 5
parent cc173b6b94
commit 5bf599a767
5 changed files with 137 additions and 19 deletions
+25
View File
@@ -1310,6 +1310,22 @@ SSS: рубрика на узел без нового понятия порож
ступени, делает ревью **дороже**: тот же объём тем же составом, но костяк оплачен ступени, делает ревью **дороже**: тот же объём тем же составом, но костяк оплачен
дважды. Резать — когда разрез снимает дорогой проход с большей части диффа. дважды. Резать — когда разрез снимает дорогой проход с большей части диффа.
**XXX. Верхняя ступень задана тестом, а не списком.** «Идентичность, слияние,
разбор» — формулировка, пришедшая из одного проекта, и в общем виде она не
читалась: вопрос «как это применить к моему проекту» не имел ответа в тексте.
Теперь класс задан тремя условиями, независимыми от домена и языка: вариантов
несколько и оба защитимы; спека между ними не выбирает; неверный выбор не падает,
а молча меняет смысл данных. Отрицательный тест сильнее положительных — то, что
красит гейт или роняет запрос, в класс не входит. Три слова остались как **три
места**, где такие правила водятся (граница входа данных и место их встречи), а
проект перечисляет свои места в `docs/review.md` — перечень производен от теста и
не расширяет класс.
Оговорка, без которой правило вырождается: триггер — **новое или изменённое по
существу правило**, а не код рядом с ним. Проект, чей домен и состоит из таких
правил, иначе оказывался бы в `deep` всегда — та же болезнь, от которой лечилась
ступень `wide`.
### Что из этого следует ### Что из этого следует
72. **Порога в числе границ не заводится.** Тот же принцип, что в теме 16 (CCC): 72. **Порога в числе границ не заводится.** Тот же принцип, что в теме 16 (CCC):
@@ -1323,3 +1339,12 @@ SSS: рубрика на узел без нового понятия порож
75. **Замер остаётся за обкаткой.** Правило выведено из состава проходов, а не из 75. **Замер остаётся за обкаткой.** Правило выведено из состава проходов, а не из
статистики прогонов: считать, какая доля задач попадает в каждую ступень, статистики прогонов: считать, какая доля задач попадает в каждую ступень,
можно только на спринтах нового процесса (TODO шаг 4). можно только на спринтах нового процесса (TODO шаг 4).
76. **Отсутствие верхней ступени — законное состояние проекта.** Бывают проекты,
где данные приходят нормализованными, ничего ни с чем не сливается, а внешних
форматов нет: `deep` там не срабатывает никогда, и придумывать ему повод не
надо. Раньше это читалось как недонастройка.
77. **Ступень определяет класс правила, а не вид работы.** Миграция схемы —
`standard`, но миграция, переносящая данные по правилу («сложить дубли»,
«привести к одному виду перед сравнением»), несёт правило идентичности и
потому `deep`. Одно слово в описании задачи попадает в разные ступени — это не
противоречие, смотрят не на слово.
+26 -5
View File
@@ -38,11 +38,32 @@ color: yellow
твоя версия проще, потому что не знает, чего проект боится. твоя версия проще, потому что не знает, чего проект боится.
**Тебя запускают только в верхнем профиле, `deep`, а не всегда.** Он выбирается **Тебя запускают только в верхнем профиле, `deep`, а не всегда.** Он выбирается
ровно тогда, когда изменение вводит **новое правило идентичности, слияния или ровно тогда, когда вводится или меняется по существу **правило идентичности,
разбора** (проектная формулировка — в разделе слияния или разбора**; ты — единственное, чем `deep` отличается от соседней
`docs/review.md`, если он там записан); ты — единственное, чем `deep` отличается ступени `wide`.
от соседней ступени `wide`. Вне этого случая твой счёт — самый большой в
конвейере (он Класс задан тестом, а не списком, и тест не зависит ни от домена, ни от языка.
Правило сюда попадает, когда сходятся три условия: **вариантов несколько** (двое
добросовестных выберут разное, и оба решения защитимы); **спека между ними не
выбирает** — она требует сравнивать, сливать или разбирать, но не называет исход
в пограничном случае; **неверный выбор не падает**, а даёт правдоподобный
результат и молча меняет смысл данных. Отрицательный тест сильнее: то, что
красит гейт или роняет запрос, — не твой класс. Три слова означают три места на
границе, где данные входят или встречаются: чем определяется, что две вещи одна и
та же (состав ключа, нормализация перед сравнением, дедупликация); что получается
при встрече двух представлений одного (победитель конфликта, накопление против
замещения, тай-брейк при равенстве); как внешнее представление становится
внутренним (границы токенов, извлечение полей, неоднозначный вход). Проектный
перечень мест — в `docs/review.md`, если он там записан; он производен от теста,
а не расширяет его.
**Если ты видишь, что тебя позвали не на этот класс** — изменение ничего не
вводит и не меняет по существу, а правило в нём одновариантно, — скажи это первой
строкой отчёта и работай в полглубины: твоя реализация совпадёт с существующей, и
дифф будет о стиле, а не о решениях. Это строка в границы покрытия, а не отказ
работать.
Вне этого случая твой счёт — самый большой в конвейере (он
определяется объёмом вывода: ты пишешь реализацию целиком), а независимый взгляд определяется объёмом вывода: ты пишешь реализацию целиком), а независимый взгляд
в значительной мере уже дал профиль `design` — код писался под его находки. Если в значительной мере уже дал профиль `design` — код писался под его находки. Если
тебя позвали, значит случай тот самый: работай в полную глубину и не экономь на тебя позвали, значит случай тот самый: работай в полную глубину и не экономь на
@@ -177,8 +177,8 @@ charter'а, а модель потом двигает калибровка, и
Правило выбора профиля — **по факту изменения, не по ощущению важности**: Правило выбора профиля — **по факту изменения, не по ощущению важности**:
- трогается правило, определяющее **идентичность, слияние или разбор** данных → - вводится или меняется по существу правило, определяющее **идентичность, слияние
`deep`; или разбор** данных (тест — ниже) → `deep`;
- иначе изменение вводит **новое понятие или структурную единицу**: новый пакет - иначе изменение вводит **новое понятие или структурную единицу**: новый пакет
или слой, новая точка входа, второй способ делать то, что уже делается, перенос или слой, новая точка входа, второй способ делать то, что уже делается, перенос
ответственности между узлами → `wide`; ответственности между узлами → `wide`;
@@ -215,6 +215,63 @@ charter'а, а модель потом двигает калибровка, и
правда архитектурное (публичный SDK, чужие потребители), там же поднимает его до правда архитектурное (публичный SDK, чужие потребители), там же поднимает его до
`wide` — и это уточнение, а не возврат прежнего умолчания. `wide` — и это уточнение, а не возврат прежнего умолчания.
### Идентичность, слияние, разбор — тест, а не список
Три слова названы затем, чтобы верхнюю ступень нельзя было выбрать по ощущению.
Читаются они **тестом**, применимым к любому проекту на любом языке; домен, стек
и имена узлов в тест не входят.
Правило принадлежит этому классу, если сходятся **три условия**:
1. **вариантов несколько** — два добросовестных исполнителя выберут разное, и оба
решения защитимы;
2. **спека между ними не выбирает** — она требует, чтобы вещи сравнивались,
сливались или разбирались, но не называет исход в пограничном случае;
3. **неверный выбор не падает** — он даёт правдоподобный результат и меняет смысл
данных молча.
**Отрицательный тест, и он важнее трёх положительных:** если неверная реализация
красит гейт, роняет запрос или ломает тест — это **не** сюда. Такое ловят проходы
дешевле, и платить за него верхней ступенью не за что.
Отсюда же и причина, по которой класс достался самому дорогому проходу:
независимая реализация **выберет другой вариант**, и дифф между двумя вариантами
и есть находка. Там, где вариант один, она совпадёт с существующей — и верхняя
ступень оплатит подтверждение того, что и так известно.
Три слова — это **три места**, где такие правила водятся, и все три стоят на
границе, где данные входят или встречаются:
| Слово | Вопрос, на который правило отвечает | Что в нём выбирается |
|---|---|---|
| **идентичность** | когда две вещи считаются одной и той же | состав ключа и что в него намеренно не входит; нормализация перед сравнением — регистр, пробелы, кодировка, время, единицы, округление; дедупликация |
| **слияние** | что получается, когда два представления одного встретились | кто побеждает при конфликте; накопительное против замещающего; что делать с отсутствующим полем; тай-брейк при равенстве |
| **разбор** | как внешнее представление становится внутренним | границы токенов; извлечение полей; сопоставление с известным набором; поведение на неоднозначном входе |
**Триггер — новое или изменённое правило, а не код рядом с ним.** Правка
сообщения об ошибке в узле, который разбирает вход, ступень не поднимает.
Поднимают: заводится ключ или меняется его состав; в слияние добавляется источник
или меняется победитель при конфликте; у разбора появляется новый вид входа или
новая ветка неоднозначности. Без этой оговорки проект, чей домен и **состоит** из
таких правил, оказывался бы в `deep` всегда — та же болезнь, от которой лечилась
ступень `wide`.
**Ступень определяет класс правила, а не вид работы.** Миграция схемы сама по
себе `standard` — но миграция, которая **переносит данные** по правилу («сложить
дубли», «привести к одному виду перед сравнением»), несёт правило идентичности и
потому `deep`. Одно и то же слово в описании задачи попадает в разные ступени, и
это не противоречие: смотрят не на слово, а на то, есть ли выбор, которого спека
не сделала.
**Проект, у которого таких правил нет вовсе, `deep` не запускает никогда.** Это
законное состояние, а не признак недонастройки: бывают проекты, где данные
приходят уже нормализованными, ничего ни с чем не сливается, а внешних форматов
нет. Верхняя ступень там просто не срабатывает, и придумывать ей повод не надо.
Свои места проект перечисляет в `docs/review.md`, подраздел «Триггеры профиля» —
поимённо, узлами или capability. Перечень **производен от теста**: он не расширяет
класс, а называет, где этот класс живёт именно здесь.
### Профиль — максимум по поверхности, и отсюда размер задачи ### Профиль — максимум по поверхности, и отсюда размер задачи
Условия читаются сверху вниз, и **первое подошедшее отвечает за весь дифф**. Условия читаются сверху вниз, и **первое подошедшее отвечает за весь дифф**.
@@ -458,14 +515,20 @@ Recall обоих равен длине их источника — это и е
- `review-reimpl` — пишет свою реализацию, не открывая существующую, затем - `review-reimpl` — пишет свою реализацию, не открывая существующую, затем
диффит по решениям. **Профиль и есть его условие:** `deep` выбирается ровно диффит по решениям. **Профиль и есть его условие:** `deep` выбирается ровно
тогда, когда изменение вводит новое правило идентичности, слияния или разбора тогда, когда вводится или меняется по существу правило идентичности, слияния
(проектная формулировка — в `docs/review.md`, если записана). Это самый дорогой или разбора — по тесту из раздела «Идентичность, слияние, разбор»; проектный
проход конвейера (его счёт определяется объёмом вывода — он пишет реализацию перечень мест, где такие правила живут, — в `docs/review.md`, если записан. Это
целиком), а вне этого случая независимый взгляд в значительной мере уже дал самый дорогой проход конвейера (его счёт определяется объёмом вывода — он пишет
профиль `design`: код писался под его находки. Условие выбрано по факту: реализацию целиком), а вне этого случая независимый взгляд в значительной мере
уже дал профиль `design`: код писался под его находки. Условие выбрано по факту:
единственный раз, когда триаж назвал отсутствие `reimpl` дырой покрытия, — это единственный раз, когда триаж назвал отсутствие `reimpl` дырой покрытия, — это
была задача с новым правилом слияния сущностей. была задача с новым правилом слияния сущностей.
Такие правила обычно занимают десятки строк, но определяют смысл **всех** данных
проекта. Отсюда особенность верхней ступени, из-за которой её легко выбрать
неверно: самый дорогой проход тратится на самый **маленький** дифф. `deep` не про
размер изменения и не про его опасность — он про класс правила.
Раньше это условие стояло **внутри** профиля, и `deep` означал то семь проходов, Раньше это условие стояло **внутри** профиля, и `deep` означал то семь проходов,
то восемь. Реестр состава, который «проверяется взглядом», проверять было нечем: то восемь. Реестр состава, который «проверяется взглядом», проверять было нечем:
у профиля не было одного правильного ответа. Теперь ступеней две — `wide` и у профиля не было одного правильного ответа. Теперь ступеней две — `wide` и
+5 -4
View File
@@ -184,10 +184,11 @@ kebab-case.
(<провенанс>)`; (<провенанс>)`;
- **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью: - **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью:
что в этом проекте считается **новым понятием или структурной единицей** (это что в этом проекте считается **новым понятием или структурной единицей** (это
поднимает прогон до `wide`) и что — правилом идентичности, слияния или разбора поднимает прогон до `wide`) и **где живут правила идентичности, слияния и
(до `deep`). Уточняет умолчания конвейера, а не отменяет их. Рабочее умолчание разбора** (до `deep`) — перечнем мест, производным от теста конвейера, а не
— `standard`: миграция схемы и публичный контракт ступень **не** поднимают, вторым определением класса. Уточняет умолчания, а не отменяет их. Рабочее
их проверяют проходы, которые в `standard` и так есть; умолчание `standard`: миграция схемы и публичный контракт ступень **не**
поднимают, их проверяют проходы, которые в `standard` и так есть;
- **Недоступно проверке** — два подраздела: «не проверит ни один проход» - **Недоступно проверке** — два подраздела: «не проверит ни один проход»
(принципиальная граница, по факту промаха не пересматривается) и «перестали (принципиальная граница, по факту промаха не пересматривается) и «перестали
проверять сознательно» (пересматривается первым). проверять сознательно» (пересматривается первым).
+11 -3
View File
@@ -284,9 +284,17 @@
Проектная конкретизация правила выбора профиля: что здесь считается **новым Проектная конкретизация правила выбора профиля: что здесь считается **новым
понятием или структурной единицей** (поднимает прогон до `wide` и запускает понятием или структурной единицей** (поднимает прогон до `wide` и запускает
архитектурный проход) и что — правилом идентичности, слияния или разбора архитектурный проход) и **где живут правила идентичности, слияния и разбора**
(до `deep`, запускает независимую реализацию). Уточняет умолчания конвейера, не (до `deep`, запускает независимую реализацию) — перечнем узлов или capability,
отменяет их; рабочее умолчание — `standard`. поимённо. Уточняет умолчания конвейера, не отменяет их; рабочее умолчание —
`standard`.
Перечень для `deep` **производен от теста конвейера**, а не заменяет его:
правило попадает в класс, когда вариантов несколько, спека между ними не
выбирает, а неверный выбор не падает, а молча меняет смысл данных. Перечисляй
места, где этот класс здесь живёт, а не переписывай определение. Таких мест нет
вовсе — так и напиши: `deep` тогда не запускается никогда, и это законное
состояние.
### Недоступно проверке ### Недоступно проверке