17 KiB
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— jobchangesдействительно экспортирует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. Готово к разработке.
Материал раунда
Материал раунда
- Ветка:
dev, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Якоря снять не удалось: ветки задачи нет, материал читался по
dev.