Files
houseplan-card/docs/reviews/CODE-REVIEW-587-r1.md
T
2026-09-16 18:50:14 +00:00

172 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CODE-REVIEW-587-r1
**Вердикт: зелёный · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 0**
Материал: `978035c49193a7d367d3dba6a22bebef149e1eef` (совпадает с текущим `HEAD`,
рабочая копия детачнута на нём). Трек: инфраструктурный (метки `bug`, `P2`,
`infra`, `S7-code-review`; без продуктового `S*`).
## Скоуп
Один коммит, диапазон `origin/dev..HEAD` = сам коммит `978035c4`. Класс B
целиком: `.github/workflows/performance.yml`, `scripts/mutation-registry.mjs`,
`demo/performance/README.md`, плюс новые `scripts/performance-baseline.mjs` и
`test/performance-baseline.test.mjs`. Продуктовый код (`src/**`,
`custom_components/**`) не тронут — подтверждено `git diff origin/dev...HEAD
--stat`.
Проблема из issue: гейт «Полных бенчмарков» для кандидата стабильного релиза
сравнивал линейку саму с собой, потому что `performance.yml` запускается
только на push в `main`, а базой относительного сравнения брался «предыдущий
push в main» — то есть первый коммит той же релизной линейки. Наблюдалось
вживую на v1.76.0: честный красный прогон `35097102695` на `c3d64789` и
зелёный `35109734519` на `9683a590` независимо от правки бюджетов.
Решение: выбор базы вынесен из shell в `scripts/performance-baseline.mjs`;
стабильный кандидат (head несёт трейлер `Release: vX.Y.Z` без пре-релизного
суффикса) сравнивается с предыдущим стабильным тегом, остальные случаи
(бета, обычный push, ручной `comparison_ref`) — без изменений.
## Как проверялось
Дешёвые гейты для этого SHA уже подтверждены зелёным прогоном Validate
(системная метка ревью ссылается на `35135781823`; сам исполнитель в хендоффе
называет соседний прогон `35135276439` — оба независимо проверены мной,
`headSha` обоих совпадает с материалом ревью, `conclusion: success`):
`npx tsc --noEmit`, `npm test`, `npm run build` не перегонялись повторно.
Что прогнал сам, потому что это точечные, дешёвые и по существу задачи проверки:
| Гейт | Команда | Результат |
|---|---|---|
| Целевой тест AC2 | `node --test --test-name-pattern="AC2" test/performance-baseline.test.mjs` | зелёный, 1/1 |
| Мутационный гейт по задаче | `node scripts/mutation-gate.mjs --id=stable-candidate-compares-against-itself` | «заявленный тест покраснел на мутанте» — оба шага (чистый прогон, прогон с мутацией) зелёные в ожидаемом смысле |
| Реестр мутантов (целостность якорей) | `node scripts/mutation-gate.mjs --check` | прошёл, новый якорь `stable-candidate-compares-against-itself` в списке |
| Выбор смоков по дифу | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «Исполняемого frontend-диффа нет… браузерные смоки этим диффом не выбираются» — `src/**` не тронут, смоки не нужны |
Не прогонял и почему:
- `npm test` целиком, `npx tsc --noEmit`, `npm run build` со сверкой бандла —
уже зелёные на этом SHA (см. выше), диф не в `src/**`/`custom_components/**`,
перегонять нет смысла;
- `node scripts/check-docs.mjs` — диф не трогает `src/**`, отпечаток
скриншотов документации им не задет; исполнитель называет тот же известный
устаревший отпечаток (#586), не относящийся к этой задаче;
- инварианты модели (`npm run invariants`) — диф не касается геометрии, стен,
`layout`, `marker.space`, `open_spans`;
- `npm run golden:verify` — диф не меняет рендер/геометрию/стили;
- `python -m pytest tests_backend -q` — `custom_components/**/*.py` не тронут;
- performance-профили (реальный прогон бенчмарков) — задача меняет только то,
*с чем* сравнивается прогон, а не измеряемый код; AC не требуют реального
запуска `performance.yml`, только доказательства выбора базы, что и
покрывают юнит-тесты с инжектированным `git`.
Дополнительно прочитал код инвариантов, на которые опирается AC4 (см. ниже), и
сверил регекспы/веточную логику `scripts/performance-baseline.mjs` построчно
против старой shell-реализации, которую он заменяет.
## Разбор по AC (issue #587)
**AC1** — «Push в `main` коммита с трейлером `Release: vX.Y.Z` (стабильный)
берёт базой предыдущий стабильный тег; это видно в сводке прогона.»
Доказано тестом `AC1: кандидат стабильного релиза судится о предыдущий
стабильный тег` (`test/performance-baseline.test.mjs:43`) — прогнан лично,
зелёный. Видимость в сводке: `resolveComparisonBase` возвращает `source`,
запись в `$GITHUB_STEP_SUMMARY` не убрана (`scripts/performance-baseline.mjs:206-208`),
проверено чтением — идентична строке из прежней shell-версии.
Чем краснеет: тот же мутант, что и AC2 (see below) убирает ветку
`previous-stable` целиком — тест AC1 тоже упал бы на этом мутанте (не
зарегистрирован отдельно, но код-путь общий: см. «Защитный AC» ниже).
**AC2** — «Два коммита подряд одной линейки не могут дать зелёный гейт "сам с
собой" — второй сравнивается с тем же стабильным тегом, что и первый.»
Доказано тестом `AC2` — прогнан лично, зелёный. Защитный AC доказан таблицей:
| AC | чем доказан | чем краснеет |
|---|---|---|
| AC2 | `node --test --test-name-pattern="AC2" test/performance-baseline.test.mjs` | мутант `stable-candidate-compares-against-itself` (`scripts/mutation-registry.mjs`) убирает условие входа в ветку «предыдущий стабильный тег»; прогнал лично `node scripts/mutation-gate.mjs --id=stable-candidate-compares-against-itself` → «заявленный тест покраснел на мутанте» |
Для чистого юнита (не дорогой гейт — смок/бэкенд/golden) прогона со снятой
защитой достаточно по §2.7; сделано.
**AC3** — «Бета-кандидат и обычный push сохраняют текущую базу.» Доказано
тестом `AC3` (тот же файл) — три под-случая (бета с трейлером, push без
трейлера, `workflow_dispatch` без `comparison_ref`), прогнан лично, зелёный.
**AC4** — «Заодно проверить, не опирается ли на "родителя" ещё какой-нибудь
релизный гейт.» Разобрано **чтением, не исполнением**:
- `validate.yml` использует `github.event.before` только как `FALLBACK` для
диапазона классифицируемых файлов (`base`/`range_base`, строки 240–295,
606, 700); действующая база диапазона — «самый новый предок с успешно
завершённым Validate» (#387/#388), не сырой родитель — комментарий на
строке 249 прямо это фиксирует как урок предыдущего инцидента (#86 r5).
Циклическая проблема из #587 к этой логике неприменима: она не измеряет
релизную линейку саму собой, а определяет объём проверки файлов.
- `scripts/release-gate.mjs`, `release-contract.mjs`, `release-membership.mjs`,
`release-bookkeeping.mjs`, `e2e-gate.mjs` — grep на `parent|HEAD^|event.before`
ничего не находит; `docs/DEVELOPMENT.md:485-504` подтверждает, что
`release.yml` резолвит кандидата по точному тегу/SHA и требует полное
доказательство Validate для этого SHA, родителя не касаясь.
Вывод соответствует утверждению исполнителя; независимая проверка грепом и
чтением документации его подтверждает.
## Что проверено и корректно
- Регекспы `releaseTrailerTag`/`isStableTag` соответствуют существующей
конвенции трейлера `Release:` в `scripts/classify-changes.mjs` и
`scripts/process-gate.mjs` — это не новый придуманный контракт, а уже
используемый в кодовой базе (`docs/DEVELOPMENT.md:489`: стабильный релиз
обязан нести `Release: <tag>` на кандидате).
- `previousStableTag` корректно исключает и тег кандидата (по номеру версии,
independent от текущего SHA — для случая «ремонт уже выпущенного релиза»),
и тег, стоящий на самой голове; оба случая покрыты тестом
«свой тег и тег на голове предыдущим не считаются».
- Порядок отказов (недоступная база → родитель → последний достижимый
релизный тег → ошибка) сохранён из старой shell-реализации построчно;
тесты «непригодная база уводит...», «без предыдущего тега...», «база
старше HP-PERF-01...» подтверждают эквивалентность поведения не-стабильных
путей.
- Тест `workflow берёт базу из скрипта...` (`test/performance-baseline.test.mjs:161`)
проверяет сам YAML-файл: вызов `scripts/performance-baseline.mjs` на
месте, `HEAD_MESSAGE` прокинут, старая shell-развилка
(`source="candidate parent"`) удалена целиком — защищает именно от риска
«два места принятия одного решения расходятся», названного исполнителем.
- Коммит несёт корректные трейлеры `Issue: #587`, `User-Visible: no` —
продуктовое поведение не меняется, changelog не требуется.
- `demo/performance/README.md` обновлён в том же коммите — документация
подсистемы синхронна с кодом.
- Одно число — один источник: изменение не вводит новых пользовательских
величин, дублирования нет (CI-внутренний артефакт, не UI).
## Чего не проверял
- Реальный прогон `performance.yml` на push в `main` с трейлером `Release:` —
недоступно из код-ревью (нет push-события), поведение доказано юнит-тестами
с инжектированным `git`, а не сквозным прогоном workflow. Риск невысокий:
единственная переменная, которую тесты не видят, — фактический формат
`github.event.head_commit.message`, но он прокидывается без обработки как
строка, и `releaseTrailerTag` уже проверен на многострочных сообщениях с
трейлерами после текста коммита (тест «трейлер релиза читается...»).
- `npm run mutation-gate` полный прогон всех ~760 мутантов (гонял только
`--check` для целостности реестра и точечный `--id` для новой записи) —
полный прогон не относится к дельте этой задачи и дорог.
## Материал раунда
- SHA: `978035c49193a7d367d3dba6a22bebef149e1eef`
- Дерево: `git rev-parse HEAD^{tree}` на момент ревью соответствует рабочей
копии (детач на материале, без локальных изменений — `git status` чист).
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/587-stable-perf-baseline`, коммит `978035c49193` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `8f80b742f032f854e5b54f5184adb4c8daa52cc1`
```
git log --all --format='%H %T' | grep 8f80b742f032
```
- Тело issue: `ff876b4abd609814b1700c764a650cba2a3ccc262d9ae744e02c16f0d1cbd186`
- Вердикт конвейера: `green` · High 0