diff --git a/docs/reviews/CODE-REVIEW-548-r1.md b/docs/reviews/CODE-REVIEW-548-r1.md new file mode 100644 index 00000000..599af5d8 --- /dev/null +++ b/docs/reviews/CODE-REVIEW-548-r1.md @@ -0,0 +1,157 @@ +# 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) + +## Скоуп + +Два коммита: + +1. `76d8017e` — основная реализация: отдельная reusable job `geometry_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`). +2. `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](https://github.com/Matysh/houseplan-card/actions/runs/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»: + ``` + geometry parity: 15 scenarios executed; TS/Python verdicts identical + ``` + Это прямое доказательство AC1/AC4 — не «verified» без команды, а конкретный прогон с конкретным выводом. +- Run [34746690349](https://github.com/Matysh/houseplan-card/actions/runs/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` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `e062bddd5488d8aef8049c726d85fedee1ea2a8e` + ``` + git log --all --format='%H %T' | grep e062bddd5488 + ``` +- Тело issue: `583f18e1a114a5eaab171e7394d7c74ddcb51ca9741beb2e9126afefc27f44bb` +- Вердикт конвейера: `green` · High 0