14 KiB
CODE-REVIEW #250 — заход r1
- Issue: #250
- ТЗ:
docs/specs/250-opening-centerline.md, зелёный SPEC-REVIEW r1 на2c776b0(docs/reviews/SPEC-REVIEW-250-r1.md). - Ветка:
issue/250-opening-centerline - Диапазон:
origin/dev..HEAD=2c776b0,2534916,0eb9ffe,7269550(4 коммита) - Продуктовый коммит:
0eb9ffe(Issue: #250,User-Visible: yes, оба changelog). Второй коммит7269550— принятый Docs screenshots артефакт (User-Visible: no).
Скоуп проверки
Диапазон небольшой (production-дельта — 3 файла src/**, остальное тесты,
мутанты, golden-контракт, документация), поэтому разбор полный: заход первый,
предыдущего раунда нет, «Унаследовано из r» неприменимо.
Прочитаны docs/SCOPE.md (J1/J6), тело issue #250 и все пять комментариев, docs/USER-GUIDE.ru.md, docs/WALL-THICKNESS.md, docs/ARCHITECTURE.md, docs/ISOMETRIC.md, ТЗ целиком.
Как проверялось
Прочитан весь git diff origin/dev...HEAD построчно (29 файлов). Дополнительно
исполнено (все команды дают тот же результат, что заявлен автором):
npx tsc --noEmit— green.npm test— 1114/1114 pass, 0 fail (автор сообщил 1113 pass/1 skip; локально skip не воспроизведён — см. «Чего не проверял», не блокирует).npm run build— green;sha256sumтрёх копий бандла совпадает друг с другом и с хэшем806648ae6036…, указанным автором.node scripts/check-docs.mjs— green (7 файлов, 10 внешних ссылок).node scripts/process-gate.mjs --range origin/dev..HEAD --issues— green, 0 предупреждений.node scripts/mutation-gate.mjs --id=opening-symbol-flip-restores-edge-offset— новый мутант пойман 1 из 1 (см. AC1 ниже).node scripts/mutation-gate.mjs --id=opening-gate-flip-cancels-turn— 0 из 1, см. находку CR-1.node demo/smoke_wall_thickness.mjs,node demo/smoke_isometric_contract.mjs,node demo/smoke_opening_preview.mjs— все три green на свежепересобранном бандле (скопирован вcustom_components/houseplan/frontend/иdemo/srv/assets/).node scripts/smoke-select.mjs --base origin/dev --head HEAD— НЕОПРЕДЕЛЁННОСТЬ (169 смоков, порог «широкого» символа >33; изменённых символов проекта — 6:OpeningCfg,OpeningFaceOffset,_type,_flipV,_angle,_face; ни один смок не назван инструментом напрямую). Решение ревьюера: три выбранных автором smoke — прямое покрытие темы (wall thickness/iso/opening preview), этого достаточно вместе с unit/golden-unit матрицей; полная матрица 167 смоков не оправдана диффом в 6 символов на 3 файлах.git diff --check origin/dev...HEAD— чисто, whitespace ошибок нет.- Трейлеры
0eb9ffe/7269550прочитаны напрямую (git log -1 --format=...) — оба корректны,User-Visible: yesдействительно несёт оба changelog в том же коммите.
Не прогонялось (осознанно, ниже обоснование): npm run golden:verify (полный),
python -m pytest tests_backend (backend/schema не тронуты, AC7 подтверждён
чтением — модель flip_v не меняется), полный smoke_* набор (167 смоков),
performance-профили (не названы в AC, дельта — константный helper без цикла).
Находки
CR-1 (Medium, в скоупе) — мутационный guard opening-gate-flip-cancels-turn сломан переименованием теста
scripts/mutation-gate.mjs:1678 вызывает
node --test --test-name-pattern="shared renderer centres defaults" test/opening-symbol.test.mjs.
Этот тест был переименован этим же диффом
(test/opening-symbol.test.mjs, было 'shared renderer centres defaults and preserves explicit door/window edge alignment', стало 'shared renderer centres every flip while preserving opening direction') — подстрока
"shared renderer centres defaults" больше не встречается ни в одном имени
теста файла.
Воспроизведение:
$ node scripts/mutation-gate.mjs --id=opening-gate-flip-cancels-turn
ok чистый прогон: ... node --test --test-name-pattern="shared renderer centres defaults" test/opening-symbol.test.mjs
FAIL opening-gate-flip-cancels-turn: тест остался зелёным на сломанном коде
поймано 0 из 1
Причина видна напрямую: паттерн не матчит ни один подтест, node --test
выполняет 0 тестов и рапортует pass 1 (файл целиком) с exit code 0 — и на
чистом дереве, и на мутированном. Guard стал тождественно зелёным независимо
от того, сломано направление ворот или нет.
Дешёвая проверка test/mutation-gate.test.mjs («якоря живы») этого не ловит:
она проверяет только присутствие патч-анкора в исходнике, а не то, что
--test-name-pattern matches. Полный mutation-gate.mjs (единственное место,
где это видно) — предрелизный/еженедельный гейт
(.github/workflows/mutation-gate.yml), не гейт этого code review; поэтому
автор, прогнавший только новый мутант по имени, не был обязан это заметить
типовым implementation-гейтом, но ТЗ §14 прямо требует: «Существующий
opening-gate-flip-cancels-turn сохраняется и обязан продолжать падать».
Сейчас это не так — обязательство не выполнено.
Почему это Medium, а не High: реальную регрессию направления поворота
ворот по-прежнему ловит обычный npm test (assertions теста выполняются
целиком без фильтра имени, см. demo/smoke_isometric_contract.mjs /
isoGateFlipReversesTurn — независимое дублирующее покрытие тоже green).
Пострадала только дисциплина «тест обязан уметь падать» применительно к этому
конкретному mutation-guard, не пользовательское поведение.
Почему в скоупе, не отдельный issue: тест переименовало само это изменение
(test/opening-symbol.test.mjs — файл диффа задачи), а не соседняя
подсистема; PROCESS.md/#202 требует чинить Medium-в-скоупе на месте.
Исправление: привести --test-name-pattern в
scripts/mutation-gate.mjs:1678 в соответствие новому имени теста (например,
"shared renderer centres every flip") и повторно прогнать
node scripts/mutation-gate.mjs --id=opening-gate-flip-cancels-turn, ожидая
поймано 1 из 1.
Других находок нет.
Что проверено и корректно
- AC1 —
openingSymbolOffset()возвращает точный{ox:0, oy:0}для всех комбинаций; сигнатура сохранена (_type/_flipV/_angle/_face, допущение §19.2).test/opening-symbol-placement.test.mjs— единый тест-матрица: 4 типа × 2 flip × 5 углов (включаяNaN) × 5 faces (включаяInfinity/NaN/нулевую грань) — честная замена прежних трёх сценариев, мутант ловится 1/1 (проверено исполнением, см. «Как проверялось»). - AC2/AC3 — Flat (
src/render/opening-symbol.ts) и Iso (src/iso-openings.ts:87, не тронут — берёт тот же offset) используют один helper;test/opening-symbol.test.mjsиtest/iso-openings.test.mjsраздельно проверяют «один origin» и «противоположный knob направления» (scale(1 1)/scale(1 -1),quarterVector[1]знак). Прогонкойdemo/smoke_isometric_contract.mjsподтверждено на реальном рендере. - AC4 — gate: offset остаётся нулевым, знак 10° поворота меняется;
opening-gate-flip-cancels-turnmutant текстуально сохранён (но см. CR-1), дублирующее покрытие (isoGateFlipReversesTurnsmoke, полныйnpm test) подтверждает поведение реально работает. Passage не создаёт символ — не тронут (type === 'passage'ветка убрана из helper, ноrenderOpeningVisibleGeometryдля passage не рисует geometry вообще, проверено чтением). - AC5 — room-wall/partition parity:
demo/smoke_wall_thickness.mjs(doorSavedFlipStaysCentered) иdemo/golden/matrix.mjs(обе grаниpositiveFace/negativeFaceв unit-тесте дают одинаковый ноль) — проверено исполнением. - AC6 — толстая стена:
src/wall-thickness.tsтронут только в комментарии,openingInnerFaceOffset(physical half-depth/jamb) не задет;demo/smoke_wall_thickness.mjs(thickWallFullHatched,thickWallStaticHatched,openingCutsSlab) green — проверено исполнением. Lock badge/hitbox/actions — не тронуты диффом (проверено чтением: ни один файл лок-гарда/hitbox в diff не участвует). - AC7 — модель данных: diff не содержит правок schema/backend/migration
(
git diff --statподтверждает — толькоsrc/opening-symbol-placement.ts,src/render/opening-symbol.ts,src/wall-thickness.tsвsrc/**);flip_vостаётся тем же boolean-полем — проверено чтением. - AC8 — документы больше не обещают edge-align:
WALL-THICKNESS.md,ARCHITECTURE.md,ISOMETRIC.md,USER-GUIDE.ru.md,TESTING.mdсинхронно правлены, оба changelog заполнены в том же коммите0eb9ffe— прочитано построчно,check-docsgreen. - AC9 — typecheck/test/build green, три бандла побайтово идентичны —
подтверждено исполнением (SHA
806648ae6036…совпал). - Golden-контракт:
demo/golden/matrix.mjs/harness.mjs/run.mjsзаменяютoffset: 'edge'на'center'и убирают саму опцию'edge'из допустимых значений;test/golden-matrix.test.mjsпроверяет обновлённый контракт четырёх сцен и счётчик impact-сет = 67 (совпадает с числом, заявленным автором) — прогнано вместе сnpm test, green. - Мутанты (реестр): три устаревших якоря (
opening-symbol-default-uses-room-face,opening-symbol-partition-follows-endpoints,opening-gate-flip-translates-leaves) корректно удалены изscripts/mutation-gate.mjsи нигде больше не упоминаются (grepпо всему дереву) — контракт §14 ТЗ выполнен, кроме CR-1. - Трейлеры и changelog-требование
AGENTS.md/PROCESS.md — оба коммита корректны.
Чего не проверял
- Полный
npm run golden:verify(77 different, 2 error, 3 missing — цифры автора не перепроверены построчно): предрелизный гейт по ТЗ §13.3, локальный accept всё равно запрещён; не влияет на вердикт code review. - Полный
smoke_*набор (167 смоков) — только три выбранных вручную плюс выводsmoke-select(НЕОПРЕДЕЛЁННОСТЬ) учтены; более широкий прогон не оправдан 6-символьной дельтой в 3 файлах. python -m pytest tests_backend— backend/schema не в диффе (AC7 разобран чтением).- Расхождение «1113/1 skip» (автор) vs «1114/0 skip» (у меня) в
npm test— вероятно, разница окружения (Chromium/иное), не влияет на код; не расследовалось глубже, так как оба результата green по существу задачи.
Вердикт
Единственная находка — CR-1, Medium, в скоупе задачи. High нет. Продуктовое
поведение (offset, направление, jamb, parity, документация, changelog)
подтверждено и исполнением, и чтением, полностью соответствует ТЗ и решению
владельца. Возврат автору для точечного исправления --test-name-pattern в
scripts/mutation-gate.mjs и повторного прогона мутанта.