23 KiB
CODE-REVIEW-485-r4
Issue: #485 — presence-radar Stage 1. Этап: code (PROCESS.md §2.7). Заход r4, блокирующих циклов израсходовано 3 из 4 до этого раунда (r1 — красный, High 6/Medium 6; r2 — жёлтый, High 0/Medium 3; r3 — жёлтый, High 0/Medium 1).
Материал: ветка issue/485-radar-presence, git log --oneline origin/dev..HEAD /
git diff origin/dev...HEAD, HEAD = ca25e910876067a067b303497022d6a9a988b39f
("test: keep radar bundle ratchet outside gzip noise") — рабочая копия уже на этом
SHA, git fetch/checkout не выполнялись.
Почему разбор полный, а не по дельте (PROCESS §2.10)
Вердикт r3 (комментарий issue, 2026-09-09T00:32:39Z) называет HEAD f474793d.
Этот SHA мёртв в текущей истории: git cat-file -t f474793d9b5f... →
fatal: could not get object info. Проверка показала тот же паттерн, что r3 уже
описала для перехода r2→r3:
- committer-время всей цепочки коммитов ветки, от
78afb689(спецификация) доca25e910(правки этого раунда), сбито в узкий интервал03:50:50–04:01:00 +030009.09 — сквозная переигровка ветки, а не точечныйamend; git merge-base origin/dev HEAD=3434747d("build: prepare v1.73.0-beta.7 candidate") — это САМ ТЕКУЩИЙ tiporigin/dev. Прежний merge-base на момент r3 был687056f3, и687056f3— предок3434747d(git merge-base --is-ancestorподтверждает), то есть между r3 и этим раундомdevпродвинулся ещё дальше: влился #493 (13a0bfdf/578cae52/260435e4) и был вырезан кандидатv1.73.0-beta.7(3434747d);- пересечение путей
git diff --name-only 687056f3..3434747dс путями ветки радара (git diff --name-only origin/dev...HEAD) непустое и включает продуктовый код, не только бандл/доки:custom_components/houseplan/websocket_api.py,custom_components/houseplan/validation.py,src/houseplan-card.ts,src/houseplan-editor-runtime.ts,tests_backend/test_ha_websocket.py.
Это прямо названный в процессе триггер («ребейз на ушедший вперёд dev — после
ребейза это другой код»), причём websocket_api.py — тот самый файл находки M-2
из r2/r3. Поэтому ниже — полный повторный разбор пересечённых файлов плюс
построчная перепроверка присутствия всех прежних находок, а не только диффа по
единственной находке r3.
Как проверялось
Прочитан весь diff origin/dev...HEAD (см. состав ниже) и построчно —
пересечение путей ребейза с веткой радара, и оба новых коммита раунда:
3f0b4aee ("test: cover radar radial calibration guard" — закрывает M-1 из r3)
и ca25e910 ("test: keep radar bundle ratchet outside gzip noise" — калибровка
bundle:budget после ребейза на #493 + пересборка бандла/отпечатка скриншотов).
Пересечение путей ребейза с веткой радара проверено построчно, конфликтов смысла нет:
custom_components/houseplan/websocket_api.py:1459— фикс M-2 из r2/r3 (**({"radar_stage1_api": 1} if rt.radar_coordinator is not None else {})) на месте, не затронут посторонними правками #493 в этом же файле.custom_components/houseplan/validation.py— упоминанияradar(vol.Optional ("radar"), строки 1818-1820, 2183) не изменились по смыслу; правки #493 в этом файле относятся к summary-panel, не пересекаются построчно.src/houseplan-editor-runtime.ts—radarAfterBindingChange(фикс H6 r1) и оба места вызова (13015, 13071 в r3; строки сместились из-за вставки summary-panel-кода, но логика та же —...radarAfterBindingChange(d.radar, d.radarTouched, d.radarRemove)) не тронуты.src/houseplan-card.ts— синхронное закрытие диалога маркера (фиксd433b64cиз r2, регрессия общего пути) на месте:_closeMarkerDialog()вhouseplan-editor-runtime.ts:7331-7335по-прежнему разветвляется наisDirty(), синхронный путь не стал асинхронным повторно.tests_backend/test_ha_websocket.py— тест M-2 (test_config_get_advertises_radar_only_while_coordinator_is_ready, строки 40-58) присутствует, не переписан слиянием с #493-тестами (test_summary_panel.py— отдельный файл, не тот же диапазон строк).
Прогнанные гейты:
| Гейт | Результат |
|---|---|
npx tsc --noEmit |
зелёный |
node --test test/*.test.mjs (полный фронтенд-набор) |
2378 passed, 1 skipped, 0 failed (2379 всего, npm test) |
npm run build |
собрано (rollup, 19.1s) |
npm run bundle:sync |
dist → custom_components/houseplan/frontend → demo/srv/assets; git status чист и до, и после — бандл воспроизводим побайтно |
npm run bundle:budget |
0 — initial View 298 523 Б gzip / потолок 299 100 (±2000), запас до потолка 577 Б, до бюджета 301 066 Б — 2543 Б (предупреждение о малом запасе — унаследованный долг #367→#474, не новый) |
node scripts/check-docs.mjs |
«Documentation checks passed» — отпечаток скриншотов актуален после пересборки в ca25e910 |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
60 прямых совпадений + 1 зарегистрированная связь — та же картина, что в r3 (артефакт полного диффа branch-vs-dev, не нового кода раунда: см. обоснование ниже) |
node demo/smoke_radar_setup.mjs |
OK, все поля отчёта true |
node demo/smoke_radar_live.mjs |
OK, все поля отчёта true |
python3 -m pytest tests_backend/test_radar_geometry.py tests_backend/test_radar_validation.py -q (системный Python 3.12 + pip install pytest voluptuous) |
57 passed (14 geometry + 43 validation — на один больше r3: новый тест M-1) |
Ручная мутация нового гварда-теста M-1 (backend): radial_ok and rms<=20… → rms<=20… в radar_geometry.py:199 |
новый тест test_two_point_fit_rejects_hidden_radial_mismatch красный (DID NOT RAISE); откат подтверждён (git diff --stat чист) |
Ручная мутация того же гварда (TS): radialOk && rmsCm<=20… → rmsCm<=20… в radar-geometry.ts:79, пересборка test-build (tsc -p tsconfig.test.json && fix-test-build.mjs) |
новый тест «rejects a reference with a hidden radial mismatch» красный (1 fail из 7); откат подтверждён |
git diff --name-only 687056f3..3434747d ∩ git diff --name-only origin/dev...HEAD |
непустое пересечение, разобрано построчно выше — не находка, конфликтов смысла нет |
Merge-conflict маркеры (<<<<<<</=======/>>>>>>>) по всему дереву |
не найдены |
Трейлеры новых коммитов раунда (git show -s --format=full) |
3f0b4aee/ca25e910 — Issue: #485, User-Visible: no на обоих (тест-фикс и внутренняя калибровка бюджета — без видимого пользователю поведения, changelog не требуется) |
npm run invariants |
не запускался — diff не трогает рёбра комнат/толщину/layout/marker.space/open_spans (grep по всем изменённым backend/frontend/тестовым файлам раунда и по пересечению с ребейзом — 0 совпадений) |
CI Validate на точном SHA ca25e910 (34297455739) |
«Бэкенд: pytest в Home Assistant» — completed/success, 823 passed, 2 skipped (полный HA-харнесс, включая test_ha_radar*.py/test_ha_websocket.py); «Фронтенд: типы, юниты, мутанты, синхрон бандла» — success; HACS/hassfest/preflight — success; «Мутанты по диффу» (3 job) — ещё in_progress на момент публикации; смоки/perf/golden — skipped (heavy-гейт не по расписанию для обычного пуша, ожидаемо) |
Зелёного полного Validate на этом SHA пока нет (мутационные job не завершены) — как и предупреждает задание, гейты выше прогнаны самостоятельно. В отличие от r3, здесь уже есть завершённый успешный прогон job «Бэкенд: pytest в Home Assistant» на точном SHA — это закрывает главный пробел r3 («Чего не проверял»: HA-харнесс недоступен в песочнице ревью) фактическим результатом, а не предположением автора.
Находки
High — нет
Medium — нет
Единственная Medium-находка r3 (M-1: четвёртый гвард решателя radial_ok/
radialOk не покрыт тестом) закрыта предметно — см. таблицу ниже.
Low (унаследовано, не растёт)
- Нет golden-сцены для радара (
find demo/golden -iname "*radar*"— по-прежнему пусто). Не эскалирую третий раунд подряд по той же логике r2/r3: защитные утверждения (частичное здоровье, отрисовка зон, гейт допустимости калибровки, отказ неподдерживаемых команд) уже доказаны прицельными негативными тестами. - Планка 4 кадра/с общая на координатор, а не «на радар» буквально
(
radar.py:236-256, поля_pending/_last_publish_monotonic— координатора). Не новый риск, не изменился в этом раунде.
Закрытие раунда r3
| Находка r3 | Чем закрыта | Где видно |
|---|---|---|
M-1 — четвёртый числовой гвард решателя (radial_ok/radialOk, согласованность радиальной дистанции) не покрыт ни одним тестом ни в backend, ни в TS-двойнике |
tests_backend/test_radar_geometry.py::test_two_point_fit_rejects_hidden_radial_mismatch (коммит 3f0b4aee) — референс с верным направлением, но радиальной ошибкой 25 см (RMS 17.68/индив. ошибка 25,0 — оба проходят), требует ValueError(invalid_selection); симметричный тест «rejects a reference with a hidden radial mismatch» в test/radar-geometry.test.mjs |
Подтверждено ручной мутацией на обеих сторонах в этом раунде (таблица гейтов выше): снятие radial_ok/radialOk из условия красит ровно эти новые тесты и ничего больше (DID NOT RAISE на backend, 1 fail из 7 на TS) |
Унаследовано из r3 (без повторного разбора по существу — только подтверждение присутствия после ребейза)
Документ: docs/reviews/CODE-REVIEW-485-r3.md, SHA материала r3 f474793d9b5f
(мёртв после ребейза — см. «Почему разбор полный» выше; дерево 33297f22…).
- Все 6 High из r1 (partial-health, teardown/await race, LD2450 same-device
guard, no-contour warning vs refusal, zones render, radar binding-change
wipe) — подтверждены присутствующими построчным чтением тех же мест кода,
что называл r3 (
radar.py:552-554,radar.py:106-151,radar_validation.py:147-180,radar-render.ts:18-24,radar-editor.ts:65-75/houseplan-editor-runtime.tsвызовыradarAfterBindingChange). Ни один из этих файлов не входит в пересечение путей ребейза этого раунда (687056f3..3434747d), кромеhouseplan-editor-runtime.ts— который перепроверен заново в этом раунде (раздел «Как проверялось»), не унаследован вслепую. - M-2 (капабилити
radar_stage1_apiусловна наradar_coordinator) и M-3 (radar.bad_referencesреально достижим и промаршрутизирован отдельно отradar.bad_fit) из r2 — оба файла (websocket_api.py,radar-setup.ts/radar-geometry.ts) перепроверены заново в этом раунде (не унаследованы вслепую), потому чтоwebsocket_api.pyвходит в пересечение путей ребейза — см. «Как проверялось». - i18n-структура (
en/ru/de/fr, 1363/1363 ключей без расхождений) — принято без повторного построчного обхода: diff этого раунда не касаетсяsrc/i18n/*.json, а полный фронтенд-наборnpm test(2378/2379, включаетtest/i18n.test.mjs/test/i18n-dead-keys.test.mjs) прогнан заново и зелёный. - Права и лимиты координатора (ACL на подписку/инспекцию,
MAX_RADARS=32, лимиты подписок) — приняты без повторного чтения:radar.py/radar_websocket.pyне входят в пересечение путей ребейза этого раунда и не менялись диапазоном коммитов3f0b4aee/ca25e910. single-source-numbers(радар не создаёт вторых источников чисел) — принято по тому же аргументу: код рендера/диагностики не менялся, тест прогнан заново в составе полного набора и зелёный (3/3 внутри 2378).
Что проверено сверх таблиц
- Легитимность повышения потолка
INITIAL_VIEW_GZIP_CEILING(299 000→299 100, коммитca25e910). Не тихое ослабление гейта: фактическая пересборка в этой песочнице дала 298 523 Б — под новым потолком на 577 Б, что и обосновывает комментарий коммита («восстановить >500 Б запаса шума по обе стороны существующей 2 кБ ratchet-полосы» после того, как ребейз на #493 сдвинул gzip-размер до 298 527 Б — почти вплотную к старому потолку 299 000). Проверено самостоятельной пересборкой, а не принято на слово из текста коммита. - Трейлеры и changelog.
e9fb255a(«fix: close radar review gaps», предок обоих новых коммитов раунда,User-Visible: yes) правит оба changelog в себе же — подтвержденоgit show --stat. Оба новых коммита этого раунда (3f0b4aee,ca25e910) —User-Visible: no,Issue: #485— корректно: ни один не меняет видимое пользователю поведение. - Инварианты геометрии неприменимы: ни пересечённые с ребейзом файлы, ни
два новых коммита раунда не касаются рёбер комнат/толщины/
layout/marker.space/open_spans.
Чего не проверял
- Полный
demo/smoke_*.mjs(234 файла) — не гонял.smoke-selectвернул 60 прямых совпадений, но это артефакт того же полного диффаorigin/dev...HEAD, что видели r2/r3 (символы_markerDialog/_editorRuntime/_mode/_cfgRev/cellCm— из файлов, которые фактический дифф этого раунда (3f0b4aee/ca25e910) не трогает вовсе: они меняют толькоtest/radar-geometry.test.mjs,tests_backend/test_radar_geometry.py, бандл-артефакты,scripts/bundle-budget.mjsиdocs/images/screenshots.json). Два прямых радар-смока (smoke_radar_setup.mjs,smoke_radar_live.mjs) прогнаны и зелёные; остальные 58 — код диалога/навигации, не тронутый этим раундом и уже прогнанный прицельно в r2/r3. - Мутационный CI-гейт «Мутанты по диффу» (3 job) — не завершён на момент
публикации документа (in_progress). Не жду: ручная мутация конкретно тех
строк, что добавил этот раунд (
radial_ok/radialOk), выполнена лично и задокументирована в таблице гейтов — это прямая, а не косвенная замена той же роли для нового кода. Если по завершении job «Мутанты по диффу» на этом SHA окажется красным на не рассмотренной мной строке — это отменяет вердикт независимо от прочтения кода. python -m pytest tests_backend -q(полный набор, без HA) — не гонял весь модуль, толькоtest_radar_geometry.py+test_radar_validation.py(57 тестов, прямо относящихся к дельте и к M-1). Полный backend-набор вне HA харнесса r3 уже прогоняла целиком (451 passed) на код, который этот раунд не меняет за пределами двух перечисленных файлов.npm run golden:verify— не прогонял: diff не трогает рендер; r2/r3 уже зафиксировали отсутствие golden-сцен для радара как Low, не растущий третий раунд подряд.- Perf-профили — не запускал: diff не касается
iso-*/live-*/render-*/houseplan-render-lifecycle.ts, AC стадии не требуют perf-профиль отдельным доказательством. node scripts/mutation-gate.mjsлокально — не запускал; см. пункт про CI-джобы «Мутанты по диффу» выше — сам гейт ещё выполняется в CI на этом SHA.
Итог
0 High, 0 Medium. Единственная Medium-находка r3 (четвёртый числовой гвард
solve_two_point/solveRadarTwoPoint без теста) закрыта точечным тестом на
обеих сторонах, подтверждена ручной мутацией (гвард снят → новый тест красный
на backend и на TS). Ребейз на ушедший дальше dev (влился #493, вырезан
кандидат v1.73.0-beta.7) потребовал полного, а не дельта-разбора; пересечение
путей с продуктовым кодом (websocket_api.py, houseplan-editor-runtime.ts,
houseplan-card.ts и др.) проверено построчно — конфликтов смысла ребейз не
внёс, все находки r1/r2 на месте. CI Validate на точном SHA уже подтвердил
полный HA-харнес бэкенда (823 passed/2 skipped) — это закрывает главный пробел
предыдущего раунда независимым прогоном, а не заявлением автора. Вердикт —
зелёный.
Материал раунда
- Ветка:
issue/485-radar-presence, коммитca25e9108760— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
ca12394f18186c16362c75919099036c3213a5f2git log --all --format='%H %T' | grep ca12394f1818 - ТЗ
docs/specs/485-radar-presence-stage1.md, блоб322f040bfcfdba41802caad302da99188ab9d710git log --all --find-object=322f040bfcfdba41802caad302da99188ab9d710 -- docs/specs/485-radar-presence-stage1.md - ТЗ
docs/specs/485-radar-presence-stage2.md, блобb403e73fc26ba0ffb97be4ffa73be067d179880fgit log --all --find-object=b403e73fc26ba0ffb97be4ffa73be067d179880f -- docs/specs/485-radar-presence-stage2.md - ТЗ
docs/specs/485-radar-presence-stage3.md, блобc65e28389d762216c47f00249bdb751f3c9d5082git log --all --find-object=c65e28389d762216c47f00249bdb751f3c9d5082 -- docs/specs/485-radar-presence-stage3.md - ТЗ
docs/specs/485-radar-presence.md, блоб3297ed0140265ad86f2072a40cb9bf8097c0674dgit log --all --find-object=3297ed0140265ad86f2072a40cb9bf8097c0674d -- docs/specs/485-radar-presence.md - Вердикт конвейера:
green· High 0