Files
houseplan-card/docs/reviews/CODE-REVIEW-486-r1.md
T
claude[bot] 1747d1f05a
Проверка (CI) / Классификация изменённых файлов (push) Successful in 26s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 44s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 24s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 31s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Failing after 45s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Проверка (CI) / Мутанты по диффу (1/3): затронутые свидетели краснеют (push) Failing after 3m59s
Проверка (CI) / Мутанты по диффу (2/3): затронутые свидетели краснеют (push) Failing after 3m58s
Проверка (CI) / Мутанты по диффу (3/3): затронутые свидетели краснеют (push) Failing after 4m6s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 6m9s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
docs: review document for #486
Issue: #486
User-Visible: no
2026-09-07 22:58:22 +00:00

34 KiB
Raw Blame History

CODE-REVIEW-486-r1

  • Issue: #486 — Панель House Plan в боковом меню Home Assistant
  • ТЗ: docs/specs/486-house-plan-panel.md @ 2821c359, ревью ТЗ зелёное — docs/reviews/SPEC-REVIEW-486-r1.md
  • Материал ревью: ветка issue/486-house-plan-panel, диапазон git diff origin/dev...HEAD
  • SHA на момент вывода вердикта: 603ee3584b8908ee3372548da1d5be907f48992f (сверено git rev-parse HEAD непосредственно перед выводом; рабочее дерево чистое)
  • Заход: r1 (первый код-ревью проход по этой задаче) · блокирующих циклов израсходовано 0/4

1. Скоуп диффа

