18 KiB
CODE-REVIEW-548-r1
Issue: #548 — CI: TS/Python parity геометрии должна исполняться на чистом backend runner, а не skip
Трек: инфраструктурный (§1 AGENTS.md/PROCESS.md) — ни одного файла класса A в диапазоне
Материал: git log --oneline origin/dev..HEAD, git diff origin/dev...HEAD
SHA материала: b15d357fa2d9b4596864905008be82c7239f47cf (HEAD ветки issue/548-backend-parity, рабочая копия проверена на нём — git rev-parse HEAD совпадает)
Заход: r1 · блокирующих циклов израсходовано 0 из 4 (первый заход этапа code-review; предыдущий прогон конвейера на 76d8017e остановился на красном Validate до чтения кода и цикл не потратил — комментарий issue от 2026-09-13T07:56:49Z)
Скоуп
Два коммита:
76d8017e— основная реализация: отдельная reusable jobgeometry_parityвvalidate.yml, узкийtsconfig.junction-parity.json, новый исполняемый fail-closed харнессtests_backend/junction_parity.py, перенос 15 сценариев из старого optional pytest-теста вtest/fixtures/junction-limits-parity.json, удаление старогоtest_parity_with_the_frontend_checks(тихий skip), новыйtests_backend/test_junction_parity.py, обновлениеclassify-changes.mjs,check-inputs.mjs,gate-reuse.mjs,ci-proof.mjs, новый мутационный свидетельjunction-limit-ts-python-parity-drift, правки документации (AGENTS.md,PROCESS.md,docs/ARCHITECTURE.md,docs/DEVELOPMENT.md).b15d357f— точечный фикс:evaluateCiProofне принимал reuse-заявкуgeometry_parity(«unsupported proof mode»), найдено предыдущим (несостоявшимся) прогоном Validate с мутантами; добавленgeometry_parityв разрешённый список reuse-режимов и регрессионный тест на верифицированный источник.
Файлы только классов B/C/D-адъютант (test/**, tests_backend/**, scripts/**,
.github/workflows/validate.yml, tsconfig.junction-parity.json, docs/**,
AGENTS.md, PROCESS.md) — ни одного src/** или custom_components/**/*.py
продуктового файла. Инфраструктурный трек применён корректно.
Продуктовая геометрия не менялась (заявлено автором и подтверждено чтением:
src/junction-limits.ts и custom_components/houseplan/junction_limits.py
отсутствуют в диапазоне изменённых файлов).
Как проверялось
| Гейт | Статус | Как |
|---|---|---|
npx tsc --noEmit |
green | прогнан локально на SHA b15d357f |
npm test (полный) |
green | не перегонял — уже подтверждён зелёным Validate на этом SHA (см. ниже); вместо этого прогнал целевой поднабор |
Целевой node --test (ci-proof, check-inputs, classify-changes, gate-reuse, validate-workflow, mutation-gate) |
green, 129/129 | node --test test/ci-proof.test.mjs test/check-inputs.test.mjs test/classify-changes.test.mjs test/gate-reuse.test.mjs test/validate-workflow.test.mjs test/mutation-gate.test.mjs |
npm run build + сверка копий бандла |
не гонял | diff не касается src/**/rollup.config.mjs; бандл не мог измениться, и это уже подтверждено зелёным Validate на точном SHA |
node scripts/check-docs.mjs |
не гонял, не нужен | diff не касается src/** |
node scripts/model-invariants.mjs |
не гонял, не нужен | геометрия/ссылки на неё не менялись — менялся только тестовый харнесс вокруг уже существующих зеркал |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
прогнан | «Исполняемого frontend-диффа нет — Browser-smoke этим диффом не выбираются». Смоки не нужны |
python -m pytest tests_backend -q |
не гонял (pytest не установлен в среде ревьюера, .venv-backend не провизирован) |
вместо этого руками воспроизвёл обе новые проверки test_junction_parity.py вызовом их тела через python3 -c (см. «Защитные AC») и реальным прогоном tests_backend/junction_parity.py в отдельном git worktree |
node scripts/process-gate.mjs --issues |
green (1 WARN, ожидаемый) | «инфраструктурный диапазон (#562): статусная метка не требуется до S7-code-review» |
| Реальный прогон новой job на чистом runner | воспроизведено на живом CI | см. ниже |
Дешёвые гейты уже подтверждены зелёным Validate на этом SHA:
https://github.com/Matysh/houseplan-card/actions/runs/34746690349 (типы/юниты/сборка/бандл-синхрон/no-new-any) — поэтому полный npm test/npm run build не перегонялись, бюджет раунда потрачен на чтение кода и целевые прогоны.
Живая проверка на реальном CI (не из отчёта автора)
Проверил напрямую через gh run view, а не поверил хендоффу:
- Run 34746521147 (SHA
b15d357f): job «Геометрия: TS/Python parity исполнена» реально выполнилась (не reuse) —npm ci,tsc -p tsconfig.junction-parity.json,fix-test-build.mjs,python tests_backend/junction_parity.py --build-dir=test-build/junction-parity, все шаги green. Лог шага «Сравнить настоящие TS/Python validators»:Это прямое доказательство AC1/AC4 — не «verified» без команды, а конкретный прогон с конкретным выводом.geometry parity: 15 scenarios executed; TS/Python verdicts identical - Run 34746690349 (тот же SHA, повторный push-триггер): job
geometry_parity—conclusion: skippedиз-за reuse (тот же контент уже проверен), а job «Доказательство выполненных проверок» (proof) — success. Это подтверждает вживую, что фиксb15d357f(принятиеgeometry_parityв списке reuse-режимовevaluateCiProof) реально работает на настоящем прогоне, а не только в юнит-тесте.
Защитные AC — таблица «чем краснеет» (§2.7)
| AC | Чем доказан | Чем краснеет |
|---|---|---|
| AC1: чистый runner исполняет parity реально; отсутствие prerequisites — setup failure, не skip | tests_backend/test_junction_parity.py::test_missing_typescript_build_is_a_setup_failure_not_a_skip + реальный прогон job на CI (лог выше) |
Воспроизвёл сам: python3 -c с вызовом frontend_rules(spaces, build_dir=<несуществующий путь>) → FileNotFoundError: geometry parity prerequisites are missing: …, не pytest.skip. Старый test_parity_with_the_frontend_checks с pytest.skip(...) физически удалён (diff подтверждён чтением) |
| AC2: намеренное расхождение одного TS/Python правила красит guard | Мутант junction-limit-ts-python-parity-drift в scripts/mutation-gate.mjs, guard = npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs && python3 tests_backend/junction_parity.py --build-dir=test-build/junction-parity |
Воспроизвёл сам в отдельном git worktree (не в рабочей копии материала): применил патч MIN_NODE_DISTANCE_CM = 5 → 4, собрал TS через tsc -p tsconfig.junction-parity.json, прогнал junction_parity.py — получил AssertionError: TypeScript/Python geometry parity diverged: {"distance-4": {"typescript": [], "python": ["distance"]}}, exit code 1. Совпадает буквально с тем, что заявил автор в хендоффе («обнаружено на сценарии distance-4») — я это не переписал с его слов, а получил тем же прогоном независимо. Worktree удалён после проверки, рабочая копия чистая |
| AC3: selection/reuse учитывают обе стороны, fixture, runtime/pins | test/classify-changes.test.mjs («#548: parity выбирается по обоим зеркалам…»), test/check-inputs.test.mjs («§8.1 представители» + «§8.1 обратная проба») |
Прогнал: node --test test/classify-changes.test.mjs test/check-inputs.test.mjs — green. Тест на обратную пробу явно проверяет, что src/houseplan-card.ts и custom_components/houseplan/websocket_api.py НЕ входят в manifest geometry_parity |
| AC4: результат CI явно показывает исполненный сценарий, а не только число тестов | Job печатает geometry parity: N scenarios executed; TS/Python verdicts identical; ci-proof.mjs требует точное имя job Геометрия: TS/Python parity исполнена (JOB_RULES.geometry_parity) |
Подтверждено логом реального прогона (см. выше) — вывод буквально такой |
Reuse-заявка geometry_parity не проходит без верифицированного источника (фикс b15d357f) |
test/ci-proof.test.mjs: «#548: geometry parity reuse is accepted only with a verified source job» |
Прогнал: тест сам содержит отрицательную пробу (reuseRuns: new Map() → status === 'failed') — воспроизводить руками нечего, тест уже параметризован обоими исходами и я убедился, что оба ветвления присутствуют и осмысленны |
Проверено чтением, не исполнением
- Точность миграции всех 15 сценариев из старого inline pytest-теста в
test/fixtures/junction-limits-parity.json: построчно сверил каждый кейс (включая тонкие —debris-nodeс сырым-1e-8в units,collinear-forkсunits_offset: [0, 1e-9]поверхcm-точки) с оригинальными Python-выражениямиcm(...)/ray(...)в удалённом кодеtests_backend/test_junction_limits.py(версияorigin/dev). Расхождений не нашёл — числа воспроизведены байт-в-байт по семантике, не только по количеству кейсов. tsconfig.junction-parity.jsonкомпилирует толькоsrc/junction-limits.tsкак root, но TS транзитивно подтягивает и эмититspace-geometry.tsчерез реальный import (import { GRID_STEP_N } from './space-geometry') — подтверждено и структуройtsconfig, и тем, что job реально получает оба файла вtest-build/junction-parity/(лог CI-прогона это показывает: harness падал бы наFileNotFoundError, если быspace-geometry.jsне собрался, а он собрался).scripts/fix-test-build.mjsне менялся содержательно (просто уже обходит всё деревоtest-build/рекурсивно) — новый выходной каталогtest-build/junction-parity/подхватывается без правок скрипта.- Не тронут
backend-job (обычный HA pytest) — егоneeds/шаги не изменились; требование issue «не возвращать всему backend зависимость от всего UI ради одного теста» выполнено буквально:geometry_parity— отдельная job сneeds: [changes, reuse], без зависимости отfrontend. - Trailer-дисциплина: оба коммита несут
Issue: #548иUser-Visible: no; changelog не тронут — корректно, поведение продукта не менялось. process-gate.mjs --issues— green, единственный WARN ожидаемый (инфраструктурный трек безS*до первого code-review, что уже не актуально — меткаS7-code-reviewуже стоит).
Находки
Нет. High: 0, Medium: 0, Low: 0.
Чего не проверял и почему
- Полный
npm testиnpm run buildцеликом — не гонял, положился на зелёный Validate на точном SHA (https://github.com/Matysh/houseplan-card/actions/runs/34746690349), дополнительно прогнав целевой поднабор (129 тестов) руками. python -m pytest tests_backend -qцеликом — в среде ревьюера нетpytestи нет.venv-backend(он провизируется только для облачных агентов). Вместо полного прогона: (а) сверил зелёныйbackend-job на предыдущих прогонах ветки не требовался для этого диапазона, так как правки вtests_backend/**не меняют HA-специфичные тесты; (б) вручную выполнил тела обеих новых функцийtest_junction_parity.pyчерезpython3 -c— обе ведут себя как заявлено.- Полный набор мутантов (
scripts/mutation-gate.mjsцеликом, все ~256 мутантов) — не гонял, это ночной/relase-гейт, а не гейт ревью (§8). Один целевой мутант (junction-limit-ts-python-parity-drift) воспроизведён руками персонально. - Golden/скриншоты/perf-смоки — diff не касается
src/**/визуального вывода, не применимо. npm run model-invariants— diff не меняет геометрию, только тестовый харнесс поверх неизменных зеркал; неприменимо.
Вердикт
Зелёный. Задача узкая, полностью в скоупе issue, инфраструктурный трек применён
верно (ни одного файла класса A), все четыре AC issue доказаны — три из них я
лично воспроизвёл (позитивный сценарий на живом CI, негативный сценарий через
собственноручную мутацию в отдельном worktree, классификацию через целевые
юнит-тесты), а не принял со слов автора. Отдельный точечный фикс b15d357f
исправляет ровно то, что назвал предыдущий (нерасходующий цикл) прогон Validate,
и сам доказан тестом с положительной и отрицательной ветками.
Материал раунда
- Диапазон:
origin/dev..HEAD, коммиты76d8017e,b15d357f. - SHA материала ревью:
b15d357fa2d9b4596864905008be82c7239f47cf. - Дерево рабочей копии на момент подведения итогов совпадает с
git rev-parse HEADна момент начала разбора — новых коммитов в ходе ревью не появилось.
Материал раунда
- Ветка:
issue/548-backend-parity, коммитb15d357fa2d9— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
e062bddd5488d8aef8049c726d85fedee1ea2a8egit log --all --format='%H %T' | grep e062bddd5488 - Тело issue:
583f18e1a114a5eaab171e7394d7c74ddcb51ca9741beb2e9126afefc27f44bb - Вердикт конвейера:
green· High 0