From 88e4cf50e338c0f53f008a30acca5b46a8611289 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 12:37:04 +0000 Subject: [PATCH] docs: review document for #472 Issue: #472 User-Visible: no --- docs/reviews/CODE-REVIEW-472-r2.md | 150 +++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 docs/reviews/CODE-REVIEW-472-r2.md diff --git a/docs/reviews/CODE-REVIEW-472-r2.md b/docs/reviews/CODE-REVIEW-472-r2.md new file mode 100644 index 00000000..04bd0c3c --- /dev/null +++ b/docs/reviews/CODE-REVIEW-472-r2.md @@ -0,0 +1,150 @@ +# 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 + ```