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

24 KiB
Raw Blame History

SPEC-REVIEW-306-r3

  • Issue: #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 → в задаче