Files
houseplan-card/docs/reviews/CODE-REVIEW-475-r2.md
T
claude[bot]andCodex a4bcf834e6
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 1m16s
Проверка (CI) / Классификация изменённых файлов (push) Successful in 34s
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Мутанты по диффу: затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Skipped
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 1m11s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
docs: review document for #475
Issue: #475
User-Visible: no
2026-09-06 19:33:08 +03:00

19 KiB
Raw Blame History

CODE-REVIEW-475-r2

Issue: #475 · «mutation-gate --check не отличает живого свидетеля от мёртвого» Трек: small (лёгкий) · Этап: код-ревью · Заход: r2 · блокирующих циклов 0/2

Разбор — полный, не по дельте. Ветка приведена конвейером к dev до ревью: поверх позиции r1 (3ed8c920) легло 5 коммитов dev, итог — 0540901c. После ребейза это другой код (§7.2): полный разбор обязателен, а не сокращение по дельте §2.10, хотя раунд формально второй. Проверено: сами 5 коммитов dev (isometric overlay plates, ревью #471/#472, обновление скриншотов) не пересекаются ни по одному файлу с диапазоном задачи (git diff origin/dev...HEAD — 5 файлов, все из диапазона origin/dev..HEAD для этой ветки, см. «Скоуп») — риск скрытого конфликта, ради которого предписан полный разбор, не материализовался, но раздел «Унаследовано» всё равно фиксирует это явно, а не тихо.

Скоуп

git diff origin/dev...HEAD — 5 файлов, класс B (issue переиспользован, класса A нет ни одного файла):

  • .github/workflows/validate.yml — выход mutants у job changes, третий дизъюнкт в триггере changed_mutants, сама job (уже существовала с r1, фикс только про триггер);
  • scripts/mutation-gate.mjs — guardFiles(), расширенный selectChangedMutants(), 2 мутанта (без изменений с r1);
  • test/mutation-gate.test.mjs — AC1–AC3, AC5–AC7; AC7 переписан (фикс r1);
  • test/validate-workflow.test.mjs — контракт на YAML (AC4), дополнен проверкой третьего дизъюнкта и классификатора mutants (фикс r1);
  • docs/reviews/CODE-REVIEW-475-r1.md — артефакт предыдущего раунда, контенту не рецензируется повторно.

Как проверялось

Зелёного Validate на 0540901c нет — прогон не найден, гейты прогнаны самостоятельно. Объём соразмерен классу B без правок в src/** (issue #127, PROCESS.md §8):

Гейт Команда Результат
Typecheck npx tsc --noEmit зелёный
Юниты npm test 2083 pass / 0 fail / 1 skip
Юниты по затронутым файлам отдельно node --test test/mutation-gate.test.mjs · node --test test/validate-workflow.test.mjs 17/17 и 11/11 pass, все #475 AC*-тесты и новый AC4-тест зелёные
Сборка + синхрон бандла npm run build → cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js зелёный, байт-в-байт; git status после сборки чист — правка не задевает src/**, бандл не изменился
Новый код не добавляет any node scripts/no-new-any.mjs --base origin/dev --head HEAD «добавленных строк в src/**/*.ts: 0» — diff не касается src/**
Отбор браузерных смоков node scripts/smoke-select.mjs --base origin/dev --head HEAD «Browser-smoke этим диффом не выбираются… src/**/*.ts не тронут»
Реестр мутантов применим node scripts/mutation-gate.mjs --check 509/509 ok, включая оба новых мутанта, дублей якоря нет
Мутант changed-selection-ignores-guard-files node scripts/mutation-gate.mjs --id=changed-selection-ignores-guard-files чистый прогон ok → патч красит тест: «поймано 1 из 1»
Мутант changed-selection-matches-any-token node scripts/mutation-gate.mjs --id=changed-selection-matches-any-token то же, «поймано 1 из 1»
Самоприменение отбора к своему же диффу node scripts/mutation-gate.mjs --changed=origin/dev..HEAD «файлов в диффе 5, мутантов затронуто 4 из 509»: оба новых #475-мутанта плюс два не связанных с задачей (workflow-scan-hardcodes-the-list, typing-gate-stops-running — патчат тот же validate.yml); механизм отбирает сам себя корректно
AC7 напрямую по реальному реестру node -e "...MUTANTS.filter(patch.file==='.../frontend_registration.py')..." ровно 4 id, включая frontend-reload-notice-forgets-persisted-flag — тот самый пропущенный в Low r1
Процессный гейт node scripts/process-gate.mjs --range fc5973c2..HEAD «гейт пройден, предупреждений 0»; ветка issue/475-changed-mutants-on-push, трейлеры Issue:/User-Visible: no на всех 3 коммитах диапазона

Не прогонялось и почему:

  • python -m pytest tests_backend -q — окружения ревью .venv-backend/pytest не содержит (pip show pytest → not found), задокументированное ограничение (AGENTS.md «Environments», PROCESS.md «Backend»), не следствие задачи. Диф не трогает ни один .py-файл продукта; самоприменение отбора наткнулось на ту же нехватку pytest на чужом мутанте (typing-gate-stops-running, не из #475) — это подтверждает ограничение среды, а не пробел задачи. Оба мутанта #475 проверены индивидуально через --id=, их гварды — node --test, pytest не требуют.
  • check-docs.mjs, golden:verify, model-invariants, performance-профили, single-source-numbers — diff не трогает src/**, визуал, геометрию или пользовательские числа; неприменимо, не пропуск.

Находки

Блокирующих находок нет.

Low, снят без правки (новый, не из r1): AGENTS.md:398-400 перечисляет «Gate jobs, matching the actual validate.yml» поимённо (docs, provenance, process-gate, hacs, hassfest, frontend, smoke, golden, performance_smoke, backend) и не включает новую changed_mutants, хотя она блокирующая (без continue-on-error, проверено test/validate-workflow.test.mjs). Проверено чтением scripts/release-gate.mjs:9 (classifyValidateRuns) — критерий готовности релиза читает общий вывод workflow-прогона Validate через GitHub Actions API, а не имена отдельных job; красная changed_mutants красит весь прогон и блокирует релиз тем же путём, что и остальные job из списка. AC4 («job… входит в итоговый check») выполнен по факту — список в AGENTS.md чисто описательный для человека и не читается никаким скриптом (проверено: grep -rn "Gate jobs, matching" вне AGENTS.md пусто, синхронности с кодом никто не проверяет). Обновление списка не входило в DoD ТЗ (документация класса C не предмет этой задачи), и его отсутствие не создаёт функционального дефекта — только неполноту одного предложения в другом документе. Снимаю без правки.

Проверка AC — таблица «чем доказан / чем краснеет»

AC Доказано Чем Чем краснеет
AC1 unit test/mutation-gate.test.mjs #475 AC1 не защитный AC (сравнение множества)
AC2 unit + мутант #475 AC2 changed-selection-ignores-guard-files: «поймано 1 из 1» (прогнано лично)
AC3 unit + мутант #475 AC3 changed-selection-matches-any-token: «поймано 1 из 1» (прогнано лично)
AC4 unit (YAML-контракт) + чтение test/validate-workflow.test.mjs (новый тест #475 AC4) + чтение validate.yml:411-465 не защитный AC (расположение/текст job) — обычное сравнение ожидаемого текста с фактическим; тест прогнан лично, зелёный
AC5 unit + чтение #475 AC5 + guardNeedsBundle вызывается только для отобранных (чтение, не исполнение полного CI) не защитный AC — расположение раннего выхода
AC6 unit #475 AC6, реальный реестр не защитный AC — сравнение множества id
AC7 unit (полный, фикс r1) + независимая проверка #475 AC7 (теперь фильтр по patch.file, не по префиксу id) + прямой запрос к MUTANTS (см. таблицу гейтов) не защитный AC — сравнение множества id, но полнота множества (4, не 3) проверена мной по факту, а не на слово

Закрытие раунда r1

Находка r1 Чем закрыта Где видно
Medium — триггер changed_mutants не покрывал изменение scripts/mutation-gate.mjs (третий дизъюнкт контракта §2 отсутствовал и в if:, и в контрактном тесте) Классификатор получил выход mutants по точному пути ^scripts/mutation-gate\.mjs$; триггер job — frontend || backend || mutants; все 3 fallback-ветки полного прогона выставляют mutants=true validate.yml:185,241,265,275,287,414 (прочитано построчно) + test/validate-workflow.test.mjs тест #475 AC4 (прогнан лично, зелёный, проверяет все три места)
Low — тест AC7 фильтровал бэкенд-мутанты по префиксу id (frontend-registration-*) и пропускал 4-й (frontend-reload-notice-forgets-persisted-flag), хотя код отбирал все 4 Тест переписан на фильтр по patch.file === 'custom_components/houseplan/frontend_registration.py', что автоматически включает все 4 test/mutation-gate.test.mjs тест #475 AC7 (прогнан лично) + независимый запрос к MUTANTS в этом раунде подтвердил ровно 4 id, включая четвёртый

Обе находки закрыты кодом, а не заявлением автора — обе перепроверены самостоятельным прогоном/чтением в этом раунде.

Унаследовано из r1

Формально не применимо в смысле «сокращения объёма» — разбор в этом раунде полный (см. причину в шапке документа), каждый AC1–AC7 и обе находки r1 перепроверены заново на 0540901c, а не приняты на слово. Из документа docs/reviews/CODE-REVIEW-475-r1.md (SHA материала c0207af4) взяты без повторного вывода только два факта, не подлежащие пересмотру кодом: перечень файлов диапазона (сверен заново и совпал) и формулировки самих находок (процитированы дословно в разделе «Закрытие раунда r1» — их текст не переизобретается, а их закрытие доказано отдельно). Спор о продуктовом контракте (что должен покрывать триггер) на этапе ТЗ решён в SPEC-REVIEW-475-r1/r2 (жёлтый → зелёный) — это другой этап (§2.4, а не §2.7) и не переоткрывается здесь; код-ревью проверяло только соответствие уже принятому контракту, а не сам контракт.

Что проверено и корректно

  • guardFiles() — только токены [\w./-]+\.(mjs|py), не начинающиеся с -, существующие в репозитории; проверено на --test-name-pattern="magnet presses|x.mjs" (юнит #475 AC3, прогнан лично) — фрагмент шаблона presses|x.mjs" корректно не считается файлом гарда (несуществующий символ | вне допустимого набора).
  • selectChangedMutants(mutants, changedFiles, exists) обратно совместима: вызов без третьего параметра (как в CLI, mutation-gate.mjs:6891) использует дефолтный existsSync внутри guardFiles; старый тест #332 (шардирование) по-прежнему проходит без правок сигнатуры.
  • Мутанты #475 верно устроены: якорь changed-selection-ignores-guard-files собран из двух конкатенированных строк ровно затем, чтобы --check не находил его дважды (в коде и в определении) — подтверждено: --check даёт ok, дублей нет, реестр 507 → 509.
  • База диапазона в changed_mutants (PROVEN_BASE/BEFORE_SHA/ merge-base-фолбэк) побайтово идентична уже работающему блоку в job frontend (гейт «новый код не добавляет any», #387/#388) — не новая логика, а переиспользованная; сверено построчно чтением обоих мест.
  • Установка Python и Chromium в changed_mutants дословно повторяет шаги mutation-gate.yml (checkout → setup-node → npm ci → setup-python 3.14 → pip install -r tests_backend/requirements.txt → кэш Playwright → установка при промахе) — сверено построчно.
  • Триггер mutants — точный путь (^scripts/mutation-gate\.mjs$), не расширяет отбор ложно на соседние файлы; регресса для уже принятых frontend/backend-веток нет — они лишь дополнены третьим условием через ||, не заменены.
  • Трейлеры всех 3 коммитов диапазона (Issue: #475, User-Visible: no) — корректны для инфраструктурной правки без пользовательского эффекта; changelog не тронут, что верно при User-Visible: no; process-gate.mjs подтверждает нулём предупреждений.
  • Ни один другой файл репозитория не вызывает selectChangedMutants/ guardFiles — изменение сигнатуры не создаёт риска регрессии на стороне вызывающего кода (проверено grep по *.mjs/*.ts).

Чего не проверял

  • Полный pytest tests_backend (среда без .venv-backend) — см. «Как проверялось». Ограничение среды, не задачи.
  • Поведение changed_mutants job живьём в GitHub Actions (реальный запуск runner, кэш Playwright, реальный github.event.before на push-событии) — логика веток EVENT_NAME/REF/PROVEN_BASE проверена чтением и сверкой с идентичным уже работающим кодом job frontend, не отдельным прогоном workflow (это доступно только через реальный CI прогон на push, вне ревью).
  • Полноту списка «Gate jobs» в AGENTS.md как автоматизированного контракта — там нет читающего его скрипта; см. Low.

Вердикт

Зелёный. Обе находки r1 (Medium и Low) закрыты и проверены самостоятельно, а не на слово автора. Новых High/Medium находок нет. Единственная новая находка — Low вне блокирующего порога, снята без правки с объяснением, почему функционального дефекта нет. Полный разбор всех AC подтверждает, что задача решает заявленный сценарий: разрыв «неделя–релиз» для отбора мутантов по диффу закрыт, включая триггер по правке самого реестра и по бэкенд-мутантам.


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

  • Ветка: issue/475-changed-mutants-on-push, вершина 0540901cda486724a65ebcd9c4cb4f6d36bde906.
  • Диапазон разбора: origin/dev..HEAD (fc5973c2..0540901c), 3 коммита задачи.
  • Предыдущий заход: docs/reviews/CODE-REVIEW-475-r1.md, SHA материала c0207af4 (осиротевший после ребейза — ожидаемо, §2.10).

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

  • Ветка: issue/475-changed-mutants-on-push, коммит 3ed8c9204473 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 12506be1e214ea1080a510d34089b505127c22bb
    git log --all --format='%H %T' | grep 12506be1e214