Files
houseplan-card/docs/reviews/CODE-REVIEW-595-r1.md
T
2026-09-19 06:12:46 +00:00

16 KiB
Raw Blame History

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-306 ci-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 на точном SHA eb145aba… (сверено 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 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 75ffd0790d06ec566a719305f13afe271807617f
    git log --all --format='%H %T' | grep 75ffd0790d06
    
  • Тело issue: 2a34c60ce3779c75b08a16df990b4b0f4bdefc31396d9c5a81eb9fb71752a516
  • Вердикт конвейера: green · High 0