# CODE-REVIEW-472-r2 Issue: #472 — «Полный мутационный прогон по расписанию падал дважды подряд без реакции: у отказа нет адресата». Лёгкий трек (`small`). Код-ревью, заход r2, блокирующих циклов израсходовано 1 из 2. ## Материал раунда - SHA материала: `57a4a0ed69d01f0e1bf710f44f287cdc32c1e8fd` (`git rev-parse HEAD`) - Предыдущий заход (r1): `docs/reviews/CODE-REVIEW-472-r1.md`, вердикт — жёлтый, найдена M1 (SHA отчёта берётся из `github.sha`, а для `schedule` это вершина `main`, не `dev`), проверено на `dec6c03e89af48f570bf60994b99aeacba0cb966`. - **Этот SHA (`dec6c03e…`) сегодня недостижим ни из одной ветки/тега** — проверено `git cat-file -t dec6c03e` (объект не найден) и `git log --all`. Ветка была ещё раз приведена к `dev` конвейером между r1 и r2 (тот же механизм, что уже осиротил `a8ea3b8a` между исходной реализацией и r1 — «material-anchors», PROCESS.md §7.2). Восстановил доверие к преемственности не по названию SHA, а по содержимому: коммит `e76f3909` («ci: отчёт об отказе расписания мутантов…», текущий предок делты) даёт **побайтово тот же `git diff --stat`**, что зафиксирован в «Скоуп диффа» CODE-REVIEW-472-r1.md (105/28/177/34/99/49 строк по тем же шести файлам) — содержимое, прорецензированное в r1, и содержимое под `e76f3909` — один и тот же текст, просто с новым SHA после ребейза. Дельта раунда — ровно один коммит поверх этого дерева: - `57a4a0ed ci: SHA отчёта mutation-gate — от чекаута dev, не github.sha (#472 r1)` — единственный коммит после `d77d1953` (публикация документа r1). - Трейлеры дельты: `Issue: #472`, `User-Visible: no` — верно, диапазон CI-only, changelog не тронут. - Зелёного `Validate` на `57a4a0ed` нет (см. системное примечание к заходу) — дешёвые гейты прогнаны мной, см. ниже. ## Дельта раунда ``` .github/workflows/mutation-gate.yml | 5 ++++- test/mutation-gate.test.mjs | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) ``` ```diff - SHA: ${{ github.sha }} run: | + # SHA — того дерева, которое чекаутили и гоняли (dev), а не + # github.sha: для расписания это вершина default-ветки main, и отчёт + # называл бы «dev @ » (ревью r1). Образец — process.yml. + SHA=$(git rev-parse HEAD) mkdir -p artifacts node scripts/mutation-gate-report.mjs \ --logs=artifacts/mutation-logs --shards=4 \ --run-url="$RUN_URL" --ref=dev --sha="$SHA" \ ``` плюс новый контрактный тест `#472 r1: SHA отчёта — от чекаута dev, а не github.sha`. Диапазон не тронул ничего за пределами `mutation-gate.yml` и одного тестового файла: ни `src/**`, ни `custom_components/**/*.py`, ни другие workflow. ## Закрытие раунда r1 | Находка r1 | Чем закрыта | Где это видно | |---|---|---| | **M1** — SHA в отчёте брался из `github.sha` (вершина `main` для `schedule`), а не из чекаутнутого `dev` | Строка `env: SHA: ${{ github.sha }}` убрана из блока `env`; вместо неё в теле шага `SHA=$(git rev-parse HEAD)`, выполняется **после** `actions/checkout@v7` с `ref: dev` (проверено чтением: шаг checkout идёт раньше в том же job `report`, `mutation-gate.yml:124-153`) | `.github/workflows/mutation-gate.yml`, шаг «Собрать отчёт» job `report`; подтверждено исполнением — новый тест «17 pass» при фиксе, и я вручную откатил строку на `${{ github.sha }}` в рабочей копии: тест падает (17 pass / **1 fail**), значит утверждение теста небутафорское | | **L1** — поиск открытого issue по заголовку (кириллица) не проверен вживую против реального GitHub API | Не код-находка, а решение: автор явно принял риск («Low — принято, понаблюдаем на первом реальном отказе») — сам r1-документ и автор согласились, что это не блокирует и альтернативы без сетевого доступа нет | Комментарий автора в issue от `12:17:55Z`: «L1 — принято, понаблюдаем на первом реальном отказе»; изменений кода не требовалось и не вносилось | Оба закрытия проверены не по заявлению автора: M1 — исполнением теста в обе стороны (падает без фикса, проходит с фиксом), L1 — это принятый риск с явной записью, а не пропуск. ## Унаследовано из r1 Всё, чего не касается делта (весь диапазон, кроме `env: SHA` и одного нового теста), наследуется из `docs/reviews/CODE-REVIEW-472-r1.md` (SHA материала `dec6c03e`, содержимое эквивалентно текущему `e76f3909` — см. сверку diff-stat выше) без повторной проверки: - **AC1** (concurrency-группы `schedule`/`workflow_dispatch` разные) — контрактный тест + чтение. - **AC2** (лог шарда — артефакт, `if: always()`, `PIPESTATUS`/`pipefail` сохраняет код выхода) — контрактный тест + чтение. - **AC3** (сбор `escaped`/`redGuards`/`unparsed`, дедуп, статус `missing`) — 8 юнитов + 3 мутанта задачи, каждый прогнан индивидуально «поймано 1 из 1» на реальном исполнении (изолированный git-worktree). - **AC4** (заголовок с маркером, тело — прогон/SHA/`--id=`) — юнит; замечание r1 (SHA неверный) устранено делтой этого раунда, сама структура тела не менялась. - **AC4b** (отдельные разделы `redGuards`/`unparsed`, ни один не превращается в `--id=`) — юниты + мутант `mutation-report-red-guard-as-mutant`, прогнан индивидуально. - **AC5** (job `report`: только `schedule` и не-успех, права `contents/actions/issues` полностью) — контрактный тест + чтение. - **AC6** (повторный отказ — комментарий, не второе issue) — контрактный тест на порядок вызовов `gh` + `jq`-фильтр воспроизведён локально на фикстуре автором ревью r1. - **AC7** (нет Telegram-секретов — не отказ) — контрактный тест + чтение условного шага. - **AC8** (`mutation-gate.yml` под сверкой `main`/`dev` наравне с `process.yml`) — контрактный тест + чтение `validate.yml`. - Плюмбинг артефактов (`upload-artifact` → `download-artifact` → путь, который читает `mutation-gate-report.mjs`) — прослежен логически в r1. - Откат («Одним коммитом») — не менялся этой делтой. Эти пункты дельта не задевает: делта — это одна переменная окружения и один тест поверх неизменного контракта. Причин перепроверять AC1-AC3, AC5-AC8 нет — код, который их доказывает, не тронут. ## Гейты этого раунда | Гейт | Команда | Результат | |---|---|---| | typecheck | `npx tsc --noEmit` | зелёный, 0 ошибок | | unit (полный набор) | `npm test` | 2107 тестов, pass 2106, fail 0, skipped 1 | | unit (точечно, дельта) | `node --test test/mutation-gate.test.mjs` | 18/18 pass; при ручном откате `SHA=$(git rev-parse HEAD)` → `SHA=${{ github.sha }}` — 17 pass / **1 fail** (новый тест ловит регресс) | | build + сверка 3 копий бандла | `npm run build` (tsc+rollup), затем `node scripts/bundle-sync.mjs` | build зелёный; `bundle-sync` не оставил diff в `custom_components/houseplan/frontend` и `demo/srv/assets` — три копии уже совпадали | | provenance | `node scripts/validate-commit-provenance.mjs 57a4a0ed~1..57a4a0ed` | exit 0 | | smoke-select | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «Исполняемого frontend-диффа нет… смоки этим диффом не выбираются», тронуто файлов: 7 — браузерные смоки не нужны | Диапазон `origin/dev..HEAD` (не только делта раунда) не трогает `src/**` — `check-docs`, `no-new-any`, golden, backend pytest, model-invariants, perf-профили и полный (255) мутационный прогон неприменимы по тем же причинам, что и в r1 (см. «Чего не проверял» там); делта этого раунда их тем более не касается. ## Что проверено и корректно - M1 закрыта корректно: `SHA=$(git rev-parse HEAD)` выполняется после `checkout … ref: dev` в том же job — вычисленное значение соответствует дереву, которое реально гоняли мутанты, а не вершине `main`. - Новый тест умеет падать: проверено вручную откатом строки в рабочей копии (17/18 → 1 fail), а не «Verified» без исполнения. - Трейлеры дельты (`Issue: #472`, `User-Visible: no`) верны; changelog не тронут — корректно для CI-only правки. - Дешёвые гейты (typecheck/unit/build+3 копии бандла/provenance) зелёные на `57a4a0ed`. - Унаследованные AC1-AC8+AC4b не задеты делтой — перепроверка не требовалась. ## Чего не проверял - Живое поведение `gh issue list --search` против реального API (L1) — не проверялось ни в r1, ни сейчас: принятый риск, не в скоупе делты. - Полный (255) мутационный прогон — предрелизный гейт, не гейт ревью. - `check-docs`, golden, backend pytest, browser-smokes, perf-профили — diff не трогает `src/**`/Python/визуал/perf-путь; `smoke-select` подтверждает отсутствие исполняемого frontend-диффа. - Зеркалирование `mutation-gate.yml` в `main` после мержа — ручной постмерж-шаг вне этого коммита, как отмечено в r1. ## Вердикт Зелёный. Единственная находка r1 (M1) закрыта по существу и подтверждена исполнением (тест падает без фикса, проходит с фиксом); L1 — принятый автором риск без кода. Дельта раунда минимальна и не затрагивает ничего, что требовало бы переоткрытия AC1-AC8. Дешёвые гейты зелёные. --- ## Материал раунда - Ветка: `issue/472-mutation-gate-report`, коммит `57a4a0ed69d0` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `f6df55ea4b6305533915ed7682917d70322708e3` ``` git log --all --format='%H %T' | grep f6df55ea4b63 ```