# SPEC-REVIEW — issue #318 · заход r1 - Этап: spec (PROCESS.md §2.4) - Артефакт ТЗ: `docs/specs/318-empty-controller-roster.md` (полный трек; `docs/specs/README.md` — строка добавлена) - SHA ревью: `1242a3b1badf13a8785c7e176da2c3f765ccdad8` (ветка `issue/318-switch-render-state`, коммит "docs: specify empty-roster controller state", `Issue: #318` · `User-Visible: no` — трейлеры корректны: коммит документальный, пользовательского поведения ещё не меняет) - Заход: r1 · блокирующих циклов израсходовано 0 из 4 ## Скоуп Issue #318: маркер физического беспроводного выключателя без единой собственной сущности registry (`device:` binding активен, `own roster = []`, `controls: [light.wall_lights]`) навсегда остаётся в приглушённой (`unavailable`) подаче независимо от состояния управляемой цели. Owner уже принял продуктовое решение в комментариях issue до написания ТЗ: активный физический device-binding с пустым собственным roster считается доступным и следует `controls`; непустой roster без единого живого состояния остаётся `unavailable` по контракту #251. ТЗ реализует это решение. Трек — полный, обоснованно: меняется публичный UX-контракт (строка S05 таблицы решений `docs/DEVICE-PRESENTATION.md`), затронут общий presentation resolver на всех поверхностях (plan/preview/hosted Static). Названный критерий лёгкого трека, который задача не проходит, указан в самом ТЗ. Затронутая подсистема: `docs/DEVICE-PRESENTATION.md` / `docs/ARCHITECTURE.md` (канон availability/status controller), плюс `docs/USER-GUIDE.ru.md` для пользовательской терминологии. ## Как проверялось 1. Прочитан `docs/SCOPE.md` — задача закрывает J1 («что происходит сейчас») и J3 («быстрое очевидное действие»); ложный сигнал недоступности прямо противоречит J1. 2. Прочитаны `AGENTS.md`, `PROCESS.md` §1–2.4, §4, §5, §7.1–7.3 — формат документа, обязательные разделы, бюджет циклов, критерий полного трека. 3. Прочитано тело issue #318 и все 4 комментария: заявка → анализ аналитика с продуктовым вопросом → решение владельца → хендофф автора ТЗ. Продуктовый вопрос был ровно один, и он закрыт до старта этого ревью. 4. Прочитан весь файл ТЗ `docs/specs/318-empty-controller-roster.md` (295 строк) целиком, раздел за разделом на соответствие §7.1. 5. Прочитаны канонические документы: `docs/DEVICE-PRESENTATION.md` (таблица решений, строки S03–S09), `docs/ARCHITECTURE.md` (цитаты «Yellow means working» из issue подтверждены как существующий канон), `docs/USER-GUIDE.ru.md` (раздел 12 «Визуальные состояния устройств», строки 958–1004 — текущая документированная формулировка, которую ТЗ обязано будет уточнить в коде). 6. Сверены с реальным кодом все технические утверждения диагноза (§3) и контракта (§6.1–6.3), а не приняты на веру: - `controllerAvailability()` (`src/device-presentation.ts:208-217`) — `(d.entities || []).some(...)` на пустом массиве безусловно даёт `false` → `unavailable`. Диагноз ТЗ («пустой список безусловно даёт unavailable») подтверждён чтением, не исполнением. - `resolveHaBindingStatus()` (`src/ha-binding-status.ts:414-463`) — ветка `kind === 'device'`, `allEntityIds.length === 0`: `isRegistryEntryEnabled(device)` истинно (устройство активно), `enabledEntityIds = [].filter(...) = []`, условие `allEntityIds.length && !enabledEntityIds.length` ложно (первый операнд `0`) → возвращается `{ kind: 'active', enabledEntityIds: [], allEntityIds: [] }`. Это ровно сценарий #318 и ровно предикат §6.1 ТЗ («active», `bindingKind === 'device'`, roster `[]`). Подтверждено чтением. - Термины `bindingKind`, `bindingStatus.kind` (`active`/`ha_disabled`/ `orphaned`/`unverified`), `DevItem.entities` — все существуют в `src/devices.ts`, `src/ha-binding-status.ts` с ровно теми значениями, которые ТЗ им приписывает. - Идентификаторы тестовых артефактов из плана и AC — все существующие, не выдуманные: `test/device-presentation.test.mjs` (1092 строки), `demo/smoke_wireless_controller_parity.mjs` (162 строки), `scripts/mutation-gate.mjs` содержит мутанты `controller-availability-follows-target`, `wireless-controller-loses-filtered-target-role`, `wireless-controller-preview-drops-sibling-markers` (используются также в `test/fixtures/device-presentation-decisions.mjs` для S03/S05/S07). - `docs/DEVICE-PRESENTATION.md` строка S05 сегодня буквально гласит «цель работает, controller не имеет live entity → faded controller» — ТЗ §9 корректно называет её на разделение. - `docs/USER-GUIDE.ru.md:979` и таблица строка 1000 сегодня документируют «отсутствуют» (нулевой roster) как один из триггеров бледного маркера — это ровно текст, который реализация обязана сузить; ТЗ §9 называет этот документ в списке правок, не давая финальной формулировки (корректно — формулировка не продуктовый факт, а редактура канона). 7. Проверено соответствие обязательного чек-листа `docs/specs/README.md` («Обязательные release-артефакты номерного ТЗ») — все четыре пункта закрыты разделами §9 и §15 ТЗ. 8. Проверено, что коммит на ветке ровно один, docs-only, класс C по AGENTS.md (не требует ревью само по себе, но здесь — часть DoD этапа spec), трейлеры корректны для docs-only коммита. Гейты не гонялись: этап — ревью ТЗ, продуктового кода в диффе нет (класс C, только `docs/**`). Раздел «чего не проверял» — ниже. ## Находки Нет. Ни одной High- или Medium-находки. Проверены типичные классы дефектов спек-ревью и не обнаружены: - **Догадка, выданная за факт.** Не найдена: единственный продуктовый вопрос (эмпти-roster active device = доступен?) задан аналитиком и решён владельцем до старта написания ТЗ (комментарии от 12:37 и 12:42). §16 ТЗ содержит явный блок «принято предположительно, поменять свободно» ровно для технических, не продуктовых решений (расположение предиката, отсутствие нового enum, ID decision trace) — как и требует §7.1. - **Отсутствие открытого вопроса при сложной задаче.** Вопрос был; он снят корректно, до, а не во время написания ТЗ. - **Несуществующие идентификаторы/файлы в AC и плане.** Все имена функций, тестовых файлов и mutation ID существуют в дереве репозитория (см. выше). - **Расхождение с каноном подсистемы.** Диагноз и предлагаемое правило не противоречат ни одному существующему решению таблицы S01–S09; #251 и #274 явно сохранены как негативные тесты (AC3, AC4). - **Нарушение SCOPE.md.** Изменение — узкое исправление существующего J1/J3 сценария, не расширяет функциональность и не вводит новый визуальный элемент (§5 «Не входит» явно запрещает новый badge/glyph/pulse/цвет/текст). - **Единое число, два источника.** Неприменимо: изменение касается только availability/цвета подложки, не вводит и не дублирует числовое значение. - **Отсутствие раздела §7.1.** Все обязательные разделы присутствуют: сценарий и персона (§1), что человек увидит до/после (§2), проблема (§3), скоуп/не-скоуп (§5), контракт поведения (§6), UX (§7), модель данных/миграция (§8), i18n (§9), AC1–AC9 с доказательствами (§10), план автотестов (§11), перф/безопасность (§12), риски (§13), откат (§14), release-артефакты (§15). - **Владельцу вынесен технический вопрос под видом продуктового.** Не найдено: единственный заданный вопрос («считать ли активный device-binding без сущностей доступным») — ровно продуктовый по определению §7.1 (что человек видит). ## Что проверено и корректно - Формат документа, нумерация разделов, наличие всех обязательных пунктов §7.1 PROCESS.md. - Трек задачи (полный) обоснован названным критерием, а не молчаливым умолчанием. - Диагноз причины (§3 ТЗ) — подтверждён построчным чтением `controllerAvailability()` и `resolveHaBindingStatus()`, соответствует фактическому коду один в один. - Предикат «active physical device binding» (§6.1) — реализуем на существующих полях (`bindingKind`, `bindingStatus.kind`, `d.virtual`), не требует новых данных. - Матрица §6.2 непротиворечива и покрывает все девять комбинаций binding×roster×target из отдельного анализа, включая явно исключённые virtual/ha-disabled/orphaned строки. - Скоуп/не-скоуп (§5) точно очерчивает границу: entity-bound markers, registry-как-online-для-непустого-roster, новый visual vocabulary — всё явно исключено, что закрывает главный риск (широкое ослабление контракта #251). - AC1–AC9 каждый называет способ доказательства через существующий или явно спланированный тестовый механизм (unit, production-bundle smoke, mutation-gate, diff audit), ни один не оставлен голословным. - Release-артефакты (§15) и обязательный чек-лист `docs/specs/README.md` закрыты: changelog RU/EN, список затронутой пользовательской документации, явное заявление об отсутствии новых screenshot/golden baseline при условии зелёных существующих сьютов. - Откат (§14) описан и не требует обратной миграции данных, так как модель/ конфигурация не меняются (§8 подтверждён отдельно). - Ветка, коммит и трейлеры соответствуют `AGENTS.md`: `issue/318-switch-render-state`, один docs-only коммит, `Issue: #318` + `User-Visible: no`, SHA хендоффа (`1242a3b`) совпадает с фактическим HEAD. ## Чего не проверял - **Гейты `typecheck`/`test`/`build`/`bundle:sync`/`bundle:budget`/`check-docs`** — не гонялись. На этом этапе в диффе нет ни одного файла класса A/B/D (только `docs/specs/**`), продуктовый код не менялся, гонять их бессмысленно: они проверяют код, которого ещё нет. Ссылка на зелёный Validate `1242a3b1` (https://github.com/Matysh/houseplan-card/actions/runs/33172444930) относится к этому же SHA и подтверждает, что репозиторий в целом зелёный, но это не подменяет проверку самого ТЗ — она сделана вручную чтением. - **Смоки, golden, mutation-gate, invariants** — не прогонялись и не должны: это план на этап реализации (§11 ТЗ), сейчас проверяется только существование названных файлов/идентификаторов, не их поведение под ещё не написанный код. - **Финальная формулировка правок канона** (`DEVICE-PRESENTATION.md` S05, `ARCHITECTURE.md`, `USER-GUIDE.md`/`USER-GUIDE.ru.md`, `TESTING.md`) — ТЗ правомерно откладывает точный текст на этап реализации; проверено только, что список затронутых документов полон и совпадает с реальными текущими формулировками, которые придётся менять (см. «Как проверялось», пункт 6). - **UI/браузер** — на этапе ревью ТЗ ручного тестирования нет по определению этапа; кода для проверки не существует. ## Унаследовано из r Неприменимо — это первый заход (r1), предыдущего раунда нет. ## Вердикт Зелёный. Спецификация полна по §7.1, продуктовый вопрос закрыт владельцем до написания, технический диагноз и контракт проверены построчным чтением кода и совпадают с реальностью, скоуп/не-скоуп корректно ограничивают риск ослабления контракта #251, AC доказуемы существующими или явно запланированными механизмами. High: 0. Medium: 0.