25 KiB
SPEC-REVIEW-290-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/290
- Этап: spec (PROCESS.md §2.4)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4
- Ревьюируемый артефакт:
docs/specs/290-near-axis-authoring-and-repair.md, веткаissue/290-near-axis-authoring-repair, коммитdcdb7565 - Вердикт: жёлтый
Скоуп ревью
Issue не помечен small/trivial → обычный трек, ТЗ обязано жить файлом в
docs/specs/. Диапазон изменений от origin/dev — ровно один файл
(docs/specs/290-near-axis-authoring-and-repair.md, класс C), что и требуется
на этапе S4-spec-review: продуктовый код не тронут.
Задача — часть цепочки #284 (канонизация координат записи) → #279 (устойчивый
рендер почти-ортогонального T-стыка) → #290 (не создавать такую геометрию
молча и дать явный способ её исправить). Продуктовая рамка по docs/SCOPE.md
— J6 («Keep the plan true as the home evolves»): администратор должен видеть и
контролировать изменение геометрии, Optimize обязан явно сообщать о
lossy-исправлениях. Это совпадает с собственной оценкой аналитика в issue.
Как проверялось
- Прочитаны
docs/SCOPE.md,PROCESS.md(жизненный цикл, §7.1, §8),AGENTS.md. - Прочитано тело issue #290 целиком и все три комментария (аналитика, продуктовые вопросы с defaults, ссылка на готовое ТЗ).
- Прочитан весь текст
docs/specs/290-near-axis-authoring-and-repair.md(313 строк). - Сверено с каноническими документами задетых подсистем:
docs/CANVAS.md(снап/Shift/hover-resolver, барьер канонизации координат записи, «Оптимизировать планы»),docs/RESIZE.md(safe-resize contract, eligibility, pure pipeline, Undo),docs/WALL-THICKNESS.md(модельwalls[],wallKey, rekey после Resize/Optimize),docs/USER-GUIDE.ru.md(текущий текст диалога «Оптимизировать планы» и его таблица операций). - Прочитаны связанные issue #279 (полная переписка причины/фикстуры), #277 (safe Resize), #248 (идемпотентность Optimize), #284 (канонизация записи) — все указаны в ТЗ как связанные.
- Сверены существующие тестовые фикстуры:
test/fixtures/279-near-orthogonal-junction.json,test/fixtures/real-plan-second-floor.json,test/fixtures/coordinate-canonicalization.json— на предмет соответствия тому, что AC4 называет «minimized fixture из #284». - Прочитан существующий продуктовый код на предмет уже введённой константы
MULTI_WALL_NEAR_ORTHOGONAL_MAX_DEGREES = 0.25(src/wall-thickness.ts:84), чтобы проверить, не конфликтует ли предлагаемый общийNEAR_AXIS_MAX_DEGREESс уже существующим порогом того же значения. - Для сравнения формата прочитаны полностью аналогичные недавние ТЗ той же
геометрической линии —
docs/specs/277-safe-resize.mdиdocs/specs/279-near-orthogonal-junction.md— как ориентир на «Риски», «Rollback» и формат таблицы AC/Доказательство, принятый в этом репозитории.
Гейты (typecheck/test/build) не запускались: на этапе spec продуктовый
код не менялся, диапазон diff состоит из одного файла документации, гоны не
применимы к этому этапу.
Находки
Medium (в скоупе задачи — чинится в этом же ТЗ)
M1. Отсутствуют обязательные разделы §7.1 «риски» и «откат».
- Файл:
docs/specs/290-near-axis-authoring-and-repair.md - PROCESS.md §7.1: «Обязательные разделы ТЗ: сценарий · что человек увидит до и после · проблема · скоуп и не-скоуп · контракт поведения · UX · модель данных и миграция · i18n · критерии приёмки AC1…ACn с указанием доказательства · план автотестов · риски · откат · release-артефакты.»
- Что не так: в документе нет ни одного раздела «Риски» и ни одного раздела/абзаца «Откат/Rollback». Единственное упоминание слова «риск» — в шапке, это число самооценки аналитика (9/10), не анализ. Раздел 11 («Release») описывает только трейлеры коммита и требование Linux-артефакта для golden — про то, как вернуть изменение назад (revert commit? нужен ли Labs-флаг? что произойдёт со старыми планами, если поведение окажется ошибочным и придётся откатывать) не сказано ни слова.
- Почему это не мелочь: это не формальность, а действующая практика в этой
же геометрической линии задач. Оба непосредственных предшественника —
docs/specs/277-safe-resize.md(§12.1 «Риски и меры», таблица из 10 строк, и §17 «Release-артефакты и rollback» с явным «Rollback — revert implementation commit... После rollback вернётся широкий нестабильный UI, поэтому release rollback должен быть полным и сопровождаться предупреждением») иdocs/specs/279-near-orthogonal-junction.md(§5 «Совместимость, риски и performance») — оба содержат этот анализ. У #290 риск выше, чем у #277: здесь вводится автоматическое изменение геометрии при рисовании без обходного модификатора (§5.1 ТЗ, п. «Специального modifier bypass нет») — то есть редактор будет менять то, что нарисовал пользователь, без явного согласия в моменте. Ровно такой случай (переопределение поведения без spec на риски) — то, ради чего DoR требует «влияние… названо (или явно «нет»)» и «риски перечислены» (PROCESS.md §2.5). - Воспроизведение:
grep -niE "риск|откат|rollback"по файлу ТЗ (кроме строки самооценки в шапке и одного упоминания «downgrade не требует migration» в §9) не находит содержательного раздела. - Требуется: добавить раздел «Риски» (минимум: ложные срабатывания auto-straightening на длинных почти-диагональных стенах около границы 0.25°; двойной подсчёт shared-стены между Optimize-кандидатами; конфликт с параллельно идущими #276/#278/#281, которые тоже трогают Optimize-геометрию, как это явно оговорено в #277 §12.1) и раздел «Откат» (что происходит, если после релиза найден дефект: revert коммита, нет миграции схемы, старые планы с уступом остаются как есть до следующего Optimize).
M2. AC исходного issue про npm run invariants на реальных данных и
видимое снижение числа почти-ортогональных узлов не перенесён в ТЗ.
- Файл:
docs/specs/290-near-axis-authoring-and-repair.md, раздел 8 (AC1–AC10), в сравнении с AC6 тела issue #290. - Что не так: issue формулирует шестой критерий приёмки явно: «
npm run invariantsна обоих реальных планах — код 0; после выпрямления уступа число почти-ортогональных узлов уменьшается, и это видно в отчёте Optimize». В ТЗ этому соответствует только AC5, но AC5 требует нулевых нарушенийcheckWallKeys/checkMixedRoleRecords/preflight на минимизированной фикстуре, а не на «обоих реальных планах», и не требует показать уменьшение числа почти-ортогональных узлов ни в выводеscripts/model-invariants.mjs, ни вOptimizeReport. Раздел 10 («Ожидаемые файлы» → Tests/evidence) тоже не называетnode scripts/model-invariants.mjs --config <...>явно, хотя PROCESS.md §8 делает эту команду обязательной ровно для диффов, трогающих геометрию и ссылки на неё (edges,wall thickness records,open_spans) — а это ядро задачи #290. - Почему это не формальность: минимизированная фикстура доказывает, что
механизм работает на сконструированном случае; она не доказывает, что на
реальном плане (с сотнями узлов, где уже случались #253/#258/#259) число
почти-ортогональных узлов действительно падает, а не создаётся новый класс
случаев в другом месте плана. Именно необходимость такой проверки на
реальных данных и породила #279 (реальный экспорт
44.json) и практику «второго реального плана» в текущей серии коммитов ветки (test: add a second real plan to the masonry gate,test: one thickness record must not describe two wall roles) — то есть инфраструктура для анонимизированной проверки на реальных/близких к реальным данным в этом репозитории уже есть и используется соседними задачами. - Требуется: либо явно перенести требование issue в AC (анонимизированная
фикстура, произведённая из реального плана, а не полностью синтетическая
«minimized»; факт снижения near-orthogonal-count через
scripts/model-invariants.mjsдо/после Optimize), либо явно и обоснованно сузить критерий с указанием, почему полный реальный план как источник проверки не нужен — так же, как #277 §16 прямо объясняет использование анонимизированной регрессионной фикстуры вместо сырого экспорта. Молчаливое сужение критерия приёмки, сформулированного в issue, без объяснения — ровно тот случай, который ревью обязано ловить.
Low (снимается либо правится, с записью)
L1. AC4 неточно называет источник fixture.
- Файл:
docs/specs/290-near-axis-authoring-and-repair.md, AC4: «Minimized fixture из #284 содержит duplicated shared physical edge316×1». - Наблюдение: узел, который АС4 описывает (
316×1, координаты(-1.670833333, 2.95)/(-0.354166667, 2.954166667)), — это буквальноtest/fixtures/279-near-orthogonal-junction.json(сверено байт в байт: тот жеnode, те же координаты стен). Issue #284 — про барьер канонизации координат записи (snap = v => Math.round(v*240)/240), отдельная задача про другой механизм; она не производила эту fixture. Вероятная причина — смешение «issue #284 как общее исследование, откуда взят пример» и «issue #279 как задача, где эта fixture уже закоммичена». Это не блокирует реализацию (fixture физически существует и полностью подходит под описание AC4), но при написании автотеста первое, что сделает исполнитель — будет искать/создавать fixture «из #284», где её нет. - Решение ревьюера: Low, не эскалируется. Достаточно поправить ссылку на
test/fixtures/279-near-orthogonal-junction.json(или явно сказать «тот же случай, что и в #279») при следующей правке файла; отдельного цикла ради этого не требуется.
Что проверено и признано корректным
- Обязательные продуктовые разделы §7.1 («сценарий», «что человек увидит») присутствуют и содержательны (разделы 1 и 3); ТЗ отвечает на оба вопроса без терминов реализации в пользовательской части.
- Продуктовые вопросы владельцу закрыты. Ровно три вопроса (выравнивать/предупреждать, поведение Optimize, единый допуск) заданы одним комментарием с defaults, каждый — что человек видит/делает, не техническое решение, выданное за продуктовое. Раздел 2 ТЗ фиксирует все три ответа дословно, раздел «Открытых продуктовых вопросов нет» подтверждён — при чтении issue и ТЗ вместе противоречий не найдено.
- Единый допуск и его источник (раздел 4) сформулирован проверяемо:
включительная граница
0.25°,316×1классифицируется,316×2— нет, zero-length исключён, инвариантность к endpoint order/winding/scale/theme названа явно. AC1 требует единый source threshold и source-guard против отдельных литералов0.25— реализуемо и falsifiable (мутант «порог ниже0.181315°» и «strict<» в AC9 корректно бьют по обеим сторонам границы). То, что предлагается использовать то же числовое значение, что уже занятоMULTI_WALL_NEAR_ORTHOGONAL_MAX_DEGREESвsrc/wall-thickness.ts:84для другого геометрического сравнения (угол между двумя лучами, а не отклонение одного луча от глобальной оси) — техническое решение, а не путаница: раздел 4 явно оговаривает, что renderer может продолжать использовать «эквивалентный dot/sine form for orthogonal pairs», то есть общий источник — это значение допуска, а не буквально один и тот же exported symbol. Это подпадает под «принято предположительно, поменять свободно» и не требует отдельного продуктового решения. - Негативный контракт (диагонали не трогаются) описан симметрично положительному на всех трёх путях (Walls, Resize, Optimize) и покрыт отдельным AC6 плюс мутантами AC9 «считать room copies как две стены» / «repair only one owner shared wall», что закрывает риск двойного счёта расшаренной стены — именно то, из-за чего первоначальный AC issue выделял этот пункт отдельно.
- Совместимость с существующими контрактами подсистем. Раздел 5.2
корректно наследует существующий контракт
docs/RESIZE.md(«только numerically horizontal/vertical edge eligible», epsilon поглощает только storage noise) и не пытается расширить Resize eligibility на near-axis случаи — вместо этого явно направляет их через Optimize (раздел 12, пункт 2), что не противоречит ниRESIZE.md, ниWALL-THICKNESS.md. Раздел 6.1 корректно ссылается на существующий канонический механизм ключей толщины (wallKey, exact-span lookup) изWALL-THICKNESS.mdвместо изобретения нового формата хранения — толщина не становится отдельным «candidate», а reprojection'ится из исправленной геометрии существующим pipeline, что соответствует моделиWALL-THICKNESS.md(«key is computed from lattice-stable endpoints»). - Touch/производительность (раздел 9) соответствует терминологии
docs/TOUCH-SUPPORT.mdдословно («View/kiosk rendering fully supported» ↔ «Fully supported» в таблицеTOUCH-SUPPORT.md), не расширяет editor parity на touch сверх «best effort», не блокирует pinch/pan/pointercancel — соответствует установленному продуктовому решению («editors are desktop-first»). Заявление «Authoring classifierO(1)» и «Optimize pass линейный по edges» не противоречит существующим performance-бюджетамRESIZE.mdи не заявляет числ, которые нечем будет доказать: сохранена ссылка на «full performance gate обязателен». - Никаких догадок, выданных за факт, не найдено среди технических
утверждений о существующем поведении: каждая проверенная ссылка на
текущий контракт (Shift 45°, safe Resize eligibility,
wallKey, барьер канонизации записи #284) точно соответствует канону вCANVAS.md/RESIZE.md/WALL-THICKNESS.md. Пункт раздела 12 («Принятые технические предположения») корректно ограничен вопросами, которые пользователь не наблюдает (какой endpoint двигается при равной безопасности кандидатов, что unsafe-repair не обязан чинить любой ценой) — то есть именно тем классом решений, которые PROCESS.md §7.1 разрешает решать автору/ревьюеру без владельца. - i18n, DoR-чеклист. Раздел 10 называет оба файла (
en.json/ru.json); конкретные ключи на этапе ТЗ не требуются — сверено с #277/#279, которые на этом же этапе тоже ограничиваются общей фразой «RU/EN i18n» без перечисления ключей. - Файл ТЗ лежит в правильном месте и правильно назван —
docs/specs/290-near-axis-authoring-and-repair.md, номер совпадает с номером issue, что соответствует PROCESS.md §2.3.
Чего не проверял
- Не проверял, что перечисленные в ТЗ файлы (
src/resize.ts,src/align-grid.ts/src/plan-optimizer.ts,src/houseplan-card.ts) действительно являются правильными точками внедрения — на этапе ТЗ кода ещё нет, а раздел 10 явно помечен как «Ожидаемые файлы», то есть прогноз, а не обязательство; проверка предметна для код-ревью. - Не оценивал реальную достижимость перфоманс-бюджетов («Authoring
classifier O(1)», сохранение p95 из
RESIZE.md) — это будет проверяться performance-гейтом перед бетой, не на этапе спецификации. - Не проверял мутационный registry (AC9) на предмет технической реализуемости каждого конкретного мутанта в текущей кодовой базе — на этом этапе mutation ids не существуют, это работа код-ревью.
- Не запускал никаких гейтов (
typecheck/test/build) — diff этого раунда состоит из одного файла документации класса C, гейты к этапу spec не относятся (см. «Как проверялось»). - Не проверял docs/CONFIG-COMPATIBILITY.md registry на конкретные записи —
ТЗ утверждает отсутствие изменений схемы (раздел 9), новых persisted-полей
нет, поэтому реестр совместимости не должен получать новых записей; это
утверждение принято на веру как техническое (не продуктовое) без
дополнительной проверки скрипта
config-field-registry.mjs, так как задача явно заявляет «schema не меняется».
Вывод
Продуктовая часть ТЗ (сценарий, видимый результат, продуктовые решения владельца, критерии приёмки как таковые, негативный контракт для диагоналей) выполнена тщательно и без домыслов, выданных за факт. Причина жёлтого вердикта — два содержательных пробела в обязательных разделах и трассируемости AC: отсутствие анализа рисков и плана отката (§7.1), и молчаливое сужение AC6 issue (проверка на реальных данных, видимое снижение числа почти-ортогональных узлов) без объяснения. Оба пункта — Medium в скоупе этой же задачи и чинятся правкой того же файла, без нового issue и без обращения к владельцу.