mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 13:18:58 +00:00
277 lines
24 KiB
Markdown
277 lines
24 KiB
Markdown
# 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 → в задаче**
|