Files
houseplan-card/docs/reviews/SPEC-REVIEW-398-r1.md
T
2026-08-31 00:26:38 +00:00

184 lines
16 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-398-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/398
- Этап: ТЗ на ревью (PROCESS.md §2.4)
- Материал: `docs/specs/398-sysmodules-guard-scope.md`, коммит `69dd09a7` (тело
issue #398 без изменений)
- Заход: r1 · блокирующих циклов израсходовано до этого вердикта: 0 из 4
- Комментариев в issue к моменту ревью: 0 (первый заход, полный разбор)
## Скоуп проверки
Issue #398 просит расширить гвард `test/backend-test-hygiene.test.mjs`,
который сегодня ловит запись стаба интеграции в `sys.modules` только по
строковому литералу и поэтому не видит `tests_backend/pure_imports.py:31`
(`sys.modules[name] = module`, запись через переменную). Изменение полностью
внутри класса B (`test/**`, `tests_backend/**`), не трогает `src/**` и
`custom_components/**/*.py`, `User-Visible: no`. ТЗ оформлено полным треком —
`docs/specs/398-sysmodules-guard-scope.md`, файл на месте, ссылка issue↔ТЗ
двусторонняя.
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` целиком.
2. Прочитано тело issue #398 и лента меток (`gh api .../timeline`) — задача
ушла `S1-new → S3-spec → S4-spec-review` без публичного комментария
аналитики (см. «Наблюдения», п.2).
3. Прочитан ТЗ `docs/specs/398-sysmodules-guard-scope.md` целиком.
4. Сверены с реальным кодом все цитаты из ТЗ: `test/backend-test-hygiene.test.mjs`
(регэксп на строке 39), `tests_backend/pure_imports.py` (строка 31),
`tests_backend/conftest.py` (условная подмена под `if not HAS_HA:`),
`tests_backend/test_junction_limits.py` (безусловный вызов `load_pure` с
каноническим именем `custom_components.houseplan.junction_limits`),
`tests_backend/test_validation.py` (собственный `_load_pure`, пишет в
`sys.modules` под префиксом `hp_pure.*`, вне описанного скоупа гварда).
5. Канонические документы подсистем (`SUN.md`, `LIGHT.md`, `CANVAS.md`,
`WALL-THICKNESS.md`, `UX-MODES.md`, `CONFIG-COMPATIBILITY.md`,
`TOUCH-SUPPORT.md`) — не применимы, задача не меняет видимое поведение
продукта.
6. `docs/USER-GUIDE.ru.md` — не применим, `User-Visible: no`, персона —
разработчик, а не пользователь продукта.
7. Гейты кода не гонялись: на этапе ревью ТЗ продуктового/тестового кода ещё
нет, есть только новый markdown-файл — `typecheck`/`test`/`build` к нему
неприменимы.
## Находки
### Medium (в скоупе задачи) — AC3 разрешает «путь 2», AC4 делает его невозможным
`docs/specs/398-sysmodules-guard-scope.md:73-84` называет два одинаково
допустимых исхода для `load_pure`:
1. снимать регистрацию за собой после `exec_module` (самоочистка);
2. объявить `pure_imports.py` вторым легальным исключением рядом с
`conftest.py`, с тестом, фиксирующим список ровно из двух файлов.
Текст явно оставляет выбор реализации: «Предпочтителен (1)… — но завтра
станет прецедентом» (строка 83) описывает предпочтение, не запрет второго
пути, и **AC3** (строки 117-119) прямо предусматривает путь 2: «единственное
исключение (**либо два файла, если выбран путь 2** — тогда список
зафиксирован тестом и его рост краснеет)».
Но **AC4** (строки 120-122) требует: «После прогона всего `tests_backend/` в
`sys.modules` нет ключей `custom_components*` **сверх тех, что положил
conftest**». Это не совместимо с путём 2 по конструкции: если `pure_imports.py`
объявлен вторым легальным исключением и не убирает за собой запись, то после
прогона `tests_backend/` в `sys.modules` останутся ключи
`custom_components.houseplan.junction_limits`,
`custom_components.houseplan.wall_segment_model`,
`custom_components.houseplan.coordinate_canonicalization` — ровно то, что
issue проверил исполнением как текущее наблюдаемое поведение — и они не
«положены conftest», а положены вторым разрешённым файлом. AC4 в буквальной
формулировке тогда красный при полностью корректной, явно допустимой
реализации.
**Почему это находка ТЗ, а не мелочь для код-ревью.** DoR требует
«пронумерованные проверяемые критерии приёмки» — набор AC должен быть
одновременно выполним. Сейчас AC3 и AC4 совместно выполнимы только при выборе
пути (1); реализатор, прочитавший ТЗ и выбравший путь (2) как более простой
(сам документ признаёт его допустимым и даже кладёт под него отдельный
тест-пункт «план автотестов», п.4), гарантированно упрётся в красный AC4 при
исполняемой проверке — и потратит цикл код-ревью на то, что было видно уже на
этапе ТЗ.
**Как править (для автора, не предписание):** либо явно исключить путь 2 из
допустимых исходов (тогда «выбор — за реализацией» в контракте лишний — есть
только путь 1), либо переформулировать AC4 так, чтобы он допускал оба исхода:
«…нет ключей `custom_components*` сверх положенных `conftest.py` **и**, если
выбран путь 2, кроме предъявленных явным списком исключений в
`pure_imports.py`».
**Воспроизведение** (не гипотеза — уже исполнено issue-автором и
переподтверждено при разборе): `cd tests_backend && python3 -c "import
conftest, test_junction_limits, sys; print([k for k in sys.modules if
k.startswith('custom_components')])"` печатает три ключа
`junction_limits`/`wall_segment_model`/`coordinate_canonicalization` сверх
пустышек `custom_components`/`custom_components.houseplan`, поставленных
`conftest.py`. При выборе пути 2 эти три ключа остаются легальными по AC3, но
проваливают AC4 буквально.
## Наблюдения (не блокируют, не находки к ТЗ)
1. **Контракт vs риски о способе разбора.** Раздел «Проблема и контракт»
говорит «Форма проверки меняется с регулярного выражения на разбор
синтаксиса Python» (что совпадает с просьбой issue: «разбор AST вместо
регулярки»), а раздел «Риски» тут же ограничивает: «Полноценный парсер
тянуть не нужно и нельзя: достаточно узкого разбора строк… с fail-closed на
всё непонятое». Формулировки не запрещают друг друга буквально (узкий
разбор конкретно конструкции присваивания — тоже «разбор синтаксиса», не
обязательно generic-грамматика), но провисает связка: если ключевая
гарантия ТЗ — «отказ на всё недоказуемое» — реализована самим fail-closed
по умолчанию, то выбор между вызовом Python `ast` через subprocess (0 новых
зависимостей, репозиторий и так тянет Python 3.13) и узким текстовым
разбором в Node не влияет на корректность настолько, насколько намекает
контраст формулировок. Технический вопрос — не продуктовый, решается
автором/ревьюером по существу, не эскалируется владельцу; не блокирую,
но рекомендую снять формулировочное противоречие в следующей редакции.
2. **Маршрутизация issue мимо инфраструктурного пути.** Диапазон правки —
исключительно класс B (`test/**`, `tests_backend/**`), ни одного файла
класса A. По `AGENTS.md` («Infrastructure-only work runs outside this
flow… The test… is mechanical: not a single class A file») и
`PROCESS.md` §1 такая задача идёт «без ТЗ, ревью ТЗ, код-ревью и без
прохода по статусам». Лента меток issue (`gh api .../timeline`) показывает,
что все переходы `S1-new → S3-spec → S4-spec-review` проставил лично
владелец (`Matysh`) без публичного комментария аналитики S2 — то есть это
не сбой автоматики, а его собственное решение вести задачу полным треком.
Это не дефект ТЗ и не встаёт в оценку документа: владелец вправе выбрать
больше строгости для третьего по счёту случая одного класса дефектов.
Фиксирую как наблюдение для гигиены процесса, не как находку ревью.
## Что проверено и корректно
- Оба обязательных продуктовых раздела на месте: «Сценарий» называет персону
верно (разработчик, добавляющий backend-тест — не пользователь продукта по
`docs/SCOPE.md`, что для чисто инфраструктурной задачи ожидаемо) и
поверхность (backend test harness, `tests_backend/`); «Что человек увидит до
и после» отвечает без терминов реализации.
- Все обязательные разделы §7.1 присутствуют: проблема, скоуп/не-скоуп,
контракт, UX (Н/П), модель данных и миграция (Н/П), i18n (Н/П), AC1…AC6 с
указанием доказательства, план автотестов, риски, откат, release-артефакты.
- Цитаты кода в ТЗ (регэксп `test/backend-test-hygiene.test.mjs:39`, запись
`pure_imports.py:31`) дословно совпадают с текущим содержимым файлов — не
придуманы задним числом.
- AC1, AC2, AC5, AC6 однозначны и проверяемы: у каждого назван способ
доказательства (тест-контракт по форме записи, backend-прогон, мутант).
AC2 корректно перечисляет конкретные проверяемые формы (f-строка,
конкатенация, `setdefault`/`update`) вместо общего «и другие похожие».
- Скоуп/не-скоуп разграничены точно: `tests_backend/conftest.py` (контракт
#394) и правило про `sys.path` (#393) явно исключены из скоупа этой задачи —
граница, названная в issue #394, не ослабляется этим ТЗ.
- `docs/SCOPE.md` не нарушается: задача не претендует ни на одну строку Core
user jobs, это ожидаемо для чисто инфраструктурного изменения, и ТЗ не
делает вид, что решает продуктовую задачу.
- Риски названы по существу (разбор Python из Node, снятие регистрации ломает
относительные импорты, скрытая зависимость от повторного импорта) со
смягчениями, а не общими словами.
- Откат дешёвый и конкретный: «два коммита назад, продуктовый код не
затронут» — соответствует факту (диапазон правки — исключительно class B).
- Ни одна догадка о поведении не выдана за факт: утверждения о текущем
дефекте («переживает свой тест», перечень трёх модулей) сопровождаются
пометкой «проверено исполнением» и совпадают с тем, что показал сам issue.
## Чего не проверял
- Не гонял `npx tsc --noEmit` / `npm test` / `npm run build` — на этапе
ревью ТЗ нет ни строки продуктового или тестового кода, есть только новый
`docs/specs/*.md`; эти гейты неприменимы к чтению документа.
- Не проверял технической реализуемости «разбора синтаксиса Python из Node»
экспериментом (не писал прототип) — вопрос вынесен в наблюдение 1 как
формулировочный, а не как проверенный тупик.
- Не проверял `tests_backend/test_validation.py` построчно за пределами
найденного вызова `_load_pure`/`sys.modules[f"hp_pure...`] — этого было
достаточно, чтобы подтвердить корректность утверждения ТЗ «при
необходимости» про этот файл (префикс `hp_pure` вне заявленного скоупа
гварда `custom_components*`); полный разбор файла оставляю код-ревью, если
реализация всё же тронет этот файл.
## Вердикт
Жёлтый. Единственная блокирующая находка (Medium, в скоупе задачи) —
несовместимость AC3 и AC4 для явно допущенного документом «пути 2»: описанный
как разрешённый исход не может пройти собственный исполняемый критерий
приёмки. Правится в тексте ТЗ этим же автором, без выхода за рамки текущей
задачи.