docs: review document for #398

Issue: #398
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-31 00:26:38 +00:00
parent 69dd09a7c9
commit a4b686a7fe
+183
View File
@@ -0,0 +1,183 @@
# 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»: описанный
как разрешённый исход не может пройти собственный исполняемый критерий
приёмки. Правится в тексте ТЗ этим же автором, без выхода за рамки текущей
задачи.