Files
houseplan-card/docs/reviews/CODE-REVIEW-399-r2.md
T
claude[bot] 6bf39ee9a7
Проверка (CI) / Классификация изменённых файлов (push) Successful in 20s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 33s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 15s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 44s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 17s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 4m55s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 5m59s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
docs: review document for #399
Issue: #399
User-Visible: no
2026-08-31 01:54:28 +00:00

13 KiB

CODE-REVIEW-399-r2

  • Issue: https://github.com/Matysh/houseplan-card/issues/399
  • Этап: code (PROCESS.md §2.7)
  • Заход: r2 · блокирующих циклов израсходовано 1/4
  • HEAD: 5c4d8cba9ebb2815dd45f91641d39621435a1144 (детач на origin/issue/399-backend-gate-honesty)
  • Материал: git log --oneline origin/dev..HEAD, git diff origin/dev...HEAD; для этого раунда — дельта git diff 98028a30..HEAD относительно SHA предыдущего код-ревью
  • ТЗ на входе: docs/specs/399-backend-gate-honesty.md, ревизия 3, принята зелёным вердиктом SPEC-REVIEW-399-r3 (без изменений с r1)

Дельта этого раунда

Предыдущий код-ревью (CODE-REVIEW-399-r1, документ в 8b2a146c) прошёл на 98028a3093713122d6dbc1b08fc830b730e8a2a3 с вердиктом жёлтый, High: 1. Автор ответил единственным коммитом:

