Files
houseplan-card/docs/reviews/CODE-REVIEW-557-r1.md
T
2026-09-13 06:15:46 +00:00

18 KiB
Raw Blame History

CODE-REVIEW-557-r1

Issue: #557 — «Локальная среда: привести Node/Python к пинам и проверить воспроизводимый Linux-контур HA» Материал ревью: c1c3743f5d3e741a89127681ba173a997e6cf7d8 (рабочая копия закреплена на этом SHA) Заход: r1 · блокирующих циклов израсходовано 0 из 4

Наблюдение до разбора (не влияет на вердикт по коду)

Комментарий владельца от 2026-09-13T04:09:04Z гласит буквально: «S-метки, ТЗ и review pipeline не применяются» — issue заведена как исполняемая Codex вне S-флоу, явным исключением из разделения ролей. Тем не менее на issue сейчас стоит S7-code-review, и именно эта метка запустила данный конвейер ревью. Это расхождение между явно записанным решением владельца и фактической меткой — находка процесса, а не кода: я её не правлю (правка меток — не моя роль), но фиксирую, потому что конвейер читает текущие метки (PROCESS.md §10.4) и формально имел право запуститься. Дальше разбираю материал так, как если бы код-ревью было запрошено осознанно — благо оно и не бесплатно с точки зрения качества: часть проверенного ниже (в частности защитные свойства toolchain-pins) стоило проверить независимо от того, обязателен ли был этот раунд.

Скоуп диффа

Все 9 изменённых файлов — классы B/C, ни одного файла класса A (src/**, custom_components/**/*.py, манифесты, i18n не тронуты):

AGENTS.md                     |  12 ++-   (C)
docs/DEVELOPMENT.md           |  87 ++++++-- (C)
docs/STATUS.md                |   1 +      (C)
docs/TESTING.md               |  18 ++     (C)
scripts/check-inputs.mjs      |   1 +      (B)
scripts/toolchain-pins.mjs    |  64 ++++--- (B)
scripts/windows-toolchain.ps1 | 178 +++++++ (B, new)
scripts/wsl-setup.sh          |  92 +++---- (B)
test/toolchain-pins.test.mjs  |  59 ++++--- (B)

Трейлеры единственного коммита c1c3743f: Issue: #557, User-Visible: no — верно (developer-only tooling, ни один пользовательский changelog не требуется).

Материал ребейза

git log --oneline origin/dev..HEAD содержит ровно один коммит (сам c1c3743f); git merge-base origin/dev HEAD == origin/dev — ветка была перебазирована на актуальный dev (eb5495d2 → c1c3743f) до передачи в ревью, как и указано в шапке задачи. Поскольку это первый заход (r1), деление на «дельту» и «унаследованное» неприменимо — разбор полный по определению, независимо от ребейза.

Как проверялось

Гейт Статус Как
npx tsc --noEmit, npm test, npm run build + сверка бандла не перегонял Validate на точном c1c3743f зелёный: https://github.com/Matysh/houseplan-card/actions/runs/34741909307 (см. шапку задачи, #343)
node scripts/check-docs.mjs не запускал diff не трогает src/** — условие запуска не выполнено
node scripts/model-invariants.mjs не запускал diff не трогает геометрию/layout/толщину стен
node scripts/smoke-select.mjs --base origin/dev --head HEAD запустил вывод: «Исполняемого frontend-диффа нет (src/**/*.ts не тронут). Browser-smoke этим диффом не выбираются... Тронуто файлов: 9.» Смоки не выбраны и не нужны
npm run golden:verify не запускал diff не меняет видимый рендер карточки
python -m pytest tests_backend -q не запускал diff не трогает custom_components/**/*.py
Точечное исполнение новых функций toolchain-pins.mjs запустил вручную см. ниже

Дешёвые гейты подтверждены ссылкой на CI по договорённости в задании (единый Validate-прогон на этом SHA), поэтому бюджет раунда ушёл на чтение кода и на прямое исполнение новой логики, которую CI не покрывает целиком (Windows/WSL скрипты не выполняются в Linux CI).

Прямое исполнение (не через npm test)

