18 KiB
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 — правятся на усмотрение автора либо снимаются с этой записью, отдельного цикла не открывают:
- Low. CLI-разбор
--python/--python=вscripts/toolchain-pins.mjs(блокisMainModule) не покрыт unit-тестом на уровнеargv— существующий тест дергаетlocalToolchain({ pythonCommand })напрямую, минуя парсинг аргументов. Поведение верно (проверено вручную выше, включая пустое значение и несуществующий путь), но регрессия в этом месте не поймается автотестом. Дешёвое улучшение — не блокирует. - 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— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
936599df3d798fa91322dbb8cffe52a045684dc3git log --all --format='%H %T' | grep 936599df3d79 - Тело issue:
262503aba6ef449b2c9695754329d6261461bb6e2950e662179378c9299879f8 - Вердикт конвейера:
green· High 0