docs: review document for #475

Issue: #475
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-06 11:09:53 +00:00
parent f52b2449ea
commit 2a7e09ee39
+178
View File
@@ -0,0 +1,178 @@
# SPEC-REVIEW — issue #475 · заход r2
**Тема:** `mutation-gate --check` не отличает живого свидетеля от мёртвого —
добавить отбор мутантов по диффу («гард») и запуск на каждом пуше.
**Трек:** лёгкий (`small`). ТЗ живёт в теле issue. Файла в `docs/specs/` нет
и не должно быть.
**Класс изменения:** B (гейты и инструменты) — `scripts/mutation-gate.mjs`,
`test/mutation-gate.test.mjs`, `.github/workflows/validate.yml`. Ни одного
файла класса A не затронуто.
Это второй заход ревью ТЗ. Первый (r1, документ
`docs/reviews/SPEC-REVIEW-475-r1.md`, коммит `f52b2449`) дал жёлтый вердикт:
1 Medium в скоупе, 0 High. Бюджет циклов лёгкого трека — 2, зелёный вердикт
бюджет не тратит (#227), поэтому на входе в r2 израсходован 1 из 2.
## Материал этого раунда
Разбор — по дельте (PROCESS.md §2.10), не заново. Предмет — изменения тела
issue между комментарием «ТЗ готово» (2026-09-06T10:54:23Z, материал r1) и
текущим телом issue на момент этого раунда.
Спецификация лёгкого трека живёт в теле issue, а не в файле репозитория, и
`git diff <SHA>..HEAD` к ней неприменим в принципе — тело issue не версии
git. Документ r1 сам это фиксирует в блоке «Материал раунда»: «ветка `dev`,
коммит `` — ... Якоря снять не удалось: ветки задачи нет, материал читался
по `dev`». Поле SHA там пустое не потому, что кто-то забыл его заполнить или
подменил значение перед выводом (это и было бы находкой по §2.10), а потому,
что на стадии ревью ТЗ лёгкого трека веток и коммитов задачи ещё не
существует — код не написан. Отмечаю это как наблюдение по механике шаблона
документа, а не как находку против самого ТЗ: дельта тела issue
устанавливается надёжно и без git SHA — через точную цитату исходного текста
в документе r1 (раздел «Находки», кавычки контракта и AC-таблицы) и явный
комментарий владельца «Medium исправлен» (2026-09-06T11:02:13Z), построчно
перечисляющий, что изменено. Я сверил оба источника с текущим телом issue
дословно (см. ниже) — расхождений между заявленным и фактическим текстом нет.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| Medium: извлечение файлов гарда ограничено суффиксами `.mjs`/`.test.mjs` — токен `tests_backend/test_ha_frontend_registration.py` (`.py`) файлом гарда не считался, 4 бэкенд-мутанта `frontend-registration-*` выпадали из отбора | Список суффиксов расширен до `.mjs`, `.test.mjs`, **`.py`** | Тело issue, раздел «1. Отбор по диффу…»: «все токены команды, оканчивающиеся на `.mjs`, `.test.mjs` **или `.py`**… Бэкенд-мутанты (`frontend-registration-*`... покрываются наравне с фронтендовыми» |
| Medium: триггер job — только `frontend == 'true'`; паттерн `frontend` в `changes` не включает `tests_backend/` и `custom_components/houseplan/frontend_registration.py` (это `backend`) — диапазон, меняющий только эти файлы, не запускал job вовсе | Триггер расширен до `frontend == 'true' \|\| backend == 'true'` (плюс правка `mutation-gate.mjs`) | Тело issue, раздел «2. `--changed` гоняется на каждом пуше»: «запускается при `frontend == 'true'` **или `backend == 'true'`** или изменении `scripts/mutation-gate.mjs` — бэкенд-мутанты патчат `.py` и охраняются pytest, и дифф только по `custom_components/…/frontend_registration.py` и `tests_backend/` даёт `backend=true` без `frontend=true`» — то есть новый триггер прямо называет прежде пропущенный случай |
| (следствие находки) отсутствовал AC, доказывающий закрытие именно этого сценария | Добавлен **AC7** | Таблица AC, строка: «AC7 \| Воспроизведение находки ревью: дифф только из `custom_components/houseplan/frontend_registration.py` и `tests_backend/test_ha_frontend_registration.py` отбирает все четыре `frontend-registration-*` мутанта \| unit по реальному реестру» |
| (сопутствующее) job не ставил Python-зависимости — без них бэкенд-гварды (`pytest`) неисполнимы даже при верном отборе | В job добавлена установка Python и `tests_backend/requirements.txt`, тем же способом, что в `mutation-gate.yml` | Тело issue, раздел 2: «Job ставит Python и `tests_backend/requirements.txt` так же, как `mutation-gate.yml`, чтобы pytest-гварды были исполнимы» |
| (сопутствующее) граница по фикстурам называла только `test/fixtures/*`, хотя бэкенд-мутанты получили свою фикстурную директорию | Граница расширена на `tests_backend/fixtures/*` | Тело issue, раздел 1, последнее предложение: «Фикстуры гардов (`test/fixtures/*`, `tests_backend/fixtures/*`) по команде не выводятся и отбором не покрываются» |
## Как проверялась дельта
Не поверил формулировке фикса на слово — перечитал реальную инфраструктуру,
на которую ссылается новый текст, потому что именно это и было предметом
находки r1 (техническая граница, а не догадка):
- `.github/workflows/validate.yml:281-282` — паттерн `backend`:
`^(custom_components/.*\.py$|tests_backend/|scripts/support-relay/|
pytest\.ini$)`. Подтверждено: и `custom_components/houseplan/
frontend_registration.py`, и `tests_backend/test_ha_frontend_registration.py`
попадают именно под него, а не под `frontend` — сценарий находки
воспроизводится буквально, и добавление `backend == 'true'` в триггер
закрывает его именно так, как описано в тексте;
- `scripts/mutation-gate.mjs:120-164` — реестр действительно содержит ровно
4 мутанта `frontend-registration-*`, `patch.file` каждого —
`custom_components/houseplan/frontend_registration.py`, `guard` каждого —
`python3 -m pytest tests_backend/test_ha_frontend_registration.py …`.
Число «четыре» в AC7 и в комментарии владельца не выдумано;
- `scripts/mutation-gate.mjs:6800-6802` — `selectChangedMutants` на сегодня
фильтрует **только** по `patch.file` (`m.patches.some((patch) =>
changed.has(patch.file))`), гард не смотрит вовсе. Это подтверждает
исходную формулировку проблемы («второй способ сгнить» — правка гарда без
правки патча — сегодня действительно не покрыт ничем) и то, что расширение
до guard-файлов — не косметика, а реальный новый путь отбора;
- `.github/workflows/validate.yml:181-190` — job `changes` действительно
экспортирует `base` и отдельно `range_base` (разные имена нарочно, для PR
и для `dev`), как и заявлено в тексте issue;
- `.github/workflows/mutation-gate.yml` — шаг «Установить backend test
dependencies» (`actions/setup-python@v7` + `pip install -r
tests_backend/requirements.txt`) существует и именно так и называется
«тем же способом» в тексте issue — не изобретённый паттерн;
- `scripts/mutation-gate.mjs` — ранний зелёный выход при пустой выборке
(`if (!selected.length) { …; return 0; }` в `main()`) существует уже
сегодня — AC5 (не тронут дельтой) остаётся доказуемым без новой логики,
как и было установлено в r1.
Гейты кода не гонялись — на стадии ревью ТЗ лёгкого трека продуктового кода
(в терминах этой задачи — кода гейта) ещё нет, дельта целиком лежит в тексте
issue; это симметрично тому, что зафиксировал r1.
## Находки
Нет. Единственная находка r1 (Medium, в скоупе) закрыта корректно и полно —
см. таблицу выше и её техническую верификацию. Новых противоречий,
недоказуемых AC или догадок, выданных за факт, дельта не вносит: оба
изменённых раздела и новый AC7 опираются на подтверждённую существующую
инфраструктуру (составы `backend`/`frontend` в `changes`, реальный реестр
мутантов, существующий приём установки Python-зависимостей в
`mutation-gate.yml`), а не на предположение.
Отдельно проверено, не сломал ли фикс что-то из уже принятого: расширение
списка суффиксов до `.py` не расширяет отбор ложно — правило по-прежнему
требует, чтобы токен совпадал с **существующим в репозитории файлом**
(AC3), поэтому произвольный `.py`-аргумент команды (флаг, шаблон) в отбор
не попадёт; добавление `backend` в триггер не запускает job на диффах, не
трогающих ни `frontend`, ни `backend` — правило OR только расширяет
множество, ранее пропущенное, и не сужает уже работавшую фронтенд-ветку.
## Унаследовано из r1
Ссылка: `docs/reviews/SPEC-REVIEW-475-r1.md`, коммит `f52b2449` (по дереву
`dev` на момент r1 — веток задачи нет, см. «Материал этого раунда» выше).
Принято без повторной проверки в этом раунде, так как дельта их не
затрагивает:
- обязательные для лёгкого трека разделы (§5) — проблема · контракт ·
AC с доказательством · откат — присутствуют, ни один не пропущен;
дельта добавила один AC (AC7) и расширила два раздела, структуру не
меняла;
- AC1, AC3, AC5, AC6 — формулировки не менялись дельтой, доказуемость,
установленная в r1, наследуется как есть (AC5 при этом попутно
переподтверждён чтением кода — см. выше, без изменения вывода);
- отказ от «отметки последнего доказательства» из исходного текста issue —
явное техническое решение с причиной, дельтой не затронуто;
- критерии лёгкого трека §5 (сложность ≤3, одна поверхность, без миграции
конфига, без нового UX-контракта, без влияния на touch) — дельта
добавляет один суффикс расширения и одно условие OR в YAML, порядок
сложности не меняет; вывод r1 «критерии не нарушены» остаётся в силе;
- продуктовый код не затронут ни одним файлом (класс A отсутствует) —
дельта тоже класса A не касается.
## Что проверено и корректно (сверх унаследованного)
- Дельта строго соответствует тому, что заявил комментарий владельца
«Medium исправлен»: все четыре перечисленных им изменения (суффикс `.py`,
условие `backend`, установка Python-зависимостей, AC7, расширение
границы фикстур) присутствуют в теле issue дословно.
- Выбранный путь закрытия («расширить, а не исключить», второй из двух
предложенных r1 вариантов) технически состоятелен: подтверждено чтением
`validate.yml` и `mutation-gate.yml`, а не принято на слово.
- Новый AC7 — не декоративный: он называет конкретное число («все четыре»),
которое совпадает с фактическим размером подмножества `frontend-
registration-*` в реестре, и явно завязан на реальные пути
(`custom_components/houseplan/frontend_registration.py`,
`tests_backend/test_ha_frontend_registration.py`), а не абстрактный
случай.
## Чего не проверял
- Не читал остальные 55+ мутантов реестра повторно на предмет суффиксов
гарда, отличных от `.mjs`/`.test.mjs`/`.py` — в r1 такая выборочная
проверка уже была сделана и её вывод («других суффиксов не встречено»)
дельтой не оспорен, поэтому не переделывал.
- Не запускал `node scripts/mutation-gate.mjs --changed=...` и не писал сам
тест — кода ещё нет, это предмет код-ревью (§2.7), как и в r1.
- Не оценивал фактическую длительность нового job на CI при непустой
выборке для бэкенд-гвардов (установка Python + pytest) — числовая оценка
осталась на уровне S2-комментария владельца («минуты»); проверка по факту
возможна только на код-ревью.
## Вердикт
Вердикт: зелёный · заход r2 · блокирующих циклов 1/2 · High: 0 · Medium: 0
Единственная находка предыдущего раунда закрыта полно и технически
корректно; дельта не вносит новых находок и не ставит под сомнение AC,
унаследованные из r1. Готово к разработке.
---
<!-- material-anchors: раздел ниже пополняется конвейером публикации -->
## Материал раунда
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `dev`, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Якоря снять не удалось: ветки задачи нет, материал читался по `dev`.