docs: #398 spec revision 3 per SPEC-REVIEW-398-r2

User-Visible: no
Issue: #398
This commit is contained in:
Codex
2026-08-31 03:40:36 +03:00
parent 692e3b72f4
commit 9fde407e4e
+27 -7
View File
@@ -3,7 +3,8 @@
- Issue: https://github.com/Matysh/houseplan-card/issues/398
- Приоритет: P2, infra/tests; полный трек — класс B (тесты и гейт), меняется
правило, а не продуктовый код
- Ревизия: 2 (2026-08-31) — по SPEC-REVIEW-398-r1 (Medium: путь 2 несовместим с AC4)
- Ревизия: 3 (2026-08-31) — по SPEC-REVIEW-398-r2 (Medium: статический гвард
против самоочищающегося файла)
## Сценарий
@@ -84,7 +85,19 @@ custom_components.houseplan.coordinate_canonicalization
`custom_components` за время `exec_module`, — тогда после вызова остаются ровно
пустышки conftest. Проверено исполнением, включая повторный вызов подряд.
Единственное исключение остаётся одно — `tests_backend/conftest.py`.
**Исключений в статическом гварде два, и это не возврат снятого пути.**
Гвард читает исходники, не исполняя их, и физически не может знать, что
`load_pure` возвращает `sys.modules` в исходное состояние сразу после
`exec_module`. По букве AC1 он обязан отклонить `pure_imports.py` — но тогда
файл, делающий ровно то, чего задача требует, оказывается вне закона.
Поэтому исключений в списке два — `tests_backend/conftest.py` (условная
подмена, контракт #394) и `tests_backend/pure_imports.py` (запись, снимаемая
тем же вызовом). Разница со снятым «путём 2» существенная: там файл
исключался ВМЕСТО очистки, здесь — при обязательной очистке, которую держит
исполняемый AC4 и отдельный AC8. Статический список отвечает на вопрос «кому
разрешено писать в `sys.modules`», исполняемая проверка — на вопрос «осталось
ли что-нибудь после»; подменять второе первым и было исходной ошибкой #394.
## Скоуп / не-скоуп
@@ -117,8 +130,13 @@ custom_components.houseplan.coordinate_canonicalization
`sys.modules.setdefault(...)`/`update(...)` тоже отклоняются либо приводят к
явному отказу «не могу доказать безопасность». Доказательство: по одному
контракту на форму.
- **AC3**. `tests_backend/conftest.py` остаётся разрешённым и остаётся
единственным исключением; список зафиксирован тестом, его рост краснеет.
- **AC3**. Список статических исключений состоит ровно из двух поимённых
файлов — `tests_backend/conftest.py` и `tests_backend/pure_imports.py`;
любой третий краснеет. Доказательство: контрактный тест на список.
- **AC8**. Исключение не превращается в дыру: `pure_imports.py` обязан
восстанавливать `sys.modules` в `finally`, и это проверяется отдельно от
гварда. Доказательство: тест, который убирает восстановление и получает
красный AC4 (мутант `pure-imports-stops-cleaning`).
- **AC4**. После прогона всего `tests_backend/` в `sys.modules` нет ключей
`custom_components*` сверх тех, что положил conftest. Доказательство:
исполняемая проверка, а не чтение исходников.
@@ -136,7 +154,7 @@ custom_components.houseplan.coordinate_canonicalization
1. Синтетический файл с записью через переменную → гвард краснеет (AC1).
2. По контракту на каждую форму из AC2.
3. `conftest.py` со своей условной подменой → зелено (AC3).
4. Попытка добавить второй файл в список исключений → красный.
4. Попытка добавить третий файл в список исключений → красный (AC3).
**Backend** (`tests_backend/test_backend_quality.py` или новый файл):
@@ -145,8 +163,10 @@ custom_components.houseplan.coordinate_canonicalization
**Мутант** (`scripts/mutation-gate.mjs`):
- `sysmodules-guard-blind-to-variable`: вернуть запись через переменную в
`pure_imports.py` → гвард красный.
- `sysmodules-guard-blind-to-variable`: добавить запись через переменную в
файл ВНЕ списка исключений → гвард красный.
- `pure-imports-stops-cleaning`: убрать восстановление `sys.modules` из
`finally` в `pure_imports.py` → исполняемая проверка AC4 красная (AC8).
## Риски