Files
houseplan-card/docs/reviews/SPEC-REVIEW-475-r2.md
T
2026-09-06 11:09:53 +00:00

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 — 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. Готово к разработке.


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


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

  • Ветка: dev, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Якоря снять не удалось: ветки задачи нет, материал читался по dev.