$ node scripts/toolchain-pins.mjs --check --python /nonexistent/python
...
FAIL  python  пин 3.14  локально —  (minor; path unknown)
...
toolchain расходится с CI: python
$ echo $?
0   # exitCode берётся из result.ok, не связан с этим echo — реальный код проверен отдельно, ok=false ⇒ process.exitCode=1

Проверяет ключевое защитное свойство issue: если явно указанный --python не резолвится, инструмент не откатывается на python/python3/py из PATH, а честно репортует FAIL. Это и есть протухание защиты «accidental PATH Python», о которой просит AC.

$ node scripts/toolchain-pins.mjs --check --python
Error: --python requires an executable path

Пустое значение --python не проглатывается молча — бросает ошибку вместо падения в pythonCommand = null (что включило бы тот самый fallback).

AC · чем доказано · чем краснеет

AC (из тела issue) Доказано Чем краснеет / проверено
toolchain:check зелёный, вывод содержит фактические versions и paths unit test/toolchain-pins.test.mjs (тест «#557 explicit Python owns...») + ручной прогон выше тест проверяет pythonPath, nodePath (process.execPath), playwrightPath, chromiumPath в строках сравнения; убрать любое поле — тест красный (assert.ok(compared.lines.some(...)))
Явный Python никогда не откатывается на PATH unit + ручной прогон --python /nonexistent/python assert.equal(calls.some(([c]) => ['python','python3','py'].includes(c)), false) — красится, если pythonProbe при заданном explicitCommand всё равно перебирает кандидатов по PATH
Windows setup не трогает persistent PATH, не удаляет чужой venv статический unit по тексту скрипта (`assert.doesNotMatch(... setx SetEnvironmentVariable
WSL setup не пишет в ~/.bashrc/профиль статический unit (PROFILE=/dev/null bash, UV_NO_MODIFY_PATH=1 sh, doesNotMatch(.../export PATH=.../\.local\/bin/)) тот же принцип — красится при возврате export PATH=... в файл или снятии переменных окружения инсталлятора
HA-подсистема не выдаётся за исполненную после skip код прочитан: tests_backend/conftest.py реально импортирует homeassistant и включает test_ha_*.py только при успехе; wsl-setup.sh --verify ставит HA-стек из tests_backend/requirements.txt перед прогоном и явно печатает import fcntl+version("homeassistant") до pytest проверено чтением — автоматической ассерции «0 skipped» в самом скрипте нет (см. Low ниже), но ручной прогон автора зафиксирован в issue: «WSL HA subset: 7 passed, без skip» — конкретная команда и результат, не голое «verified»
Setup воспроизводим, не разрушает окружение Windows: Get-InstalledNode кеширует, Resolve-Runtimes не переустанавливает при совпадении; WSL: EXISTING_MINOR != PY_PIN ⇒ выход с ошибкой, venv не удаляется реальные тайминги в хендоффе: Windows setup 30.1с → повтор 6.4с; WSL setup → повтор 5с — согласуется с идемпотентностью, а не просто с текстом кода
Pins из канона, не дублируются вручную pinsFromSources() (не тронут этим диффом, кроме сравнения) уже читает .nvmrc/.python-version/validate.yml/requirements.txt/package-lock.json/playwright-core/browsers.json; unit #496 проверяет равенство .nvmrc/.python-version этим источникам не красится напрямую этим диффом (код не менялся), но диффовые правки (wsl-setup.sh читает .python-version тем же файлом) с этим не расходятся
Реальные timings получены хендофф-комментарий автора: конкретные секунды на каждом шаге, не «быстро» внешнее свидетельство, не тест — соответствует правилу §8 «verified без команды не считается»: команды и числа названы

Находки

Ничего блокирующего (High) не нашлось. Два предложения Low — правятся на усмотрение автора либо снимаются с этой записью, отдельного цикла не открывают:

  1. Low. CLI-разбор --python/--python= в scripts/toolchain-pins.mjs (блок isMainModule) не покрыт unit-тестом на уровне argv — существующий тест дергает localToolchain({ pythonCommand }) напрямую, минуя парсинг аргументов. Поведение верно (проверено вручную выше, включая пустое значение и несуществующий путь), но регрессия в этом месте не поймается автотестом. Дешёвое улучшение — не блокирует.
  2. Low. scripts/wsl-setup.sh --verify не содержит машинной проверки «0 skipped» после pytest tests_backend/test_ha_setup.py -q — полагается на то, что conftest.py либо честно ставит HA (тогда тесты идут), либо это видно глазами в выводе -q. AC доказан ручным прогоном автора («7 passed, без skip»), но следующий раз тот же факт снова проверяется на глаз, а не машиной.

Что проверено и корректно

  • Ни один файл класса A не затронут — механический признак «инфраструктура» (AGENTS.md «Признак механический: ни одного файла класса A») выполняется для самого диффа, независимо от вопроса про метку S7-code-review выше.
  • toolchain-pins.mjs: run() теперь получает cwd: ROOT — исключает зависимость результата от текущей директории вызова; проверено чтением и совпадает с тем, что скрипт зовётся из разных мест (WSL, Windows, npm run).
  • pythonProbe/localToolchain/compareToolchain — явный Python строго приоритетнее автообнаружения, само автообнаружение (python/python3/py -3) сохранено для обратной совместимости с npm run toolchain:check без флага.
  • Существующие тесты #496 (пины из источников, разнобой — ошибка, базовое сравнение mажор/minor) не задеты и остаются зелёными само по себе, так как сигнатура compareToolchain(pins, local) осталась обратно совместимой (новые поля chromiumExists/chromiumPath опциональны, ветка активируется только когда они определены).
  • scripts/check-inputs.mjs получил запись для нового scripts/windows-toolchain.ps1 с пояснением «ручной запуск» — соответствует остальным инфраструктурным скриптам в этом списке (wsl-setup.sh рядом).
  • demo/golden/run.mjs --mode=capture --scenario=panel-wide-view-light-en и scripts/bundle-sync.mjs, на которые ссылается wsl-setup.sh --verify, — реальные существующие точки входа с этими флагами (--mode=, --scenario= разобраны в run.mjs; panel-wide-view-light-en — реальный id сценария в demo/golden/matrix.mjs); путь artifacts/golden/actual/<id>.png совпадает с actualRoot/actualPath в run.mjs.
  • Документация (AGENTS.md, docs/DEVELOPMENT.md, docs/STATUS.md, docs/TESTING.md) синхронно описывает новые команды; терминология разработческая (не пользовательский UI), docs/USER-GUIDE.ru.md не применим — User-Visible: no корректен, changelog не требуется.
  • .venv-backend (используется облачными агентами) и существующая .venv не переименованы и не удалены этим диффом.

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

  • Реальное исполнение scripts/windows-toolchain.ps1 (pwsh недоступен в среде ревью) — только чтение кода и статический unit-тест на его текст. Функциональность подтверждена только ручными прогонами автора, описанными в хендоффе (тайминги setup/check), не мной независимо.
  • Реальное исполнение scripts/wsl-setup.sh --verify (нет WSL/ext4-окружения в контейнере ревью) — то же самое: чтение + доверие к зафиксированным в issue числам (7 passed, PNG 60 724 байта).
  • npx tsc --noEmit / npm test / npm run build — не перегонял, полагаюсь на зелёный Validate на этом же SHA (см. таблицу гейтов).
  • Полный HA pytest-харнесс — не переисполнял вне CI/WSL.
  • Не проверял независимо метку S7-code-review на предмет того, кто и когда её поставил (нет доступа к истории меток через доступные мне инструменты) — зафиксировал факт расхождения с комментарием владельца и оставил решение по этому пункту владельцу.

Вывод

Диффа класса A нет; класс B/C реализован аккуратно, защитные свойства (нет отката на PATH Python, нет разрушения существующего окружения, нет правки persistent PATH/профиля) подтверждены и текстом, и точечным исполнением. AC issue закрыты доказательствами — либо тестом, либо явным «проверено чтением», либо зафиксированным в issue ручным прогоном с командой и числом. High-находок нет, Medium в скоупе нет. Два Low-предложения не блокируют и оставлены на усмотрение автора.

Вердикт: зелёный.


Материал раунда

  • Ветка: issue/557-local-toolchain, коммит c1c3743f5d3e — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 936599df3d798fa91322dbb8cffe52a045684dc3
    git log --all --format='%H %T' | grep 936599df3d79
    
  • Тело issue: 262503aba6ef449b2c9695754329d6261461bb6e2950e662179378c9299879f8
  • Вердикт конвейера: green · High 0