16 KiB
CODE-REVIEW-595-r1
Issue: #595 — Тест #573 краснеет на любом коммите приёмки эталонов: заглушка loadContext не моделирует Baseline-Reviewed run.
Класс: B (инфраструктура/гейты) — ни одного файла класса A. Вход сразу на S7-code-review (#562), спецификация не требуется, AC заданы в теле issue.
Заход: r1 · блокирующих циклов израсходовано 0 из 4.
Материал: eb145aba485fe62d363298bb4b09d33bd0bb096e (рабочая копия пинована на этом SHA; git fetch/checkout на другой коммит не выполнялся).
Diff: scripts/mutation-registry.mjs (+12), test/release-gate.test.mjs (+72/-4). Никаких изменений в src/**, custom_components/**, dist/**.
Скоуп
Дефект: test/release-gate.test.mjs строил ожидания composite-evidence (own) из
живого HEAD через candidateExpectations, а localEvidence читает
baselines.reviewedRun из последнего сообщения коммита. Пока HEAD — обычный
коммит, поле null; как только HEAD несёт трейлер Baseline-Reviewed:
(коммит приёмки golden-эталонов), поле становится непустым, и
evaluateCiProof законно требует объявленный прогон, которого тестовая
заглушка loadContext не отдавала. Тест падал на любой ветке, чья
вершина — коммит приёмки эталонов (воспроизведено автором на
issue/594-form-kit-room@68bb7558, 2776/1). Продуктовый код (scripts/ci-proof.mjs,
scripts/release-gate.mjs) не менялся и не должен был — контракт «объявленный
прогон обязан существовать, быть завершённым, не отменённым, быть Validate»
именно то, что задумано #573.
Правка: зелёный путь теста #573 теперь считается на evidence с явно
обнулённым baselines.reviewedRun, независимо от того, чем оказался HEAD.
Контракт объявленного прогона вынесен в отдельный тест #595 и проверяется с
трёх сторон (завершённый неотменённый Validate с conclusion: 'failure' →
green; прогона нет → failed; прогон отменён → failed). Добавлен мутант
declared-baseline-review-run-never-checked на защиту scripts/ci-proof.mjs.
Как проверялось
Диапазон origin/dev..HEAD содержит один коммит и не трогает src/** —
браузерные смоки, golden, backend pytest, perf-профили и geometry-invariants
не выбираются диффом (node scripts/smoke-select.mjs --base origin/dev --head HEAD
подтвердил: «Исполняемого frontend-диффа нет… Browser-smoke этим диффом не
выбираются»). check-docs не требуется — отпечаток скриншотов не мог устареть,
диффа в src/** нет.
Дешёвые гейты уже подтверждены зелёным Validate на этом самом SHA —
перепроверено напрямую (gh api repos/Matysh/houseplan-card/actions/runs/35425580065
→ status: completed, conclusion: success, head_sha: eb145aba…), поэтому
npx tsc --noEmit, npm test, npm run build не перегонялись повторно.
| Гейт | Результат | Как |
|---|---|---|
| typecheck/test/build (Validate) | green | ссылка на прогон 35425580065, SHA сверен вручную — совпадает |
node --test test/release-gate.test.mjs (локально, на материале) |
10 pass / 0 fail | выполнено в этой сессии |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
«нечего выбирать» | выполнено в этой сессии |
node scripts/process-gate.mjs --issues |
гейт пройден, WARN п.8 (инфра-трек #562, статус до S7 не нужен) | выполнено в этой сессии, gh доступен в песочнице |
node scripts/mutation-gate.mjs --check (весь реестр, включая новый id) |
все ok, включая declared-baseline-review-run-never-checked |
выполнено в этой сессии |
| golden / backend / браузерные смоки / perf | не прогонялись | diff не трогает src/**, custom_components/**/*.py; smoke-select подтвердил «нечего выбирать» |
AC → доказательство (перепроверено независимо, не со слов автора)
| AC | Заявлено | Проверено ревьюером |
|---|---|---|
AC1 — тест зелёный и на ветке с Baseline-Reviewed: на вершине, и без него |
автор привёл 8/1 → 10/0 на временной фикстуре, снесённой после замера | Воспроизведено независимо в отдельном git worktree (не в материале ревью): пустой коммит с трейлером Baseline-Reviewed: …/actions/runs/35407468491 поставлен вершиной; на нём: старый (origin/dev) test/release-gate.test.mjs — 8 pass / 1 fail, AssertionError: 'failed' !== 'green', текст Baseline-Reviewed run 35407468491 is missing, cancelled or not a Validate run — то есть баг реален и воспроизводится не только на исходной ветке автора; новый test/release-gate.test.mjs на том же коммите — 10 pass / 0 fail. Worktree удалён (git worktree remove --force), рабочая копия материала не пострадала (git status — чисто, HEAD не сдвигался) |
AC2 — контракт проверяется обеими сторонами: существующий Validate → green, отсутствующий → failed с текстом про Baseline-Reviewed run |
тест #595, 3 фикстуры |
прочитан код теста и evaluateCiProof (scripts/ci-proof.mjs:295-306): conclusion: 'failure' в фикстуре — законный случай (status==='completed' && conclusion !== 'cancelled', без требования success), совпадает с продуктовым контрактом. Прогнано локально — все три under-фикстуры дают заявленный статус |
AC3 — зелёный путь не читает reviewedRun из HEAD |
withoutReviewed() явно зануляет поле перед использованием в #573; #595-тест переопределяет expected.baselines.reviewedRun литералом, не читая его из HEAD |
подтверждено чтением: оба места явно не зависят от фактического сообщения HEAD-коммита. Дополнительно подтверждено воспроизведением AC1 выше — тест зелёный именно тогда, когда HEAD несёт трейлер, то есть путь через localEvidence(HEAD) не мог быть источником зелёного результата |
| AC4 — гейты + мутант ловит подмену | npm test/typecheck/build зелёные (хендофф), мутант 1/1 |
typecheck/test/build — через Validate на этом SHA (см. таблицу выше); мутант перепроверен вручную в этой сессии двумя способами: (а) ручной патч scripts/ci-proof.mjs (const declared = null;) + node --test --test-name-pattern="#595" → AssertionError: expected 'failed', actual 'green' на кейсе «прогона нет»; (б) node scripts/mutation-gate.mjs --check — весь реестр включая новый id ok |
Защитный AC — таблица «чем краснеет» (§2.7)
| AC | Чем доказан | Чем краснеет |
|---|---|---|
Объявленный Baseline-Reviewed прогон обязателен к резолву |
node --test --test-name-pattern="#595" test/release-gate.test.mjs |
Мутация scripts/ci-proof.mjs: const declared = proof.evidence.baselines?.reviewedRun ?? null; → const declared = null;. Воспроизведено вручную в этой сессии (см. AC4) — тест #595 краснеет (expected 'failed', actual 'green' на кейсе с отсутствующим прогоном), а не просто заявлен автором |
Зелёный путь #573 не зависит от содержимого HEAD |
воспроизведение AC1 (worktree с фиктивным Baseline-Reviewed на вершине) |
старый тест на этой фикстуре краснеет (8/1), новый — зелёный (10/0); граница показана прогоном, а не чтением |
Что проверено и корректно
- Диапазон коммитов и трейлеры: один коммит,
Issue: #595,User-Visible: no— верно, поведение продукта не меняется.User-Visible: noне требует правок changelog — оба файла не тронуты, это корректно. - Заявление «продуктовый код исправен» подтверждено:
scripts/ci-proof.mjsиscripts/release-gate.mjsне входят в diff; контрактevaluateCiProof(строки 295-306ci-proof.mjs) действительно не требуетsuccessот объявленного прогона — фикстура сconclusion: 'failure'в новом тесте соответствует реальности, а не поблажке теста. - Существующее покрытие
evaluateCiProofвtest/ci-proof.test.mjs(неверный id, неверный workflow, отменённый прогон — строки 311-325) не дублируется новым тестом:#595вrelease-gate.test.mjsбьёт по другому слою — фикстуреloadContext/classifyValidateProofs, где жил именно этот баг. Разделение ответственности между файлами тестов оправдано. process-gate.mjs --issuesподтверждает, что задача корректно распознана как инфраструктурная (#562): WARN, а не ошибка, статусная метка не требуется.- Реестр мутаций синтаксически корректен и интегрирован —
mutation-gate.mjs --checkпроходит по всему реестру без падений после добавления записи. - Диагностика бага в issue (##Что сломано / Почему) точно соответствует коду:
localEvidence(ci-proof.mjs:103-127) действительно читаетbaselines.reviewedRunизgit log -1 --format=%B <rev>, аcandidateExpectations(release-gate.mjs:18-26) действительно считаетownтолько на checkout самого кандидата — ровно то место, которое правка меняет.
Находки
Нет находок High или Medium. Скоуп задачи полностью закрыт минимальной правкой теста и мутационного якоря; изменение не расширяет и не сужает скоуп issue.
Чего не проверял
- Golden, браузерные смоки, backend pytest, perf-профили, geometry-invariants —
diff не трогает
src/**,custom_components/**/*.py, геометрию или ссылки на неё;smoke-select.mjsподтвердил «нечего выбирать». Прогон был бы пустой тратой времени, а не тщательностью. npm run inventory— число тестов не менялось в отчёте вручную (не копировалось в документ), в документ вставлен только фактический выводnode --test, полученный в этой сессии.- Полный
npm test/npx tsc --noEmit/npm run buildзаново не гонялись — засчитан зелёный Validate на точном SHAeb145aba…(сверено GitHub API напрямую, не со слов хендоффа). Локально прогнан только целевой файлtest/release-gate.test.mjs, поскольку именно он — предмет правки. npm run docs:accept/check-docs.mjs— не требуется, diff не вsrc/**.- Воспроизведение бага и мутанта выполнено во временном
git worktree, не в материале ревью: рабочая копия наeb145abaне сдвигалась и не менялась (git status— чисто до и после), worktree удалён по завершении проверки.
Вердикт
Зелёный. AC1-AC4 доказаны исполнением, а не заявлением — в том числе независимым воспроизведением исходного бага и подтверждением, что новый тест и его мутационный якорь действительно способны краснеть. Скоуп соответствует issue, класс B подтверждён, трейлеры корректны, лишних гейтов не прогонялось.
Материал раунда
- Ветка/SHA:
eb145aba485fe62d363298bb4b09d33bd0bb096e(HEAD рабочей копии на момент ревью, detached). - Дерево:
git rev-parse HEAD^{tree}на момент ревью соответствует рабочей копии; расхождений с материалом не найдено (git statusчисто на всём протяжении ревью). - Диапазон:
origin/dev..HEAD— 1 коммит.
Материал раунда
- Ветка:
issue/595-release-gate-reviewed-run-fixture, коммитeb145aba485f— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
75ffd0790d06ec566a719305f13afe271807617fgit log --all --format='%H %T' | grep 75ffd0790d06 - Тело issue:
2a34c60ce3779c75b08a16df990b4b0f4bdefc31396d9c5a81eb9fb71752a516 - Вердикт конвейера:
green· High 0