20 KiB
SPEC-REVIEW-186-r2
- Issue: https://github.com/Matysh/houseplan-card/issues/186
- ТЗ под ревью:
docs/specs/186-partition-opening-jamb-margin.md(коммит62f73faef737df44ad8bb838aac4beceb74db9a1), обычный трек — неsmall/trivial - Роль: ревьюер ТЗ (не автор), этап
S4-spec-review - Трек: обычный, лимит циклов ревью ТЗ — 4 (§4 PROCESS.md)
- Цикл: r2/4
- Предыдущий цикл:
SPEC-REVIEW-186-r1.md— красный, High-1 (§8/AC4 «full import — всегда strict» противоречил принятому decision #3), Low-1 (нет заголовка «Проблема»)
Скоуп ревью
Только правка после r1: коммит 62f73fa («docs: preserve legacy jambs on full
restore») меняет §3 (пункт 6), §5, §8, UX-таблицу (§9), AC4, AC6 и таблицу
рисков (§13), плюс добавляет заголовок «Проблема» (§2). Формула margin (§4),
scope/не-scope (§5/§6 кроме одной строки), frontend-контракт (§7), AC1/AC2/AC3/
AC5, план проверок (§12), откат (§14), release-артефакты (§15) и принятые
предположения (§16) не менялись — их точность уже подтверждена в r1 и
переподтверждается здесь только там, где новая правка их касается.
Продуктовый код по-прежнему не существует: ветка issue/186-partition-jamb-margin
содержит три коммита (7d4d3f0, 85f54b4, 62f73fa), все класса C
(документация), все с трейлерами Issue: #186 / User-Visible: no. Гейты
typecheck/test/build/smoke/golden/pytest не прогонялись — на этапе
ревью ТЗ они не относятся к предмету (PROCESS.md §2.4/§8).
Как проверялось
- Перечитаны
docs/SCOPE.md,AGENTS.md,PROCESS.md(действующая редакция) — без изменений с r1, инварианты те же. - Прочитаны все восемь комментариев issue #186 подряд: аналитика (Q1–Q3 с defaults), решение владельца по Q1–Q3, хендофф «ТЗ готово к ревью», вердикт r1 (красный, High-1), дополнительный вопрос владельцу Q4 (после ревью), решение владельца по Q4, хендофф «Правки ТЗ после ревью r1», и служебный комментарий о несработавшем автоматическом ревью r1 (метка не переставилась — к предмету этого ревью не относится, поднято отдельно перестановкой метки вручную/владельцем).
- Построчно сверен
git show 62f73fa -- docs/specs/186-partition-opening-jamb-margin.mdс текстом решения владельца по Q4 и с хендоффом «Правки ТЗ после ревью r1»: правка ограничена ровно перечисленными разделами, посторонних изменений (формулы, AC1-3/5, scope-исключений, отката) нет — «попутных правок» не обнаружено. - Формулировка §3 п.6 и §8 сверена дословно с текстом решения владельца по Q4 («полный backup/restore всегда сохраняет legacy near-end проёмы... при восстановлении поверх текущего плана, на пустой и на другой инсталляции. Full import проверяет структурную целостность и попадание проёма внутрь host, но не применяет новый jamb safety margin») — совпадение по существу полное, без добавленных условий и без потерянных случаев (текущий / пустой / чужой инстанс перечислены во всех трёх местах: §3, §8, AC4).
- Независимо перепроверена архитектурная посылка исправления (что full
import физически не обязан быть strict) чтением текущего кода на этой же
ветке:
custom_components/houseplan/import_export.py:1304-1345(prepare_apply) — дляkind == "full"вызывается толькоCONFIG_SCHEMA(config)в конце функции (строка 1345);validate_partition_opening_hostsв этой ветке не вызывается вовсе.grep -n "validate_partition_opening_hosts" custom_components/houseplan/*.py— ровно три вызова:import_export.py:995внутриbuild_space_merge()(путьkind == "space", т.е. частичный merge-импорт, не полный),websocket_api.py:1260(config/set) иwebsocket_api.py:1372(optimize). Ни один не относится кkind == "full".- Вывод:
validate_partition_opening_hosts()(и, следовательно, будущий jamb-контракт «рядом с ним либо в его расширении», §8) уже сегодня структурно не покрывает full import — это не новое послабление ради #186, а естественное продолжение существующей архитектуры. Формулировка §8 не вводит специальный обход, а корректно описывает то, что и так происходит. - Отдельно проверено (не для находки, а чтобы не спутать с общим
паттерном):
docs/CONFIG-COMPATIBILITY.md:59документирует другой прецедент (#157,invalid_passage_fields), гдеvalidate_opening_passagesдействительно вызывается для каждого импорта, включаяfull(import_export.py:1159,create_preview(),validate_all=True, до ветвления поkind). Это не противоречие: у #157 и #132/#186 разные валидаторы с разной историей вызова на full-import пути, и decision Q4 явно называет цену этого выбора («вручную изменённый полный backup тоже может содержать near-end geometry») — то есть асимметрия с #157 осознанная, а не незамеченная.
- Сверено, что merge-импорт (
kind == "space",build_space_mergeвызываетvalidate_partition_opening_hosts(merged_config, current_config)наimport_export.py:995) остаётся вне пересмотренного decision Q4 (тот называет только «полный backup/restore»). ТЗ (§5/§8/AC4/AC6) не утверждает ничего противоположного про merge-путь и не молчит о нём как о решённом факте — merge и раньше (#132) переприсваивает id и потому уже трактует переносимые записи как новые для семантического валидатора; #186 просто наследует этот действующий паттерн, не решая для него ничего нового. Дополнительных вопросов владельцу это не требует. - Перечитан заново весь текст ТЗ (не только диф) на предмет новых утверждений о поведении, поданных как факт без пометки assumption или ссылки на решение владельца — целенаправленно искал второй High-1. Не найдено: каждое утверждение §3/§5/§8/§9/AC4/AC6 после правки либо дословно повторяет decision Q1–Q4, либо является технической деталью уже покрытой §16 (имена helper/reason, previous-параметр).
- Перепроверены обязательные разделы §7.1 (таблица ниже) и добавленный
заголовок «Проблема» — сравнение старой и новой версии (
git show 62f73fa) подтверждает: над «До/после» появился отдельный вводный абзац с заголовком## 2. Проблема, ровно то, что требовал Low-1. - Проверена запись
docs/specs/README.md:102— ссылка issue ↔ ТЗ двусторонняя и не менялась в этом цикле, ещё присутствует. - Проверены трейлеры всех трёх коммитов ветки (
git log --format='%H%n%s%n%b') —Issue: #186иUser-Visible: noна каждом;User-Visible: noкорректен, т.к. ни один коммит не меняет продукт.
Гейты (typecheck/test/build, browser smoke, golden, backend pytest) не
прогонялись — продуктового кода нет, это ожидаемо на этапе ревью ТЗ.
Обязательные разделы (§7.1 PROCESS.md)
| Раздел | Есть | Комментарий |
|---|---|---|
| Сценарий (персона/поверхность/момент) | ✅ | §1, без изменений в r2 |
| Проблема | ✅ | §2, теперь отдельный заголовок с вводным абзацем — Low-1 закрыт |
| Что человек увидит до/после | ✅ (см. Low-2) | «До/После» под §2; формулировка не изменилась в r2 и содержит термины реализации, см. находку ниже |
| Скоуп / не-скоуп | ✅ | §5/§6; один пункт §5 переформулирован под decision Q4, без потери содержания |
| Контракт поведения | ✅ | §7/§8; §8 переписан под decision Q4, внутренне непротиворечив |
| UX | ✅ | §9, добавлена строка про full restore |
| Модель данных и миграция | ✅ | §10, без изменений |
| i18n | ✅ | §10, без изменений |
| AC1…ACn с доказательством | ✅ | AC4/AC6 переписаны под decision Q4, доказательство расширено на current/empty/foreign install |
| План автотестов | ✅ | §12, без изменений |
| Риски | ✅ | §13, добавлена строка «Full restore ошибочно становится strict» |
| Откат | ✅ | §14, без изменений |
| Release-артефакты | ✅ | §15, без изменений |
Блок «Принятые технические предположения» (§16) не менялся в r2; при повторном чтении ни один из четырёх пунктов по-прежнему не маскирует продуктовый вопрос.
Находки
Low-2 — «что человек увидит» использует термины реализации
Файл: docs/specs/186-partition-opening-jamb-margin.md, §2, подраздел
«До/После» (не менялся в r2, существовал и в r1).
PROCESS.md §7.1 требует для этого элемента «одной фразой, без терминов реализации». Текущий текст: «До: frontend считает валидным любой проём... Backend повторяет эту границу с одним лишь floating-point epsilon...», «После: у обоих торцов host резервируется остаток... математическими endpoints host». Используются архитектурные/кодовые термины (frontend, backend, epsilon, endpoints), а не то, что видит администратор в редакторе плана (например: «проём можно было поставить впритык к торцу стены, без места под наличник; теперь редактор всегда оставляет отступ, равный половине толщины стены»).
Ни один AC не опирается на эту формулировку и не становится двусмысленным — дефект чисто редакционный, той же природы, что и снятый в r1 Low-1.
Решение ревьюера: Low, не блокирует. Снимаю без возврата ТЗ на правку; рекомендую при следующей правке файла (не отдельным циклом) заменить «До/После» на формулировку в терминах того, что видит администратор в Plan editor, не изобретая новых решений — переформулировка, не новый контракт.
Что проверено и корректно
- High-1 из r1 закрыт корректно и полностью. Коммит
62f73faменяет ровно те разделы, что были нужны (§3 п.6, §5, §8, §9, AC4, AC6, §13), дословно соответствует принятому владельцем decision Q4, и не оставляет старой формулировки «full import без trusted previous — strict» ни в одном месте документа (проверено построчным перечитыванием всего файла, не только дифа). - Архитектурная посылка исправления подтверждена независимым чтением кода,
а не принята на слово:
validate_partition_opening_hosts()сегодня вызывается только вbuild_space_merge(частичный merge),config/setиoptimize— ни разу дляkind == "full"вprepare_apply(). Значит decision Q4 не вводит специальный обход общей архитектуры, а продолжает уже существующее устройство full-import пути. Асимметрия с прецедентом #157 (invalid_passage_fields, который действительно строг на full import) — реальная и явно принятая владельцем цена, а не незамеченное расхождение. - Merge-импорт (частичный,
kind == "space") остаётся вне decision Q4, и ТЗ не утверждает о нём ничего, что противоречило бы этому — унаследованный из #132 паттерн переприсвоения id продолжает работать без нового решения. - Второй High не найден. Целенаправленный повторный проход по всему документу (не только по дифу r1→r2) не выявил новых утверждений о поведении, поданных как факт без пометки assumption или ссылки на решение владельца.
- Формула margin (§4), AC1/AC2/AC3/AC5, откат (§14), touch-декларация (§10)
и release-артефакты (§15) не менялись в этом цикле; их точность была
независимо перепроверена в r1 (арифметика
GRID_N=240, реальные call sites, существующий паттернdocs/CONFIG-COMPATIBILITY.md) и остаётся в силе. - Трейлеры и трассируемость: все три коммита ветки несут
Issue: #186/User-Visible: no, запись issue ↔ ТЗ вdocs/specs/README.mdна месте,docs/specs/186-partition-opening-jamb-margin.mdссылается на issue и на #132. - Трек подтверждён обычным (не
small/trivial) — критерии §5 PROCESS.md по-прежнему не выполняются (новый видимый граничный контракт, compatibility-решение), лимит цикла — 4, использован r2/4.
Чего не проверял
- Реализацию — её по-прежнему нет: три коммита ветки все класса C
(документация), продуктовый код (
src/**,custom_components/houseplan/**/*.py) не менялся этой веткой — проверено чтениемgit show --statкаждого коммита. - Гейты
typecheck/test/build/browser smoke/golden/pytest tests_backend— не относятся к этапу ревью ТЗ. - Полный повторный пересчёт формулы margin и всех восьми call sites
resolvePartitionOpening()— не требовался: эта часть документа не менялась между r1 и r2, независимая проверка из r1 остаётся в силе и не переделывалась. - Оставшиеся детали merge-импорта (
build_space_merge, duplicate_policy,same_source) за пределами вопроса «вызывается ли здесьvalidate_partition_opening_hosts» — не относится к предмету находки High-1 и её закрытия. - Численные оценки аналитики (5/10 · 6/10 · 5/10 · P2) — поле владельца, решено до написания ТЗ, вне предмета ревью.
Вердикт
Зелёный. High: 0 (High-1 из r1 закрыт decision Q4 и корректно отражён во всех
затронутых разделах ТЗ; архитектурная посылка независимо перепроверена
чтением текущего кода). Medium: 0. Low: 1 новая (Low-2 — «До/После» использует
термины реализации вместо описания того, что видит администратор; снимается
ревьюером без возврата, рекомендация — поправить при следующей правке файла).
Low-1 из r1 (отсутствие заголовка «Проблема») закрыт правкой. ТЗ готово к
переходу в S5-ready.
Вердикт: зелёный · цикл r2/4 · High: 0 · Medium: 0 → в задаче · Документ: docs/reviews/SPEC-REVIEW-186-r2.md