Files
houseplan-card/docs/reviews/SPEC-REVIEW-440-r1.md
T
2026-09-03 13:41:26 +00:00

152 lines
15 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 #440 — r1
- Issue: https://github.com/Matysh/houseplan-card/issues/440
- Этап: spec (PROCESS.md §2.4)
- ТЗ: `docs/specs/440-v171-beta2-polish.md`, коммит `735710f1`
- Заход r1 · блокирующих циклов израсходовано 0/4
- Вердикт: **зелёный**
## Скоуп ревью
Полный разбор — заход первый, дельты нет. ТЗ покрывает семь пунктов аудита
v1.71.0-beta.2 (§3.3): (а) зависание verifier на не-обычном файле, (б) потеря
pointer-modality на room pointermove при выключенном tooltip, (в) getter с
побочным эффектом `_dangerConfirmLocaleGate`, (г) TOCTOU в физической
инвентаризации ассетов + неверный HTTP 507, (д) source-regex свидетель AC5
#434, (е) отсутствие ретроактивных review-документов для #429/#430, (ж)
`importorskip("homeassistant")`, скрывающий чистые Python-тесты канонизации.
Читал в заданном порядке: `docs/SCOPE.md`, `PROCESS.md` §1–§8, тело issue
#440 и оба комментария (аналитика + сдача ТЗ), само ТЗ целиком,
`docs/TOUCH-SUPPORT.md` §«Pointer modality and hover ownership».
## Как проверялось
Ревью ТЗ — не код-ревью, но каждое фактическое утверждение ТЗ о текущем
поведении кода я сверил с реальным деревом на `origin/issue/440-audit-polish`
(= `735710f1`), чтобы отличить обоснованный контракт от догадки, выданной за
факт:
| Пункт ТЗ | Файл:строка | Что проверено |
|---|---|---|
| (а) verifier/FIFO | `custom_components/houseplan/asset_integrity.py:42-129` | `_signature()` = голый `path.stat()` (проходит на FIFO/device), `_stream_sha256()` открывает `open("rb")` без таймаута, followers ждут `event.wait()` без границы. Совпадает с описанием ТЗ дословно |
| (б) pointer modality | `src/houseplan-card.ts:11589-11675`, `:7186-7193`, `:7291-7304` | `tip(e)` (bound на `@pointermove`) при `!showRoomTooltipOf(...)` возвращается на 11592-11593 **до** вызова `_notePointer`; `enterRoom` (bound на `@pointerenter`) вызывает `_notePointer` всегда. Подтверждает узкую щель: смена pointer type внутри уже наведённой комнаты не долетает до `_notePointer`, когда tooltip выключен |
| (в) locale getter | `src/houseplan-card.ts:2160-2166,2188,4193`, `src/i18n/language-runtime.ts:105-131` | Геттер `_dangerConfirmLocaleGate` вызывает `languageRenderGate()`, которая мутирует `host.inert`, `aria-busy`, `lang`, два `WeakSet`, планирует `requestUpdate()` — вызывается из `_confirmDanger()` и `updated()`, вне рендера. Подтверждено |
| (г) TOCTOU/507 | `custom_components/houseplan/decor_assets.py:376-394`, `http_api.py:262-347` | `physical_asset_blobs` не оборачивает `path.stat(follow_symlinks=False)` внутри цикла `iterdir()` — исчезновение даёт необработанный `OSError`, который поднимается через `physical_asset_usage` → `_store()` → перехватывается только на `except OSError` (500). Отдельно `except DecorAssetError: … status=507` на строке 344 ловит **любой** `DecorAssetError` из `_store()`, включая `invalid_image` (273, 289) — сейчас всегда 507. Оба факта подтверждены |
| (д) AC5 regex | `test/space-card-audit-lows.test.mjs:28-39` | Ровно пять `assert.match` по тексту `src/space-card.ts`/`src/config-store.ts`, как описано в ТЗ |
| (ж) importorskip | `tests_backend/test_coordinate_canonicalization.py:1-40` | Модульный `pytest.importorskip("homeassistant")` стоит выше импорта `coordinate_canonicalization` и `DECOR_BOX_KINDS` — чистые тесты (`test_decor_box_catalog_matches_shared_contract`, `test_all_4801_lattice_nodes_and_nine_decimal_forms_share_exact_bits`) реально скипаются без HA |
| AC6 инфраструктура | `scripts/mutation-gate.mjs:2402-2450` | Мутанты `image-box-python-canonicalization-omitted` и `all_4801_lattice_nodes...` уже указывают на `tests_backend/test_coordinate_canonicalization.py` через `backend-test-guard.mjs` — перенаправление на новый модуль механически осуществимо, путь не изобретён |
| TOUCH-SUPPORT.md | `docs/TOUCH-SUPPORT.md:43-57` | «Touch and pen input immediately clear transient room and device hover, including tooltips» — контракт п.2 ТЗ («независимо от настройки») не изобретён, а прямая цитата канона |
| i18n | `src/i18n/{en,ru,de,fr}.json` | Все четыре словаря существуют, ТЗ верно перечисляет «не меняются» |
| Смоки в «Затронутых модулях» | `ls demo/smoke_danger_confirm_branches.mjs demo/smoke_room_tooltip_toggle.mjs demo/smoke_space_card_decor_capability.mjs` | Все три файла существуют |
| `docs/specs/README.md` | `git show HEAD -- docs/specs/README.md` | Строка на #440 добавлена в том же коммите, ссылка на файл корректна |
Проверка кодом не подтверждает и не опровергает пункт (е) — это решение
процесса, а не факт кода; ТЗ фиксирует его как аналитический вывод без
кодовых изменений, что соответствует PROCESS.md §1 (инфраструктурные задачи
без файлов класса A идут без ТЗ/код-ревью).
## §7.1 — обязательные разделы
Все присутствуют: Сценарий · Что человек увидит до и после · Проблема и
подтверждённые причины · Скоуп/Не-скоуп · Контракт поведения (7 пунктов) ·
Touch и доступность (UX) · Модель данных, совместимость и i18n · Производительность
и безопасность · Затронутые модули · Критерии приёмки AC1–AC8 с доказательством
· таблица «чем краснеет» для AC1–AC6 · План автотестов · Риски · Откат ·
Release-артефакты · блок принятых технических предположений. Трек (`full`)
обоснован явно названным нарушенным критерием лёгкого трека (несколько
поверхностей, TS+Python, touch-контракт) — соответствует PROCESS.md §5.
## Догадки, выданные за факт
Не найдены. Каждое утверждение о текущем поведении сверено с кодом (таблица
выше) и совпадает буквально. Технические решения, не вытекающие однозначно из
существующего контракта (точное значение follower-таймаута, точное имя
command-метода locale-gate, единство/раздельность test harness), явно
вынесены в раздел «Принятые технические предположения» с пометкой «может быть
свободно скорректировано ревьюером» — я эту пометку принимаю без правок: ни
одно из шести предположений не меняет продуктовый контракт и не требует
продуктового решения владельца.
## Продуктовые вопросы владельцу
Нет. Ни один AC не требует решения о том, что человек видит или делает —
задача исключительно про hardening существующих контрактов (пункты 1–4, 6) и
про качество тестового свидетеля (пункты 5, 7), без нового UX. Раздел
«Аналитика» в issue уже верно это фиксирует.
## Находки
Ни одной блокирующей (High) или требующей возврата (Medium) находки.
**Low (снимается без правки, с записью).** Формулировка контракта AC4/§4
«Если root либо отдельная entry исчезла… этот кандидат пропускается» лексически
объединяет два разных случая: исчезновение одной записи между `iterdir()` и
`stat()` (ровно то, что описывает аудит и что покрывает план автотестов п.4) и
исчезновение самого каталога `root` целиком (у которого нет «кандидата» —
пропадать нечему, весь скан просто должен дать `count=0, bytes=0` без
исключения). План автотестов раздела «План автотестов» п.4 явно строит только
первый случай (подмена stat одного кандидата), про второй не говорит. Это не
блокирует: реализация свободна закрыть оба случая одним try/except вокруг
`iterdir()`-цикла, а второй случай на порядок более гипотетичен (нужно, чтобы
директория ассетов исчезла во время активного апдейта HA) и не был частью
исходной находки аудита (г), которая специально про TOCTOU на отдельной
записи. Снимаю без возврата автору — реализация и ревьюер кода в состоянии
решить механику по контексту; если разработчик по факту не покроет
root-vanishing веткой теста, это не расхождение с ТЗ, а вопрос полноты теста
для код-ревью.
## Что проверено и корректно
- Все семь пунктов аудита в ТЗ имеют прямое соответствие в коде на текущем
SHA — ни один не является пересказом чужих слов без проверки.
- AC1–AC8 однозначны, у каждого указан способ доказательства; AC1–AC6
дополнительно снабжены таблицей «чем краснеет» — это избыточно для этапа
spec (обязательно только для код-ревью, §2.7), но облегчает будущий цикл.
- Скоуп/не-скоуп разделены чётко, включая явный отказ трогать API/схему/
golden/performance-baseline.
- Откат описан как atomic revert с оговоркой про частичный откат по
компонентам — соответствует стилю других ТЗ пакетных полишей.
- Touch-контракт п.2 — прямая цитата `docs/TOUCH-SUPPORT.md`, не изобретение.
- i18n/release-артефакты корректны по факту дерева (все 4 локали существуют,
правок нет; `docs/specs/README.md` обновлён в том же коммите).
- Названные для редактирования тестовые/смок-файлы существуют в дереве.
## Чего не проверял
- Не запускал автотесты/гейты — ревью ТЗ не требует прогона (нет кода для
тестирования, реализация ещё не начата).
- Не проверял `custom_components/houseplan/http_api.py` целиком построчно —
только участки, относящиеся к пунктам (а) и (г).
- Не проверял `docs/ARCHITECTURE.md`/`docs/TESTING.md` на точное текущее
содержание тех разделов, которые ТЗ предлагает обновить «если требуют» —
условная формулировка допустима на этапе ТЗ (детали редактирования
документации решает исполнитель).
- Не оценивал реализуемость точного числового значения follower-таймаута —
ТЗ прямо помечает это как техническое предположение, свободное для
корректировки, поэтому численное значение не является предметом ревью ТЗ.
## Материал раунда
- SHA ТЗ: `735710f1` (ветка `origin/issue/440-audit-polish`, HEAD на момент
ревью).
- Дерево материала: рабочая копия на `735710f1`, `git status` чист.
- Файл ТЗ: `docs/specs/440-v171-beta2-polish.md` (422 строки, добавлен этим
коммитом вместе с записью в `docs/specs/README.md`).
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/440-audit-polish`, коммит `735710f16b8f` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `927a83664db31061974ea9103581848a0cc8470e`
```
git log --all --format='%H %T' | grep 927a83664db3
```
- ТЗ `docs/specs/440-v171-beta2-polish.md`, блоб `0de292e2f2dc734f08a9f3eb4d37d2a6734da3e4`
```
git log --all --find-object=0de292e2f2dc734f08a9f3eb4d37d2a6734da3e4 -- docs/specs/440-v171-beta2-polish.md
```