Files
houseplan-card/docs/reviews/SPEC-REVIEW-473-r1.md
2026-09-06 15:00:29 +03:00

19 KiB
Raw Permalink Blame History

SPEC-REVIEW-473-r1

  • Issue: https://github.com/Matysh/houseplan-card/issues/473
  • Этап: spec (ревью ТЗ, PROCESS.md §2.4)
  • Заход: r1 · блокирующих циклов израсходовано 0 из 4
  • ТЗ: docs/specs/473-iso-perf-witnesses-and-smoke.md, SHA 2cedcc6221e40ea6ead2f48ff7fb37ddc96a51ca
  • Вердикт: жёлтый

Скоуп ревью

Материал раунда — весь диапазон HEAD..HEAD (первый заход, дельты по предыдущему раунду нет): два файла, docs/specs/473-iso-perf-witnesses-and-smoke.md (новый, 125 строк) и одна строка в docs/specs/README.md. Продуктового и тестового кода нет — задача ещё в статусе ТЗ.

Задача — процессная/инфраструктурная (постфактум-ревью perf-дельты #160 плюс диффозависимый перф-смок в Validate), класс файлов будущей реализации — только B (test/**, scripts/mutation-gate.mjs, .github/workflows/validate.yml, demo/performance/*.json, PROCESS.md). docs/SCOPE.md прямо не применяется: задача не меняет ни один Core user job, а укрепляет процесс, которым продукт разрабатывается — это соответствует прецеденту #404/#399/#422 (те же инфраструктурные ТЗ полного трека).

Как проверялось

Гейты (typecheck/test/build) на этом раунде неприменимы: дифф — два документных файла, продуктового и тестового кода ещё нет. Прогонять их незачем, и ссылка в задании на зелёный Validate (2cedcc62) сюда не адресуется — там просто нечего было ломать.

Разбор шёл построчной сверкой каждого технического утверждения ТЗ с текущим деревом (2cedcc62, тот же SHA что HEAD):

  • построчные ссылки §4 (iso-scene-render.ts:783,786,805,821, iso-overlays.ts:342-345) — прочитаны и сверены с исходником;
  • существование и сигнатуры скриптов, упомянутых в §5 (demo/benchmark_large_house.mjs, demo/benchmark_glow.mjs, demo/performance/compare.mjs, evaluate.mjs) — прочитаны целиком в интересующих местах (valueArg, --variants, --absolute-only, minimumSamples);
  • существующие бюджеты (budgets-glow-smoke.json, budgets-large-house-isometric.json и др.) — сверены на предмет прецедента «smoke-бюджет как урезанное подмножество полного» и совпадения hardMaxMs с цифрами регрессии из Проблемы;
  • .github/workflows/validate.yml — job changes, reuse, performance_smoke прочитаны целиком, чтобы понять, как диффозависимый триггер (§5) сочетается с механизмом переиспользования по содержимому (scripts/gate-reuse.mjs) и есть ли ручной способ форсировать путь (workflow_dispatch — проверено по всем workflow, в validate.yml его нет);
  • test/validate-workflow.test.mjs, test/performance-budget.test.mjs — существуют, стиль контрактных тестов (regex по тексту YAML) подтверждает реализуемость AC3/AC4/AC5 как статических проверок;
  • de215578 — коммит существует в истории, воспроизведение AC6 технически доступно;
  • docs/specs/README.md — формат строки таблицы сверен с соседними записями.

Находки

Все три — Medium, в скоупе задачи: без High это жёлтый вердикт, правки вносятся в тот же файл ТЗ и проходят повторный цикл (PROCESS.md §2.4, #202). Отдельный issue не заводится.

Medium-1 — AC7 недоказуем предписанным способом

Файл: docs/specs/473-iso-perf-witnesses-and-smoke.md:103

AC7 | На текущем dev оба новых профиля укладываются в потолки за 3 образца | прогон в CI этой ветки

§5 (той же строкой 68-71) делает триггер профилей диффозависимым от ПРОДУКТОВЫХ путей: perf_iso — если дифф задел src/iso-*, perf_interaction — если дифф задел src/live-*, src/render-*, src/houseplan-render-lifecycle.ts, src/houseplan-card.ts. Это выходы job changes, которая судит дифф ветки против последнего доказанно зелёного предка (validate.yml:270-284), а не содержимое дерева.

Сама реализация этой задачи, согласно §6, «продуктовый код не меняет» — дифф ветки состоит из test/**, scripts/mutation-gate.mjs, .github/workflows/validate.yml, demo/performance/*.json. Ни один из этих путей не матчит шаблоны src/iso-* / src/live-* / src/render-* / houseplan-card.ts. Значит на пуше этой самой ветки perf_iso и perf_interaction резолвятся в false, новые шаги смока не выполняются вовсе, и «прогон в CI этой ветки» не производит того свидетельства, которое AC7 требует.

Проверено, что обхода через ручной триггер нет: validate.yml не имеет workflow_dispatch (в отличие от performance.yml, mutation-gate.yml, docs-screenshots.yml, у которых он есть) — форсировать классификацию нечем.

Способ существует (ровно как для AC6 — разовый замер npm run benchmark:large-house-isometric -- --samples=3 --warmups=1 + benchmark:compare --absolute-only --budgets=..., команда и результат записываются в issue), но ТЗ называет другой, автоматический и, как показано выше, недостижимый на этой ветке. Либо AC7 меняет способ доказательства на ручной (аналогично AC6), либо явно описывает временный форс-коммит в продуктовый файл для проверки с последующим откатом перед мержем — сейчас в тексте нет ни того, ни другого.

Medium-2 — --variants=60 не поддерживается скриптом, на который распространён контракт

Файл: docs/specs/473-iso-perf-witnesses-and-smoke.md:74-79

Профили идут с --variants=60 --samples=3 --warmups=1 --absolute-only и своими smoke-бюджетами…

Флаг --variants относится к сценариям, где он читается: demo/benchmark_glow.mjs:27 (requestedVariants = valueArg('variants')?.split(',')...) — там это число одновременных обновлений состояния ([1, 10, 30, 60]). Оба новых профиля, large-house-isometric-v1 и large-house-interaction-v1, идут через demo/benchmark_large_house.mjs (package.json:22,25), который вообще не читает --variants — там только samples, warmups, target-root, profile, output (demo/benchmark_large_house.mjs:17-22). Формулировка §5 одной строкой распространяет флаг glow-скрипта на все три профиля — фактическая ошибка в записанном как готовый контракт наборе CLI-аргументов.

Эффект сегодня безвреден (неизвестный --variants= молча игнорируется valueArg), но это ровно тот класс утверждения, который ТЗ обязано не допускать: техническая деталь подана как проверенный факт, не будучи сверенной с кодом инструмента. Правка — убрать --variants=60 из вызовов benchmark:large-house-isometric/-interaction, оставить только у benchmark:glow.

Medium-3 — четыре обязательных раздела §7.1 отсутствуют без пометки «не применимо»

Файл: docs/specs/473-iso-perf-witnesses-and-smoke.md (весь документ, разделы 1-10)

PROCESS.md §7.1 перечисляет обязательные разделы ТЗ: сценарий · что человек увидит до и после · проблема · скоуп и не-скоуп · контракт поведения · UX · модель данных и миграция · i18n · критерии приёмки · план автотестов · риски · откат · release-артефакты. В документе есть Проблема (§1), Скоуп/Не-скоуп (§2-3), контракт (§4-5), откат (внутри §6), критерии приёмки (§7), release-артефакты (§8) — но полностью отсутствуют как разделы: Сценарий, Что человек увидит до и после, UX/модель данных/i18n, Риски. Не встречается даже фраза «не применимо» — разделы просто не написаны.

Прямой прецедент — docs/specs/404-smoke-exception-guard.md (та же инфраструктурная категория, тот же полный трек, тот же автор): там есть ## Сценарий (кто и когда столкнётся — разработчик и CI, не пользователь), ## Что человек увидит до и после (явно «видимого поведения продукта задача не меняет»), ## UX, модель данных, i18n (одной строкой «не применимо»), и отдельный ## Риски с тремя названными рисками и смягчениями. Здесь этот минимум не выполнен.

Содержательно часть материала для «Рисков» в тексте есть, но рассыпана по другим разделам без общего заголовка: рост времени job (§10, как предположение с планом отката, а не как риск), достижимость мутации AABB (§10). Не названы вовсе: шумность абсолютного порога на 3 образцах на разделяемом раннере (ложный красный на чужой, легитимной правке — именно то, против чего заведена вся задача #473, доведённая до абсурда: диффозависимый смок может начать шуметь на легитимных изменениях и стать тем самым «гейтом не там, где нужно», о котором вторая половина Проблемы), и хрупкость четырёх новых мутантов к будущим рефакторингам подписи кэша. Раздел «Риски» обязателен не как формальность, а чтобы этот перечень стал видимым до, а не после инцидента.

Что проверено и корректно

  • Числа и ссылка на прогон в §1 совпадают дословно с телом issue #473.
  • Все построчные ссылки §4 на iso-scene-render.ts (783, 786, 805, 821) и iso-overlays.ts (342-345) точны на текущем дереве.
  • Мутация AABB (boundsNear без gap и без равенства) описывает реальный код: boundsNear использует нестрогие <=/>= с допуском gap — мутация в строгие операторы без допуска валидна и достижима.
  • Предметы кэширования (WeakMap по input.wallSilhouettes, подпись shapeSignature/signature, гард !previous.nearWallBefore || …) — описаны верно и совпадают с кодом дословно, вплоть до имён полей.
  • budgets-glow-smoke.json / budgets-space-glow-smoke.json — уже существующий прецедент «smoke-бюджет как урезанное подмножество полного», подтверждает реализуемость §5 без нового механизма.
  • hardMaxMs: 3500 в budgets-large-house-isometric.json (firstStableRenderMs) против измеренных 9870 мс в Проблеме — AC6 действительно ловится с одним образцом, как заявлено.
  • de215578 существует в истории — AC6 воспроизводимо.
  • test/validate-workflow.test.mjs, test/performance-budget.test.mjs существуют и уже проверяют YAML/JSON текстовыми утверждениями — AC3-AC5 реализуемы заявленным способом (статический контракт, не запуск CI).
  • scripts/gate-reuse.mjs: ключ переиспользования — хеш содержимого (sourceFingerprint + оснастка job), не зависит от диапазона диффа; добавление признака набора профилей в ключ (AC5) технически сводится к добавлению строки в key: кэша в validate.yml, без изменения самого скрипта — реализуемо, хотя способ (какая именно строка) оставлен автору как техническая деталь (правомерно, не продуктовый вопрос).
  • Пути src/live-*, src/render-*, houseplan-render-lifecycle.ts, houseplan-card.ts, src/iso-* — все существуют, шаблоны has() из Проблемы issue корректны.
  • Обоснование полного трека (не small): две поверхности (тесты продуктового кода + workflow), перф — часть контракта задачи — корректно дисквалифицирует лёгкий трек по критерию §5 PROCESS.md.
  • docs/specs/README.md: строка добавлена в верном месте таблицы, формат совпадает с соседними записями.
  • Не-скоуп (§3) дословно соответствует «Не входит» issue: пороги полных профилей и §11.4 не переоткрываются.

Чего не проверял

  • Не прогонялись typecheck/test/build/bundle:sync — дифф раунда не содержит ни продуктового, ни тестового кода, гонять нечего; это станет предметом код-ревью.
  • Не проверялась фактическая укладываемость трёх новых профилей в 20-минутный лимит performance_smoke (§10, «Принятые предположения») — это требует реального прогона CI, которого на этапе ТЗ ещё нет; ТЗ само помечает это как предположение с планом отхода («лимит поднимается»), что корректно по формату §7.1.
  • Не проверялась фактическая шумность абсолютных порогов на 3 образцах на разделяемых GitHub-раннерах (см. Medium-3) — это открытый риск, а не проверяемый факт на этой стадии.
  • Не читались все 122+ существующих мутанта в scripts/mutation-gate.mjs построчно — только формат записи и подтверждение, что регистрация новых идёт тем же путём.
  • Не проверялась точная фикстура «три комнаты, одна стена, плита у стены и плита вдали» на предмет буквального совпадения с какой-то одной существующей — это техническая деталь реализации, не продуктовый вопрос, и ревьюер вправе спросить о ней при коде, если фикстура не даст нужных условий.

Материал раунда

  • SHA ветки: 2cedcc6221e40ea6ead2f48ff7fb37ddc96a51ca (= HEAD на момент ревью)
  • Дерево ТЗ: docs/specs/473-iso-perf-witnesses-and-smoke.md на этом SHA
  • git show 2cedcc62 --stat: docs/specs/473-iso-perf-witnesses-and-smoke.md (new, 125 lines), docs/specs/README.md (+1 line)

Материал раунда

  • Ветка: issue/473-iso-perf-witnesses, коммит 2cedcc6221e4 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 41b642d61cff58dea0718d19efa6209b897a5a33
    git log --all --format='%H %T' | grep 41b642d61cff
    
  • ТЗ docs/specs/473-iso-perf-witnesses-and-smoke.md, блоб 03c7d24cb503bea7ac083ed9a1ffc12a77b3c0e0
    git log --all --find-object=03c7d24cb503bea7ac083ed9a1ffc12a77b3c0e0 -- docs/specs/473-iso-perf-witnesses-and-smoke.md