mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 12:18:51 +00:00
@@ -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`.
|
||||
Reference in New Issue
Block a user