Files
houseplan-card/docs/reviews/SPEC-REVIEW-306-r1.md
T
2026-08-26 12:55:27 +03:00

277 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 → в задаче**