mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 05:41:34 +00:00
237 lines
19 KiB
Markdown
237 lines
19 KiB
Markdown
# 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)
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `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
|
||
```
|