# SPEC-REVIEW-306-r3 - **Issue:** [#306](https://github.com/Matysh/houseplan-card/issues/306) — Редактор плана: заменить виртуальные стены обычными стенами толщиной 0 - **Этап:** ревью ТЗ (PROCESS.md §2.4, повторный раунд — §2.10) - **Заход:** r3 · блокирующих циклов израсходовано (до этого вердикта) 1 из 4 - **ТЗ:** `docs/specs/306-zero-thickness-walls.md`, HEAD ветки `issue/306-zero-thickness-walls` = `abbd4904641b196ab9c2ed841600e2b260c75653` («docs: adapt zero-wall spec to model v8») - **Метка issue:** `S4-spec-review` (полный трек, не `small`/`trivial`) ## Расхождение метаданных запуска с фактическим состоянием issue (процессная находка, не по ТЗ) Входные метаданные этого запуска указывали «Заход: r1 · блокирующих циклов израсходовано 0 из 4». Фактическое состояние по GitHub и репозиторию другое — и это тот же класс расхождения, который уже фиксировал `SPEC-REVIEW-306-r2.md` для своего запуска: - `docs/reviews/SPEC-REVIEW-306-r1.md` (SHA `904c47e4`) — вердикт жёлтый, High 0, Medium 3, опубликован комментарием issue (2026-08-25T16:49:42Z); - `docs/reviews/SPEC-REVIEW-306-r2.md` (SHA `2c801bf5`) — вердикт зелёный, High 0, Medium 0 (2026-08-25T16:57:24Z), спущен цикл §4 не тратил; - после зелёного r2 задача ушла в разработку («Взял: Codex», 16:58:32Z), но после rebase на `dev` реализация нашла продуктовый конфликт с только что слитым #282 (comment 2026-08-26T00:18:24Z: model v8 уже материализует каждый contour atom, включая старые тонкие стены, как `wall_segments[].cm=0`, и единый default стиля по этому единственному `cm:0` бьёт по одной из двух групп старых планов); - владелец принял решение не вводить `zero_kind`/compatibility-marker (2026-08-26T07:15:44Z) — стены неотличимы по происхождению, а не по решению спецификации; - автор переписал ТЗ под target model v9 на authoritative `wall_segments[]` и явно передал «на независимое spec-review r3/4» (2026-08-26T07:22:47Z). Возврат в работу между r2 и этим запуском не был вердиктом ревью с блокирующими находками — он вызван конфликтом, обнаруженным в реализации после слияния #282, поэтому по определению цикла (§4: «отправка на ревью → вердикт с блокирующими находками → возврат») бюджет циклов между r2 и этим запуском не тратится. Итого до этого вердикта: 1 цикл израсходован (жёлтый r1), r2 — зелёный, не тратит. Провожу разбор как **r3** по фактическому состоянию GitHub, а не как первый заход. Это находка процесса запуска ревью, а не находка по содержанию ТЗ #306 — отдельный issue не заводится (параметр одного прогона, не продуктовый дефект). ## Скоуп этого раунда Дельта между r2 (`2c801bf5`) и текущим HEAD (`abbd4904`) — **не локальна** по критерию §2.10: `git diff 2c801bf5..abbd4904 -- docs/specs/306-zero-thickness-walls.md` даёт 136 добавленных / 87 удалённых строк (≈31% документа), и это правка, которая **задевает новую подсистему** — целевую модель хранения меняет с плоской `space.walls[]`/`open_spans` (v7) на authoritative `space.wall_segments[]` со stable ID (v8, реализовано #282), а целевая версия документа поднята с v8 до v9. Меняется контракт identity, миграции, лимитов и нормализации — ровно тот случай, где §2.10 прямо предписывает полный разбор, а не разбор по дельте. Провожу разбор целиком, с явным наследованием тех частей r1/r2, которые модель v8/v9 не затронула по существу (см. раздел «Унаследовано» ниже). ## Как проверялось Технические утверждения ТЗ о текущей системе проверены по коду и документам, не приняты со слов автора: - `git show --stat` / `git log --follow` на историю issue #306: коммиты `dc6d0aaf`, `41469d0a`, `0436850d`, `20070a75`, `abbd4904` и все 11 комментариев issue прочитаны целиком (`gh issue view 306 --json body,comments`); - `custom_components/houseplan/const.py:54` и `src/plan-optimizer.ts:42` — `PLAN_MODEL_VERSION = 8` подтверждён как текущий (не 7, как было на момент r1) — согласуется с заявленным ТЗ переходом «v8 → v9»; - `src/wall-thickness.ts:215` — `clampWallCm` существует, используется в местах, которые ТЗ требует сделать zero-aware (АС2/АС11); - `custom_components/houseplan/validation.py:1394-1405` — `WALL_SEGMENT_SCHEMA.cm` уже `Range(min=0, max=100)` (backend принимает `cm:0` для `wall_segments[]` уже сейчас, после #282); `ROOM_DRAFT_SCHEMA` (:1425) и `PARTITION_SCHEMA` (:1448) — всё ещё `Range(min=1, max=100)`, `WALL_COLUMN_SCHEMA` (:1471) — `Range(min=1, max=150)`. Формулировка §6.1 ТЗ («Backend принимает cm: 0..100 для wall_segments[], room_drafts[] и partitions[]») соответствует действительности как целевое состояние: часть уже верна, часть (`room_drafts`, `partitions`) требует правки — ТЗ не выдаёт частично готовое за полностью новое; - `custom_components/houseplan/validation.py:1038-1070` — прямая проверка лимитов, см. находку M1 ниже; - `docs/specs/282-stable-wall-segment-identity.md` (§6.5, AC7, разделы про `walls[]` compatibility projection) — подтверждает заявления ТЗ §5.2 о том, что `space.walls[]` уже сейчас генерируемая проекция, а не источник истины, и что `wall_segments[]` уже authoritative для identity; - `scripts/config-field-registry.mjs:206-219` и `docs/CONFIG-COMPATIBILITY.md:37-46` — `spaces[].rooms[].open_to` уже зарегистрирован как `deprecated-read`; статусы `deprecated-read`/ `migrate-on-write`, на которые ссылается §6.2 ТЗ, существуют в реестре; - `docs/USER-GUIDE.ru.md` — grep `Граница` подтверждает, что инструмент документирован (строки 432, 483, 537, 1510 и др.) и требует правки, что ТЗ фиксирует в §16/§19; - `docs/TOUCH-SUPPORT.md` — формулировки «desktop-first», «best effort» совпадают с §14 ТЗ; - `src/open-spans.ts` (781 строк) и `sharedBoundary` (найден в 5 файлах, включая `src/logic.ts`) существуют — ссылки §5.3/§10 п.2 на существующий resolver не выдуманы; - `node --test test/docs-accept.test.mjs test/process-gate.test.mjs` — 40 passed, 0 failed, 0 skipped (Linux; ближе к последней ревизии автора, который называл 37/39 passed + ожидаемый Windows-skip — разница чисто по площадке и времени, не расхождение); - `ls demo/smoke_*.mjs | wc -l` = 192 — для справки; новых смоков в этом ТЗ не требуется прогонять (спецификация, не код), упомянуто в §17/§18 ТЗ как будущий gate код-ревью. ## Закрытие раунда r2 r2 (`docs/reviews/SPEC-REVIEW-306-r2.md`, SHA `2c801bf5`) был **зелёным**, High 0, Medium 0 — открытых находок к закрытию в этом раунде нет. Все три находки r1 (M1 разделы «Сценарий»/«Что человек увидит», M2 отсутствие `USER-GUIDE.ru.md`, M3 несуществующие статусы реестра) были закрыты уже в r2 и остаются закрытыми: соответствующий текст (§1-2, §16/§19 упоминания `USER-GUIDE.ru.md`, §6.2 статусы `deprecated-read`/`migrate-on-write`) присутствует и в текущей ревизии `abbd4904` — переработка под model v8 не откатила ни одну из трёх правок (проверено построчно по текущему файлу, номера строк указаны в разделе «Унаследовано»). ## Унаследовано из r1/r2 Без повторной проверки по существу в этом раунде принято — переработка под model v8 не касалась этих частей текстуально и по содержанию: - продуктовые решения владельца по световому режиму (таблица dashed/solid, запрет фиктивной толщины, единый resolver для Glow и солнца, инвалидация кэшей при переключении, RU/EN copy под селектором) — документы `SPEC-REVIEW-306-r1.md`/`r2.md`, полностью проверены на SHA `904c47e4`/`2c801bf5`, текст §4 п.4-7, §9.1, §15 текущей ревизии не отличается по смыслу; - соответствие `docs/SCOPE.md` (J4/J6) и отсутствие конфликта с out-of-scope — раздел 1 ТЗ не менялся по сути между r2 и r3; - присутствие `docs/USER-GUIDE.ru.md` в §16/§19 (M2 r1) — строки 483-484, 682 текущей ревизии; - статусы compatibility-реестра `deprecated-read`/`migrate-on-write` (M3 r1) — §6.2, строки 172-175 текущей ревизии, повторно подтверждены и в этом раунде прямой проверкой реестра (см. «Как проверялось»), а не только по наследству; - i18n-таблица §15 (ключи `space.zero_wall_*`, `toast.zero_wall_*`, `gs.zero_walls_migrated`) — не менялась между r2 и r3, RU/EN парность есть; - touch/accessibility требования §14 — не менялись по существу. Дельта r2→r3 не касается доказательств AC1, AC2, AC5, AC6, AC7, AC14, AC16 — их текст либо не менялся, либо менялся только терминологически (ссылки на `wall_segments[]` вместо `walls[]`) без изменения проверяемого поведения; они не переразбирались заново по существу. AC3, AC4, AC8, AC9, AC10, AC11, AC12, AC13, AC15, AC17 разобраны заново ниже, так как их доказательство прямо зависит от модели v8/v9, миграции и лимитов. ## Находки ### M1 (Medium, в скоупе) — лимит `wall_segments[]` в ТЗ устарел и противоречит уже слитому #282 ТЗ трижды называет лимит записей числом **500**: - §10 шаг 10: «При превышении 500 `wall_segments[]` или любого лимита отказать целиком» (`docs/specs/306-zero-thickness-walls.md:352`); - §13: «Лимит записей остаётся 500; результат не truncates» (`:423`); - §17 AC13: «Лимит 500, invalid span, opening conflict или revision conflict отклоняет весь candidate» (`:604`); - §20, риск «Atomization превысит лимит» → «atomic failure, no truncation» — привязан к той же цифре. Фактический предел `MAX_WALL_SEGMENTS` в `custom_components/houseplan/validation.py:1048` равен **200 000**, не 500. Это не опечатка автора, а устаревший факт: до #282 `MAX_WALLS` действительно был `500` (подтверждено `git log -p -S "MAX_WALL_SEGMENTS"`, коммит `b336eee9`: `-MAX_WALLS = 500` / `+MAX_WALLS = 200_000` / `+MAX_WALL_SEGMENTS = 200_000`, с комментарием в коде «v8 atomises room boundaries» — лимит был поднят именно потому, что атомизация контура увеличивает число записей). Ревью r1 (2026-08-25T16:49, до слияния #282 в `dev`) корректно зафиксировало «лимиты `MAX_* = 500`» как факт **на тот момент**. #306 переписан под model v8 (после слияния #282, комментарий 2026-08-26T00:18) и в остальном тексте последовательно опирается на `wall_segments[]` как authoritative-каталог (§5.2, §6.1, §16) — но конкретно эту цифру ревизия «adapt to model v8» не обновила ни в одном из четырёх мест. Отдельно — `MAX_OPEN_SPANS = 500` (:1056) действительно равен 500, но это предел удаляемого этим же ТЗ поля `open_spans`, а не `wall_segments[]`; ни `docs/specs/282-stable-wall-segment-identity.md`, ни код не подтверждают цифру 500 для итогового каталога — собственный perf-бенчмарк #282 оперирует масштабом 10 000 атомов (`docs/specs/282-...md:431`), на два порядка выше. **Почему это Medium, а не Low:** формулировка не «уточнить», а прямое техническое утверждение, встроенное в проверяемый AC13 и в шаг миграции §10 — если реализовать буквально, миграция/Optimize начнёт отказывать легитимным пространствам, которые #282 уже поддерживает (200 000 записей), то есть ТЗ, как написано, предписывает регресс уже принятой возможности. Это не открытый продуктовый вопрос (владельцу нечего решать — предел объективно другой), и не блокирует понимание архитектуры документа — значит, не High. Правка механическая: заменить «500» ссылкой на актуальный `MAX_WALL_SEGMENTS` (200 000) или убрать конкретное число из §10/§13/§20 и оставить «действующий лимит записей», сохранив только в AC13 существующее поведение «превышение лимита отклоняет весь candidate целиком». ### Снято с записью (Low, не требует правки) Как и в r1, в документе нет одного консолидированного блока «принято предположительно, поменять свободно» (PROCESS.md §7.1) — часть технических допущений оформлена инлайн с пометками свободы («имя можно уточнить без изменения контракта», §5.3), часть — как явные решения с обоснованием. Ревью r1 уже приняло этот формат как достаточный при отсутствии открытых продуктовых вопросов; в этом раунде замечание не переоткрывается. ## AC, разобранные заново (затронуты моделью v8/v9) - **AC3** (единая семантика всех `cm:0`) — соответствует принятому владельцем 2026-08-26T07:15:44Z решению не вводить `zero_kind`; текст АС и §5.2/§10 п.5 согласованы, критерий проверяемый (source-contract + смешанная v8 fixture). - **AC4, AC8, AC9, AC11** — используют authoritative `wall_segments[]` и lineage #282 корректно (§8.1, §8.3, §10 п.3-4); ссылки на «lineage #282» соответствуют реально реализованному identity barrier (`docs/specs/282-...md` §6.5 и далее). - **AC10** — защита проёмов на миграции распространена на **все** итоговые `cm:0` (§10 п.6), не только на бывшие legacy spans — корректно закрывает edge case, который отдельно требовала аналитика (comment 0, «Перевод физического участка с уже размещённым проёмом»). - **AC12** — read-only и mutation-gate формулировка не изменилась по смыслу относительно v7-версии, применима к v8/v9 без правок. - **AC13** — проверена выше в M1: текст в остальном (atomic transaction, no partial apply, one-deep Undo) корректен и соответствует §10.1; конкретная цифра лимита — единственный дефект. - **AC15** — соответствует текущему backend-состоянию (см. «Как проверялось»): `wall_segments[]` уже принимает `cm:0`, `room_drafts`/`partitions` требуют правки диапазона — AC формулирует именно это, без завышения того, что уже сделано. - **AC17** — бюджеты §13 корректно исключают лимит 500 из перф-раздела нет, see M1; сам перф-контракт (fingerprint по `space id + geometry revision + zero_wall_style`, отсутствие rebuild на HA state tick) сформулирован однозначно и проверяем benchmark-артефактом. ## Что проверено и корректно - Оба продуктовых раздела §7.1 (сценарий, что человек увидит) присутствуют и не описывают реализацию; - открытых продуктовых вопросов к владельцу нет, оба его решения (комментарии 2026-08-25T15:10:53Z о световом режиме пространства и 2026-08-26T07:15:44Z о запрете `zero_kind`) полностью отражены в §4 и §5.2; - модель v8/v9 описана не как догадка, а с явной опорой на уже слитый #282, и эта опора проверена по факту (не по заявлению автора) в разделе «Как проверялось»; - AC1-AC18 пронумерованы, у каждого указан способ доказательства (unit/backend/smoke/golden/perf), формулировки однозначны; - release-артефакты (§19) включают оба changelog, оба user-guide, канонические документы подсистем (WALL-THICKNESS, LIGHT, CONFIG-COMPATIBILITY, ARCHITECTURE, STATUS) и явно требуют bundle sync — соответствует PROCESS.md §8 и §11.2 таблице класса D; i18n-таблица симметрична RU/EN, включает пояснение под селектором отдельным ключом (не только tooltip), что было прямым требованием владельца; - откат (§12) отдельно разбирает forward compatibility и invalid-downgrade, без Labs-флага — обоснование («две одновременно пишущие модели — больший риск, чем флаг снимает») продуктово осмысленно и не противоречит `docs/CONFIG-COMPATIBILITY.md`; - зависимости (§21) корректно называют #282 как реализованную основу и не претендуют на дублирование её identity-writer; #148/#173 помечены как требующие superseded-note, а не тихо игнорируются. ## Чего не проверял - Не прогонялись `npm run typecheck`/`npm test`/`npm run build` — на этапе ревью ТЗ src не менялся, эти гейты относятся к код-ревью (PROCESS.md §2.7, §8) и здесь неприменимы; - не прогонялись browser-смоки, golden, `python -m pytest tests_backend` — код отсутствует, нечего исполнять; упомянутые в ТЗ файлы тестов (`test/zero-wall-migration.test.mjs`, `demo/smoke_zero_walls.mjs` и др.) ещё не существуют, это ожидаемо для стадии спеки; - не проверялась математика perf-бюджетов §13 (p95 +10%/+5%) эмпирически — оценена только как формулируемая и измеримая величина, без числового прецедента на реальном large-house fixture; - не проверялись все 21 раздел построчно на перенумерацию (в отличие от r2, где это было предметом дельты) — в этом раунде перенумерации не было, структура документа (1…21) стабильна между `2c801bf5` и `abbd4904`, что подтверждено `grep -n '^## '` без более глубокой сверки каждой внутренней `§N`-ссылки за пределами тех, что упомянуты выше. ## Вердикт Одна находка Medium (M1, в скоупе, устаревший лимит `wall_segments[]`), High — нет. По PROCESS.md §2.4/§4 это жёлтый вердикт: автор правит текст в трёх/четырёх местах (§10 п.10, §13, §17 AC13, §20), фикс проходит повторный раунд ревью по дельте (§2.10, дельта в этом случае локальна — правка одной цифры и её контекста, не новая подсистема). **Вердикт: жёлтый · заход r3 · блокирующих циклов 2/4 · High: 0 · Medium: 1 → в задаче**