docs: #398 spec revision 2 per SPEC-REVIEW-398-r1

User-Visible: no
Issue: #398
This commit is contained in:
Codex
2026-08-31 03:30:10 +03:00
parent a4b686a7fe
commit 480d202fa5
+28 -22
View File
@@ -3,7 +3,7 @@
- Issue: https://github.com/Matysh/houseplan-card/issues/398 - Issue: https://github.com/Matysh/houseplan-card/issues/398
- Приоритет: P2, infra/tests; полный трек — класс B (тесты и гейт), меняется - Приоритет: P2, infra/tests; полный трек — класс B (тесты и гейт), меняется
правило, а не продуктовый код правило, а не продуктовый код
- Ревизия: 1 (2026-08-31) - Ревизия: 2 (2026-08-31) — по SPEC-REVIEW-398-r1 (Medium: путь 2 несовместим с AC4)
## Сценарий ## Сценарий
@@ -68,20 +68,23 @@ custom_components.houseplan.coordinate_canonicalization
статически — тогда гвард обязан отказывать (fail-closed): «не могу доказать, статически — тогда гвард обязан отказывать (fail-closed): «не могу доказать,
что ключ не из `custom_components`» — это отказ, а не пропуск. что ключ не из `custom_components`» — это отказ, а не пропуск.
**Что делать с `load_pure`.** Два допустимых исхода, выбор — за реализацией **Что делать с `load_pure` — один путь: снимать за собой.** Ревизия 1
после замера: допускала второй исход (объявить `pure_imports.py` вторым легальным
исключением), и ревьюер справедливо показал, что он несовместим с AC4:
регистрация тогда остаётся, и требование «после прогона нет лишних ключей»
краснеет по построению. Держать в ТЗ путь, ведущий в заведомо красный
критерий, нельзя — вариант снят.
1. **Снимать за собой.** `load_pure` восстанавливает `sys.modules` к состоянию `load_pure` восстанавливает `sys.modules` к состоянию до вызова (образец —
до вызова (образец — обратимая подмена в `scripts/dump-config-schema.py` обратимая подмена в `scripts/dump-config-schema.py` после #389). Существенная
после #389). Плюс: класс закрыт полностью. Минус: относительные импорты деталь, выясненная замером: снимать только собственное имя **недостаточно**.
внутри загруженного модуля должны успеть отработать до восстановления — Относительные импорты внутри загружаемого модуля подтягивают соседей, и после
проверить, что `exec_module` завершается раньше. загрузки `junction_limits` в `sys.modules` остаются ещё `wall_segment_model` и
2. **Объявить единственным легальным исключением** рядом с conftest, с `coordinate_canonicalization`. Снимать нужно всё, что появилось под префиксом
комментарием, почему именно ему можно, и с тестом, фиксирующим, что список `custom_components` за время `exec_module`, — тогда после вызова остаются ровно
исключений состоит ровно из двух файлов и не растёт. пустышки conftest. Проверено исполнением, включая повторный вызов подряд.
Предпочтителен (1): исключение, которое нельзя объяснить в одну строку, Единственное исключение остаётся одно — `tests_backend/conftest.py`.
завтра станет прецедентом.
## Скоуп / не-скоуп ## Скоуп / не-скоуп
@@ -114,9 +117,8 @@ custom_components.houseplan.coordinate_canonicalization
`sys.modules.setdefault(...)`/`update(...)` тоже отклоняются либо приводят к `sys.modules.setdefault(...)`/`update(...)` тоже отклоняются либо приводят к
явному отказу «не могу доказать безопасность». Доказательство: по одному явному отказу «не могу доказать безопасность». Доказательство: по одному
контракту на форму. контракту на форму.
- **AC3**. `tests_backend/conftest.py` остаётся разрешённым, и это единственное - **AC3**. `tests_backend/conftest.py` остаётся разрешённым и остаётся
исключение (либо два файла, если выбран путь 2 — тогда список единственным исключением; список зафиксирован тестом, его рост краснеет.
зафиксирован тестом и его рост краснеет).
- **AC4**. После прогона всего `tests_backend/` в `sys.modules` нет ключей - **AC4**. После прогона всего `tests_backend/` в `sys.modules` нет ключей
`custom_components*` сверх тех, что положил conftest. Доказательство: `custom_components*` сверх тех, что положил conftest. Доказательство:
исполняемая проверка, а не чтение исходников. исполняемая проверка, а не чтение исходников.
@@ -134,8 +136,7 @@ custom_components.houseplan.coordinate_canonicalization
1. Синтетический файл с записью через переменную → гвард краснеет (AC1). 1. Синтетический файл с записью через переменную → гвард краснеет (AC1).
2. По контракту на каждую форму из AC2. 2. По контракту на каждую форму из AC2.
3. `conftest.py` со своей условной подменой → зелено (AC3). 3. `conftest.py` со своей условной подменой → зелено (AC3).
4. Попытка добавить третий файл в список исключений (если выбран путь 2) → 4. Попытка добавить второй файл в список исключений → красный.
красный.
**Backend** (`tests_backend/test_backend_quality.py` или новый файл): **Backend** (`tests_backend/test_backend_quality.py` или новый файл):
@@ -155,10 +156,15 @@ custom_components.houseplan.coordinate_canonicalization
строковых литералах; смягчение: тест-контракты на «похожий, но безопасный» строковых литералах; смягчение: тест-контракты на «похожий, но безопасный»
код (например, `# sys.modules[...]` в комментарии) должны оставаться код (например, `# sys.modules[...]` в комментарии) должны оставаться
зелёными. зелёными.
- **Снятие регистрации ломает относительные импорты.** Если выбран путь (1) и - **Снятие регистрации ломает относительные импорты.** Восстановление слишком
восстановление происходит слишком рано, `from .x import y` внутри модуля рано уронит `from .x import y` внутри модуля. Смягчение: восстановление
упадёт. Смягчение: восстановление строго после `exec_module`, AC5 покрывает строго после `exec_module` (замер: модуль полностью рабочий после очистки),
оба существующих места использования. AC5 покрывает оба существующих места использования.
- **Соседи, подтянутые относительными импортами.** Снятие одного лишь
собственного имени оставляет `wall_segment_model` и
`coordinate_canonicalization` — замер это показал. Смягчение: снимать
разницу по префиксу `custom_components`, а не одно имя; AC4 проверяет
исполнением итог, а не намерение.
- **Скрытая зависимость от повторного импорта.** Модуль, загруженный дважды, - **Скрытая зависимость от повторного импорта.** Модуль, загруженный дважды,
даёт два объекта классов; тесты, сравнивающие типы, могли на этом молча даёт два объекта классов; тесты, сравнивающие типы, могли на этом молча
держаться. Смягчение: AC5 гоняет весь набор, а не отдельный файл. держаться. Смягчение: AC5 гоняет весь набор, а не отдельный файл.