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

202 lines
19 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-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).
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/475-changed-mutants-on-push`, коммит `3ed8c9204473` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `12506be1e214ea1080a510d34089b505127c22bb`
```
git log --all --format='%H %T' | grep 12506be1e214
```