23 KiB
SPEC-REVIEW-485-r3
Issue: #485.
Материал: ветка issue/485-radar-presence, SHA 21950437b4343deead48c955d06191ea15bb567a
(baseline r2 материал f4bab4a4bd5f4ac9b91cc16363985323a20ae907, докс-only diff,
проверено git diff --stat — только docs/specs/485-radar-presence.md и
docs/specs/485-radar-presence-stage1.md; коммит docs/reviews/SPEC-REVIEW-485-r2.md
самого документа r2 в дельту не входит, по прецеденту r2).
Этап: spec (S4-spec-review). Заход: r3. Блокирующих циклов израсходовано 1 из 4
(зелёный вердикт r2 цикл не образовал и бюджет не потратил, #227). Трек: полный
(метка small отсутствует). Ревьюер ≠ автор.
Скоуп разбора (по дельте, PROCESS §2.9/§2.10, issue #214)
Round r2 закрылся зелёным (High 0 / Medium 0) на материале f4bab4a4; findings
предыдущего раунда закрывать нечего — этот раунд разбирает не исправление r2, а
новое уточнение владельца, полученное уже после зелёного r2 (комментарий issue
2026-09-08T11:30:04Z): раздел «Присутствие на плане» в редакторе устройства
показывается только подходящим устройствам, ранее настроенным радарам сохраняется
путь исправления; общий переключатель не меняется.
git diff f4bab4a4..21950437 --stat (без коммита самого документа r2):
docs/specs/485-radar-presence-stage1.md | 66 ++++++++++++++++++++++++++++++---
docs/specs/485-radar-presence.md | 15 ++++++--
Формально дельта локальна по объёму (два файла, точечные абзацы в §2/§3 общего контракта и §2/§3/AC-таблицы этапа 1). Но по содержанию это не техническая правка и не декларативная фиксация ранее решённого — это новый поведенческий контракт (кто вообще видит точку входа в настройку радара), которого не было ни в r1, ни в r2. Согласно правилу §2.10 «разбор остаётся полным, если... смена контракта поведения» — я не ограничился построчной сверкой «что просил владелец / что дописано», а перечитал §3 обоих документов (модель источников/профилей) и точки входа в этапах 2–3, чтобы проверить, не рвёт ли новый гейт видимости то, что r1/r2 уже приняли как рабочее. Остальное содержимое пакета (модель идентичности, совместимость, i18n, перф, этапы 2–3 целиком) дельта не задевает — там разбор по дельте, без повторного прохода (см. «Унаследовано из r2»).
Как проверялось
gh issue view 485 --json labels,state— меткиP2, feature, S4-spec-review,smallотсутствует: полный трек подтверждён, счётчик заходов/циклов совпадает с описанием задачи.- Построчный
git diff f4bab4a4..21950437по обоим изменённым файлам (полный текст цитирую в разделе «Находки»). - Перечитано начало и конец тела issue #485 (
gh issue view 485 --json body) — включая раздел «Что есть у easy-floorplan и что из этого брать» и его «Берём»/«Не берём» — источник профиляrange_v1/«любой числовой сенсор». - Сверены точки входа в
docs/specs/485-radar-presence-stage2.md:75иdocs/specs/485-radar-presence-stage3.md:86(«Device editor → Presence on plan → …») — обе описывают вход из уже определённого/настроенного радара, новому гейту видимости не противоречат. - Проверено техническое утверждение stage1 §2 про «existing conditional vacuum
section» как прецедент:
src/houseplan-editor-runtime.tsдействительно содержит условный рендерrenderVacuumMapsSection(строка 10701, внутри... ? html\... ${renderVacuumMapsSection(...)}` : nothing`) — утверждение не выдумано. docs/UX-MODES.mdпросмотрен на предмет канонического правила про показ/скрытие секций в редакторе устройства (в противовес «показывать, но дизейблить») — прямого правила, которое запрещало бы условный показ по типу устройства, не найдено; секция «пылесоса» — рабочий прецедент того же рода.docs/SCOPE.md— дельта его не трогает; новый гейт видимости не расширяет и не сужает ранее записанное исключение (это вопрос UI-точки-входа в существующий редактор устройства, не новый класс данных/хранения).- Трейлеры коммита
21950437:Issue: #485,User-Visible: no— верно для докс-only правки без видимого поведения. - Таблица i18n (§8 stage1) дельтой не тронута: новый гейт видимости не вводит собственного текста (ineligible-устройства просто не получают раздел), поэтому отсутствие нового ключа — не пропуск.
Гейты
Диапазон затрагивает только docs/specs/*.md (class C), продуктовый код и тесты
не тронуты (git diff --stat origin/dev..HEAD -- src custom_components — пусто).
Прогнаны сам, дёшево и напрямую по этому SHA:
| Гейт | Команда | Результат |
|---|---|---|
| Markdown/diff-гигиена | git diff --check f4bab4a4..HEAD -- docs/specs/485-radar-presence.md docs/specs/485-radar-presence-stage1.md |
чисто (exit 0) |
| Документационные проверки | node scripts/check-docs.mjs |
Documentation checks passed (7 files, 12 external links) |
tsc/test/build/invariants/смоки/golden/pytest — не запускал: diff не
касается src/**/custom_components/**/*.py, ни один AC этой стадии их не
требует (эта же логика применялась в r1/r2 и подтверждена авторским
git diff --stat). Отдельный зелёный Validate на этом SHA для данного раунда не
искал — предыдущий раунд (r2, f4bab4a4) уже подтвердил репозиторий в целом, а
эта дельта докс-only и не расширяет исполняемую поверхность.
Закрытие раунда r2
r2 закрылся с High 0 / Medium 0 — закрывать нечего, таблица «находка → чем закрыта» неприменима. Материал этого раунда — не фикс находок r2, а новое уточнение владельца, полученное после зелёного вердикта (см. «Скоуп разбора»).
Унаследовано из r2
Принято без повторной проверки в этом раунде, так как дельта их не задевает;
основание — SPEC-REVIEW-485-r2
на SHA f4bab4a4bd5f4ac9b91cc16363985323a20ae907:
- Продуктовая рамка, границы этапов 1→2→3 и их отнесение к J1/
docs/SCOPE.md. docs/SCOPE.md-исключения для производных HA-сущностей и записи/тепловой карты (закрытые в r1→r2) — эта дельта их не расширяет и не сужает.- Touch-декларация мастера калибровки этапа 1 (
best effort / intentionally degraded) и этапов 2/3 — не тронута. - Единая модель идентичности (
source_generation,calibration_revision,server_session_id/seq), лимиты, WS-контракт §7 stage1. - Полнота AC-таблиц C-1…C-8, S2-1…S2-22, S3-1…S3-18 и S1-1…S1-18 (кроме новой S1-19, разобранной в этом раунде отдельно).
- Матрицы совместимости/жизненного цикла, i18n-таблица §8 stage1, перф-бюджеты §10 stage1.
- Ответы Q1–Q4 и их количественное соответствие тексту.
Находки
M-1 (Medium, в скоупе) — новый гейт видимости, по тексту, исключает ранее принятый адаптер «любой числовой сенсор» (класс range), не оставляя описанного пути его настроить
docs/specs/485-radar-presence-stage1.md:55-59 (после дельты):
A positive presence-radar device/profile match or a verified radar-specific source-role descriptor on the exact owning device makes it eligible. Match evidence is structural adapter/registry metadata, not a friendly-name search.
и stage1.md:69-73:
A normal light, switch, temperature/humidity sensor, ordinary PIR, virtual marker, or a device with merely arbitrary numeric/binary entities is not a radar candidate. Two numeric values or a generic occupancy/motion class alone do not prove a radar profile.
Оба условия — «структурные метаданные адаптера/реестра» и явное исключение «merely arbitrary numeric… entities» — требуют, чтобы устройство было автоматически опознано ещё до того, как раздел вообще появится. Ни здесь, ни где-либо ещё в дельте нет описанного пути «пользователь вручную объявляет устройство радаром», отличного от двух метаданных-путей.
Это прямо противоречит собственному решению задачи, зафиксированному в теле issue #485 (раздел «Что есть у easy-floorplan и что из этого брать», подтверждено владельцем 07.09 вместе с остальным «Решение владельца»):
Берём: presence gate с fail-safe семантикой (сомнение → не показывать); «любой числовой сенсор» как низший общий знаменатель (класс «дальность»); линия/дуга для одной оси; контур зоны только в редакторе; сглаживание между отсчётами 1–4 Гц.
и в самом контракте (stage1.md:144-151, таблица профилей, не тронута дельтой):
range_v1| One or two named distance channels, explicit length unit, optional occupancy gate | Arc(s), not two people…
range_v1 — это буквально «любой числовой сенсор» из принятого решения: датчики
класса Tuya ZY-M100/TS0601, Aqara FP1E, Linptech ES1, Seeed MR24 и любой
самодельный узел, перечисленные в самой постановке issue (раздел «Что радары умеют
отдавать»), никак не опознаются «структурными метаданными адаптера» — у них нет
признанного дескриптора вроде LD2450. До этой дельты пользователь мог зайти в
раздел устройства и вручную назначить источники (range_v1/cartesian_v1/
polar_v1 — «generic explicit Cartesian/polar/range/occupancy inputs», common
contract 485-radar-presence.md:86-88, тоже не тронуто дельтой). После дельты
раздел для такого устройства попадает под «merely arbitrary numeric… entities is
not a radar candidate» и не показывается вовсе — то есть ручной путь входа,
на котором держится вся «дальность»-ветка и вся идея «класса устройств, а не
конкретной модели» (Суть issue: «Инструмент работы с любыми радарами присутствия…
не конкретная модель, а класс устройств»), не имеет точки входа в UI.
AC S1-19 (новая, stage1.md:558) требует протестировать в том числе «range»-радар
как один из допущенных типов — то есть сам автор рассчитывает, что такой путь
существует, но текст §2 его не описывает.
Почему это не мелочь, а не редактирование формулировки. Уточнение владельца было одной строкой: «показывать только подходящим устройствам» — это про то, чтобы не засорять редактор лампочек и выключателей пустыми разделами. Реализация этой строки как «только структурно опознанные устройства» — решение автора, а не владельца, и оно тише удаляет из скоупа именно ту часть задачи (произвольный числовой датчик, взятый из easy-floorplan как «низший общий знаменатель»), которую до сих пор явно поддерживали r1 и r2. Это ровно тот случай из PROCESS §7.1: «поведение в пограничном случае» — граница между «опознанный радар» и «обычный датчик» — продуктовый вопрос, а не инженерный, и здесь он решён предположением, выданным за факт, без пометки «assumed, change freely» и без вопроса владельцу.
Как чинится (любой из вариантов, в скоупе задачи):
- Добавить в §2 явный ручной путь — например, действие «Это радар присутствия»
на неопознанном устройстве, которое ведёт в тот же мастер с профилями
range_v1/cartesian_v1/polar_v1, отдельное от двух автоматических путей и не открывающее раздел на самом деле неподходящих устройствах (лампах, выключателях) без явного действия пользователя; согласовать с «no friendly-name search» так, чтобы ручной путь не читался как автоматическое распознавание. - Либо явно сузить AC S1-19 и профильную таблицу так, чтобы
range_v1/cartesian_v1/polar_v1были помечены как доступные только через путь ремонта уже сохранённой конфигурации (т.е. фактически недостижимы для нового радара без признанного дескриптора), и одним пакетным вопросом с вариантом по умолчанию спросить владельца, допустимо ли это сужение — вопрос ровно того типа, который PROCESS §7.1 адресует владельцу («relates to what a person sees or does», здесь — сможет ли человек вообще настроить нераспознанный радар).
Находка в скоупе задачи (правится в этом же ТЗ, отдельный issue не заводится, #202).
Что проверено и корректно
- Правило скрытия для обычных устройств (лампы/выключатели/температура/обычный
PIR/виртуальный маркер) и сохранение пути ремонта для уже настроенного радара
(в том числе при
unavailable/неподдерживаемой версии) — сформулировано недвусмысленно, с исчерпывающим списком случаев и негативными свидетелями в AC S1-19 (ordinary-device header assertion,offline/range/manual repair assertion,empty-installation settings assertion). - Общий переключатель «Показывать присутствие на плане» остаётся видимым без
радаров и не зависит от gate — текст (
stage1.md:108-110,485-radar-presence.md:69-71) дословно совпадает с формулировкой уточнения владельца в issue. - Точки входа этапов 2 и 3 (
stage2.md:75,stage3.md:86) остаются валидны при новом гейте: обе описывают вход из уже определённого/настроенного радара, не из произвольного устройства. - Новых i18n-ключей дельта не требует и не добавляет — раздел на неподходящих устройствах просто отсутствует, не превращается в текст с пустым состоянием.
- Техническое утверждение про существующий условный раздел пылесоса как прецедент
подтверждено чтением
src/houseplan-editor-runtime.ts:10701— не выдумано. docs/SCOPE.mdне требует правки для этой дельты: вопрос — точка входа в существующий редактор устройства, не новый класс данных/хранения.- Трейлеры коммита корректны; дельта не касается
src/**/custom_components/**/*.py.
Чего не проверял
- Не перепроверял AC-таблицы C-1…C-8, S2-1…S2-22, S3-1…S3-18 и S1-1…S1-18 (кроме новой S1-19), модель идентичности, совместимость, перф-бюджеты, WS-контракт — дельта их не задевает; раздел «Унаследовано из r2» перечисляет, что принято без повторной проверки.
- Не запускал
npx tsc --noEmit,npm test,npm run build,npm run invariants, браузерные смоки,golden:verify,python -m pytest tests_backend— diff докс-only, не касаетсяsrc/**/custom_components/**/*.py, ни один AC пакета не требует их прогона на стадии spec-review. - Не проверял
docs/USER-GUIDE.ru.md/docs/USER-GUIDE.mdпострочно на предмет терминологии — фича не выпущена, руководство ещё не содержит раздела о радарах (grepпо обоим файлам — без совпадений), сверять не с чем. - Не оценивал остальные два варианта устранения M-1 на предмет объёма следующего раунда — это решение автора и, при необходимости, владельца.
Вердикт
Жёлтый. High — 0, Medium — 1 (M-1, в скоупе задачи, чинится в этом же issue,
#202). Находка блокирует переход S4 → S5: новый гейт видимости, введённый этой
дельтой, тише сужает ранее принятое решение «любой числовой сенсор — класс
range» без пометки предположения и без вопроса владельцу. Остальная часть
пакета (унаследованная из r2) находок не имеет.
Этот жёлтый вердикт образует цикл и расходует бюджет §4: было израсходовано 1 из 4, после этого раунда — 2 из 4.
Материал раунда
- Ветка:
issue/485-radar-presence, коммит21950437b434— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
717b51fb35f3c98731eb72e53d0cc2ab87db25d4git log --all --format='%H %T' | grep 717b51fb35f3 - ТЗ
docs/specs/485-radar-presence-stage1.md, блобc5d9960cfaa0fa5573e58d384e8e2b7eeddc6fccgit log --all --find-object=c5d9960cfaa0fa5573e58d384e8e2b7eeddc6fcc -- 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, блобdfc1edc3aa10d19c16eb37e1ddacf7c81c439216git log --all --find-object=dfc1edc3aa10d19c16eb37e1ddacf7c81c439216 -- docs/specs/485-radar-presence.md