17 KiB
SPEC-REVIEW-274-r1
- Issue: #274 — «Беспроводной выключатель на плане приглушён, хотя превью того же маркера обещает нейтральную подложку»
- Этап: spec (PROCESS.md §2.4)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4
- ТЗ:
docs/specs/274-wireless-controller-presentation-parity.md - Коммит:
9b76427(веткаissue/274-wireless-controller-parity, совпадает с текущимHEAD) - Класс изменения этого коммита: C (документация) —
docs/specs/274-...md+docs/specs/README.md, продуктовый код не тронут.
Скоуп ревью
Первый заход, разбор полный (иное не требуется — предыдущего вердикта нет). Проверено:
- Соответствие
docs/SCOPE.md— какую строку Core user jobs закрывает задача. - Обязательные разделы ТЗ по PROCESS.md §7.1 и чек-лист DoR §2.5.
- Однозначность и проверяемость AC1…AC8, способ доказательства у каждого.
- Достоверность технического диагноза — сверка построчных ссылок на код, приведённых в ТЗ и в issue, с фактическим
src/device-presentation.tsиsrc/houseplan-card.ts. - Отсутствие догадки, выданной за факт, и корректность обращения с продуктовыми вопросами владельца (issue поднимал два вопроса; проверено, закрыты ли они уже принятым контрактом #251, а не додуманы).
- Согласованность с каноническим предшествующим ТЗ
docs/specs/251-controller-target-availability.md, на контракте которого строится #274.
Как проверялось
- Прочитаны
docs/SCOPE.md,AGENTS.md,PROCESS.mdцеликом. - Прочитано тело issue #274 и оба комментария (аналитика владельца, хендофф ТЗ на ревью).
- Прочитан сам файл ТЗ целиком (317 строк) и связанный
docs/specs/251-controller-target-availability.mdцеликом. git show 9b76427 --statиgit diff d83b0c5..9b76427 --stat— подтверждено, что коммит трогает толькоdocs/specs/**, класс A/B не задет.- Построчная сверка диагноза:
src/device-presentation.ts:559-235,574-597,600-635,src/houseplan-card.ts(_renderDeviceSnapshot, вызовы preview-резолва вокруг строки 20279),src/hp-device-preview.ts:171,src/i18n/en.json(marker.preview.reason.*). - Проверено существование файлов, на которые ссылается план тестов:
test/devices.test.mjs,test/device-presentation.test.mjs,test/device-toggle.test.mjs, а также уже существующийdemo/smoke_device_preview_parity.mjs(готовая инфраструктура именно для сравнения plan/preview одного и того же маркера).
Гейты
Диапазон коммита — только docs/specs/**, класс C, продуктовый код не менялся. typecheck/test/build/check-docs/смоки/инварианты в этом раунде не гоняются: ни один их предмет (src, тесты, конфигурация геометрии) в диффе не участвует, а сам этап — ревью ТЗ, а не кода. Это решение, а не пропуск: перечисляю его явно.
Проверка диагноза по коду (не на слово автору)
ТЗ и issue называют точные строки текущего кода как основание диагноза. Все они подтвердились при чтении:
src/device-presentation.ts:561—if (visual.availability === 'unavailable') return 'unavailable';(причина для preview) — совпадает дословно.src/device-presentation.ts:583—else if (visual.availability === 'unavailable') classes.push('unavail');(класс для плана) — совпадает дословно.src/device-presentation.ts:218-235— комментарий и телоcontrollerAvailability()— совпадает дословно, включая формулировку «Controls are not controller evidence…».resolveDevicePresentation()(строка ~600-635) действительно вызываетcontrollerAvailability(hass, d)только дляsourceKind === 'controls', то есть один и тот же чистый резолвер используется и планом, и preview — значит расхождение объясняется входом (hass/d.entities), а не веткой логики. Это ключевое утверждение ТЗ (§3), и оно подтверждается.src/houseplan-card.ts— план читаетthis._renderDeviceSnapshot?.presentations.get(...)(предвычисленный, иммутабельный снэпшот), тогда как ветка preview (около строки 20279) вызываетresolveDevicePresentation(this._planHass, previewDevice, {...})заново, гдеpreviewDevice— результат_markerPreviewDevice(d). Второй верхнеуровневый путь подтверждён так же, как заявляет ТЗ.
Диагноз ТЗ не является догадкой, выданной за факт: там, где точная причина ещё не доказана (какое именно поколение registry/roster вызывает расхождение), текст прямо говорит «без живого runtime dump не доказан» и запрещает реализации выбирать ветку по догадке (§3, §14 п.1 промаркирован как предположение). Это ровно то поведение, которого требует процесс.
Продуктовые вопросы владельца — закрыты корректно, не додуманы
Issue поднимал два вопроса владельцу («что видеть для кнопки без своего состояния» и «считается ли unknown у event.* признаком недоступности»). ТЗ (§4) не додумывает ответ, а показывает, что оба уже даны решением по #251: правило 5 контракта #251 («доступность по любому живому own entity state, диагностика участвует, controls — нет») отвечает на оба вопроса дословно, и это решение уже принято владельцем (не техническое, не спорное). Проверено, что #251 действительно формулирует именно эти пункты (см. docs/specs/251-controller-target-availability.md §4 п.1,5 и §6.1). Трактовка ТЗ верна: #274 — это регрессия/рассинхронизация уже принятого контракта, а не новое продуктовое решение, поэтому пачка вопросов владельцу здесь не нужна.
Проверка AC1…AC8
Все восемь пронумерованы, для каждого указан способ доказательства (unit/smoke/golden/mutation/review), как того требует DoR §2.5:
- AC1 — точная фикстура (event=unknown, battery=100, LQI=164, update=off, group=off) и точный ожидаемый результат (
available,neutral, безunavail,lqiText=164). Однозначно, проверяемо unit-тестом. - AC2 — сравнение DOM плана и preview для того же сохранённого маркера без правок; для этого уже существует инфраструктура —
demo/smoke_device_preview_parity.mjs(сравнивает.devна плане и вhp-device-previewпо классам/иконке/значению). AC не называет файл по имени, но выбор файла — техническое решение автора (§7.1), а не пробел ТЗ. - AC3 — матрица переходов
off → on → unavailable → off, однозначные ожидания для каждого шага. - AC4 — перечислена конкретная минимальная матрица поколений (registry arrival, state tick, save/rebind, continuity hold/candidate/commit, reconnect), критерий «одно поколение — одно лицо» сформулирован проверяемо.
- AC5 — явно требует не ослаблять негативный контракт #251 (event-only binding, all-unknown case), ссылается на существующую table-driven matrix для расширения, а не переписывания.
- AC6 — parity View/kiosk/Static/preview в light/dark; корректно относит semantic golden к предрелизному циклу («локальный baseline не принимается»), что соответствует общему правилу PROCESS §8/§13.
- AC7 — два конкретных, содержательных мутанта (не source-regex) с указанием, какие AC обязаны покраснеть при каждом. Это именно то, что требует «дисциплина падающего теста».
- AC8 — гейты реализации, корректно разделяет локальный цикл и предрелизный цикл.
Ни один AC не сформулирован расплывчато; у каждого есть точка отказа, по которой можно судить о провале.
Обязательные разделы (PROCESS.md §7.1) и DoR (§2.5)
Все обязательные разделы §7.1 присутствуют по существу: сценарий/персона (§1), что человек увидит до/после (§2), проблема и подтверждённые факты (§3), контракт поведения (§4, §6), scope/не-scope (§5), UX/touch/compatibility (§7), критерии приёмки с доказательством (§8), план автотестов (§9), риски (§12), откат (§13), release-артефакты (§11). Продуктовых открытых вопросов нет, что явно зафиксировано (§4).
Один пункт чек-листа DoR §2.5 — «перечислены затронутые файлы и модули» — не оформлен отдельным списком. Функции и файлы фактически названы по тексту (buildDevices(), controllerAvailability(), RenderDeviceSnapshot, test/devices.test.mjs, test/device-presentation.test.mjs, документация в §11), но сведены не в один список. Не считаю это блокирующим: сама граница диагноза (§3) сознательно не зафиксирована («без runtime dump не доказан»), и заранее заявленный список конкретных production-файлов был бы ровно той вставленной вперёд догадкой, которую процесс запрещает. Снимаю как Low с этой записью, а не превращаю в Medium — фиксировать список файлов раньше, чем найден точный источник рассинхронизации, значило бы гадать.
Находки
Low — «До»-описание в §2 использует внутренний термин вместо простой фразы (снято)
docs/specs/274-wireless-controller-presentation-parity.md:35 — фраза «план приглушает выключатель классом unavail» вставляет имя CSS-класса в раздел, который по формату должен быть «одной фразой без терминов реализации» (PROCESS.md §7.1). Не блокирует: это репродукция симптома вплотную к тексту issue, вторая часть того же предложения («показывает зелёный LQI 164, а preview обещает нейтральную подложку») уже передаёт смысл на пользовательском языке, и точное имя класса скорее помогает второй-ревьюер/автору однозначно опознать баг. Снимаю как Low с этой записью, править не обязательно.
Low — DoR-пункт «затронутые файлы и модули» не оформлен отдельным списком (снято)
Разобран выше — снят с записью «список файлов сознательно не даётся до red production-path теста, это соответствует принципу „граница диагноза не додумывается“».
High/Medium находок нет.
Что проверено и корректно
- Диагноз построен на подтверждаемых номерах строк, все сверены с фактическим кодом на
HEAD— не пересказ автора. - Контракт #274 не переопределяет и не ослабляет уже принятый контракт #251 (явная негативная AC5 плюс явный список «не входит»).
- Продуктовые вопросы владельца из тела issue закрыты ссылкой на уже принятое решение #251, а не выданы за новую догадку.
- AC1…AC8 пронумерованы, у каждого — однозначный ожидаемый результат и способ доказательства; мутанты (AC7) содержательны.
- Компатибилити/миграция/i18n/touch/perf/security разделы присутствуют и корректно говорят «без изменений» там, где это действительно так (persisted config, backend, touch-жесты, i18n-ключи).
- Release-артефакты (§11) перечисляют оба changelog, ARCHITECTURE.md, USER-GUIDE.{md,ru.md}, TESTING.md — соответствует правилу «документация в том же коммите».
- Трассируемость issue ↔ ТЗ ↔ индекс (
docs/specs/README.md) на месте в обе стороны. - Коммит ТЗ несёт корректные трейлеры (
Issue: #274,User-Visible: no) для чисто документационного класса C изменения.
Чего не проверял
- Не гонял
typecheck/test/build/check-docs/смоки/инварианты — диапазон коммита не содержит класса A/B (толькоdocs/specs/**), поэтому эти гейты не относятся к предмету этого раунда ревью ТЗ. - Не проверял глубину архитектуры
RenderDeviceSnapshot/continuity commit barrier построчно — это территория код-ревью после того, как красный production-path тест по AC1/AC2 будет написан; на этапе ТЗ важно, что граница явно не додумана, а не то, где именно лежит фикс. - Не связывался с владельцем — продуктовых вопросов, требующих его решения, не осталось.
Вердикт
Зелёный. ТЗ технически обосновано, ссылки на код проверены и совпадают, AC однозначны и проверяемы, продуктовые вопросы закрыты ссылкой на уже принятое решение, а не догадкой. Обе Low-находки сняты с записью в этом документе.