17 KiB
SPEC-REVIEW-545-r1
Issue: #545 «Радары: range/zone entity_id теряются из списка источников для
подписок и проверки прав»
Этап: ревью ТЗ (PROCESS.md §2.4)
Заход: r1 · трек: full (не small) · блокирующих циклов израсходовано 0 из 4
Ревьюер: Claude (роль «ревьюер ТЗ», §6)
Материал: тело issue #545, раздел ## ТЗ, на состоянии на момент разбора;
issue-комментарий «S2 — аналитика» (тот же материал, не решение по существу
вопроса).
Скоуп ревью
Проверить, что ТЗ в теле issue #545 удовлетворяет §7.1 (обязательные разделы, однозначность AC1…AC7 и указанный способ доказательства для каждого), что продуктовые разделы («Сценарий», «Что человек увидит») отвечают на свои вопросы, и что ни одно утверждение о существующем поведении не выдано за факт без проверки. Это первый заход по этому ТЗ — версия для r2+ «разбор по дельте» (§2.10) здесь неприменима.
Как проверялось
Материал — исключительно текст: код смотрел не для того, чтобы утвердить реализацию (её ещё нет), а чтобы отличить утверждение о факте от догадки, которую §7.1 требует помечать явно. Прочитано и сверено построчно:
docs/SCOPE.md,AGENTS.md,PROCESS.md(§1, §2.3–2.5, §5, §7.1, §7.2);docs/RADAR.md— канонический документ подсистемы, раздел «Privacy, permissions and storage» и таблица профилей;custom_components/houseplan/radar_validation.py—_source_ids(),radar_source_entity_ids(),_validate_sources()для всех шести профилей;custom_components/houseplan/radar.py—_resubscribe_sources(),source_ids(),teardown(),_ensure_tick();custom_components/houseplan/radar_websocket.py—_can_read(),ws_radar_subscribe,ws_radar_setup_subscribe,source_restricted/health: restrictedпути;tests_backend/test_ha_radar_websocket.py— существующий_Permissionsfake (булев allow/deny, не per-entity) — сверка риска, названного в ТЗ;scripts/mutation-gate.mjs,docs/TESTING.md— подтверждение, что реестр поддерживает Python-мутанты сpytest-гвардом (не только фронтенд), и что формат идентификатора мутанта из ТЗ соответствует принятой конвенции;- список файлов
tests_backend/test_radar_validation.py,test_ha_radar.py,test_ha_radar_websocket.py— существование подтверждено (ls).
Гейты не гонялись: на этапе ревью ТЗ кода ещё нет, гонять typecheck/test/
build не над чем — это ожидаемо для S4-spec-review, а не пропуск.
Проверка утверждений ТЗ о текущем поведении (не догадка ли это)
Раздел «Проблема» и «Контракт поведения» делают несколько фактических заявлений о существующем коде. Каждое сверено с источником, а не принято на слово:
| Заявление ТЗ | Где проверено | Результат |
|---|---|---|
_source_ids() собирает вложенные поля только по суффиксу _entity |
radar_validation.py:99-105 |
Подтверждено: if key.endswith("_entity") |
range_v1/zones_v1 называют основной источник entity_id, а не *_entity |
radar_validation.py:244, 259 |
Подтверждено: unique_role(item.get("entity_id"), ...) для ranges, _entity(item.get("entity_id"), ...) для zones. "entity_id".endswith("_entity") — ложно (сравнил посимвольно) |
Coordinator реально читает entity_id при построении frame |
radar.py:502, 531 |
Подтверждено: self.hass.states.get(source.get("entity_id")) |
Подписки коллектора берутся из того же неполного radar_source_entity_ids() |
radar.py:189-193 |
Подтверждено: entity_ids = sorted({... for entity_id in radar_source_entity_ids(...)}), передаётся в async_track_state_report_event/_change_event |
_can_read()/ACL получает тот же неполный список |
radar_websocket.py:56, 128, 184, 224-225 |
Подтверждено: все проверки идут через coordinator.source_ids() → radar_source_entity_ids() либо validate_radar_draft() → та же функция |
| Setup subscribe не имеет секундного tick-фолбэка, в отличие от live | radar_websocket.py:196-251 vs radar.py:258-268 (_ensure_tick) |
Подтверждено: _ensure_tick/периодическая публикация существует только у RadarCoordinator (для активных add_listener), у ws_radar_setup_subscribe publish идёт исключительно по source_reported на пересчитанном source_ids |
restricted/source_restricted — уже существующее fail-closed поведение, а не новое |
radar_websocket.py:57,132,185,226,239 |
Подтверждено, задача переиспользует существующий путь, не изобретает новый |
| Существующий тест использует общий allow/deny boolean, а не per-entity fake (риск в разделе «Риски») | tests_backend/test_ha_radar_websocket.py:23 (_Permissions, connection.user.permissions.allowed = False/True) |
Подтверждено: это ровно тот недостаточный дизайн, который ТЗ требует не повторять в AC4 |
mutation-gate.mjs умеет патчить backend .py и гонять pytest-гвард |
scripts/mutation-gate.mjs (например записи с guard: 'python3 -m pytest tests_backend/...', file: 'custom_components/houseplan/*.py') |
Подтверждено, AC5 технически осуществим на этом реестре |
Перечисленные роли по профилям (общие optional + по группам slots/ranges/zones) |
radar_validation.py:198-266 |
Построчно совпадает с контрактом ТЗ п.2–3 для всех шести профилей, включая отсутствие presence_entity у zones и обязательность occupancy_entity только для presence_v1 |
Ни одно из проверенных утверждений не оказалось догадкой, выданной за факт: там, где ТЗ формулирует именно новое поведение (например, п.6–7 контракта — «saved coordinator подписывается на оба HA event stream для полного инвентаря»), это явно нормативное требование к реализации, а не описание того, что уже так работает.
Обязательные разделы §7.1
Все присутствуют: сценарий · что человек увидит до/после · проблема · скоуп и не-скоуп · контракт поведения · UX · модель данных/миграция/совместимость · затронутые файлы · AC1…AC7 с доказательством · план автотестов · перф · безопасность · риски · откат · release-артефакты · блок «принято предположительно». Пункт DoR по i18n закрыт явным «frontend/i18n не меняются» — это корректная форма пустого списка, а не пропуск раздела.
Проверка AC1…AC7 на однозначность и доказуемость
- AC1 (backend unit) — таблица для всех 6 профилей, включая точное имя
роли
entity_idу range/zone и отрицательный случай произвольного future-поля. Проверяемо механически, способ доказательства назван. - AC2 (backend HA) — саму развилку «что считается обходом» ТЗ снимает прямым запретом полагаться на occupancy/count/presence в фикстуре; это устраняет ровно тот тип ложного прохождения теста, что PROCESS.md требует предотвращать (§2.7, «двух источников с разными списками»).
- AC3 (backend HA) — три отдельных факта (subscribe range/zone id, rebind заменяет набор целиком, unload снимает всё) — все три проверяемы по отдельности, не слиты в одно расплывчатое утверждение.
- AC4 (backend HA, security) — явно требует записывающий per-entity fake permission (не общий boolean) и раздельно saved/draft путь — снимает собственный риск, названный в разделе «Риски», а не просто декларирует его.
- AC5 (mutation) — конкретный id мутанта, конкретная механика удаления
роли, обязательный
--check. Соответствует формату существующего реестра. - AC6 (backend + ревью кода) — числовой инвариант («два listener»)
сверяемый по факту: сейчас уже два (
state_report+state_change) и на коордиторе, и на setup — AC фиксирует неизменность этого числа при расширении набора entity, а не декларирует что-то новое и непроверяемое. - AC7 (backend + docs review) — стандартный набор доказательств совместимости и артефактов, ничего скрытого.
Ни один AC не сформулирован как «неявное намерение», каждый называет способ
доказательства (unit/backend HA/mutation/ревью кода), как того
требует §2.5.
Продуктовые разделы и вопрос владельцу
«Сценарий» называет персону/поверхность/момент (home admin, мастер настройки
или обычный план, изменение основного датчика диапазона/зоны), «Что человек
увидит» — одной фразой без терминов реализации. Автор аналитики (S2)
заявила, что открытых продуктовых вопросов нет, потому что ожидаемое
поведение уже зафиксировано существующим контрактом docs/RADAR.md
(«House Plan subscribes only to the exact configured entity IDs») и не
вводит нового UI/UX. Прочитав материал независимо, соглашаюсь: расхождение
между валидацией и inventory — техническая ошибка одной реализации, а не
неопределённость продукта, поэтому пачка вопросов владельцу здесь была бы
избыточной (§7.1 «вопрос, не блокирующий написание ТЗ, не задаётся вовсе»).
Скоуп и не-скоуп
Не-скоуп корректно исключает Stage 2/3, историю, heatmap, изменения
may_write/прав HA и миграцию — всё это действительно посторонние
подсистемы. Скоуп ограничен одной точкой истины (profile → source roles) и
её тремя потребителями (validation, subscriptions, ACL), что соответствует
корню проблемы, а не симптомам по отдельности.
Откат
Явно запрещён частичный откат «только ACL» или «только подписок» — важная деталь: такой частичный откат воссоздал бы именно тот дефект, который задача чинит (расхождение списков), и явный запрет предотвращает случайную регрессию через невнимательный cherry-revert.
Что проверено и корректно
- Все шесть заявленных факта о текущем коде подтверждены построчным чтением
(таблица выше), включая тонкий момент:
"entity_id".endswith("_entity")действительно ложно. - Все файлы из «Затронутые файлы» существуют.
- Роли по профилям в контракте поведения дословно совпадают с
_validate_sources(). - Мутационный гейт поддерживает заявленный тип мутанта на Python-файле.
- Откат, риски и не-скоуп внутренне непротиворечивы.
Чего не проверял
- Не запускал автотесты и не собирал бандл — на этапе ревью ТЗ кода ещё нет, проверять нечего; это не гейт этого этапа.
- Не оценивал качество будущей реализации — вне скоупа ревью ТЗ.
- Не проверял
docs/specs/485-radar-presence-stage1.mdпострочно на полное совпадение (упомянут вdocs/RADAR.mdкак нормативный контракт Stage 1); сверка ограничилась текущим кодом, что для проверки этого багфикса достаточно.
Находки
Нет. Ни одной High, ни одной Medium, ни одной Low.
Вердикт
Зелёный. ТЗ полное по §7.1, каждый AC однозначен и снабжён способом
доказательства, продуктовые разделы отвечают на свои вопросы, ни одно
техническое утверждение не оказалось непроверенной догадкой — напротив,
каждое фактическое заявление о текущем коде подтвердилось при чтении
источника, включая нетривиальные детали (dead-code ветка zones в
_resubscribe_sources, недостаточность существующего permission-fake).
Открытых продуктовых вопросов владельцу нет и не требуется.
Материал раунда
- SHA issue-материала: тело issue #545 на момент разбора (заход r1, первый документ ревью для этой задачи — предыдущего раунда нет).
- Ветка/код: не применимо (S4-spec-review, кода ещё нет).
Материал раунда
- Ветка:
dev, коммитce6650b1e05a— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
8aa210d76f61c0edd53530a6f88f7d98567918ddgit log --all --format='%H %T' | grep 8aa210d76f61 - Тело issue:
8b063fe89abbf48179a894847dff1b5c223f394fa4f6e98ec284302624b71437 - Вердикт конвейера:
green· High 0