git diff --stat origin/dev...HEAD: 114 файлов, +4856/−662. По классам PROCESS.md §1:

  • Класс A (продукт): custom_components/houseplan/__init__.py, новый panel_registration.py, frontend_registration.py, system_health.py, config_flow.py, manifest.json, strings.json, translations/{en,ru,de,fr}.json; src/houseplan-panel.ts (новый), src/houseplan-card.ts, src/styles/base.styles.ts, src/i18n/{en,ru,de,fr}.json.
  • Класс B (гейты/инструменты): rollup.config.mjs, scripts/bundle-{manifest,sync,tree,budget}.mjs, scripts/{release-prerelease,verify-houseplan-zip,mutation-gate,pre-push-gate}.mjs, demo/** (включая новый demo/smoke_houseplan_panel.mjs, 449 строк), test/** (включая новый test/houseplan-panel.test.mjs, test/bundle-sync.test.mjs, test/houseplan-zip.test.mjs), tests_backend/** (включая новый tests_backend/test_ha_panel_registration.py, 517 строк).
  • Класс C (документация): docs/{ARCHITECTURE,STATUS,TESTING,TOUCH-SUPPORT,USER-GUIDE.md,USER-GUIDE.ru.md,UX-MODES,CHANGELOG,CHANGELOG.ru}.md, README*.
  • Класс D (сгенерированное): dist/**, custom_components/houseplan/frontend/**, demo/golden/baselines/*.png — приняты коммитом 603ee358 с трейлерами Release:/Baseline-Reviewed: (см. §4).

Задача полностью соответствует заявленному полному треку: новая поверхность (sidebar panel), два entry-point'а бандла, backend lifecycle, responsive/touch-контракт.

2. Как проверялось

2.1 Дешёвые гейты — приняты по ссылке, не перегонялись

Validate зелёный на точном SHA 603ee3584b8908ee3372548da1d5be907f48992f: https://github.com/Matysh/houseplan-card/actions/runs/34167305131 (conclusion: success). Это покрывает npx tsc --noEmit, npm test, npm run build + сверку копий бандла, node scripts/check-docs.mjs — не перегонял по инструкции задания.

2.2 Тяжёлые гейты — не приняты вслепую, прослежены по истории CI ветки

В финальном прогоне на 603ee358 часть тяжёлых job (Бэкенд: pytest, Hassfest, HACS, шардированные браузерные смоки) вышли со статусом skipped — не success. Прежде чем принять это на веру, прошёл по истории прогонов ветки (gh run list --branch issue/486-house-plan-panel) и подтвердил, что «skipped» здесь означает legit reuse (scripts/gate-reuse.mjs, ключ кэша — хэш backend/frontend-relevant путей), а не «не проверялось никогда»:

Job SHA, где реально выполнился и позеленел Ссылка
Бэкенд: pytest в Home Assistant 597488f7 (push-прогон, backend-код идентичен 603ee358: последующие коммиты трогали только docs/baselines) — 706 passed, 2 skipped, полный HA harness run 34166103104, job 101877397881
Браузерные смоки, все 3 шарда + агрегатор 8bf30947 (workflow_dispatch full=true, frontend-код идентичен 603ee358) — все 3 шарда success, Смоки: все шарды зелёные success run 34166326951
Golden-кадры против принятых эталонов 603ee358 (сам финальный прогон) — success (эталоны приняты этим же коммитом) run 34167305131
Hassfest/HACS 8bf30947 (push-прогон 34166318284) — оба success (манифест не менялся после) run 34166318284
Мутанты по диффу (3 шарда) 603ee358 — success run 34167305131
Перф-смок 603ee358 — success run 34167305131

Один нюанс, проверенный отдельно: прогон 34166103104 (SHA 597488f7) сам получил статус cancelled (перебит следующим push), а его job «Мутанты по диффу (3/3)» — failure. Прочитал лог этого job: падение было не пропущенной мутацией, а check-docs.mjs, ругавшимся на неосвежённый отпечаток скриншотов (FAIL чистый прогон... ERROR screenshot source fingerprint is stale) — на этом SHA скриншоты ещё не были пересняты; это исправил следующий коммит 8bf30947 («docs: refresh screenshot provenance»), после чего check-docs зелёный во всех последующих прогонах. Не пропущенная защита, а ожидаемое промежуточное состояние между коммитами задачи.

Вывод: и backend pytest (реальный HA harness), и полный браузерный смок-набор, и golden, и мутанты по диффу — все реально выполнялись в CI на коде, совпадающем с финальным SHA, и зеленели. Это сильнее, чем «зелёный Validate», потому что не полагается на reuse-механизм как на чёрный ящик.

2.3 Что прогнал/перепроверил сам

  • node scripts/smoke-select.mjs --base origin/dev --head HEAD — 20 прямых совпадений (в т.ч. сам новый demo/smoke_houseplan_panel.mjs), 23 слабых. Полный список приложен ниже (§2.4); все прямые совпадения уже входят в подтверждённый прогон 34166326951 (все шарды зелёные), отдельно не перегонял.
  • Прочитал построчно: custom_components/houseplan/panel_registration.py (242 строки, целиком), diff frontend_registration.py, __init__.py, system_health.py, config_flow.py, manifest.json; src/houseplan-panel.ts (199 строк, целиком), diff src/houseplan-card.ts, src/styles/base.styles.ts; rollup.config.mjs, значимые куски scripts/bundle-manifest.mjs, scripts/bundle-budget.mjs, добавленные мутанты в scripts/mutation-gate.mjs; ключевые тесты tests_backend/test_ha_panel_registration.py, tests_backend/test_ha_setup.py (новые сценарии), test/bundle-assets.test.mjs (выборочно), test/houseplan-panel.test.mjs (целиком), i18n-файлы всех четырёх локалей.
  • Проверил, что заявленные защитные утверждения (identity vs generation guard, fail-soft, budget single-source) не просто описаны в ТЗ, а действительно реализованы и покрыты тестом, который умеет падать — конкретные мутации и их эффект см. §3.3.
  • Model-invariants (npm run invariants) — не прогонял: диф не трогает геометрию, layout, marker.space, open_spans или записи толщины (подтверждено чтением: единственные правки houseplan-card.ts — panelHost, _canManageConfiguration, getGridOptions, высота stage; ни одна не касается модели помещений).
  • python -m pytest tests_backend -q локально — не прогонял: в этой ревью-песочнице не установлен homeassistant (ModuleNotFoundError), а .venv-backend отсутствует. Заменил реальным CI-прогоном с полным HA harness (см. §2.2) — это сильнее, чем «verified локально» без командной строки.
  • npm run golden:verify/полный браузерный смок-набор своими руками — не прогонял: они уже честно прогнаны в CI на эквивалентном коде (§2.2), повторный прогон в этой сессии не добавил бы уверенности, только сжёг бы время на пере-сборку Chromium-стенда.

2.4 Вывод smoke-select.mjs (для протокола)

Изменено файлов src/**: 3 · символов проекта на изменённых строках: 22
Прямое совпадение (20): demo/smoke_houseplan_panel.mjs, smoke_area_relocation_safety,
  smoke_dialog_footer_width, smoke_editor_tabs, smoke_nav_persist, smoke_optional_space_model,
  smoke_preloader_lifecycle, smoke_support_feedback, smoke_version_recovery,
  smoke_danger_confirm_branches, smoke_device_position_history, smoke_dialog_modal_recovery,
  smoke_lattice_write_barrier, smoke_layout_sync, smoke_opening_preview,
  smoke_orphan_space_references, smoke_preloader, smoke_readonly_cold_start,
  smoke_space_card_bg, smoke_ws_resilience
Слабая связь (23): все по одному имени `_mode` — решение ревьюера: не прогонять,
  `_mode` — самый частый идентификатор в проекте (мода view/editor уже покрыта прямыми
  совпадениями `smoke_houseplan_panel`/`smoke_editor_tabs`/`smoke_nav_persist`).

Все 20 «прямых» уже зелёные в реальном CI-прогоне 34166326951 (полные 3 шарда) на коде, идентичном 603ee358. Не перегонял вручную.

2.5 Таблица гейтов

Гейт Статус Как подтверждён
tsc --noEmit, npm test, npm run build+сверка бандла ✅ принят по ссылке Validate 603ee358, success
check-docs.mjs ✅ принят по ссылке Validate 603ee358, job docs, success
no-new-any.mjs ✅ принят по ссылке входит в job «Фронтенд» Validate 603ee358
smoke-select + выбранные браузерные смоки ✅ подтверждено CI-прогоном run 34166326951, все 3 шарда success (эквивалентный код)
golden:verify ✅ Validate 603ee358, success
Мутанты по диффу (backend+frontend+bundle) ✅ Validate 603ee358, 3/3 success; прочитаны сами определения (§3.3)
python -m pytest tests_backend ✅ подтверждено CI-прогоном run 34166103104, job success, 706 passed/2 skipped, полный HA harness
hassfest/HACS ✅ подтверждено CI-прогоном run 34166318284, оба success
Perf-смок ✅ Validate 603ee358, success
npm run invariants — не применимо диф не трогает геометрию/layout/marker.space/open_spans (проверено чтением)
single-source-numbers.test.mjs — не требовалось перепрогонять файл не тронут; новый видимый пользователю дубль числа не найден (см. §3.4)

3. Находки

3.1 High — нет

3.2 Medium — нет

3.3 Low (пять; все сняты решением ревьюера, без правки в этом раунде)

# Находка Почему не блокирует
L1 docs/USER-GUIDE.ru.md использует слово «панель» и для нового sidebar-пункта, и для существующих внутренних суб-панелей редакторов (например, строки о «Основная панель каждого редактора», «суб-панели»). Новое предложение расставляет контекст («страница House Plan в боковом меню... эта полноэкранная панель»), но при беглом чтении возможна путаница. Терминология не изобретена заново, а расширяет уже перегруженное слово; разночтение снимается контекстом в том же абзаце. Не требует правки в этом раунде.
L2 AC14 («HA 2024.6 compatibility stub должен падать, если implementation начнёт передавать новые kwargs») доказан косвенно: assert calls == [{...9 ключей...}] в test_registers_exact_public_panel_contract_and_static_url ловит новый kwarg у async_register_panel через точное равенство словаря, а моки async_remove_panel(hass_arg, url_path) с фиксированной arity ловят warn_if_unknown=... через TypeError, проглатываемый except Exception в _remove_owned_panel. Защита реальна и падает при мутации (проверено рассуждением по коду и подтверждено мутантом panel-cleanup-uses-new-remove-keyword в scripts/mutation-gate.mjs), но ни один тест не называется «HA 2024.6 compat» — читающий тестовый файл может не опознать это как целевую защиту. Защита есть и доказуемо падает; это находка к называнию/читаемости теста, не к его существованию.
L3 Идентификационная защита владения (panel_registration.py:93, if panels.get(PANEL_URL_PATH) is not state._owned_panel) использует is not (identity), что семантически верно и совпадает с требованием ТЗ («exact registry object»). Тестовые «чужие» и «старые» объекты — простые object() без переопределённого __eq__, поэтому в текущем наборе тестов мутация is not → != осталась бы необнаруженной (для object() == по умолчанию эквивалентно is). Настоящий homeassistant.components.frontend.Panel, судя по общедоступному коду HA, тоже не переопределяет __eq__ — то есть риск, скорее всего, теоретический, а не практический. Код корректен (использует is not, как требует ТЗ); слабое место — в различительной силе теста на конкретно эту мутацию, а не в продуктовой логике. Не блокирует.
L4 src/houseplan-panel.ts не имеет disconnectedCallback, и ни один тест явно не доказывает «после disconnect не остаётся document/window listeners» (пункт AC6). При чтении кода никаких document/window listener'ов не добавляется вовсе (единственный listener — click на локальной shadow-DOM кнопке, которая не пересоздаётся между disconnect/reconnect), так что утверждение верно проверено чтением, а не тестом. Защищать нечего: пока в шелле нет ни одного глобального listener'а, обязательство выполняется тривиально. Риск — на будущее (регресс не будет пойман автоматически), не на этот коммит.
L5 system_health.py/panel_registration.py: если реестр панелей HA (hass.data[frontend.DATA_PANELS]) становится нечитаемым в момент cleanup (panels is None на panel_registration.py:89-92), код сознательно не переводит panel_status в "removed" (комментарий в коде объясняет: «unknown registry shape is not authority to remove a route by name»). System Health в этой ветке может продолжать показывать registered, хотя Core уже потерял способность интроспектировать свой реестр. Тест на эту ветку отсутствует. Безопасное поведение по умолчанию (не заявляет ложного "removed", не пытается слепо удалить), а не протухшая защита; ветка признана в самом ТЗ как крайний случай. Не тестируется, но и не заявлена как AC — не в скоупе «защитного AC без свидетеля».

3.4 Проверка «одно число — один источник»

Единственная новая величина, видимая CI/разработчику дважды потенциально — бюджет initialPanelOnlyGzipBytes ≤ 8 KiB. Подтверждено: единственное определение — export const INITIAL_PANEL_ONLY_GZIP_BUDGET = 8 * 1024 в scripts/bundle-budget.mjs; test/bundle-assets.test.mjs импортирует эту константу и сравнивает с ней (<=), не хардкодит 8192/8*1024 повторно. Существующий INITIAL_VIEW_GZIP_BUDGET (301_066) в диффе не менялся. Пользователю в UI новое число не показывается (панель не выводит никаких новых измерений дважды — ни как превью, ни как подпись). Дефекта не найдено.

4. Таблица «AC · чем доказан · чем краснеет» (PROCESS.md §2.7)

AC Чем доказан Чем краснеет (мутация → результат)
AC1 (exact panel registration) tests_backend/test_ha_panel_registration.py::test_registers_exact_public_panel_contract_and_static_url — точное равенство kwargs-словаря и URL static-path Мутанты panel-registers-wrong-route, panel-registers-card-static-url, panel-module-url-loses-version, panel-becomes-admin-only, panel-bypasses-panel-custom-api в scripts/mutation-gate.mjs — каждый патчит ровно один аргумент/вызов и указывает guard-тест; проверено чтением определений мутантов и логики теста, все 5 подтверждённо ловятся (точное assert calls == [{...}] не проходит уже при первом изменённом ключе)
AC2 (ownership/collision, identity+generation) test_generation_and_identity_guards_make_cleanup_exact_and_idempotent (стадии: stale-generation callback, чужой объект на маршруте после регистрации, внешний тамперинг счётчика поколений), test_collision_preserves_foreign_panel_and_redacts_exception Мутанты panel-cleanup-drops-generation-guard, panel-cleanup-drops-identity-guard, panel-accepts-unverifiable-ownership — каждый напрямую снимает соответствующую строку защиты (== state.generation → and True; is not state._owned_panel → членство по ключу; if owned_panel is None → if False) и указывает тот же guard-тест. Прочитаны и прослежены построчно — падают. Единственная оговорка — L3 (не про ==/is-мутацию конкретно этой строки, тест на неё слеп из-за object()-сентинелов без __eq__, отмечено как Low)
AC3 (fail-soft + safe error) test_panel_api_failure_does_not_abort_entry_setup (реальный async_setup_entry, entry.state.value == "loaded" после RuntimeError), test_system_health_reports_complete_panel_matrix (8/8 статусов), проверки "secret"/"private"/"token" not in caplog.text Любая замена _safe_error на str(err) ломает три теста, проверяющих отсутствие текста исключения в логах (test_collision_preserves_foreign_panel_and_redacts_exception и др.) — проверено чтением, тест целенаправленно кладёт секрет в текст exception и проверяет caplog
AC5 (panel не дублирует card graph, бюджет) test/bundle-assets.test.mjs — assert.deepEqual(bundle['houseplan-panel.js'].imports, ['houseplan-card.js']), initialViewFiles ⊆ initialPanelFiles runtime-assert в bundle-tree.mjs, initialPanelOnlyGzipBytes <= INITIAL_PANEL_ONLY_GZIP_BUDGET Мутант panel-entry-bypasses-card-graph (panelEntry.imports = [cardAsset.slice(2)] вместо стабильного CARD_ENTRY_FILE) — пойман тем же assert.deepEqual; проверено чтением, реально ломает subset-инвариант, т.к. initialPanelFiles перестал бы транзитивно включать houseplan-card.js
AC6 (одна карточка, один setConfig, без remount) demo/smoke_houseplan_panel.mjs — monkey-patch setConfig, счётчики вызовов на дочернем элементе (===1 для read-only/writer/populated/reconnect-сценариев), проверка идентичности querySelector('houseplan-card') до/после смены hass/narrow/route/panel Мутация «вызвать setConfig дважды» или «пересоздать _card на каждый hass» ловится счётчиком panelSetConfigCalls/сверкой identity дочернего узла — проверено чтением ассертов смока, они реально сравнивают счётчик/identity, а не просто наличие
AC9 (видимость панели, права без изменений) test_panel_is_visible_to_admin_and_read_only_users — реальный frontend.websocket_get_panels для admin и non-admin коннекшнов Grep всего диффа: ни один WS/HTTP write-guard не тронут — отсутствие мутации здесь равнозначно отсутствию риска (проверено чтением всего диффа custom_components/houseplan/*.py, единственная permission-строка во всём диффе — новый require_admin=False)
AC10 (read-only empty state, ленивый runtime) src/houseplan-card.ts: _serverStorage && _canManageConfiguration гейтит и CTA-кнопку, и авто-открытие onboarding; смок coldReadOnlyKeepsEditorAndOnboardingLazy проверяет реальные performance resource-записи на отсутствие houseplan-(editor|onboarding)-runtime Мутант panel-readonly-empty-bypasses-write-capability (_canManageConfiguration всегда true при _serverCanWrite !== null) — guard node demo/smoke_houseplan_panel.mjs; проверено чтением, при мутации read-only пользователь увидел бы кнопку Add space
AC12 (getGridOptions) test/houseplan-panel.test.mjs — точный текст возврата + assert.doesNotMatch(spaceCard, /getGridOptions/) Слабее прочих: регэксп по исходному тексту, не выполнение класса. Проверено чтением: space-card.ts — независимый класс без наследования от HouseplanCard, физически не может унаследовать метод — риск «утечки в чужой класс» отсутствует по архитектуре, не только по тесту
AC13/AC14 (i18n полнота, 2024.6 compat) tests_backend/test_backend_translations.py — точное равенство множества ключей source.keys() == translated.keys() по всем 4 локалям; compat — см. L2 Пропуск ключа в любой из {en,ru,de,fr} ломает точное сравнение множеств — проверено чтением теста и всех 4 файлов диффа напрямую (не только доверием к заявлению «26/26» в комментарии issue)

AC4, AC7, AC8, AC11, AC15 не заявляют защиту в смысле §2.7 (расположение/формат/UX-контракт) либо являются производными от уже покрытых выше пунктов (AC4 — та же манифест-инфраструктура, что AC5; AC8/AC11 — код прочитан, поведение прослежено, воспроизведено в §3 текстом без отдельной мутационной таблицы, так как не заявляют отказ/лимит, а описание/событие).

5. Сквозная проверка ключевых рисков ТЗ (§22)

  • Multi-entry ломает «первый entry»/initial-budget: снято — root теперь выбирается по точному fileName+facadeModuleId (exactEntryChunk), а не по порядку; старый паттерн .find(isEntry)[0] физически удалён из bundle-manifest.mjs.
  • Stale panel entry в релизе: entryFallbackPlugin переписывает panel-facade так, что он импортирует стабильный houseplan-card.js, а не хэшированный чанк напрямую — fail-loud поведение симметрично card/panel, проверено чтением обеих веток rewrite.
  • Unload снимает чужую панель: см. AC2 в таблице выше — идентичность объекта + поколение, оба протестированы прямыми сценариями (foreign-объект переживает unload; старый generation callback — no-op).
  • Двойной вычет app-bar/safe-area: panelHost-флаг реально читается в двух местах вычисления высоты (houseplan-card.ts:4140 и inline style="height:..."), переключая режим на «измеренный контейнер через flex», а не 100dvh; проверено чтением обеих точек и CSS в base.styles.ts.
  • Shell пересоздаёт card на каждый hass: см. AC6 — _ensureShell() идемпотентен, setConfig вызывается один раз, проверено и чтением, и смоком со счётчиком.
  • require_admin=False открывает write: не подтвердилось — весь дифф не трогает ни один WS/HTTP write-guard (grep по всему custom_components/houseplan/*.py).

6. Что проверено и корректно (сводно)

Все 15 AC ТЗ имеют видимое соответствие в коде и тестах, включая самые рискованные (ownership/collision identity+generation, panel graph dedup, container-owned sizing, read-only lazy-loading gap fix). i18n — 4/4 локали, ключи совпадают множествами (проверено чтением, не по заявлению автора). Changelog RU+EN добавлены в том же коммите 8a97d4e3, что и поведенческие изменения (проверено git show --stat). Бюджет 8 KiB — единственный источник истины, не задвоен. Backend/frontend/bundle-мутанты реально ловят соответствующие регрессии (прослежено построчно на 5+ мутантах, не поверхностно).

7. Чего не проверял (и почему)

  • Не перегонял tsc/test/build вручную — приняты по зелёному Validate на точном SHA (условие задания).
  • Не запускал HA-бэкенд pytest в этой песочнице (нет homeassistant/.venv-backend) — заменено прослеженным реальным CI-прогоном с полным harness на эквивалентном коде (§2.2), что сильнее локального «verified».
  • Не запускал браузерные смоки/golden:verify своими руками — уже зелёные в CI на эквивалентном коде; повторный прогон не добавил бы уверенности.
  • Не проверял demo/smoke_houseplan_panel.mjs построчно целиком (449 строк) — читал репрезентативные ассерты через агента-разведчика и перепроверил его цитаты по факту (grep/прямое чтение конкретных строк, которые он назвал); не читал файл целиком сам.
  • Не вычислял вручную npm run inventory/точные счётчики тестов — не требовалось, числа в отчёте автора использовались только как перекрёстная сверка с независимо найденными CI-числами (706 vs заявленных 707 — расхождение на 1, не критично, не завязано на конкретное число в AC).
  • Не проверял поведение в реальном браузере HA (ручного тестирования в этом цикле процессом не предусмотрено; код-ревью заменяет его согласно PROCESS.md §2.7).

8. Вердикт

Все находки — Low, ни одна не в скоупе «Medium, чинить в этой же задаче». Реализация полно и доказуемо закрывает AC1–AC15, самые рискованные защитные свойства (ownership identity+generation, panel/card graph dedup, container sizing, permission gap fix) подтверждены не заявлением, а прослеженной мутацией и/или реальным CI-прогоном на эквивалентном коде.

Вердикт: зелёный · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 0 → в задаче


Материал раунда

  • Диапазон: origin/dev...HEAD, HEAD = 603ee3584b8908ee3372548da1d5be907f48992f
  • Дерево на момент вывода вердикта: чистое (git status --short пусто)
  • Validate на точном SHA: https://github.com/Matysh/houseplan-card/actions/runs/34167305131 (success)
  • Дополнительные CI-прогоны, использованные как доказательства (не по ссылке из задания, а найденные и прослеженные самостоятельно): runs 34166103104 (backend pytest, SHA 597488f7), 34166326951 (полный browser-smoke, SHA 8bf30947), 34166318284 (hassfest/HACS, SHA 8bf30947)

Материал раунда

  • Ветка: issue/486-house-plan-panel, коммит 603ee3584b89 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: e49924475e394b89a925635bee64fe9f25cbdbcb
    git log --all --format='%H %T' | grep e49924475e39
    
  • ТЗ docs/specs/486-house-plan-panel.md, блоб dd37e779f5cd5e4dae09b6b70453634448ece9cd
    git log --all --find-object=dd37e779f5cd5e4dae09b6b70453634448ece9cd -- docs/specs/486-house-plan-panel.md