mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 19:58:50 +00:00
@@ -0,0 +1,167 @@
|
||||
# SPEC-REVIEW-398-r3
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/398
|
||||
- Этап: ТЗ на ревью (PROCESS.md §2.4)
|
||||
- Материал: `docs/specs/398-sysmodules-guard-scope.md`, коммит `9fde407e`
|
||||
(«#398 spec revision 3 per SPEC-REVIEW-398-r2»)
|
||||
- Заход: r3 · блокирующих циклов израсходовано до этого вердикта: 2 из 4
|
||||
- Предыдущий вердикт: `docs/reviews/SPEC-REVIEW-398-r2.md`, жёлтый, на коммите
|
||||
`480d202f` (ревизия 2 ТЗ). SHA назван в самом документе r2 явно
|
||||
(«Материал: …, коммит `480d202f`»), детективная работа не требуется.
|
||||
|
||||
## Разбор по дельте (PROCESS.md §2.9, #214)
|
||||
|
||||
Дельта — `git diff 480d202f..9fde407e -- docs/specs/398-sysmodules-guard-scope.md`
|
||||
(27 добавленных / 7 удалённых строк, один файл). Правка не ребейзится на ушедший
|
||||
вперёд `dev`, не меняет продуктовый контракт (`User-Visible: no`, класс B
|
||||
сохранён), не задевает новую подсистему и по объёму меньше исходной задачи —
|
||||
полный повторный разбор не требуется. Дельта касается четырёх мест: (1) шапка
|
||||
«Ревизия», (2) новый абзац после раздела «Что делать с `load_pure`», (3) AC3 +
|
||||
новый AC8, (4) план автотестов (пп. 3–4 unit, мутанты). Разделы, которых дельта
|
||||
не коснулась (Сценарий, «Что человек увидит», Скоуп/не-скоуп, UX, модель данных,
|
||||
i18n, AC1/AC2/AC5/AC6, Риски, Откат, Release-артефакты), унаследованы из r2 —
|
||||
см. раздел ниже. Отдельно проверено, не задевает ли дельта совместность с AC,
|
||||
которые сама не редактировала (AC1/AC2 vs новый AC3/AC8) — это и есть предмет
|
||||
находки r2, разбор ниже.
|
||||
|
||||
## Закрытие раунда r2
|
||||
|
||||
| Находка r2 | Чем закрыта | Где это видно |
|
||||
|---|---|---|
|
||||
| Medium: устранение «пути 2» в ревизии 2 сделало AC1/AC2 (fail-closed отказ на `sys.modules[<переменная>] = …`) несовместимыми с AC3 (единственное исключение — только `conftest.py`) для `tests_backend/pure_imports.py:31`, оставленного в скоупе и обязанного по AC5 продолжать резолвить относительные импорты | Статический список исключений расширен до двух поимённых файлов; ключевое отличие от снятого «пути 2» — обязанность самоочистки (AC4) сохранена и продублирована отдельным AC8 с собственным мутантом, то есть исключение из AC1-проверки и обязанность очистки по AC4 разведены как два независимых требования, а не связаны в одно (именно эта связка и была ошибкой пути 2) | `docs/specs/398-sysmodules-guard-scope.md:88-100` (новый абзац «Исключений в статическом гварде два, и это не возврат снятого пути») дословно повторяет вариант, предложенный ревьюером в r2 («Как править», см. `docs/reviews/SPEC-REVIEW-398-r2.md`), включая обоснование «там файл исключался ВМЕСТО очистки, здесь — при обязательной очистке»; AC3 (строки 133-135) теперь называет оба файла поимённо; новый AC8 (строки 136-139) требует восстановления в `finally` с отдельным мутантом `pure-imports-stops-cleaning` |
|
||||
|
||||
Закрытие текстуально точное и логически корректное: авторская правка не просто
|
||||
переименовала проблему, а разнесла два ранее слитых требования (статический
|
||||
список «кому разрешено писать» и исполняемая проверка «осталось ли что-то
|
||||
после») по разным AC, что и было сутью замечания r2.
|
||||
|
||||
**Каскадная правка, которую отдельно стоит отметить.** Автор также обновил
|
||||
мутант `sysmodules-guard-blind-to-variable`: в ревизии 2 он ещё был
|
||||
сформулирован как «вернуть запись через переменную в `pure_imports.py` →
|
||||
гвард красный» — формулировка, оставшаяся неизменной с ревизии 1 и после
|
||||
правки r2 переставшая быть верной (раз `pure_imports.py` теперь легален по
|
||||
AC3, мутация на нём самом не могла бы покраснеть). В ревизии 3 текст исправлен
|
||||
на «добавить запись через переменную в файл ВНЕ списка исключений → гвард
|
||||
красный». Это не было прямо указано ревьюером, но вытекает из его находки —
|
||||
хороший признак того, что правка не точечная, а проверена на согласованность
|
||||
со смежными местами документа.
|
||||
|
||||
## Новые находки (в этом раунде)
|
||||
|
||||
Blocking (High/Medium в скоупе) не найдено. Два наблюдения ниже — Low,
|
||||
не блокируют.
|
||||
|
||||
### Low — план автотестов не называет явно позитивный контракт-тест для `pure_imports.py` как второго исключения
|
||||
|
||||
`docs/specs/398-sysmodules-guard-scope.md:154-157`, пункты плана автотестов:
|
||||
|
||||
```
|
||||
3. `conftest.py` со своей условной подменой → зелено (AC3).
|
||||
4. Попытка добавить третий файл в список исключений → красный (AC3).
|
||||
```
|
||||
|
||||
AC3 вводит **два** поимённых исключения, но явный позитивный тест-пример
|
||||
назван только для одного (`conftest.py`); для `tests_backend/pure_imports.py` —
|
||||
файла, ради которого вся ревизия 3 и написана, — отдельного пункта «его
|
||||
собственная запись `sys.modules[name] = module` → зелено (AC3)» в плане нет.
|
||||
Это не логическое противоречие (как было в r1/r2): без него набор AC остаётся
|
||||
формально выполнимым, поскольку `test/backend-test-hygiene.test.mjs`
|
||||
сканирует реальные файлы `tests_backend/*.py`, включая сам `pure_imports.py`,
|
||||
и при каждом прогоне `npm test` наличие исключения будет неявно упражнено
|
||||
(если реализация ошибочно не распознает второе исключение, `npm test`
|
||||
покраснеет на реальном файле, а не только на синтетическом). Поэтому не
|
||||
поднимаю до Medium. Но именно эта строка кода — центр всей ревизии 3, и явный
|
||||
пункт плана снял бы зависимость от «неявного» покрытия реальным файлом.
|
||||
|
||||
### Low — нумерация критериев приёмки нарушена: AC8 вставлен между AC3 и AC4, AC7 не существует
|
||||
|
||||
`docs/specs/398-sysmodules-guard-scope.md:133-142`: порядок в тексте —
|
||||
AC1, AC2, AC3, **AC8**, AC4, AC5, AC6. Ни в одной прежней ревизии (проверено:
|
||||
`git show 69dd09a7:...`, `git show 480d202f:...`) критерия AC7 не было — не
|
||||
удалён, а просто пропущен номер. Сути не меняет (каждый AC однозначен и
|
||||
пронумерован уникально, дублей нет), но для читателя, ищущего «AC7», это
|
||||
выглядит как потерянный критерий, а не как намеренный пропуск. Дешевле всего
|
||||
исправить переносом нового критерия в конец списка под следующим свободным
|
||||
номером (AC7) при следующей правке текста; заводить отдельный цикл ради этого
|
||||
нет оснований.
|
||||
|
||||
## Унаследовано из r2
|
||||
|
||||
Без повторной проверки в этом раунде — дельта r2→r3 не касалась этих участков
|
||||
и они не входят в цепочку доказательства новых находок или закрытия r2:
|
||||
|
||||
- Обязательные продуктовые разделы («Сценарий», «Что человек увидит до и
|
||||
после») и корректность персоны (разработчик, не пользователь продукта) —
|
||||
`docs/reviews/SPEC-REVIEW-398-r2.md`, раздел «Унаследовано из r1», на SHA
|
||||
`69dd09a7`. Текст этих разделов не изменился ни в 480d202f, ни в 9fde407e
|
||||
(сверено: ни один hunk `git diff 69dd09a7..9fde407e` их не касается).
|
||||
- Полнота обязательных разделов §7.1 (проблема, скоуп/не-скоуп, UX Н/П,
|
||||
модель данных и миграция Н/П, i18n Н/П, план автотестов, откат,
|
||||
release-артефакты) — там же. Структура документа не менялась в r3, только
|
||||
содержимое трёх блоков (проблема/контракт, AC3+AC8, план автотестов),
|
||||
разобранных выше.
|
||||
- Соответствие `docs/SCOPE.md`: задача чисто инфраструктурная, не претендует
|
||||
на строку Core user jobs — `docs/reviews/SPEC-REVIEW-398-r2.md`, «Что
|
||||
проверено и корректно». Дельта r3 не меняет ни продуктовую рамку, ни
|
||||
`User-Visible`.
|
||||
- Скоуп/не-скоуп (`tests_backend/conftest.py` вне скоупа, правило #393 вне
|
||||
скоупа) — не тронут дельтой r3, наследуется из r1/r2.
|
||||
- AC1, AC2, AC5, AC6 — однозначны и проверяемы по отдельности —
|
||||
`docs/reviews/SPEC-REVIEW-398-r1.md`, «Что проверено и корректно», п.4,
|
||||
переподтверждено в r2. Текст этих критериев дельтой r3 не тронут (см. диф
|
||||
выше — правки только в AC3, новый AC8, план автотестов).
|
||||
- Риски «разбор Python из Node», «снятие регистрации ломает относительные
|
||||
импорты», «соседи, подтянутые относительными импортами» — не изменены в
|
||||
r3 (диф `480d202f..9fde407e` не затрагивает раздел «Риски»), проверены по
|
||||
существу в r1/r2.
|
||||
- Откат и release-артефакты — не изменены дельтой r3, наследуются из r1/r2.
|
||||
- Наблюдения r1 (напряжение формулировок «контракт vs риски» про способ
|
||||
разбора; маршрутизация issue мимо инфраструктурного пути) — не блокировали
|
||||
тогда, не затронуты дельтой r3, остаются наблюдениями.
|
||||
|
||||
## Что проверено и корректно (в этом раунде)
|
||||
|
||||
- Закрытие находки r2 логически полное: две ранее слитые обязанности
|
||||
(статическое разрешение писать / исполняемая проверка отсутствия мусора)
|
||||
теперь заданы независимыми AC (AC3 и AC4/AC8), что и требовалось — сверено
|
||||
построчно по диффу.
|
||||
- Каскадное обновление мутанта `sysmodules-guard-blind-to-variable` (описано
|
||||
выше) — проверено сопоставлением текста ревизии 2 и ревизии 3; без этой
|
||||
правки план автотестов противоречил бы новому AC3.
|
||||
- Новый AC8 имеет самостоятельное, отличное от AC3 доказательство (мутант
|
||||
`pure-imports-stops-cleaning`, отдельно перечисленный в разделе «Мутант») —
|
||||
не голая декларация без способа проверки.
|
||||
- Сверено с реальным кодом: `tests_backend/pure_imports.py` на текущий момент
|
||||
не содержит `finally`-восстановления и `sys.modules[name] = module` пишет
|
||||
безусловно (строки 27-29 файла) — ожидаемо, реализация ещё не начата, текст
|
||||
ТЗ описывает будущее состояние, а не выдаёт его за существующее.
|
||||
- `docs/SCOPE.md` по-прежнему не нарушается — дельта r3 не расширяет
|
||||
заявленную поверхность.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Не гонял `npx tsc --noEmit` / `npm test` / `npm run build` — на этапе
|
||||
ревью ТЗ нет продуктового или тестового кода помимо самого `docs/specs/*.md`;
|
||||
`finally`-очистка и AST-гвард ещё не реализованы, гейты неприменимы (то же
|
||||
основание, что в r1/r2).
|
||||
- Не проверял выполнимость «разбор синтаксиса Python из Node» экспериментом —
|
||||
наблюдение 1 из r1 остаётся открытым, не переоценивалось в r3 (дельта его
|
||||
не касалась).
|
||||
- Не проверял `tests_backend/test_validation.py` повторно — его `_load_pure`
|
||||
использует префикс `hp_pure.*`, не пересекающийся с `custom_components*`,
|
||||
подтверждено в r1 и не релевантно дельте r3.
|
||||
- Не проверял, что произойдёт при переименовании одного из двух исключённых
|
||||
файлов (например, `pure_imports.py` → `pure_imports_v2.py`) — это вопрос
|
||||
реализации статического списка (по имени файла vs по содержимому), ТЗ его
|
||||
не обязано покрывать явно, а два Low-наблюдения выше достаточны для
|
||||
следующей правки текста, если автор сочтёт нужным.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Зелёный. Находка r2 закрыта корректно и по существу (не переименование
|
||||
проблемы, а разведение двух ранее слитых требований по разным AC), новых
|
||||
блокирующих несовместимостей правка не внесла. Два Low-наблюдения (нумерация
|
||||
AC8 вне последовательности; план автотестов не называет явный позитивный
|
||||
тест-пример для `pure_imports.py` как исключения) не блокируют и не образуют
|
||||
цикл — оставляю их автору на усмотрение при следующей правке текста, если
|
||||
таковая понадобится по другому поводу.
|
||||
Reference in New Issue
Block a user