5c4d8cba — test: prove AC5 by running the scanner, not the predicate (#399)

git diff 98028a30..HEAD (без документов ревью, которые сами по себе не код):

  • test/validate-workflow.test.mjs — обход каталога вынесен в экспортируемую функцию pinViolations(directory); тест «AC5» переписан: вместо вызова installsPythonDeps на строковых литералах строится настоящий временный каталог из трёх файлов и вызывается та же функция, что работает в проде.
  • scripts/mutation-gate.mjs — мутант workflow-scan-hardcodes-the-list меняет цель патча с прежней (workflows = readdirSync(...) → список из одного имени) на новую (files = readdirSync(...) → список из двух настоящих имён ['validate.yml', 'mutation-gate.yml']).

Больше ничего не менялось: pyproject.toml, tests_backend/requirements.txt, test/backend-pins.test.mjs, test/lint-scope.test.mjs дельтой не затронуты. Рёбейза не было (git merge-base origin/dev HEAD = tip dev, проверено). Delta локальна и меньше по объёму, чем исходная задача — полный разбор не требуется, разбираю только то, до чего дотягивается изменение: AC5 и связанный с ним мутант.

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
[High] Тест, названный AC5, вызывал installsPythonDeps на строковых литералах внутри теста и не исполнял реальный обход каталога (readdirSync(WORKFLOWS).filter(...)). Воспроизведение: откат к списку из двух реальных имён + подложенный третий workflow с непинованной установкой → 10/10 зелёных. Обход вынесен в pinViolations(directory) — функцию с каталогом как параметром. Тест AC5 (test/validate-workflow.test.mjs:259) строит временный каталог из трёх файлов (пинованный validate.yml, безобидный docs.yml, zz-rogue.yml с непинованной установкой) и вызывает именно эту функцию, а не предикат в изоляции. Утверждает на возвращённых scanned (3), installers (2) и точном списке problems для zz-rogue.yml. Воспроизвёл ровно сценарий r1 повторно на текущем HEAD: sed вернул обход к ['validate.yml', 'mutation-gate.yml'] внутри pinViolations → node --test test/validate-workflow.test.mjs даёт 9/10, падает именно тест «#399 AC5: тот же код ловит третий workflow в подставном каталоге» (ENOENT на втором захардкоженном имени, которого нет во временном каталоге) — не побочный assert, а прямое доказательство, что тест исполняет реальный путь. Дерево вернул (git checkout -- test/validate-workflow.test.mjs), git status --short пуст.

Мутант workflow-scan-hardcodes-the-list пересмотрен под замечание r1 о том, что прежняя форма (список из одного имени) ловилась по посторонней причине (assert.ok(workflows.length >= 2, ...)), а не по существу. Новая форма подставляет список из двух настоящих имён — неотличимую от корректного кода на сегодняшнем дереве, то есть более вероятную форму регрессии. Прогнал штатным раннером:

node scripts/mutation-gate.mjs --id=workflow-scan-hardcodes-the-list
ok   чистый прогон: node --test test/validate-workflow.test.mjs
ok   workflow-scan-hardcodes-the-list: тест покраснел, как обязан
поймано 1 из 1

Дерево после прогона чистое (git status --short пусто).

Как проверялось (этот раунд)

Зелёного Validate на 5c4d8cba нет — гейты прогнаны вручную. Диапазон изменений — конфигурация CI/тестов, src/** не тронут ни в дельте, ни в остальном диапазоне (см. r1).

  • npx tsc --noEmit — чисто, exit 0.
  • npm test — 1667/1667 pass, 1 skipped, совпадает с заявленным автором числом и с результатом r1 (delta не должна была изменить общее число тестов — так и есть: -1 старый AC5-тест, +1 новый, чистый ноль).
  • npm run build — собрался (tsc --noEmit && rollup -c, ~16s). Сверка трёх копий бандла не делалась: дельта не трогает src/**, контент бандла не мог измениться.
  • node scripts/check-docs.mjs — не прогонялся: дельта не трогает src/** (условие запуска по инструкции не выполнено, как и в r1).
  • Геометрические инварианты, golden:verify, browser-смоки, pytest tests_backend, ruff — не по необходимости и не по возможности среды: дельта не касается геометрии, рендера, продуктового Python-кода и видимого поведения; причины идентичны r1 (нет установленных homeassistant/ruff в песочнице, проверено pip show).
  • Воспроизведение регрессии, которую AC5 обязан ловить (см. таблицу выше) — прогнано лично на этом HEAD, не унаследовано с чужих слов.
  • Мутант workflow-scan-hardcodes-the-list прогнан штатным раннером на новой форме патча (см. выше).

Что проверено и признано корректным

  • AC5 — теперь доказан исполнением: pinViolations — единственная точка обхода каталога, используемая и продовым тестом на реальном .github/workflows/, и AC5-тестом на синтетическом каталоге. Оба пути проходят через один и тот же код, различие — только во входных данных. Регрессия из r1 (список из двух имён) теперь красит именно AC5-тест, без постороннего assert. Мутант заточен под ту же форму регрессии и ловится.
  • Синтетический каталог AC5-теста корректно воспроизводит оба вида нарушения контракта одним файлом (zz-rogue.yml одновременно не ставит из файла пинов и не версионирован) — соответствует двум проверкам внутри pinViolations, порядок утверждений в assert.deepEqual детерминирован (оба сообщения относятся к одному файлу, порядок не зависит от порядка обхода каталога readdirSync). Учёл это отдельно, так как порядок readdirSync не гарантирован POSIX — но здесь он не влияет на исход теста, что делает тест устойчивым к этой недетерминированности, а не случайно совпадающим.

Унаследовано из r1 (без повторной проверки)

Дельта этого раунда не касается перечисленного — принимаю выводы CODE-REVIEW-399-r1 (8b2a146c, HEAD того раунда 98028a3093713122d6dbc1b08fc830b730e8a2a3) как есть:

  • AC1 (пин фронтенда из констрейнтов HA) — подтверждён в r1 сетевым источником (gh api к home-assistant/core@2026.8.3), файлы tests_backend/requirements.txt, test/backend-pins.test.mjs дельтой не тронуты.
  • AC2/AC3 (согласование [tool.ruff] include и шага линта в validate.yml, test/lint-scope.test.mjs) — сверены в r1 построчно, файлы дельтой не тронуты.
  • AC4 (позитивный признак installsPythonDeps вместо пропуска по отсутствию подстроки) — сама функция installsPythonDeps не менялась в этой дельте (менялась только функция, которая её использует — pinViolations, и структура теста).
  • AC6 (гейты не деградировали) — подтверждён в r1 числом npm test; в этом раунде число совпало снова, деградации нет.
  • Мутанты lint-scope-drifts, frontend-pin-drifts-from-ha — прогнаны и пойманы в r1, дельта их не затрагивает.
  • Трейлеры Issue: #399 / User-Visible: no на всех девяти коммитах диапазона — перепроверены заново в этом раунде (см. «Как проверялось»), не только унаследованы: результат идентичен r1.
  • Low-наблюдение r1 про pip3 install / python3 -m pip install, не распознаваемые installsPythonDeps — вне скоупа AC5 (тот описывает текстовый pip install), дельта его не касается, остаётся наблюдением без действия.
  • Low-наблюдение r1 про склеенный текст в теле коммита 98028a30 — тот коммит не переписывался, наблюдение остаётся снятым без действия.

Чего не проверял

  • python -m pytest tests_backend, ruff check, mypy strict — среда не изменилась с r1, пакеты по-прежнему не установлены (не перепроверял pip show повторно, так как дельта их не касается и вывод не мог измениться).
  • npm run golden:verify, geometry invariants, browser-смоки — не по необходимости: дельта — только тестовый файл и файл мутантов, видимое поведение продукта не существует для этой задачи вовсе.
  • node scripts/check-docs.mjs — условие запуска (src/** в диффе) не выполнено.
  • Реальный HA-харнесс с новым пином фронтенда — ограничение среды, идентичное r1, дельта его не касается.

Вывод

High из r1 закрыт по существу и проверен воспроизведением, а не заявлением автора: тест, названный AC5, теперь действительно исполняет код обхода каталога и красится при возврате к хардкод-списку. Новых находок в дельте нет. Задача может двигаться дальше без ещё одного блокирующего цикла код-ревью.