19 KiB
CODE-REVIEW-510-r3
- Issue: https://github.com/Matysh/houseplan-card/issues/510
- Материал:
615181050b9ba4d4568834d56ac5a0ed97ca804d(рабочая копия на нём;git rev-parse HEADсверен перед выводом) - Заход: r3 · блокирующих циклов израсходовано 2 из 4 (зелёный вердикт цикла не образует, #227)
- Ветка:
issue/510-mutants-on-candidate-and-review-gate - Предыдущий раунд: CODE-REVIEW-510-r2 (жёлтый, Medium M1(r2)), материал
00130e08aa247cd8796a99125deb7f9b5131bfa1 - ТЗ:
docs/specs/510-mutants-on-candidate-and-review-waits-validate.md(без изменений в этой дельте), ревью ТЗ зелёное на r4 (docs/reviews/SPEC-REVIEW-510-r4.md)
Скоуп раунда
Ребейза между r2 и r3 не было: git merge-base --is-ancestor 00130e08 HEAD
подтверждает прямое потомство, git log --oneline 00130e08..HEAD даёт ровно
два коммита сверху:
61518105 test: the real waitValidate is exercised against a cancelled dispatch
8acbd333 docs: review document for #510
git diff 00130e08..HEAD --stat (без docs/reviews/**):
scripts/merge-candidate.mjs | 8 +-
scripts/mutation-gate.mjs | 11 ++
test/merge-candidate.test.mjs | 39 ++++
3 files changed, 55 insertions(+), 3 deletions(-)
Предмет дельты — ровно и единственно закрытие M1(r2): половина фикса
M1(r1) в scripts/merge-candidate.mjs::waitValidate (отменённый dispatch не
должен читаться как красный) была исправлена в коде, но не имела ни одного
теста или мутанта — реальная реализация waitValidate внутри realOps()
никогда не вызывалась ни в одном тесте проекта. Разбор этого раунда
сосредоточен на том, закрыт ли этот пробел; AC1–AC6 дельта не задевает и
наследуются из r1/r2 без повторной проверки (раздел ниже).
Как проверялось
Зелёного Validate на 61518105 нет — проверено лично:
gh run list --repo Matysh/houseplan-card --workflow validate.yml --commit 61518105 …
вернул [] (прогона на этом SHA не существует вовсе). Гейты прогнаны лично.
| Гейт | Команда | Результат |
|---|---|---|
| typecheck | npx tsc --noEmit |
зелёный, без вывода |
| unit (полный набор) | npm test |
2423 тестов, 2422 pass, 1 skip (предсуществующий #281, не в этом диффе), 0 fail |
| build + бандл (2 committed-копии) | npm run build затем cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js |
сборка зелёная; cmp — идентичны; git status --porcelain после сборки пуст — рабочая копия и так была на актуальном бандле |
| смок-селектор | node scripts/smoke-select.mjs --base 00130e08 --head HEAD |
«Исполняемого frontend-диффа нет (src/**/*.ts не тронут). Browser-smoke этим диффом не выбираются — выбирать нечего. Тронуто файлов: 4» — браузерные смоки не запускались, основание — вывод инструмента, а не тема диффа |
| check-docs / golden / инварианты / pytest / perf | не прогонялись | дельта не трогает src/** ни custom_components/**/*.py (подтверждено тем же diff --stat и выводом smoke-select) |
новый мутант merge-trusts-cancelled-dispatch |
node scripts/mutation-gate.mjs --id=merge-trusts-cancelled-dispatch |
поймано 1 из 1 (лично) |
пять мутантов r1 + один мутант r2 (review-returns-task-on-cancelled-dispatch) |
не перепрогонялись | делта r2→r3 не трогает их patches/guard — новая запись реестра добавлена, старые не изменены (git diff выше показывает только вставку блока) |
Демонстрация: тест умеет падать (независимая проверка, не со слов автора)
Ручной откат фикса, тот же приём, что в r2, — на текущем коде:
# вручную снял фильтр в scripts/merge-candidate.mjs:
# const runs = all.filter((x) => (!event || x.event === event) && x.conclusion !== 'cancelled');
# → const runs = all.filter((x) => (!event || x.event === event));
node --test test/merge-candidate.test.mjs
Результат: 10 pass, 2 fail — оба новых теста краснеют независимо друг от друга:
not ok 11 - #510 r2 M1: realOps.waitValidate ignores a cancelled dispatch and follows its replacement
expected: { result: 'green', url: 'https://run/3' }
actual: не тот исход (продолжает опрашивать не туда/красит раньше)
not ok 12 - #510 r2 M1: realOps.waitValidate with only a cancelled dispatch reports missing after the appear window, never red
expected: 'missing'
actual: 'red'
Файл восстановлен, git status --porcelain пуст. Это прямой контраст с r2,
где тот же ручной откат при том же полном npm test не ронял ни одного
теста из 2420 — асимметрия закрыта: обе ветки (green-цепочка через
замену и одинокий cancelled без замены) теперь имеют падающего свидетеля,
не только мутант из реестра.
Штатный мутант merge-trusts-cancelled-dispatch
(scripts/mutation-gate.mjs:8161) патчит ровно ту же строку тем же
find/replace, гвард — node --test test/merge-candidate.test.mjs,
прогнан лично: поймано 1 из 1.
Находки
Новых High/Medium/Low не найдено. Дельта узкая, точечная и делает ровно то, что требовала находка M1(r2) — не больше и не меньше.
Отдельно проверено, что фикс не меняет поведение продакшен-пути: единственный
вызов realOps({ repo, token }) в scripts/merge-candidate.mjs:242 не
передаёт exec, поэтому в реальном запуске используется тот же sh
(spawnSync-обёртка), что и до этой правки — инъекция существует только для
теста. Проверено чтением (grep -rn "realOps(" ... — единственный
продакшен-вызов без параметра exec).
Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
M1(r2): половина фикса M1(r1) в scripts/merge-candidate.mjs::waitValidate не защищена ни одним тестом/мутантом — реальная реализация не вызывалась ни в одном тесте проекта, ручной откат фильтра оставлял весь npm test (2420/2420) зелёным |
realOps() получил инъекцию exec (по умолчанию sh, поведение продакшена не изменилось — см. выше); test/merge-candidate.test.mjs:248-278 — два новых теста гоняют реальный waitValidate через скриптованные ответы gh run list, не подменяя саму функцию; мутант merge-trusts-cancelled-dispatch добавлен в scripts/mutation-gate.mjs:8161-8171 |
git diff 00130e08..HEAD -- scripts/merge-candidate.mjs scripts/mutation-gate.mjs test/merge-candidate.test.mjs; независимый ручной откат в этом раунде — 2/12 теста падают (было 0/2420 в r2); node scripts/mutation-gate.mjs --id=merge-trusts-cancelled-dispatch → «поймано 1 из 1» |
Унаследовано из r1/r2
Принято без повторной проверки в этом раунде — дельта
(scripts/merge-candidate.mjs +4/-4 внутри realOps, scripts/mutation-gate.mjs
+11, test/merge-candidate.test.mjs +39) не задевает ни один из перечисленных
предметов. Источники: CODE-REVIEW-510-r1.md (материал cbece6324f83b3400364fcd1a8eb1fa3b321a3ff)
и CODE-REVIEW-510-r2.md (материал 00130e08aa247cd8796a99125deb7f9b5131bfa1).
- AC1 (обычный push не запускает
changed_mutants; dispatch/PR/schedule/кандидат беты — запускают) — доказаноtest/classify-changes.test.mjs+test/validate-workflow.test.mjs, мутантmutants-run-on-every-push(«поймано 1 из 1» в r1). Файлы дельтой r2→r3 не тронуты. - AC2 (гейт находит/запускает/красный/таймаут/несовпадение SHA, включая
cancelled) — доказаноtest/validate-gate.test.mjs, мутантыreview-starts-on-red-validate/review-trusts-push-run-without-mutants/review-returns-task-on-cancelled-dispatch(последний подтверждён в r2).scripts/validate-gate.mjsдельтой r2→r3 не тронут вовсе. - AC3 (слияние ждёт dispatch с мутантами, не push) — доказано
test/merge-candidate.test.mjs, мутантыmerge-waits-push-run-without-mutants/merge-pushes-unvalidated-candidate. Логика диспатча и ожидания вне участкаcancelled-фильтра дельтой не изменена. - AC4 (мутанты протокола пойманы штатным раннером) — шесть мутантов r1/r2
(
mutants-run-on-every-push,review-starts-on-red-validate,review-trusts-push-run-without-mutants,merge-waits-push-run-without-mutants,merge-pushes-unvalidated-candidate,review-returns-task-on-cancelled-dispatch) не перепрогонялись — ихpatches/guardне менялись (git diffпоказывает только добавление новой записиmerge-trusts-cancelled-dispatch). Седьмой мутант этого раунда прогнан лично и подтверждён выше. - AC5 (доки описывают место мутантов и правила хендоффа) — сверены
построчно в r1, дельта r2→r3 не касается
PROCESS.md/AGENTS.md/docs/TESTING.md/docs/specs/README.md(толькоscripts/**иtest/**). - AC6 (
src/**не тронут, perf/touch/UX не задеты) — верно и для этой дельты:git diff 00130e08..HEAD --statне содержит ни одного файлаsrc/**, подтверждено также выводомsmoke-select.mjs. - Асимметрия дизайна между
validate-gate.mjsиmerge-candidate.mjs— признана обоснованной в r1, дельта её не меняет. - Замечание r2 о риске дублирования одинаковой формулы фильтра в двух файлах
(
validate-gate.mjsиmerge-candidate.mjs) — это было наблюдение о будущем рефакторинге, не находка с блокирующим статусом; r2 не потребовала унификации, только теста на существующий код. Дельта r3 её не адресует и не обязана была: предмет M1(r2) — отсутствие свидетеля, а не дублирование кода.
Что проверено и корректно (в этом раунде)
scripts/merge-candidate.mjs: инъекцияexec = shв сигнатуруrealOpsконсервативна — единственный продакшен-вызов (mergeCandidateвнутриmain(), строка 242) не передаётexec, значит в реальном запуске используется прежнийsh/spawnSync; поведение продакшена не изменилось (проверено чтением всех вызововrealOps(в дереве).test/merge-candidate.test.mjs:239-246(scriptedExec): фейковыйexecотвечает только наgh run list, на любой другой вызов бросает — новые тесты не могут случайно замаскировать вызовgit/gh workflow runреальной командой; поведение проверено запуском (12/12 pass). Оба теста используют управляемыеsleep/now(счётчикclock), поэтому не спят по-настоящему и не зависят от таймингов среды.- Тест 1 (
ignores a cancelled dispatch and follows its replacement) проверяет обе стороны контракта одним прогоном: чтоcancelledне даёт зелёный/красный результат по первому ответу (иначеgh.calls()было бы 1, а не 3) и что итоговый green берётся изreplacement, а не изcancelled. - Тест 2 (
reports missing after the appear window, never red) закрывает сценарий «замены не будет вовсе» — обязательный по тексту AC2/M1(r1) («ждём замену, а не красим») симметрично для пути слияния. - Новая запись реестра
merge-trusts-cancelled-dispatch(scripts/mutation-gate.mjs:8161-8171):find/replaceсовпадают один-в-один со строкой кода на момент проверки,guardзапускает правильный тестовый файл, лично прогнана — красит при внесении мутации (поймано 1 из 1) и не красит на чистом дереве. - Двойное независимое воспроизведение (ручной откат + штатный мутант) даёт одинаковый вывод: регрессия ловится, причём сильнее, чем закрывает минимальный порог §2.7 (для чистых юнитов достаточно ручного прогона со снятой защитой — здесь есть и он, и зарегистрированный мутант).
Чего не проверял и почему
check-docs.mjs, golden, браузерные смоки, инварианты модели,pytest tests_backend, perf-профили — дельта не касаетсяsrc/**,custom_components/**/*.pyили геометрии;smoke-select.mjsподтверждает это инструментально, а не по теме диффа.- Шесть мутантов протокола, подтверждённых в r1/r2 — не перепрогонял: дельта
не меняет ни их
patches, ниguard-файлы (диф показывает только вставку новой записи). - Реальный прогон
merge-candidate.mjsпротив живого GitHub Actions — не воспроизводил (та же причина, что в r1/r2: цель кода-ревью для скрипта без сети — юниты, мутанты и ручное воспроизведение регрессии, что и сделано). - Зеркалирование
process.yml/validate.ymlвmain(AC5, §5.3 ТЗ) — шаг публикации после слияния, вне код-ревью.
Материал раунда
- SHA:
615181050b9ba4d4568834d56ac5a0ed97ca804d(свереноgit rev-parse HEADнепосредственно перед выводом). - Предыдущий материал (r2, код):
00130e08aa247cd8796a99125deb7f9b5131bfa1. - Ребейза между раундами не было — прямые 2 коммита сверху
(
git merge-base --is-ancestor 00130e08 HEAD— успех), дельта локальна.
Вердикт
Зелёный. M1(r2) закрыта полностью: тестом на реальную реализацию
waitValidate (не фейк) и отдельно зарегистрированным мутантом
merge-trusts-cancelled-dispatch; независимое воспроизведение регрессии в
этом раунде даёт 2 упавших теста из 12 (в r2 было 0 из 2420 на том же
приёме) — асимметрия «код исправлен, но не защищён» устранена. High и Medium
не найдено. Дешёвые гейты (typecheck, npm test, build+сверка бандла)
зелёные, лично прогнаны за отсутствием зелёного Validate на этом SHA;
smoke-select.mjs подтверждает отсутствие исполняемого frontend-диффа —
браузерные проверки не нужны по инструменту, а не по теме. AC1–AC6
наследуются из r1/r2 без повторной проверки — дельта их не задевает.
Материал раунда
- Ветка:
issue/510-mutants-on-candidate-and-review-gate, коммит615181050b9b— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
1cc8f27b4d5e0648f5133206f0843a91cca54754git log --all --format='%H %T' | grep 1cc8f27b4d5e - ТЗ
docs/specs/510-mutants-on-candidate-and-review-waits-validate.md, блобd68ed7818b4c77535305d0aae7da9765a2ad6c61git log --all --find-object=d68ed7818b4c77535305d0aae7da9765a2ad6c61 -- docs/specs/510-mutants-on-candidate-and-review-waits-validate.md - Вердикт конвейера:
green· High 0