17 KiB
CODE-REVIEW #620 · заход r1
Материал: 44ee23ee33b6b1b4c4370a78697b6da4cff81faf (единственный коммит поверх
origin/dev). Класс B (.github/workflows/**, scripts/**, test/**,
docs/TESTING.md); класса A нет — подтверждено (src/**,
custom_components/**/*.py не тронуты). Инфраструктурная задача, ускоренный
вход прямо в S7-code-review (AGENTS.md «Agent-neutral workflow») — корректно.
Скоуп
Issue #620 (аудит стоимости CI от 22.09, T2/D): ночной мутационный реестр
(mutation-gate.yml) гонял все 800+ мутантов каждую ночь, даже когда dev не
менялся 4 ночи подряд; диспатч changed_mutants (validate.yml) ставил Python
и Chromium на каждый шард с непустым планом, даже если гардам шарда браузер не
нужен. Правки:
mutation-gate.yml: зелёный полный прогон (агрегатор доказал все 6 шардов) оставляет маркер в кэше Actions (tree материала +workflow_sha); ночь по расписанию с тем же деревом и тем же workflow, маркер не старше 7 суток, переиспользует прогон и пишет «reused from run N», шарды не гоняются. Красный или неполный прогон маркера не оставляет — отказ по-прежнему заводит issue (#472). Ручной dispatch гонит полный реестр всегда.validate.yml(changed_mutants): план шарда (--plan-only) теперь называет окружение своих гардов (plan-browser=/plan-python=,scripts/mutation-environment.mjs); Python/pip и Chromium ставятся только шарду, чьи гарды их реально используют.- Перевод смок-гардов на
node --test(п.3 issue) — автор явно вынес из скоупа этой задачи и предложил отдельный issue. Это не находка, а заранее объявленное сужение скоупа: п.3 не входил ни в один AC (AC1–AC3 покрывают только пп.1–2), отдельный issue заводить не требуется.
Первый круг ревью этой задачи (issue упоминает зависший раунд prepare/dispatch
на 2a95cb1f — сбой пробуждения оркестратора, отслеживается отдельно как
#636/#555; к материалу и коду этого ревью отношения не имеет).
Как проверялось
Целостность материала. Сверил git hash-object всех изменённых файлов с
блоками blob … из раздела «Материал раунда» комментария автора — совпадение
байт-в-байт по всем 10 файлам, кроме mutation-registry.mjs (ожидаемо: реестр
рос от параллельно влившихся задач после ребейза на новый origin/dev).
Дешёвые гейты. Validate на этом точном SHA зелёный (run 35961600335,
headSha подтверждён через gh run view = 44ee23ee…) — tsc, npm test,
npm run build не перегонял. Дополнительно прогнал сам (не покрыто Validate,
т.к. относится к мутационному гейту):
node scripts/mutation-gate.mjs --check→ 888 ok, 0 FAIL (чистые прогоны всех гардов реестра, включая новые).- Каждый из 10 новых мутантов индивидуально:
node scripts/mutation-gate.mjs --id=<id>для всехnightly-reuse-ignores-tree,nightly-reuse-accepts-stale-marker,nightly-reuse-on-manual-dispatch,green-marker-without-green-aggregator,nightly-reuse-decision-error-skips-registry,browser-shard-skips-chromium,unread-plan-environment-skips-install,environment-misses-playwright-import,environment-misses-backend-files,environment-ignores-spawned-scripts— все дали «поймано 1 из 1». Тесты умеют падать. node scripts/process-gate.mjs→ «гейт пройден, предупреждений 0».node scripts/action-pins.mjs→ все сторонние Actions закреплены полным SHA.node scripts/smoke-select.mjs --base origin/dev --head HEAD→ «Исполняемого frontend-диффа нет», браузерные смоки не выбираются — верно, diff не трогаетsrc/**.
Не прогонял и почему:
npx tsc --noEmit/npm test/npm run buildсо сверкой копий бандла — Validate уже зелёный на этом точном SHA (#343), дифф не трогаетsrc/**, сверять бандл нечего.npm run golden:verify— User-Visible: no, рендер не менялся, смоки это же подтвердили («исполняемого frontend-диффа нет»).python -m pytest tests_backend -q—custom_components/**/*.pyне затронут.npm run invariants— геометрия не менялась.- Живой ночной прогон с переиспользованием маркера и живой dispatch шарда без
окружения — сам автор пометил как непроверенное и обоснованно: кэш Actions и
github.workflow_shaне воспроизвести локально, только на реальном расписании/дispatch после слияния и зеркалированияmutation-gate.ymlвmain. Логика (чистая функция, CLI, структура workflow-YAML построчно) проверена мной чтением и мутационными тестами — см. ниже.
Разбор по коду.
scripts/mutation-nightly-reuse.mjs:decideNightlyReuse— чистая функция, проверил все ветки отказа (не schedule, чужое дерево, чужой workflow, нет маркера/схемы/runId, маркер из будущего/старше 7 суток/нечитаемое время) — каждая покрыта юнит-тестом и мутантом. Проверено чтением и исполнением (node --test test/mutation-nightly-reuse.test.mjs, транзитивно через Validate)..github/workflows/mutation-gate.yml: прошёл all four job-и (material,mutants,evidence,green_marker,report) вручную по семантике GitHub Actionsif:(implicitsuccess()-обёртка при отсутствииalways()/failure()/cancelled()в выражении). Убедился, что:- при
reuse=truemutantsиevidenceоба получаютif=falseи не запускаются (не просто «material упал»); green_markerпишется только когдаmutants.result == 'success' && evidence.result == 'success'— безalways(), значит ещё и implicitsuccess()требует, чтобы самmaterialне упал;reportне срабатывает на переиспользованную ночь (reuse != 'true'в условии), но по-прежнему срабатывает на настоящий частичный/красный прогон (mutants/evidenceне skip, а реальный!= 'success') — регрессии к #472 нет. Проверено чтением, не исполнением (сам workflow на реальном раннере не гонял — это и есть заявленное «не проверял» выше).
- при
.github/workflows/validate.yml: условия установки (setup-python,pip install, кэш/установка Chromium) корректно завязаны наsteps.plan.outputs.{python,browser} == 'true'вместе сcount != '0';npm ciи сам прогон гардов от окружения не зависят — верно, это шаги, нужные любому непустому плану независимо от типа гардов. Дефолт${browser:-true}/${python:-true}на непрочитанную строку — сторона ошибки верная (лишняя установка, не пропущенная).scripts/mutation-environment.mjs: графguardRuntimeFiles— точки входа (файлы из строки гарда), объявленныеGUARD_INPUTSобёрток (только когда в строке гарда нет других явных файлов — иначеwrapperInputsне вызывается), путь-литералы только у точек входа (не у всех транзитивно импортированных файлов) → относительные импорты рекурсивно. Проверил на реальномscripts/backend-test-guard.mjs: даже если бы граф не дотянулся до объявленногоGUARD_INPUTS, сам файл обёртки содержитprocess.env.PYTHONв исходнике — второй независимый путь детекции. Эвристика документированно однобокая (недо-обнаружение → лишний круг задачи через красный чистый прогон, не пропущенная поломка) — направление ошибки безопасное, задокументировано в шапке модуля и вdocs/TESTING.md. Реестровый тест (#620 (реестр)) подтверждает калибровку на реальных 556 уникальных гардах: 315 без окружения,bare > guards.length/3— признак не выродился в «ставить всё».
AC — таблица из хендоффа, проверено самостоятельно
| AC | Чем доказано (перепроверено) | Чем краснеет (перепроверено) |
|---|---|---|
| AC1: ночь на неизменённом дереве пропускает шарды, «reused from run N» в отчёте | test/mutation-nightly-reuse.test.mjs (10 тестов, все прошли), test/mutation-gate.test.mjs «#620: пропуск ночи…» (проводка workflow построчно) |
4 мутанта индивидуально прогнаны — все «1 из 1» |
| AC1: отказ по-прежнему заводит issue (#472) | маркер только после mutants==success && evidence==success; report исключает только reuse=='true' |
green-marker-without-green-aggregator, nightly-reuse-decision-error-skips-registry — оба «1 из 1» |
| AC2: диспатч без браузерных гардов не ставит Chromium ни на одном шарде | test/validate-workflow.test.mjs «#620 AC2», mutation-gate.test.mjs реестровый тест на всех гардах |
browser-shard-skips-chromium, unread-plan-environment-skips-install, environment-misses-playwright-import, environment-misses-backend-files, environment-ignores-spawned-scripts — все «1 из 1» |
AC3: тесты на оба правила, docs/TESTING.md |
тесты выше существуют и падают на мутантах; docs/TESTING.md описывает оба правила согласованно с кодом (сверено построчно с реализацией: имя скрипта, имена флагов, срок 7 суток, формула графа) |
документация — не защитный AC |
Пустых третьих столбцов не осталось ни у одной защитной строки.
Что проверено и корректно
- Оба новых файла (
mutation-nightly-reuse.mjs,mutation-environment.mjs) документируют направление ошибки в шапке и держатся этого направления в коде — недоустановка окружения или ложный полный прогон невозможны без явного отказа скрипта решения (который сам по себе трактуется как «нужен полный прогон»). docs/TESTING.mdобновлён в двух местах, согласован с реализацией построчно (имена флагов, формула графа, срок годности маркера).- Трейлеры коммита:
Issue: #620,User-Visible: no— верно (CI-инфраструктура, видимого пользователю поведения нет), changelog не тронут — согласуется. demo/golden/baselines/**не тронут — доп. трейлерыRelease/Baseline-Reviewed*не требуются..github/workflows/**изменения не расширяютpermissions:ни одной job — сверено по диффу.- Note про зеркало
mutation-gate.ymlвmainи ожидаемое кратковременное покраснение preflight после слияния до зеркалирования — принято к сведению, это эксплуатационный шаг владельца, не дефект кода (прецедент #604/#636, логика воспроизведена верно:workflow_syncсравниваетorigin/mainсorigin/dev, не с веткой, поэтому на самой ветке preflight зелёный).
Находки
Нет ни одной High или Medium находки. Задача решает заявленный сценарий, инвариант «отказ всегда заводит issue» (#472) не сломан, окружение шардов не недоустанавливается ни в одном проверенном случае.
Чего не проверял
- Живой ночной прогон с реальным переиспусзованием маркера через кэш Actions —
локально не воспроизвести (кэш,
github.workflow_sha, реальное расписание). Первая живая проверка — вторая ночь после слияния и зеркалирования вmain. - Живой dispatch
changed_mutantsна реальном PR-диффе, где видно, какие именно шарды пропустилиsetup-python/Chromium в логах Actions. - Golden/perf-профили — дифф не рендерит и не меняет геометрию/производительность продукта.
Вердикт
Зелёный. AC1–AC3 доказаны автотестами, которые проверены на умение падать (все 10 новых мутантов индивидуально дали «1 из 1»); защитные AC имеют непустой столбец «чем краснеет»; Validate зелёный на точном материале; трейлеры и класс изменений верны; документация согласована с кодом.
Материал раунда
- Ветка:
issue/620-mutants-cost, коммит44ee23ee33b6— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
a02720092a4e4a4188d499cd0f761a840554af7bgit log --all --format='%H %T' | grep a02720092a4e - Тело issue:
a4faabe94f0f4a4c0c0b40302b5d8388d19bcb788fe4697dd9103f053fc87efe - Вердикт конвейера:
green· High 0