mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
185 lines
19 KiB
Markdown
185 lines
19 KiB
Markdown
# CODE-REVIEW-659-r1
|
||
|
||
Issue: #659 — «Реестр мутантов: смок-гардов стало больше, а не меньше (206 → 262
|
||
из 979) — перевести проверяемые без браузера на `node --test`, ограничить рост
|
||
правилом».
|
||
|
||
Материал: `6f8c2929afe46a39ab91bedb0051c8a491ccbe8b` (рабочая копия проверялась
|
||
на этом SHA; `git rev-parse HEAD` сверен непосредственно перед выводом).
|
||
Автор в передаче на ревью назвал `d10f36204231473f0c37501fd2432e8a58931770` —
|
||
такого объекта в дереве нет. Это не находка: коммит `6f8c2929` — один squash-
|
||
коммит с родителем `9a7020ce` (текущая вершина `origin/dev`), то есть материал
|
||
уже перебазирован конвейером на актуальный `dev` (нормальный путь по AGENTS.md
|
||
«On a green code review the pipeline rebases…» — здесь ребейз случился ещё до
|
||
начала ревью). Содержимое диффа идентично описанному автором изменению.
|
||
|
||
`git log --oneline origin/dev..HEAD` = один коммит; `git diff origin/dev...HEAD`
|
||
= 12 файлов, 810/56. Класс B (`scripts/**`, `test/**`) + C (`PROCESS.md`,
|
||
`docs/testing-notes/**`) — файлов класса A нет, инфраструктурный вход в
|
||
`S7-code-review` обоснован.
|
||
|
||
## Скоуп
|
||
|
||
Задача сокращает число browser-smoke-гардов в реестре мутантов
|
||
(`scripts/mutation-registry.mjs`) переводом части свидетелей на `node --test`,
|
||
фиксирует потолок в `mutation-gate --check`/`npm run inventory`, добавляет
|
||
правило в `PROCESS.md` §2.7 и одну сборку бандла на шард там, где патч мутанта
|
||
не трогает бандлируемые входы. Инфраструктурная работа — прямого AC на
|
||
`docs/SCOPE.md` не закрывает, а обслуживает машинное время CI (§8 гейтов),
|
||
что и заявлено в issue.
|
||
|
||
## Как проверялось
|
||
|
||
| Гейт | Результат | Источник |
|
||
|---|---|---|
|
||
| `typecheck`, `npm test`, `npm run build`, bundle-policy verify | не перегонялись — зелёный Validate на этом SHA уже подтверждён | https://github.com/Matysh/houseplan-card/actions/runs/36317254856 (success) |
|
||
| `node --test test/process-digests.test.mjs` | 5/5 зелёных | выполнено лично |
|
||
| `node --test test/mutation-gate.test.mjs test/mutation-browser-offload.test.mjs test/monolith-text-anchors.test.mjs test/testing-notes-index.test.mjs` | 83/83 зелёных | выполнено лично, воспроизводит заявление автора |
|
||
| `node scripts/check-docs.mjs` | «Documentation checks passed (7 files, 12 external links)» | выполнено лично (не обязателен — diff не трогает `src/**`, но чисто) |
|
||
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «Исполняемого frontend-диффа нет… Browser-smoke этим диффом не выбираются» | выполнено лично — корректный ответ «нечего выбирать», диф не трогает `src/**` |
|
||
| `npm run inventory` | строка `mutation browser guards 200/200` присутствует и совпадает с заявленным пределом | выполнено лично |
|
||
| `npm run lint:unused` | «мёртвого кода нет… все числа равны базе» (без свежей сборки `bundleBytes` не судится) | выполнено лично; расхождение `bundleBytes` в `inventory.mjs` (2569393 vs база 2567812) — предсуществующий артефакт, диф не трогает `dist/**`/`src/**`, см. «Не проверял» |
|
||
| `python -m pytest tests_backend` | не прогонялся | диф не трогает `custom_components/**/*.py` |
|
||
| golden/perf/E2E | не прогонялись | нет видимого рендера (`User-Visible: no`, `src/**` не тронут) |
|
||
|
||
### Прицельные негативные пробы (мутация/защита) — таблица «чем краснеет»
|
||
|
||
| AC | Чем доказан | Чем краснеет |
|
||
|---|---|---|
|
||
| AC2: 84 переведённых свидетеля реально ловят мутацию (не по исключению) | `node scripts/mutation-gate.mjs --id=<id>` лично прогнан на 7 из 84 id, взятых из разных `UNIT_GUARD_GROUPS` (`stairs-fixed-floor-still-navigates`, `device-hit-scroll-observer-disabled`, `summary-hide-unmounts-before-animation`, `form-kit-segment-breaks-words`, `room-gear-drag-reopens-settings`, `M-615-tile`, `marker-badge-position-forgets-touch`) | все 7: `<id>: заявленный тест покраснел на мутанте`, `поймано 1 из 1` — воспроизведено лично |
|
||
| AC2/AC3: лимит 200 краснеет при росте, предупреждает без разметки, молчит когда всё на месте | `browserGuardPolicy()` (`scripts/mutation-browser-policy.mjs`) вызвана лично с синтетическими данными | `overLimit` при 201/200 → `true`; `missingReasons` при недокументированном id → 1 запись; `staleReasons` при устаревшей записи в markdown → 1 запись; на реальном реестре (200/200, документация полная) все три — пусто/`false`. Проверено исполнением функции с тремя сценариями (см. лог сессии) |
|
||
| AC4: одна сборка бандла на шард для немутируемых-в-бандле патчей | `test/mutation-gate.test.mjs` «#659: browser-only mutations reuse one clean bundle unless their patch is bundled» — `mutantBundleStrategy`/`mutantPatchesNeedBundle` на синтетическом корпусе | зелёный юнит различает `'seed'`/`'build'`/`'none'` для патча в бандле, патча вне бандла и небраузерного гварда; интеграция `captureBundleSeed`/`restoreBundleSeed` внутри `runCleanGuards`/`runMutant` — **проверено чтением, не исполнением** (сквозной прогон нескольких мутантов одного шарда с измерением количества сборок не проводился: только 4 из 200 browser guards сейчас попадают в ветку `'seed'` — `smoke-guard-blind-to-tail`, `smoke-guard-forgets-to-register-pages`, `report-page-errors-skips-round-trip`, `benchmark-page-verdict-unwatched` — посчитано лично по реестру; риск малой площади посчитан приемлемым для этого рефакторинга) |
|
||
|
||
## Числовые факты дифа (сверка «одно число — один источник»)
|
||
|
||
- **284 → 200** browser guards. Проверено лично: `origin/dev` (`git worktree add
|
||
--detach /tmp/dev-check origin/dev`) даёт `dev browser guards: 284`; HEAD даёт
|
||
`mutation browser guards 200/200`. Разница 84 совпадает с числом id в
|
||
`UNIT_GUARD_GROUPS`. Число «262» из тела issue — снимок недельной давности;
|
||
реестр успел вырасти дальше до 284 к моменту реализации, это не расхождение
|
||
фактов, а движение времени (issue сам об этом предупреждает: «стало хуже»).
|
||
Источник числа один — `browserGuardPolicy()`, и `npm run inventory` и
|
||
`mutation-gate --check` читают его же.
|
||
- Общее число мутантов (1028) не изменилось диффом — перевод меняет `guard`
|
||
существующих id, не добавляет/убирает мутанты. Проверено лично.
|
||
|
||
## Находки
|
||
|
||
### Medium (в скоупе задачи) — AC5 «замер до/после» не закрыт
|
||
|
||
Issue, пункт 5 ожидаемого результата: «Замер до/после на полном реестре
|
||
(wall-time ночного `mutation-gate`) и на одном кандидате ревью — в issue».
|
||
В единственном комментарии передачи на ревью:
|
||
|
||
- «До» дано только для полного реестра: nightly run `36298676826` — wall-time
|
||
**54:18**, сумма шагов мутантов шести шардов **4:17:19**.
|
||
- «После» на полном реестре явно не завершено автором: «полный ручной run
|
||
`36316263355` на этой ветке запущен; ссылку и точные числа добавлю после
|
||
завершения». Проверено лично в момент ревью: `gh run view 36316263355` —
|
||
`status: in_progress`, ~32 минуты с запуска из ожидаемых часов. Числа в
|
||
issue так и не появились (комментариев по-прежнему один).
|
||
- Пары «до/после на одном кандидате ревью» нет вовсе — ни до, ни после. Автор
|
||
привёл только ссылку на Validate push (`36316250835`) без extракции
|
||
wall-time мутационного шага; отдельный workflow_dispatch Validate
|
||
(`36317254856`, тот же SHA, тот из системного промпта) показывает шесть
|
||
«Мутанты по диффу» job'ов по 1m15s–1m52s (сумма ≈ 9m22s) — но это моя
|
||
собственная реконструкция по логам CI, а не число, названное автором в
|
||
issue, и без «до»-пары для сравнения она не доказывает «на кандидате
|
||
ревью стало быстрее».
|
||
|
||
Задача явно требует эти цифры как часть Definition of Done — это единственный
|
||
способ убедиться, что оптимизация действительно даёт заявленный эффект
|
||
(«≈ −3–4 ч на полный реестр, −10–15 мин на каждый кандидат»), а не только
|
||
формально закрывает счётчик 200/200. High-находок нет, поэтому вердикт —
|
||
жёлтый: автору нужно дождаться завершения ручного полного прогона, вписать
|
||
итоговые числа «до/после» для нереестра и добавить пару «до/после» для одного
|
||
кандидата ревью в issue, прежде чем возвращаться на `S7-code-review`.
|
||
|
||
## Что проверено и корректно
|
||
|
||
- **Классификация (AC1).** `docs/testing-notes/mutation-browser-guards.md`
|
||
перечисляет все 200 оставшихся browser guard id по шести категориям с
|
||
обоснованием на уровне категории; суммы по категориям (4+4+26+45+36+85)
|
||
совпадают с итоговой строкой таблицы и с фактическим числом булитов,
|
||
посчитанным построчно (`awk` по файлу) — проверено лично. `documentedBrowserGuards()`
|
||
на реальном файле даёт ровно 200 уникальных id — совпадает.
|
||
Точечно перепроверил обоснование 5 id из самой крупной категории
|
||
(«Custom-element and HA browser lifecycle», 85 шт: `config-updated-event-ignored`,
|
||
`device-echo-keeps-local-noncanonical`, `namespace-loader-returns-english`,
|
||
`locale-failure-toast-dropped`, `discovery-reset-writes-a-copy`) — каждый
|
||
`because` называет конкретную браузерную зависимость (динамический импорт
|
||
чанка локали, полный цикл custom-element события, DOM-видимость тоста), и
|
||
категория совпадает с обоснованием. Дешёвого `node --test` эквивалента не
|
||
вижу ни для одного из пяти.
|
||
- **Перевод (AC2).** 84 id получили явный `node --test`-гвард
|
||
(`UNIT_GUARD_GROUPS`/`UNIT_GUARD_OVERRIDES`, `scripts/mutation-registry.mjs`).
|
||
Мутант не исключён и не удалён — `mutant.guard` заменяется, id и патч
|
||
остаются те же, значит принцип «убивается проверкой, не исключением» (#558)
|
||
соблюдён по конструкции. Лично прогнал 7 случайно выбранных id из разных
|
||
групп через `node scripts/mutation-gate.mjs --id=<id>` — все 7 поймали
|
||
мутацию («заявленный тест покраснел на мутанте», 1 из 1).
|
||
- **Правило и гейт (AC3).** Текст `PROCESS.md` §2.7 точно описывает поведение
|
||
кода: рост выше 200 красит (`overLimit` → `stale++` → код возврата 2), id без
|
||
разметки — предупреждение, а не отказ (`missingReasons` → `warned++`,
|
||
не влияет на код возврата) — оба пути воспроизведены лично на синтетических
|
||
данных через `browserGuardPolicy()`.
|
||
- **Одна сборка на шард (AC4).** Логика выбора стратегии (`'seed'`/`'build'`/`'none'`)
|
||
доказана юнитом автора и лично воспроизведённой синтетической пробой;
|
||
откат к прежнему поведению при `bundleSeed === null` сохранён построчно
|
||
(`mutantBundleStrategy` возвращает `'build'`, когда `seedAvailable` ложно —
|
||
то же самое, что старое `if (guardNeedsBundle) buildBundle(dir)`).
|
||
Очистка временной директории (`dropBundleSeed`) обёрнута в `finally` вокруг
|
||
всего пути выполнения `main()`, включая ранний `return 2` из
|
||
`runCleanGuards` — утечки временных директорий не будет ни на одном пути
|
||
выхода (прочитано и прослежено по всем `return`).
|
||
- **Трейлеры.** `Issue: #659`, `User-Visible: no` — корректно для
|
||
инфраструктурного изменения без видимого поведения; изменений в `CHANGELOG*`
|
||
нет, что и требуется при `no`.
|
||
- **Один источник числа.** «200» и «284» видны в нескольких местах
|
||
(`PROCESS.md`, markdown-таблица, `npm run inventory`, `mutation-gate --check`),
|
||
но все читают один и тот же `browserGuardPolicy()`/`MUTANTS` — источник один,
|
||
расхождения исключены структурно (проверено чтением).
|
||
- Диф не трогает `src/**`, `custom_components/**/*.py`, `dist/**` — верно
|
||
заявленный класс B+C, и по-инфраструктурному верно, что смоки/голден/бэкенд
|
||
не выбраны (`smoke-select.mjs` подтвердил это лично).
|
||
|
||
## Чего не проверял
|
||
|
||
- Полный `node scripts/mutation-gate.mjs --check` по всем 1028 мутантам (со
|
||
сверкой якорей патчей) — не прогонял: дорого и уже покрыто зелёным Validate
|
||
на этом SHA (job «Фронтенд: типы, юниты, мутанты, синхрон бандла» + шесть
|
||
шардов «Мутанты по диффу»). Положился на CI.
|
||
- Сквозной (end-to-end) прогон нескольких мутантов одного шарда с реальным
|
||
переиспользованием `bundleSeed` — не воспроизводил (см. таблицу «чем
|
||
краснеет», AC4): только 4 id вообще проходят по этой ветке, площадь риска
|
||
малая, логика выбора стратегии проверена изолированно.
|
||
- Нативный Windows `gate:small` — не мой инструмент, положился на отчёт автора;
|
||
расхождение (`dev-build.test.mjs`) названо автором как известный
|
||
platform-only артефакт, не связанный с этим диффом.
|
||
- `python -m pytest tests_backend`, golden, browser smokes, performance —
|
||
не запускал: диф не задевает их входы (`custom_components/**/*.py` не
|
||
тронут, `src/**` не тронут), `smoke-select.mjs` и `check-docs.mjs`
|
||
подтверждают это лично, а не только по заявлению автора.
|
||
- Итоговый результат ручного полного прогона мутантов на этой ветке
|
||
(`36316263355`) — дождаться не мог, он ещё выполняется на момент вывода
|
||
вердикта; это и есть предмет находки Medium выше.
|
||
|
||
## Вердикт
|
||
|
||
Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 1 → в задаче
|
||
Документ: (публикуется шагом конвейера в docs/reviews/)
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `issue/659-mutation-smoke-guards`, коммит `6f8c2929afe4` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||
- Дерево материала: `4450383a731fca02ac12313f041a25edf68b0a44`
|
||
```
|
||
git log --all --format='%H %T' | grep 4450383a731f
|
||
```
|
||
- Тело issue: `04adecd63ada54d09bd302fb922e9982e7c99ad71ba8be8cbcbd796fc644dce7`
|
||
- Вердикт конвейера: `yellow` · High 0
|