17 KiB
CODE-REVIEW-481-r1
Issue: #481 — changed_mutants: журнал пойманных свидетелей вместо суда по диапазону
Этап: код-ревью (PROCESS.md §2.7) · трек: small (спека — в теле issue, спек-ревью — r1 жёлтый → r2 зелёный, оба принятых Medium закрыты текстом issue)
Заход: r1 · блокирующих циклов 0 из 2 (бюджет код-ревью, отдельный от спек-ревью)
SHA материала: df4104ab386fd339d9bf99d61573215b0bc869da (сверено git rev-parse HEAD непосредственно перед выводом)
Материал раунда
git log --oneline origin/dev..HEAD:
df4104ab docs: review document for #481 (класс C, коммит шага публикации спек-ревью r2 — не код автора)
f8ae546f test: бамп версии не меняет отпечатки мутантов ядра; бандл пересобран
c2913b28 docs: refresh screenshot source fingerprint
52aa3323 docs: журнал свидетелей в TESTING.md
97aabddc ci: журнал пойманных свидетелей для changed_mutants
Верхний коммит (df4104ab) добавляет только docs/reviews/SPEC-REVIEW-481-r2.md —
это собственный артефакт конвейера (публикация принятого спек-ревью), не код автора
задачи; в код-ревью не разбирается. Четыре нижних коммита — предмет этого раунда.
Это первый заход код-ревью (циклов 0/2), разбор полный.
Диапазон origin/dev...HEAD: 42 файла. Класс A (src/**, custom_components/**/*.py)
не тронут ни одним файлом — задача целиком в классах B (scripts/**, test/**,
.github/workflows/**) и C (docs/**), плюс класс D (dist/**,
custom_components/houseplan/frontend/**) как механическое следствие того, что
scripts/source-fingerprint.mjs входит в собственный корпус отпечатка сборки —
поэтому бамп корпуса потребовал пересборки трёх копий бандла и обновления
docs/images/screenshots.json, без единого изменённого пикселя (зафиксировано в
сообщении коммита c2913b28 и проверено мной запуском check-docs, см. ниже).
Трейлеры на всех четырёх коммитах: Issue: #481, User-Visible: no — верно,
видимого поведения продукта нет; оба changelog не тронуты и не должны быть.
Как проверялось (гейты)
Зелёного Validate на df4104ab нет — прогнал дешёвые гейты сам, на этом SHA:
| Гейт | Команда | Результат |
|---|---|---|
| Типы | npx tsc --noEmit |
ok, без ошибок |
| Юниты | npm test |
2163 pass / 0 fail / 1 skipped (2164 всего) |
| Сборка + 3 копии бандла | npm run bundle:sync |
бандл собран, custom_components/…/frontend и demo/srv/assets синхронны, git status после — пусто |
| Бюджет бандла | npm run bundle:budget |
ok, initial View 288573 Б (потолок 289500±2000); предупреждение о запасе 12493 Б — долг #367, предсуществующий, этим диффом не создан |
| Документация/скриншоты | node scripts/check-docs.mjs |
Documentation checks passed (7 files, 12 external links) |
| Малый гейт целиком | node scripts/gate-small.mjs |
все 6 шагов ok; smoke-select подтвердил: «Исполняемого frontend-диффа нет (src/**/*.ts не тронут)» — браузерные смоки этим диффом не выбираются, гонять нечего |
| Провенанс коммитов | node scripts/validate-commit-provenance.mjs |
exit 0 |
src/** диффом не задет — правило «check-docs обязателен, если диффа касается src/**»
формально не применяется, но я всё равно прогнал его из-за самоссылки корпуса
(source-fingerprint.mjs есть в fingerprintFiles()) — зелёный.
Не прогонял и почему:
- browser smokes (
demo/smoke_*.mjs) —smoke-selectпо диффу не выбрал ни одного: фронтенд (src/**) не тронут, смоки гоняют собранную карточку, а не скрипты гейта. npm run golden:verify— диффом не меняется ни рендер, ни геометрия, ни стили.python -m pytest tests_backend -q—custom_components/**/*.pyне тронут. Попутно (не как обязательный гейт, а как часть интеграционной проверки AC2 ниже) наткнулся наFAIL … python3 -m pytest … No module named pytest— это дыра окружения ревьюера (pytest не установлен), а не регресс диффа: соответствующий мутант к #481 отношения не имеет и не входит в реестр этой задачи.- инварианты модели (
npm run invariants) — диффом не задеты рёбра комнат, толщина,layout,marker.space,open_spans. - perf-профили — не названы в AC, чувствительные пути не тронуты.
AC → доказательство → чем краснеет (PROCESS.md §2.7, «таблица красноты»)
| AC | Чем доказан | Чем краснеет (мутация/негативная проба, результат) |
|---|---|---|
| AC1 (отпечаток версионно-неизменен, но чувствителен к правке патча/гарда/объявления, CRLF-нейтрален) | node --test --test-name-pattern="#481 AC1" test/mutation-gate.test.mjs |
Свидетель ledger-version-sensitive (патч убирает вызов normalize): node scripts/mutation-gate.mjs --id=ledger-version-sensitive → тест покраснел, как обязан, поймано 1 из 1. Я вручную применил тот же патч к рабочей копии — #481 AC1 падает: AssertionError: бамп версии — не изменение свидетеля, хеши расходятся |
AC2 (--changed --ledger: совпавший отпечаток → пропуск, чужой/отсутствующий → в прогон) |
node --test --test-name-pattern="#481 AC2" test/mutation-gate.test.mjs (unit на splitByLedger) |
Unit доказывает саму функцию отбора; интеграционного CLI-теста на фикстуре, обещанного ТЗ («unit + CLI на фикстуре»), в диффе нет — см. находку Low ниже. Я воспроизвёл интеграцию вручную: посчитал реальный отпечаток мутанта ledger-version-sensitive через witnessFingerprint, положил его в журнал и прогнал node scripts/mutation-gate.mjs --changed=origin/dev..HEAD --ledger=<seed> на настоящем реестре → по журналу пропущено 1 (отпечатки совпали), к прогону 7 при 8 отобранных диффом — счёт verно, только помеченный мутант пропущен |
| AC3 (журнал пишется сразу после каждого пойманного; сбежавший не пишется; переживает битый/чужой журнал) | node --test --test-name-pattern="#481 AC3" test/mutation-gate.test.mjs |
Свидетель ledger-written-at-end-only (запись только после >1 записи): --id=ledger-written-at-end-only → поймано 1 из 1. Я применил патч вручную — тест падает на первой записи: ENOENT …/nested/ledger.json (файла ещё нет, потому что запись отложена) |
AC4 (--ledger без --changed → код 2, внятное сообщение) |
#481 AC4 (spawnSync реального CLI) |
Убрал вручную блок отказа в main() — тест падает: 1 !== 2 (без отказа код возврата другой) |
AC5 (validate.yml: restore по шарду с restore-keys, save if: always(), уникальный на прогон ключ, --ledger в команде) |
test/validate-workflow.test.mjs, тест «журнал свидетелей changed_mutants…» |
Убрал вручную if: always() из шага Сохранить журнал свидетелей — тест падает: красный или отменённый шард обязан сохранить уже пойманное |
AC6 (структурная реконструкция #480: отбор по диффу не менялся, сужение — только splitByLedger по отпечатку, версия-инвариантность — следствие AC1) |
ревью кода (метод согласован спек-ревью r2) | Прочитано: selectChangedMutants/guardFiles (#475) в диффе не изменены (сравнение git diff — ноль строк). Числа автора (134→2, 113 из 113) — иллюстрация, не пересчитывались; структурный вывод подтверждён чтением, не цифрами |
| AC7 (три негативных свидетеля, каждый «поймано 1 из 1») | node scripts/mutation-gate.mjs --id=<witness> по каждому из трёх |
ledger-records-escaped → 1/1, ledger-version-sensitive → 1/1, ledger-written-at-end-only → 1/1. Все три реально приводят к красному тесту при обратном патче (проверено запуском, не чтением) |
Примечание к AC7/поиску по ledger-records-escaped: у этого мутанта два патча
(один в main(), второй в splitByLedger). Патч в main() в изоляции тесты
#481 AC2/AC3 не красит (они не вызывают main()); ловит его именно второй
патч — расширение сравнения в splitByLedger (|| mutant.id in ledger.caught),
которое #481 AC2 красит немедленно. Проверил оба патча по отдельности и вместе,
итог совпадает с официальным прогоном — мутант пойман, находка не подтвердилась.
Находки
Нет High и Medium. Два Low, оба сняты без правки (в рамках лёгкого трека и корректности остального изменения не блокируют):
- Low. AC2 задокументирован в ТЗ доказательством «unit на функцию отбора +
CLI на фикстуре», но в диффе — только unit на
splitByLedger; CLI-интеграцияmain()(чтение журнала → отбор → запись) не покрыта отдельным тестом. Я воспроизвёл интеграцию вручную на реальном реестре (см. таблицу AC2 выше) и она работает верно, поэтому AC для этого раунда доказан исполнением, а не находка блокирует. Снимаю с записью: будущая правкаmain()(например, случайная перестановкаrun/skipped) не будет поймана ни одним из текущих тестов — стоит закрыть отдельным маленьким CLI-тестом на фикстуре при первой же следующей правке этого файла, не обязательно прямо сейчас. - Low.
scripts/source-fingerprint.mjs: надwithoutProductVersionостались рядом два JSDoc-блока — старый (описывающий прежнее инлайновое имя отбора, #245) и новый (#481); только второй, ближайший, будет прочитан инструментами документации, первый становится сиротой. ПлюсproductVersionэкспортирован, но нигде за пределами этого файла не импортируется — сейчас не используется extern. Ни то, ни другое не влияет на поведение или гейты; снимаю без правки, чисто косметическое.
Что проверено и корректно
witnessFingerprint: сортировка файлов детерминирована, JSON.stringify объявления мутанта включён в хеш, нормализация версии — через общийwithoutProductVersion(не дублирует логикуvisualFingerprint, аvisualFingerprintтеперь сам через неё же — рефактор без изменения поведения, отпечаток скриншотов обновился только из-за самоссылки корпуса, не из-за смены алгоритма).readLedger: отсутствующий/битый/чужой-схемой файл → пустой журнал («гонять всё») — три случая закрыты тестом AC3 и вручную перепроверены.recordCaught/splitByLedger: инкрементальная запись, сравнение именно отпечатка (не факта присутствия id) — оба свойства пойманы негативными пробами.main():--ledgerбез--changed— код 2;--ledgerс--changed— сужает уже отобранный по диффу набор, не расширяет; при пустом плане после журнала — явное сообщение и код 0, не ошибка.- Полный прогон (
mutation-gate.yml,--check,--id), локальный сценарий (pre-push-gate.mjs,--changedбез--ledger) — журнал не трогают, подтверждено чтением вызовов (grep -rn "mutation-gate.mjs"по workflow и scripts), совпадает с п.5 ТЗ «что не меняется». validate.yml:cache/restoreдо шага прогона,cache/saveпосле, сif: always(); ключ уникален наrun_id,restore-keys— префикс по шарду. Порядок шагов и присутствие обоих действий проверены и тестом, и чтением самого файла.- Трейлеры, разбиение на коммиты по классам (B/C/D),
User-Visible: noбез правок changelog — верно. - Ни один файл
src/**илиcustom_components/**/*.pyне тронут — гейты golden/smoke/backend/invariants обоснованно не требуются этим диффом.
Вердикт
Вердикт: зелёный · заход r1 · блокирующих циклов 0/2 · High: 0 · Medium: 0 → в задаче
Документ: docs/reviews/CODE-REVIEW-481-r1.md
Материал раунда
- Ветка:
issue/481-mutation-ledger, коммит0f36eb1c1cc6— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
0e080a48f396df7d234779dc0f41146e50cc2180git log --all --format='%H %T' | grep 0e080a48f396