Files
houseplan-card/docs/reviews/CODE-REVIEW-659-r1.md
T
2026-09-27 17:08:06 +03:00

19 KiB
Raw Blame History

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/)


Материал раунда

  • Ветка: issue/659-mutation-smoke-guards, коммит 6f8c2929afe4 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 4450383a731fca02ac12313f041a25edf68b0a44
    git log --all --format='%H %T' | grep 4450383a731f
    
  • Тело issue: 04adecd63ada54d09bd302fb922e9982e7c99ad71ba8be8cbcbd796fc644dce7
  • Вердикт конвейера: yellow · High 0