Files
houseplan-card/docs/reviews/SPEC-REVIEW-288-r1.md
T
2026-08-24 15:56:48 +03:00

197 lines
18 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-288-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/288
- **Этап:** spec (PROCESS.md §2.4)
- **Заход:** r1 · блокирующих циклов израсходовано 0 из 4 (правило #227 —
зелёный вердикт бюджет не тратит, но этот вердикт не зелёный)
- **Документ ТЗ:** `docs/specs/288-bounded-multiwall-corridor.md`
- **SHA на момент ревью:** `cb0f9e7351e919701666dfd05b53f0e218e672a5`
(коммит `docs: specify bounded multiwall corridor`)
- **Первый заход** — раздел «объём по дельте» (§2.10) не применяется, разбор
полный.
## Скоуп ревью
Проверено: тело issue #288 и оба комментария (аналитика + «ТЗ готово»),
`docs/specs/288-bounded-multiwall-corridor.md` целиком, `docs/SCOPE.md`,
`docs/WALL-THICKNESS.md` (канонический документ подсистемы), фрагменты
`src/wall-thickness.ts` (константы `MITRE_LIMIT`/`MULTI_WALL_JOIN_LIMIT`,
типы `MultiWallNodeRay`/`MultiWallNodeRaySupport`, функции
`multiWallBevelCutsAt`, `multiWallRayStripGeometry`, `bevelMultiWallBody`),
`demo/smoke_real_plan_masonry.mjs`, `test/fixtures/real-plan-second-floor.json`
(структура), `scripts/model-invariants.mjs` (наличие `--config`), и для
сравнения структуры — специфи́кации `docs/specs/275-multiwall-strip-containment.md`
и `docs/specs/278-wall-union-isolation.md` (та же подсистема, сопоставимый
риск/сложность — использованы как прецедент требуемых разделов).
Как проверялось: ревью текстовое — сверка формулировок ТЗ с телом issue
(числа/причина совпадают буквально), с идентификаторами в реальном коде
(`MITRE_LIMIT`, `node.halfDepth`, `MultiWallNodeRay.supports` — все существуют
и используются именно так, как описывает контракт §3.1), с формулировками
`docs/WALL-THICKNESS.md` (ссылки на #249/#261/#271/#272/#275/#278/#279 в AC4
и в тексте документа совпадают с уже задокументированным поведением, не
изобретены), и структурное — сверка присутствия обязательных разделов §7.1
с прецедентными спеками той же подсистемы. Продуктового кода в диапазоне нет
(diff ограничен `docs/specs/**`, класс C) — гейты §8 к этапу spec не относятся
и не запускались; это гейты этапа code (§2.7).
## Находки
### Medium (в скоупе задачи — чинится в этом же ТЗ)
**M1. Отсутствуют обязательные разделы «Риски» и «Откат» (§7.1, DoR §2.5).**
`docs/specs/288-bounded-multiwall-corridor.md` не содержит ни одного раздела
с анализом рисков, ни раздела откат/rollback. Единственное упоминание слова
«риск» — оценочная цифра в шапке (`риск 8/10`), без содержания. Проверено
`grep -n -i "риск\|rollback\|откат"` по всему файлу — единственное совпадение
строка 7.
PROCESS.md §7.1 перечисляет обязательные разделы ТЗ явно: «…план автотестов ·
риски · откат · release-артефакты» — оба отсутствующих раздела в списке есть.
DoR §2.5 повторяет то же как отдельные обязательные пункты очереди: «влияние
на производительность… названо», «**откат**: как выключить или вернуть
назад», «риски перечислены». Пока эти пункты не выполнены, статус не может
стать «Готово к разработке» буквально по тексту §2.5: «Если хоть один пункт
не выполнен — статус не «Готово к разработке», как бы ни хотелось начать».
Это не абстрактная формальность именно для данной задачи: сам автор оценил
риск как 8/10 и сложность 7/10 (P1, обычный трек — не `small`), а фикс трогает
общий для всех T/L/multi-wall стыков код (`bevelMultiWallBody`,
`multiWallBevelCutsAt`), от которого зависят уже закрытые контракты #249,
#261, #271, #272, #275, #278, #279 — то есть реальный риск регрессии высок и
уже отражён самим объёмом AC4, но нигде не назван явно как риск с мерой
(«риск: регрессия одного из семи прежних контрактов → мера: AC4 перегоняет
весь их набор»). Откат для правки без миграции и без флага — скорее всего
«просто revert коммита», но это должно быть сказано, а не подразумеваться:
раздел 6 («Совместимость…») описывает только *отсутствие* новых
compatibility-полей, а не механику отката самой правки.
Прецедент: спеки той же подсистемы и сопоставимого риска — `#275`
(`docs/specs/275-multiwall-strip-containment.md`, разделы «11. Риски и меры» и
«12. Rollback») и `#278` (`docs/specs/278-wall-union-isolation.md`, разделы
«12.1. Риски и меры» и «17. Release-артефакты и rollback») — оба раздела
присутствуют. #288 того же класса дефекта и не легче ни по одной из этих
двух метрик, но раздела не имеет.
**Чем закрывается:** добавить в ТЗ раздел «Риски и меры» (минимум: риск
регрессии multi-wall контрактов #249/#261/#271/#272/#275/#278/#279 → мера
AC4; риск undershooting/overshooting отсечения у соседней стены → мера AC3 +
AC7 mutation) и раздел «Откат» (одна фраза: чистый revert коммита, миграции и
флага не требуется, поскольку модель данных не меняется). Это не требует
пересмотра геометрического контракта — правка текстовая.
### Low
**L1. «Связано» в шапке не включает #279, хотя AC4 явно на него ссылается.**
Шапка документа (строка 11) перечисляет «#249, #261, #271, #272, #275, #278,
#284–#286», но AC4 (строка 127) прямо требует «near-orthogonal #279 остаётся
защищённым». #279 — тот же класс контрактов, задействован в проверке
регрессии, и разумно ожидать его в списке связанных issue. Не блокирует
проверяемость ни одного AC и не влияет на выполнимость — чисто
трассируемость документа. Решение оставляю на автора: либо дополнить строку
11, либо снять как не влияющее (в этом случае — с записью в документе, а не
молчанием, по требованию §12 «Оставили в тексте ревью не считается
закрытием» — здесь это Low, а не Medium, поэтому достаточно осознанного
снятия).
## Что проверено и корректно
- **Раздел «Сценарий» и диагноз причины (п.1).** Числа `349/120/5` шагов,
толщины `30/30/30` см, соседняя стена `20` см, радиус
`MITRE_LIMIT × H = 4 × 15 = 60`, вырез `60 − 15 = 45` — воспроизводят тело
issue буквально, без искажений и без добавления недоказанных деталей.
Диагноз в issue помечен владельцем как «Подтверждённая причина» (совпадение
вычисленной величины с измеренной), так что повторение его в ТЗ как факта —
не догадка, выданная за решение, а корректное наследование уже
подтверждённого вывода.
- **Идентификаторы контракта существуют в коде.** `MITRE_LIMIT` (`src/wall-thickness.ts:78`),
`MultiWallNodeRay.supports`/`halfDepth` (там же, интерфейсы объявления и
использование в `multiWallRayStripGeometry`/`bevelMultiWallBody`) — контракт
§3.1 ссылается на реально существующие структуры, а не на вымышленные.
- **AC1/AC2 привязаны к существующему воспроизводимому инструменту.**
`demo/smoke_real_plan_masonry.mjs` уже в дереве (класс B, коммит
`c654e0ec`/`d5659478`), считает разрывы по каждому ребру каждой комнаты
(не только по четырём известным точкам), различает объявленный `open_span`
от дефекта. Требование AC1 «gapCount: 0, totalGapSteps: 0 плюс обновление
чисел `PLANS` в том же коммите» и AC2 «first-floor остаётся на нуле» —
однозначны и проверяются одной командой.
- **AC3 — table-driven unit, синтетическая, но осознанно упрощённая
конфигурация** (три луча `349/120/5` при `30/30/20` см вместо реальных
четырёх стен узла) корректна как проверка контракта §3.1 в отрыве от
конкретной топологии реального плана: контракт формулируется через
собственный half-depth соседнего ray, а не через воспроизведение точной
геометрии дома. AC1 остаётся источником истины для реального дефекта,
AC3 — независимая unit-гарантия того же правила на разных `cell_cm` и
порядках лучей.
- **AC4** — список регрессионных контрактов (#249 дискардед wedge,
#261 retained wedge, #271 finite rays, #272 enclosed holes, #275 protected
strips, #278 union failure isolation, #279 near-orthogonal) совпадает слово
в слово с формулировками §3 `docs/WALL-THICKNESS.md`, не выдуман.
- **AC7 (мутант)** — явно требует, чтобы порча корридора до зависимости
только от `MITRE_LIMIT × node.halfDepth` валила AC1 или AC3; это тест на
«умеет падать», а не декларация покрытия.
- **Раздел 4 «Scope»**, особенно «Не входит» (никакого переписывания
persisted-данных, никакого изменения глобальных констант, никакой новой
модели стен из ADR #282) корректно ограничивает скоуп и явно исключает
соседние задачи #289/#290, не позволяя дефекту раздуться в рефакторинг.
- **Раздел 8 «Порядок интеграции»** относительно ветки `#260`
(`issue/260-fixture-wall-keys`) корректно и без искажений повторяет
требование самого issue («влить до пересъёмки скриншотов») и явно
оговаривает, что #260 не входит в продуктовый скоуп #288 — не является
попыткой расширить скоуп чужой задачей.
- **«Принятые технические предположения» (п.9)** — все четыре пункта
действительно технические (верхняя граница ущерба, конкретный алгоритм
connector-а, объём фикстур, touch-политика), ни один не является
продуктовым вопросом, который следовало бы адресовать владельцу; блок
оформлен явно как предположение, что соответствует требованию §7.1
(«принято предположительно, поменять свободно»), а не выдаёт догадку за
факт.
- **Совместимость/touch/performance (п.6)** — «модель данных не меняется»
подтверждается отсутствием любых изменений схемы/`layout`/`Optimize` во
всём документе; touch-формулировка («View на touch и kiosk получает тот же
canonical result») соответствует терминологии `docs/TOUCH-SUPPORT.md`
(View — гарантированная поверхность, редактор — best effort), не изобретает
новый термин.
- **Продуктовых вопросов владельцу нет** — и это корректно: единственная
видимая пользователем перемена — «стены рисуются без разрыва», без нового
UX-контракта, без выбора между персонами. Открытых развилок, требующих
продуктового решения владельца, в тексте не найдено.
## Чего не проверял
- Не запускал `npx tsc --noEmit` / `npm test` / `npm run build` /
`node scripts/check-docs.mjs` — diff ограничен `docs/specs/288-*.md`
(класс C), продуктовый код и тесты не менялись; эти гейты относятся к
этапу code-review (§2.7) и будут обязательны там.
- Не выполнял `node demo/smoke_real_plan_masonry.mjs` живьём — на этапе spec
еще нет реализации; текущий (добавленный ранее, в другом issue) результат
`gapCount: 4, totalGapSteps: 181` уже зафиксирован в самом smoke-файле и
подтверждён текстом issue, повторный прогон здесь ничего нового не даёт.
- Не проверял, действительно ли предложенная в AC3 синтетическая
конфигурация (3 луча, включая «соседнюю» стену как один из лучей) технически
достаточна для воспроизведения именно того случая, где четвёртая
(не инцидентная узлу) стена теряет материал — если в реальности дефект
затрагивает стену, geometрически не входящую в `node.rays` того же узла,
синтетический repro AC3 может не поймать в точности этот путь кода. Это не
повод для находки: AC1 (реальный smoke) остаётся источником истины
независимо от того, что покажет AC3, и авторская реализация обязана
удовлетворить AC1 в любом случае. Точную топологию дефектного пути стоит
перепроверить на этапе code-review по фактическому диффу.
- Не проверял golden baseline и её текущее состояние (`artifacts/golden/`) —
AC6 описывает будущую работу (targeted golden для реального плана ещё не
существует), нечего сверять до реализации.
## Вердикт
High-находок нет. Одна Medium-находка (M1) — в скоупе задачи, не требует
отдельного issue (#202) и чинится добавлением двух коротких разделов в тот же
файл ТЗ. Одна Low-находка (L1) — на решение автора (дополнить или снять с
записью).
```
Вердикт: жёлтый · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 1 → в задаче
Документ: docs/reviews/SPEC-REVIEW-288-r1.md
```