Files
houseplan-card/docs/reviews/SPEC-REVIEW-398-r3.md
T
2026-08-31 00:47:06 +00:00

16 KiB

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 как исключения) не блокируют и не образуют цикл — оставляю их автору на усмотрение при следующей правке текста, если таковая понадобится по другому поводу.