15 KiB
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).
Материал раунда
- Ветка:
issue/440-audit-polish, коммит735710f16b8f— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
927a83664db31061974ea9103581848a0cc8470egit log --all --format='%H %T' | grep 927a83664db3 - ТЗ
docs/specs/440-v171-beta2-polish.md, блоб0de292e2f2dc734f08a9f3eb4d37d2a6734da3e4git log --all --find-object=0de292e2f2dc734f08a9f3eb4d37d2a6734da3e4 -- docs/specs/440-v171-beta2-polish.md