15 KiB
SPEC-REVIEW-224-r1
- Issue: #224 — «Канонизировать координаты при записи конфига»
- Этап: spec (PROCESS.md §2.4)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4
- Артефакт ТЗ:
docs/specs/224-config-coordinate-canonicalization.md - Ветка:
issue/224-config-coordinate-canonicalization, SHA на момент ревью:d5725c2fab2c93c007f9a81b7891fef3311ee1dc - Ревьюер: Claude, роль «ревьюер ТЗ» (не автор)
Скоуп ревью
Первый заход — разбор полный, дельты предыдущего раунда нет. Диапазон:
тело issue #224 и все три комментария владельца (аналитика → ТЗ), файл
docs/specs/224-config-coordinate-canonicalization.md целиком, сверка с
фактическим кодом (custom_components/houseplan/validation.py,
src/houseplan-card.ts, src/align-grid.ts, scripts/mutation-gate.mjs),
с docs/SCOPE.md, docs/CANVAS.md, docs/CONFIG-COMPATIBILITY.md,
docs/TESTING.md.
Как проверялось
- Прочитаны
docs/SCOPE.md,AGENTS.md,PROCESS.mdцеликом. - Прочитано тело issue #224 и все комментарии (аналитика, ТЗ, ссылка на файл).
- Прочитан ТЗ-файл целиком на ветке задачи (
git show origin/issue/224-config-coordinate-canonicalization:docs/specs/224-...md). - Факты ТЗ сверены с реальным кодом, а не приняты на слово:
_COORD/_GEOM/POS_SCHEMA/LAYOUT_SCHEMA/CONFIG_SCHEMA—custom_components/houseplan/validation.py:522-1130— подтверждают заявление §3 о том, что схема лишь проверяет диапазон/конечность и возвращает исходныйfloat.validation.py:593(_dedupe_open_spans,round(a[0], 6)) — подтверждает ссылку issue на прецедент округления в схеме.PLAN_SCALE_MIN/MAX,plan_angle(±360),CANVAS_LIMIT = 5000.0— подтверждают допущение §6.1 о том, чтоabs(v)*1e9не подходит к границеNumber.MAX_SAFE_INTEGER.MARKER_SCHEMA(validation.py:1025-1124) — подтверждает, что у маркера в конфиге есть толькоangle, а неx/y;x/yживёт в отдельном layout-сторе — таблица allowlist §6.2 разводит их верно._writeConfig/_persistLayoutвsrc/houseplan-card.tsсуществуют и соответствуют описанному месту вставки конвергенции (§8).async_save_config_state/async_save_layout_state— подтверждены вwebsocket_api.py,store.py,__init__.py(общий storage barrier из §7.2 адресует реальные точки записи, а не придуманные).snapN()вsrc/align-grid.ts— подтверждает границу не-скоупа («это не snap к решётке», §5) как отдельный, не переиспользуемый механизм.docs/TESTING.md:2093— строка «server Undo restores the original noisy bits» существует буквально; ТЗ §9 корректно определяет её как подлежащую замене формулировку, а не выдумывает несуществующий текст.scripts/mutation-gate.mjsсуществует и поддерживает добавление новых id — мутационные пункты §13 реализуемы.
git diff origin/dev...origin/issue/224-config-coordinate-canonicalization— толькоdocs/specs/224-....md(360 строк) и одна строка реестра вdocs/specs/README.md. Продуктовый код не тронут — ожидаемо для стадии spec review, нарушений класса файлов нет.
Гейты кода на этапе ревью ТЗ не запускались — работа состоит из одного документа, продуктового кода в ветке нет, весь код-гейт неприменим на этой стадии процесса (PROCESS.md §2.4 предметом ревью называет ТЗ, а не гейты).
Проверка обязательных разделов (PROCESS.md §7.1)
Все обязательные разделы присутствуют и в правильном порядке — сценарий и «что человек увидит» идут первыми:
| Требование §7.1 | Раздел ТЗ |
|---|---|
| Сценарий (персона/поверхность/момент) | §1 |
| Что человек увидит до/после | §2 |
| Проблема | §3 |
| Скоуп и не-скоуп | §4, §5 |
| Контракт поведения | §6, §7, §8, §9 |
| UX | явно закрыт констатацией «нового UI/уведомления/настройки нет» в §2 и §11 (нет визуальных/интерактивных изменений — раздела по существу не требуется) |
| Модель данных и миграция | §10 |
| i18n | §11 (явное «новых строк нет») |
| AC1…ACn с доказательством | §12 |
| План автотестов | §13 |
| Риски | §14 |
| Откат | §14 (последний абзац) |
| Release-артефакты | §15 |
Блок принятых технических предположений (§7.1, «размытое место... явным блоком») присутствует — §16, 5 пунктов, каждый явно помечен «принято предположительно, поменять свободно».
Проверка каждого AC на однозначность и способ доказательства
AC1–AC13 (§12) — каждый сформулирован как проверяемое утверждение с конкретным способом доказательства (fixture, unit, mutation id), не общими словами вида «работает корректно». Ключевые:
- AC1 (побитовое совпадение Python/TS) — доказывается общей fixture, тест умеет упасть при расхождении округления/tie-правил.
- AC2/AC11 — allowlist и negative-contract разведены на два разных теста
(позитивный + мутант
quantization-hits-allowlist), что не даёт «зелёному тесту без падения» проскочить. - AC4 — no-op-контракт явно завязан на CAS-проверку до сравнения payload (§7.3 п.5), что не даёт no-op стать обходом optimistic locking — формулировка исключает неоднозначность «а что если revision совпадает случайно».
- AC7/AC9 — привязаны к реальному регрессу (#218) и к реальному мутанту
(
import-path-bypasses-schema), не абстрактны. - AC10 — граница «диагональные объекты не прилипают к сетке» имеет числовой порог (максимальный сдвиг из §6.1), проверка не оставлена на усмотрение.
Ни один AC не описывает недоказуемое субъективное состояние («выглядит нормально», «работает быстро» без числа).
Проверка «не выдана ли догадка за решение»
Утверждения о поведении, которые в ТЗ выглядят как факт, сверены с кодом (см. «Как проверялось», п.4) — расхождений не найдено. Технические решения, не наблюдаемые пользователем (точность 9 знаков, единая формула round-half- away-from-zero, деление allowlist на «геометрию» и «presentation/calibration», обязательность no-op, отказ от фоновой миграции) явно вынесены в §16 как предположения, а не встроены в текст как свершившийся факт — граница между «решено» и «предположено» в тексте видна.
Продуктовых вопросов владельцу в ТЗ нет — и это корректно: единственная персона (admin, единственный, кто пишет геометрию), единственный сценарий, пользовательский результат явно нулевой («внешний вид и точность размещения не меняются», §2). Открытых продуктовых развилок (какая персона важнее, что считать приемлемой деградацией) в задаче нет — тип задачи это позволяет: чисто внутренний storage-инвариант без нового UI.
Соответствие docs/SCOPE.md
Задача закрывает J6 («Keep the plan true as the home evolves») — план не должен статистически ломаться после обычных правок. Не пересекает out-of- scope списки SCOPE.md. Явно не трогает vacuum calibration (SCOPE.md не упоминает калибровку впрямую, но задача сама явно выносит её в negative contract — корректная осторожность).
Согласованность с #218 и #223
- С #218: регрессия используется как эталонный AC (AC9), причём именно на проблемном наборе координат из issue.
- С #223: ТЗ явно называет изменение формулировки Undo-контракта
(«restores the original noisy bits» → «restores the original geometry in
canonical representation») и относит правку
docs/TESTING.mdв release-артефакты (§15) — значит, задача не оставляет устаревшее утверждение висеть в чужом документе, а берёт его обновление в свой DoD. Это корректно по §12 запрещённого списка PROCESS.md («Оставили в тексте ревью» не считается закрытием — здесь другой случай: правка чужого упоминания через собственный release-артефакт, а не оставление находки без действия).
Находки
Не найдено. High — 0, Medium — 0, Low — 0.
Рассмотренные, но не подтвердившиеся сомнения (для прозрачности разбора):
- Не размечены все 4 mutation id к конкретным AC-строкам §12 (только 2 из
4 —
import-path-bypasses-schemaиquantization-hits-allowlist— явно упомянуты в тексте AC). Не является дефектом:schema-quantization- removedиfrontend-writes-raw-coords— мутанты уровня «сломать всю канонизацию целиком», их естественно ловит позитивная проверка AC2/AC6 вместе с обычным test suite; §13 явно называет добавление всех четырёх мутаций отдельным пунктом плана реализации. Не блокирует ТЗ — это плотность документа, не пробел в доказательстве. - Числовой предел безопасности (
abs(v)*factor < MAX_SAFE_INTEGER) сформулирован как факт, а не измерен на всех полях allowlist. Проверено выборочно по действующимvol.Rangeдляplan_scale,plan_angle,_COORD/_GEOM(±5000) — во всех случаях margin на порядки больше необходимого. Не найдено поле allowlist, способное нарушить это утверждение.
Что проверено и корректно
- Полнота обязательных разделов §7.1.
- Однозначность и доказуемость каждого AC.
- Фактическая точность утверждений ТЗ о текущем коде (схемы, storage barrier, frontend write path, snap-механизм, мутационный гейт).
- Явное разведение presentation/calibration и geometry в allowlist —
сверено с реальными схемами (
MARKER_SCHEMA,vacuum.calibration,plan_aspect/view_box). - Отсутствие незаявленных догадок — все непродуктовые решения вынесены в явный блок §16.
- Отсутствие изменений продуктового кода в ветке на этой стадии (класс A
не тронут — только класс C,
docs/specs/**). - Согласованность с #218 (регрессия) и #223 (обновление устаревшей формулировки Undo в общем документе).
Чего не проверял
- Реализацию — её ещё нет, это стадия spec review.
- Работоспособность будущей общей fixture Python/TypeScript — она ещё не написана; корректность будет предметом код-ревью.
- Полный список числовых полей allowlist на предмет опечаток/пропусков сверх выборочной проверки (markers, plan_*, walls, openings, decor, room_drafts, partitions, wall_columns, open_spans) — сверены ключевые представители каждой категории, не каждое поле построчно по схеме.
- Golden/smoke/performance — неприменимо на этой стадии, кода нет.
Вердикт
Зелёный. ТЗ полное, однозначное, каждый AC проверяем и привязан к способу доказательства, факты о коде подтверждены чтением исходников, догадки не выданы за решения. Задача уходит в «Готово к разработке».