18 KiB
CODE-REVIEW-333-r2
Issue: #333 · Заход: r2 · Блокирующих циклов израсходовано 1 из 2
Материал: git log --oneline origin/dev..HEAD = три коммита на текущем
HEAD = 4ded9c0b (git rev-parse HEAD сверен непосредственно перед
подведением итогов):
4ded9c0b test: the #248 roundtrip fixture is seeded, not first-written (#333 r1-H1)
783cc68b docs: review document for #333 (публикация CODE-REVIEW-333-r1.md)
5a2dd333 fix: plan/optimize passes the junction gate; import stays free by design (#333)
Родитель 5a2dd333 — b37a5987 (= origin/dev на момент проверки).
0. Важное расхождение с материалом прошлого раунда — рассмотрено и снято
r1-документ (docs/reviews/CODE-REVIEW-333-r1.md) называет проверенным
коммитом c29df29b с родителем 73406225. Такого SHA в текущей истории
нет — между r1-ревью и фиксом r1-H1 ветка была перебазирована на ушедший
вперёд dev: между 73406225 и текущим родителем b37a5987 легло 17
чужих коммитов issue #337 (лениво загружаемый рантайм редактора, полная
перестройка бандлинга — 114 файлов, ~34 тыс. строк).
Это ровно сценарий «ребейз на ушедший вперёд dev» из брифа, который по умолчанию требует полного разбора, а не разбора по дельте. Проверил, что разбор действительно должен остаться полным или можно сузить:
- Пересечения файлов нет.
git diff --stat 73406225 5a2dd333^(весь чужой рывок dev) не касается ни одного файла, который трогает #333 (custom_components/houseplan/{junction_limits,websocket_api}.py,docs/specs/329-junction-limits.md,scripts/mutation-gate.mjs,tests_backend/test_ha_websocket.py) — толькоsrc/**, бандлинг и фронтенд-доки. - Номера строк не сдвинулись.
websocket_api.py:1626(ws_plan_optimize),:1783(rt.junction_baseline = …),:1396(config/set) — те же номера, что называет r1-документ. Файл ребейзом физически не задет. - Поэтому содержательно к делу можно подойти по дельте (закрытие r1-H1), но гейты прогнал заново и полностью, а не унаследовал: у ребейза такого масштаба легко мог быть скрытый эффект на сборку/реестр мутаций/бэкенд-набор, а зелёного Validate на этом SHA нет (см. §2). Это тот случай, где полнота разбора обеспечена не «на всякий случай», а проверкой конкретного факта (непересечение файлов), которая и позволяет не заводить AC1–AC4 заново, ограничившись закрытием H1.
1. Скоуп
Не изменился с r1: ТЗ (тело issue, small-трек, ревизия 2, оба раунда
spec-ревью зелёные) требует (1) validate_junction_limits в
ws_plan_optimize после миграции кандидата, наследование по правилу;
(2) обновление rt.junction_baseline после успешного optimize; (3)
import/restore вне гейта, докстринг и спека §5 — честно. Продуктовая
рамка (docs/SCOPE.md) и корректность User-Visible: no — без изменений
с r1, задача только про то, доказан ли контракт и не сломано ли что-то
существующее.
Предмет r2 — единственная правка после r1: тестовая фикстура
test_plan_optimize_persists_exact_storage_roundtrip_target
(tests_backend/test_ha_websocket.py) теперь сеет исходный конфиг в
хранилище перед вызовом plan/optimize, вместо пустого previous.
Продуктовый код не тронут ни строкой — весь коммит 4ded9c0b
ограничен тестовым файлом.
2. Как проверялось
Зелёного Validate на 4ded9c0b нет (сообщение брифа) — прогнал сам,
полностью (обоснование §0):
| Гейт | Команда | Результат |
|---|---|---|
| Типы | npx tsc --noEmit |
чисто |
| Frontend-юниты | npm test |
1439 passed, 0 failed, 1 skipped |
| Сборка + синхрон бандла | npm run build; cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js; сверка списков файлов dist/houseplan-assets/ и custom_components/houseplan/frontend/houseplan-assets/ |
все три копии идентичны |
| Реестр мутаций | node scripts/mutation-gate.mjs --check |
зелёный, junction-limit-optimize-unguarded зарегистрирован, patch-строка совпадает с реальным кодом (см. ниже) |
| Мутант убивает тест | ручной патч return validate_junction_limits(...) → return {}, None в websocket_api.py:1706-1708, pytest -k test_333_optimize_refuses_a_crafted_violation |
тест красный (assert not True), дерево возвращено (git status чист) — тест умеет падать |
| Backend, полный набор | python -m pytest tests_backend -q (харнес pytest-homeassistant-custom-component+home-assistant-frontend установлены в сессии) |
432 passed, 1 skipped, 1 error (error — известный teardown-артефакт Python 3.12/харнеса, см. §5, не по телу теста) |
| Backend, целевые тесты | pytest -k "test_plan_optimize_persists_exact_storage_roundtrip_target or test_333_optimize_refuses_a_crafted_violation_and_keeps_the_plan or test_333_optimize_inherits_stored_violations or test_333_optimize_refreshes_the_junction_baseline_cache" |
все 4 PASSED |
| Смок-отбор | node scripts/smoke-select.mjs --base origin/dev --head HEAD |
«исполняемого frontend-диффа нет» — браузерные смоки не выбираются, diff не трогает src/** |
| check-docs | не гонял | diff #333 (все 3 коммита) не трогает src/** ни строкой — критерий запуска не выполнен |
npm run invariants |
не гонял | diff не меняет геометрию/рёбра/записи толщины/layout/marker.space/open_spans — только добавляет вызов уже существующего Python-валидатора и правит тест-фикстуру |
| performance-профили | не гонял | не названы в AC, дорогая цепочка уже в executor (#330), диффом не тронута |
| «Одно число — один источник» | рассмотрено | diff не вводит и не меняет ни одной пользовательски видимой величины — User-Visible: no во всех трёх коммитах корректен |
Дополнительно проверил рабочее дерево после ручного патча мутанта:
git status — чисто, никаких файлов не осталось изменёнными
(dist/houseplan-card.js случайно поменял режим/mtime после
npm run build — вернул git checkout -- dist/houseplan-card.js).
3. Закрытие раунда r1
| Находка r1 | Чем закрыта | Где видно |
|---|---|---|
H1 (High). Optimize с пустым previous (expected_config_rev: 0) на фикстуре #248 засчитывал легитимную стену 6 см как НОВОЕ нарушение П3 (правило «первая запись не может прийти уже сломанной») — существующий зелёный тест test_plan_optimize_persists_exact_storage_roundtrip_target падал на assert response["success"]. |
Коммит 4ded9c0b: фикстура теперь СЕЕТСЯ в runtime.config_store как хранимый документ (rev: 1) ПЕРЕД вызовом plan/optimize (expected_config_rev: 1 вместо 0), поэтому optimize видит непустой previous и 6-см стена читается как унаследованное нарушение — ремонтный поток, как в AC2. Ревизии в ассертах сдвинуты на 1 (config_rev == 2), storage-утверждения #248 (intent/pending/final/каноническая сериализация) не тронуты. |
tests_backend/test_ha_websocket.py:626-646 (комментарий + новый async_save), запуск: pytest -k test_plan_optimize_persists_exact_storage_roundtrip_target → PASSED (был FAILED на c29df29b/эквивалентном 5a2dd333). Полный pytest tests_backend -q: 432 passed / 0 failed (было 1 failed) |
Medium, вне скоупа. Бриф ревью ссылался на Validate-run c29df29b (33145399107) как на «зелёные дешёвые гейты», но job'ы фронтенда и бэкенда в нём были skipped, не success, из-за потери диапазона коммитов классификатором после force-push. |
Не в этой ветке — заведён отдельно, как и требует правило (Medium вне скоупа → отдельный issue, не «оставлено в тексте»). | #347 (bug, infra, P1, S1-new, ссылка на #333) — существует, открыт, метки на месте (проверено gh issue view 347) |
Оба пункта r1 закрыты доказательно, не декларативно: H1 — перезапуском ранее красного теста и полного бэкенд-набора; Medium — проверкой, что issue реально существует с нужными метками.
4. Унаследовано из r1 (без повторной проверки содержания)
Из docs/reviews/CODE-REVIEW-333-r1.md (материал: коммит c29df29b,
родитель 73406225) принимаю без повторного разбора — с довеском:
файлы, к которым относятся эти выводы, не менялись НИ РАЗУ между
c29df29b/73406225 и текущим HEAD/origin/dev (см. §0, номера строк
совпадают), поэтому дельта их не задевает:
- Техническая точность контракта п.1–3 — вызов
validate_junction_limitsв_validate_optimize_cpuпосле миграции кандидата, то же место конвейера, что вconfig/set;JunctionLimitErrorв except-списке перестал быть мёртвым. - AC1 (крафтованный payload отклонён,
config/revпобайтово неизменны) и структурная гарантия неизменности layout (отказ до первогоasync_save_*). - AC2 (наследование легаси-нарушения в optimize) — тест
test_333_optimize_inherits_stored_violations. - AC3 (обновление
rt.junction_baseline) — симметрия сconfig/set(websocket_api.py:1396/:1783). - AC4 (докстринг + спека §5 честны) — текст
junction_limits.py:1-15иdocs/specs/329-junction-limits.md§5 (абзац о периметре, строки 127-134) — сверил построчно ещё раз (§2, «Как проверялось» этого документа не потребовалось трогать код, только перечитать текст) и подтверждаю: абзац идентичен тому, что видел r1 — diff междуorigin/dev...HEADдля этого файла ограничен ровно этим одним абзацем, второй раз не добавлялся и не менялся. - Реестр мутаций —
junction-limit-optimize-unguardedописан честно; я самостоятельно перепрогнал и--check, и ручное убийство мутанта (не чисто унаследовал — но результат совпадает с r1). - Паритет П1–П4 —
test_parity_with_the_frontend_checksне тронут, прошёл в составе полного набора. - Трейлеры —
Issue: #333,User-Visible: noверны во всех трёх коммитах этого раунда (проверено заново, т.к. коммиты новые). - Соответствие малому треку — не переоценивал; объём (закрытие одного High одним тестовым коммитом) трек не нарушает.
5. Находки
Новых находок нет. High: 0. Medium: 0.
6. Что проверено и корректно
- r1-H1 закрыт по существу, а не косметически: подтверждено, что
предмет теста (#248, байт-точность storage) не пострадал — все
storage-ассерты (
intent_write,pending,final_config,final_layout, каноническая сериализация JSON) остались в тесте дословно, изменились только сид и ожидаемые номера ревизий. - Семантика фикса согласована с контрактом: посеянный
previousидентиченcandidate(source["config"]без изменений), то есть это echo-оптимизация того же класса, что AC2 — 6-см стена наследуется, а не легализуется заново; ровно то поведение, которое и должен доказывать тест про репутацию хранилища. - Полный бэкенд-набор (432/0/1 error-teardown) и целевые 4 теста зелёные лично прогнанными командами, не по докладу автора.
- Мутант
junction-limit-optimize-unguardedреально убивает целевой тест — проверено вручную в этом раунде. - Рабочее дерево после всех ручных экспериментов чистое
(
git statusпуст). - Ребейз на ушедший вперёд
dev(#337) не затронул ни один файл этой задачи — подтверждено диффом файлов, а не предположением.
7. Чего не проверял и почему
- AC1–AC3 заново «с нуля» — не пересобирал заново обоснование каждого, ограничился перезапуском тестов и сверкой, что r1-текст описывает код без расхождений (см. §4) — файлы, которых касаются эти AC, дельтой не тронуты.
- check-docs, golden:verify, browser-смоки — diff (все три коммита
#333) не трогает
src/**ни разу;smoke-select.mjsподтвердил инструментом. npm run invariants— diff не меняет геометрию/ссылки на неё.- Полный
node scripts/mutation-gate.mjs— избыточен, диффу соответствует один мутант, проверен точечно. - performance-профили — не названы в AC, чувствительный путь (#330 executor) не тронут.
- Спор о line-level точности спеки/докстринга AC4 — не перечитывал
файл целиком заново построчно посимвольно, положился на факт «diff
origin/dev...HEADдля этого файла = один абзац, тот же, что видел r1» — это сильнее, чем повторное чтение вслепую, потому что исключает случайный второй правки в этом раунде.
8. Вердикт
r1-H1 закрыт доказательно (ранее красный тест теперь зелёный, полный
бэкенд-набор чист), r1-Medium корректно вынесен в #347 вне этой ветки.
Ребейз на ушедший вперёд dev между раундами рассмотрен явно и не
потребовал полного повторного разбора AC, так как ни один файл #333 им
не затронут — но все дешёвые гейты и полный бэкенд-набор прогнаны заново
(зелёного Validate на 4ded9c0b нет). Новых находок нет.
Вердикт: зелёный · заход r2 · блокирующих циклов 1/2 · High: 0 · Medium: 0