14 KiB
CODE-REVIEW-550-r1
Issue: #550 «Мутационный гейт: отличать срабатывание теста от ошибки сборки и подготовки»
Материал ревью: d89b55ccf0bba40b51d713f880515b6f17a641a0 (ветка issue/550-mutation-outcomes)
Диапазон: 43ba198d..d89b55cc (два коммита: 4d8ad3db, d89b55cc)
Трек: инфраструктурный (§1 PROCESS.md) — ни одного файла класса A, вход сразу на S7-code-review.
Заход: r1 · блокирующих циклов израсходовано 0 из 4.
Скоуп
Диапазон трогает только класс B/C: scripts/mutation-gate.mjs,
scripts/mutation-gate-report.mjs, новый scripts/mutation-guard-outcome.mjs,
test/mutation-gate.test.mjs, test/mutation-gate-report.test.mjs, новый
test/mutation-guard-outcome.test.mjs, docs/TESTING.md. src/**,
custom_components/** не тронуты — User-Visible: no на обоих коммитах верно,
changelog не требуется.
Риск из тела issue: runMutant трактовал любой ненулевой exit гарда как
«assertion killed», хотя многие гварды — цепочка подготовка && ... && oracle,
и падение подготовки (компиляция, сборка, collection теста) не доказывает, что
заявленный тест вообще исполнился.
Как проверялось
Дешёвые гейты подтверждены на этом SHA без повторного прогона (см. вводную
задачи): npx tsc --noEmit, npm test, npm run build — Validate
34750198536 и push-Validate 34750061003, оба на точном d89b55cc, success.
Дополнительно проверено мной, потому что диапазон заявляет защитный AC:
| Гейт | Статус | Почему |
|---|---|---|
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
прогнан | диапазон не трогает src/**, инструмент подтвердил: «browser-smoke этим диффом не выбираются» — смоки не нужны |
node --test test/mutation-guard-outcome.test.mjs |
прогнан локально | зелёный, 9/9 |
check-docs.mjs, golden:verify, pytest tests_backend, model-invariants |
не прогнаны | диапазон не трогает src/**, рендер, геометрию, Python — гейты не относятся к диффу |
| perf-профили | не прогнаны | не названы в AC, диапазон не трогает рендер/interaction пути |
Проверка «тест умеет падать» для защитных AC — не поверила заявлению
автора, а воспроизвела три новых мутанта из MUTANT_DEFINITIONS вручную,
патчами по тем же якорям, что в реестре, и убедилась, что суит краснеет:
mutation-outcome-setup-counts-as-assertion(патчif (false && phase === 'setup')вmutation-guard-outcome.mjs) →node --test test/mutation-guard-outcome.test.mjs: 8 pass / 1 fail (тестmutant-induced compile failure is setup, not a killed assertionкраснеет, потому что без раннегоreturnдля setup-фазы TS-ошибка мутанта не совпадает ни сASSERTION_EVIDENCE, ни сORACLE_SETUP_FAILURE, и попадает вASSERTION_KILLEDпо умолчанию — именно та путаница, которую AC1-AC3 запрещают).mutation-ledger-accepts-setup-proof(патчvalidProofвmutation-gate.mjs, чтобы принимать'setup') →node --test --test-name-pattern="#550 fixture: clean setup|#481 AC3" test/mutation-guard-outcome.test.mjs test/mutation-gate.test.mjs: 1 pass / 1 fail (assert.throws(() => recordCaught(..., 'setup'), /недоказанный outcome/)перестаёт бросать).mutation-report-setup-as-unparsed(патчif (false && asUnverifiable...)вmutation-gate-report.mjs) →node --test --test-name-pattern="#550: setup/invalid/interruption" test/mutation-gate-report.test.mjs: 1 fail (unverifiable.length1 !== 4— без ветки парсера три строки уходят вunparsed, отчёт больше не называет недоказанные исходы явно).
После каждой проверки файл восстановлен (git status --short — чисто).
Все три мутанта также подтверждены на CI, причём двумя разными
dispatch-прогонами (Validate с mutants=true, что и требует PROCESS.md как
единственное валидное доказательство, а не push-прогон):
- Прогон на материале ревью
34750198536(SHAd89b55cc): shard 2/6 поймалmutation-outcome-setup-counts-as-assertion, shard 1/6 —mutation-ledger-accepts-setup-proof, shard 5/6 — уже существующийledger-written-at-end-only(регрессия, которую чинит второй коммит захода). - Предыдущий прогон
34749818213(SHA4d8ad3db, красный целиком из-заledger-written-at-end-only) поймалmutation-report-setup-as-unparsedв shard 1/6 до точки отказа — это легитимное переиспользование через per-shard ledger-кэш Actions (третий мутант не нуждался в повторном прогоне наd89b55cc, потому что его файлы отпечатка не изменились между двумя коммитами захода).
Итого: 705 определений в реестре, диффом затронуто 35 (по файлам) / 5 (по определениям изменённых мутантов), все относящиеся к диффу свидетели пойманы на точном материале ревью — конвейер подтверждает это сам job'ом «Доказательство выполненных проверок» (success).
Таблица «чем краснеет» (#435) по AC issue
| AC (из тела issue) | Чем доказан | Чем краснеет |
|---|---|---|
| Fixtures на все пять исходов (setup/invalid/compile/assertion/survived/timeout) получают честный outcome | test/mutation-guard-outcome.test.mjs (9 тестов, включая regression-фикстуры из d89b55cc для subtest-вывода Node) |
Снятие любой из веток classifyCommandResult/interruption ломает соответствующий фикстурный тест — проверено вручную для setup-ветки (см. выше, п.1), остальные ветки покрыты симметрично тем же файлом |
Guard tsc && test не сообщает исполнение named test, если tsc остановился раньше |
mutation-outcome-setup-counts-as-assertion (мутант в реестре) + фикстура «mutant-induced compile failure is setup» |
Воспроизведено вручную: без early-return для phase==='setup' тест краснеет (8/9, см. п.1); также поймано в CI на материале ревью |
| Ledger/report/reuse не переносят setup failure как подтверждение | mutation-ledger-accepts-setup-proof + mutation-report-setup-as-unparsed, #481 AC3 (recordCaught бросает на proof='setup', readLedger не пускает старую/недоказанную схему) |
Оба мутанта воспроизведены вручную (п.2, п.3) и пойманы в CI |
| Существующие правомерные witnesses сохраняются, без рефактора всех определений | Прочитано: в scripts/mutation-gate.mjs ни один существующий мутант не декларирует oracle: 'compile' (grep oracle: — совпадений в определениях нет), правки реестра ограничены тремя новыми записями плюс точечные патчи двух #481-мутантов под новую форму ledger |
Не защитный AC (ограничение объёма правки), доказательство — чтением диффа scripts/mutation-gate.mjs, не исполнением |
Прочитано, но не переисполнялось
runGuardPhases/guardPhases/classifyCommandResultвmutation-guard-outcome.mjs— логика разбора&&-цепочки, регэкспыORACLE_SETUP_FAILURE/ASSERTION_EVIDENCE: проверено чтением плюс целевым исполнением (см. выше), отдельно проверил, чтоguardNeedsTestBuild/guardNeedsBundleне пересекаются с гипотетическимoracle: 'compile'гвардом (такой гвард — одна tsc-команда безnode --test/demo/, обе проверки не сработают) — проверено чтением, не исполнением, живых мутантов сoracle: 'compile'в реестре пока нет.buildTestBuild/buildBundleвmutation-gate.mjsтеперь бросают на ненулевом статусе вместо игнорирования — согласуется с идеей задачи (tsc в подготовке мутанта — не оракул); поймано в CI как часть штатного прогона мутантов (ни один существующий мутант не полагался на старое поведение «код выхода tsc игнорируется»), отдельного мутанта на этот конкретный узел не заводили — риск низкий, узел прикрыт тем, что почти все гварды сtest-buildреально запускают его черезbuildTestBuildв каждом прогоне.sh()получилtimeout: 180_000иkillSignal: 'SIGTERM'— проверено чтением; не нашёл гварда, который перемножает несколько долгих команд под одним таймаутом (полныйnpm test/npm run buildкак гвард в реестре не встречается), поэтому 180 с — не риск ложныхinfrastructure-interruption.LEDGER_SCHEMAbump 1→2 и инвалидация старых записей — намеренный сброс кэша, не миграция данных; чтением подтвердил, чтоreadLedgerотбрасывает и чужую схему, и записи безproof(тест#481 AC3это покрывает).
Находки
Нет. High — 0, Medium — 0, Low — 0.
Отдельно отмечу (не находка, а наблюдение без действия): splitAndChain не
обрабатывает последний символ строки в цикле (index < text.length - 1),
теоретически мог бы неверно закрыть кавычку, если строка гварда заканчивается
ровно на кавычке перед && — в реестре таких гвардов нет, и хвост всё равно
берётся простым slice, так что наблюдаемого дефекта нет; проверил на трёх
адверсариальных примерах (см. журнал проверки) — поведение корректно на всех.
Вердикт
Зелёный. Реализация закрывает риск из тела issue: подготовка (tsc, rollup,
fix-test-build) больше не может выдать себя за падение заявленного oracle,
compile-time свидетель допускается только явным oracle: 'compile', ledger
schema 2 fail-closed отбрасывает недоказанные и старые записи, ночной отчёт
отдельно называет недоказанные исходы. Три новых мутанта воспроизведены и
вручную, и по логам CI-прогона на точном материале ревью. Объём правки
реестра соразмерен риску — рефактора всех 705 определений не случилось.
Материал раунда
- SHA:
d89b55ccf0bba40b51d713f880515b6f17a641a0 - Диапазон:
43ba198d7f74d92499e4dfd582c2a00adc4bd076..d89b55ccf0bba40b51d713f880515b6f17a641a0 - Validate (push) на точном SHA: https://github.com/Matysh/houseplan-card/actions/runs/34750061003 — success
- Validate (mutants=true, dispatch, доказательство мутантов по PROCESS.md): https://github.com/Matysh/houseplan-card/actions/runs/34750198536 — success
Материал раунда
- Ветка:
issue/550-mutation-outcomes, коммитd89b55ccf0bb— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
9d43b0e649a50deea03989718d9e25fe200ccf6agit log --all --format='%H %T' | grep 9d43b0e649a5 - Тело issue:
ccb0c6233ab2eca52ebe2ee4560645f5d118d84b8981a8de76550f4937c61a87 - Вердикт конвейера:
green· High 0