Files
houseplan-card/legacy/reviews/v1.65.0/SPEC-REVIEW-178-r1.md
T
Claudeandclaude[bot] 0991c45374 fix(tools): архив переписывает относительные ссылки перенесённых документов (#682)
Ревью #682 r1, Medium: перенос добавляет документу уровень вложенности
(`docs/reviews/X.md` → `legacy/reviews/<тег>/X.md`, `docs/specs/` →
`legacy/specs/`), а относительные ссылки внутри перенесённых документов и в
соседях, ссылавшихся на них, никто не пересчитывал — на `97d19268` 53 битые
ссылки в 46 файлах (заявление «все 26 резолвятся» в `7feb6177` было верно
только до переноса документов ревью). Гейты архив не смотрят.

`reviews-archive.mjs`: `repairLinks` пересчитывает ссылку, если она не
резолвится от нового места, а цель находится от нового или старого места
через карту переносов; битая и до переноса ссылка не трогается. `--apply`
делает это само, `--repair-links=<rev>` — для всех переименований
`<rev>..HEAD`, `--check-links` печатает битые. Этим коммитом
`--repair-links=origin/dev` переписал ровно 53 ссылки в 46 файлах; остались
две прежние «...»-заглушки в CODE-REVIEW-448-r2 (битые и на dev). Тесты:
перенесённый документ, сосед со ссылкой в архив, ТЗ со ссылкой на позже
перенесённое ревью, битая-до-переноса не трогается, в `legacy/` битых нет;
мутант `reviews-archive-links-from-new-place-only`. PROCESS §2.10 и
DEVELOPMENT › Release называют переписывание и `--check-links`.

Issue: #682
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 22:10:47 +00:00

228 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-178-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/178
- **ТЗ:** [`docs/specs/178-toggle-entity.md`](../../specs/178-toggle-entity.md)
(commit `e46ef6f55c44ec1f05268cdff4ceeb4dcc5af116`, ветка `issue/178-toggle-entity`)
- **Ревьюер:** Claude (ревью ТЗ ≠ автор), этап `S4-spec-review`
- **Цикл:** r1/4 (обычный трек — issue не `small`/`trivial`, подтверждено
комментарием аналитики и меткой `feature` без `small`)
## Скоуп ревью
Ревью ТЗ #178: новое optional поле `marker.toggle_entity`, дающее пользователю
выбор конкретной собственной `light.*`/`switch.*`-сущности для действия
«Переключить состояние» у составных устройств (сейчас цель выбирает эвристика
`resolveOwnEntity()`/`ownRoleCandidates()`), плюс диалоговый селектор,
stale-fallback, влияние на explicit controls-group, backend-валидацию и
export/import.
Не в скоупе ревью: код ещё не написан (issue в `S4-spec-review`,
`git diff origin/dev...HEAD` для `src/**` и `custom_components/**/*.py` пуст —
проверено), поэтому проверка реализации, тестов и гейтов — предмет будущего
code-review (PROCESS.md §2.7).
## Как проверялось
Прочитано в заданном порядке:
1. `docs/SCOPE.md` — цель задачи привязана к J3 («Let me act on the obvious
right from the plan»); лок-инвариант (§«The lock invariant») не затронут —
`toggle_entity` ограничен `light.*`/`switch.*`, что явно исключает
`lock.*`/`alarm_control_panel.*`.
2. `AGENTS.md`, `PROCESS.md` целиком (включая §2.4, §2.5 DoR, §7.1, §5, §8).
3. Issue #178 body и все 3 комментария: аналитика владельца (оценка, связанные
задачи, подтверждённые технические контракты), «взял в работу», «ТЗ готово».
4. `docs/USER-GUIDE.ru.md` — строки 541, 571–590, 645–650, 780–781: термины
«Переключить состояние», «Ведущая сущность» (для `light_entity`, отдельное
поле) сверены с §2 и §9.2 ТЗ.
5. `docs/CONFIG-COMPATIBILITY.md` (раздел про `marker.light_entity`,
строки 176–191) и `docs/TOUCH-SUPPORT.md` целиком — канонические документы,
которые ТЗ обязано соблюсти для поля-precedent и для editor-фичи.
6. Само ТЗ `docs/specs/178-toggle-entity.md` целиком.
7. Текущий код на той же ветке/коммите (продуктовый код не менялся) — построчно
сверены все фактические утверждения ТЗ о текущем поведении:
- `src/device-toggle.ts` — `ownRoleCandidates()` (370–392), `resolveOwnEntity()`
(401–423), `resolveControls()` (573–646), `toggleOriginOf()` (459–463);
- `src/devices.ts` — `ownControllableEntities()` (317–325),
`forcedLightEntityOf()` (329–336), `persistedExternalControls()` (288–300);
- `src/houseplan-card.ts` — диалоговый паттерн `light_entity`
(11924–11968, 12356–12358, 18398–18428: native `<select>`, Auto-опция,
`friendly name · entity_id`, stale-warning с `role="status"`,
`_announceToggleDraft`/`_markerDraft`, 17735–17913, 18014–18297);
- `custom_components/houseplan/validation.py` (`validate_marker_light_entities`,
189–220) и `websocket_api.py`/`import_export.py` — границы вызова валидатора;
- `custom_components/houseplan/import_export.py:895–897` — точный allowlist
полей, удаляемых при `duplicate_policy: virtual` (подтверждает claim §7.4).
Совпадение везде построчно точное — ни одна ссылка на код в ТЗ не оказалась
пересказом или догадкой.
8. Прецедентные ТЗ этого же формата — `docs/specs/174-linked-virtual-light-controller.md`
и `docs/specs/164-washer-active-cycle.md` — чтобы установить, какие разделы
§7.1 в этом репозитории считаются обязательными на практике (обе содержат
отдельные «Риски/perf/security», «Откат», «Release-артефакты», «UX/i18n/touch»).
Гейты (`typecheck`/`test`/`build`) не прогонялись — на этапе ревью ТЗ
продуктового кода нет, прогон гейтов не относится к этому этапу (PROCESS.md
§2.7/§8).
## Находки
### High-1 — отсутствуют обязательные разделы: риски, откат, влияние на touch/производительность
**Файл:** `docs/specs/178-toggle-entity.md` (весь документ; список разделов —
строки 15–426).
PROCESS.md §7.1 называет обязательными разделами ТЗ, помимо прочих, **риски**,
**откат** и (через DoR §2.5) явно названное **влияние на производительность**
и **влияние на touch по `docs/TOUCH-SUPPORT.md`**. В документе нет ни одного
из них — ни как отдельного раздела, ни растворённым в другом разделе. Полный
список заголовков документа:
```
1. Сценарий и цель 6. Non-scope 11. Runtime group Toggle
2. Что изменится... 7. Модель данных... 12. Backend и валидация
3. Подтверждённое... 8. Кандидаты... 13. i18n и документация
4. Решения владельца 9. Диалог устройства 14. Тестовый контракт
5. Scope 10. Runtime single... 15. Acceptance criteria
16. Принятые предположения
```
Ни «риски», ни «откат», ни «touch», ни «производительность» не встречаются
(проверено `grep -n -i` по всему файлу — совпадений нет, кроме одного числового
«риск 4/10» в шапке-метаданных, унаследованного из issue, а не как раздел
самого ТЗ).
Это не формальная придирка: соседние ТЗ той же сложности и того же трека,
принятые по этому же процессу, содержат эти разделы явно —
`docs/specs/174-linked-virtual-light-controller.md` (§15 «Риски, performance и
security», §16 «Откат», §17 «Release-артефакты», §12 «UX, i18n, accessibility
и touch») и `docs/specs/164-washer-active-cycle.md` (§15/§16/§17/§18
аналогично). `docs/TOUCH-SUPPORT.md:147–151` формулирует требование буквально:
«New editor feature specifications and code reviews must state one of:
`Touch editor: supported`; `Touch editor: best effort / intentionally
degraded`; `Touch editor: not exposed`». #178 — ровно editor feature
specification (новый `<select>` в диалоге устройства), и такой строки в
документе нет вовсе.
**Воспроизведение:** `grep -n -i "риск\|откат\|rollback\|touch\|производительн" docs/specs/178-toggle-entity.md`
возвращает пустой список (кроме заголовка-метаданных с оценкой сложности).
Автор код-ревью, дойдя до вопроса «какой откат у этой фичи, если баг
проявится после релиза» или «это editor-фича, какой у неё touch-статус»,
не найдёт ответа в каноническом документе — придётся выяснять по памяти или
логике задним числом, что PROCESS.md §7.1 и прямо называет недопустимым
(«ТЗ, которое не может ответить на эти два вопроса, описывает работу, а не
изменение продукта» — тот же принцип применим к остальным обязательным
разделам).
Содержательно риск, скорее всего, невелик — поле optional, миграции нет
(§7.2), диалог основан на native `<select>` (обычно touch-нейтрален), а
основной риск совместимости (single vs group resolution) фактически разобран
внутри §10–§11. Но раздел должен явно резюмировать это как «риск», а не
рассеивать по контрактным пунктам, и явная touch-строка должна присутствовать
буквально по требованию канона, а не подразумеваться.
**Вердикт:** блокирует. Возврат в `S3-spec`: добавить разделы «Риски»,
«Откат», «Touch editor: …» и явное «Производительность: …» (или «нет»).
Содержание может быть коротким — судя по анализу выше, реальных
неразрешённых рисков не открывается, — но раздел должен существовать и
явно проговорить то, что сейчас можно только вывести по контексту.
Больше High/Medium-находок нет.
### Low-1 — способ доказательства не привязан к номеру AC явно
**Файл:** `docs/specs/178-toggle-entity.md:406–424` (§15 Acceptance criteria).
PROCESS.md §2.5/§7.1 требуют, чтобы у каждого AC было «указано, чем он
доказывается». В §15 двенадцать пунктов AC не содержат инлайн-пометки
`unit`/`smoke`/`golden`/«ревью кода» — способ доказательства восстанавливается
только косвенно, сверкой с отдельным §14 (тестовый контракт), где нумерация
тестов не совпадает 1:1 с нумерацией AC. Например AC1 («виден selector при
двух кандидатах») доказывается smoke-тестом §14.2 п.1, а не одним из
unit-тестов §14.1 — это можно восстановить логически, но не прочитать
напрямую.
**Воспроизведение неоднозначности:** код-ревьюер, сверяя «AC7 доказан?»,
должен самостоятельно найти соответствие AC7 → unit-тесты §14.1 пп.7–9, а не
прочитать это в самом §15.
**Вердикт:** не блокирует. Содержательно способ доказательства
восстанавливается однозначно по §14 для каждого AC — переноса на новый цикл
не требует. Снимаю с запиской: на будущей ревизии (или в самом коде, в имени
теста/комментарии) стоит явно указать `(doc: unit §14.1.N)` в каждом пункте
§15, чтобы код-ревью не тратило цикл на восстановление соответствия.
## Что проверено и корректно
- **Технические утверждения о текущем коде верны построчно.** Каждая ссылка
на `resolveOwnEntity`, `ownRoleCandidates`, `ownControllableEntities`,
`forcedLightEntityOf`, `persistedExternalControls`, диалоговый паттерн
`light_entity` (native select, Auto-опция, warning, `_announceToggleDraft`)
и backend-границы валидации (`validate_marker_light_entities`,
`websocket_api.py`, `import_export.py`) сверена с реальным кодом и совпадает.
Ни одна догадка не выдана за факт.
- **Продуктовые решения владельца перенесены точно.** Все 9 пунктов §4
(«Решения владельца») буквально соответствуют формулировкам issue body и
комментария аналитики от 18.08.2026 (рабочее имя поля, видимость селектора,
бит-в-бит совместимость, stale-поведение, live-preview, буквальный
entity id, независимость от `light_entity`/`tap_target`, участие в группе,
исключение `marker:*`).
- **Scope/Non-scope (§5/§6) согласованы** с прецедентом #88/#84 и явно
исключают домены за пределами `light.*`/`switch.*`, изменение confirmation
flow (#103) и `marker:*` (#107/#174) — ничего из этого владелец не просил.
- **Compatibility без миграции (§7.2, §7.3) корректна и проверяема**:
отсутствие поля = прежняя цепочка бит-в-бит; stale-значение не стирается,
runtime не вызывает service по stale id — то же поведение, что уже
реализовано и задокументировано для `light_entity`
(`docs/CONFIG-COMPATIBILITY.md:176–182`).
- **Влияние на explicit controls-group (§11) корректно разграничивает**
совместимый режим (без explicit поля группа остаётся external-only) и новый
режим (явно выбранная собственная сущность входит в группу без изменения
правила `any-on → turn_off all`) — соответствует решению владельца §4.8 и
не меняет уже принятую семантику группы.
- **Backend-раздел (§12) корректно указывает те же точки вызова валидатора**,
что использует `light_entity` (`websocket_api.py:1256,1367`,
`import_export.py:980,1160-1162`), включая `validate_all=True` для полного
импорта — подтверждено чтением кода, а не пересказом.
- **Export/import и virtualize-политика (§7.4) точно называют существующий
allowlist** (`import_export.py:895-897`) и корректно требуют добавить туда
новое поле — без этого явного шага поле осталось бы в virtual-маркере,
указывая на HA entity, которого больше нет.
- **AC однозначны по формулировке** — ни один пункт §15 не читается двумя
взаимоисключающими способами (в отличие от найденного в прецеденте #174
Low-1); граничные случаи (одна кандидатка, stale, transient unavailable,
cover/virtual paths) описаны явно и без противоречий.
- **Продуктовых вопросов владельцу не осталось.** Единственный источник
продуктовой неопределённости («что видит пользователь, до какого объёма») уже
закрыт issue body и комментарием аналитики; ТЗ не выдаёт ни одной технической
догадки за продуктовое решение — раздел §16 «Принятые технические
предположения» корректно отделяет 5 свободно изменяемых технических решений
(обобщение `ownControllableEntities()`, паттерн touched/write-fields, формат
`friendly name · entity_id`, `via` для diagnostics, отсутствие schema-версии)
от нормативных решений §4, и ни одно из них не маскирует продуктовый вопрос.
- **Трассируемость issue ↔ ТЗ на месте**: `docs/specs/README.md:98` ссылается
на `178-toggle-entity.md`, документ ссылается на issue в шапке.
## Чего не проверял
- Реализацию — её нет: issue в `S4-spec-review`, `git diff origin/dev...HEAD`
для `src/**` и `custom_components/**/*.py` пуст (проверено).
- Гейты `typecheck`/`test`/`build`/browser smoke — не относятся к этапу ревью
ТЗ; предмет будущего code-review (PROCESS.md §2.7/§8).
- `python -m pytest tests_backend` — backend не менялся на этой ветке.
- Golden/perf-эталоны — ТЗ описывает будущие сценарии (§14.3), реализации для
сверки нет.
- Не проверял вручную UI/диалог в браузере — на этом этапе кода не существует;
сверка велась только с уже реализованным прецедентом `light_entity` по коду.
## Вердикт
Единственная блокирующая находка — отсутствие обязательных по PROCESS.md §7.1
и `docs/TOUCH-SUPPORT.md` разделов (риски, откат, touch/производительность),
которые в двух прецедентных ТЗ того же трека присутствуют явно. Остальное
содержание ТЗ добротно: технические утверждения о коде проверены построчно и
верны, продуктовые решения владельца перенесены точно, AC однозначны, догадок
под видом фактов не найдено.
**Вердикт: красный · цикл r1/4 · High: 1 · Medium: 0 → нет · Документ:
docs/reviews/SPEC-REVIEW-178-r1.md**