diff --git a/docs/reviews/SPEC-REVIEW-520-r3.md b/docs/reviews/SPEC-REVIEW-520-r3.md new file mode 100644 index 00000000..d39e9ac3 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-520-r3.md @@ -0,0 +1,228 @@ +# SPEC-REVIEW-520-r3 + +## Якоря + +- Issue: [#520](https://github.com/Matysh/houseplan-card/issues/520) + «Первый устойчивый кадр большого дома вырос до 3.36 с при потолке 3.0 с — + регрессия после #509/#500», метка `S4-spec-review`, состояние `OPEN` + (подтверждено `gh issue view 520 --json labels,state`, 2026-09-10). +- ТЗ: тело issue #520, раздел `## ТЗ`, продолжает жить в теле issue (#517: + архив `docs/specs/` закрыт для новых ТЗ) — то же место, что в r1/r2. +- sha256 текущего тела issue (`gh issue view 520 --repo Matysh/houseplan-card + --json body -q .body | sha256sum`): + `5eca6e230c290483bba634505217d17072f4b4a121c4aa2111b08a0fc6d007c3`. + Отличается от хеша r2 (`7f68e4cd99242f44e13e9929cd0a7aa9dc3cb6ae5f871878670 + bade18a2436ac`) — тело менялось, как и ожидается после возврата на правки. +- Заход r3. Бюджет циклов §4 до этого ревью: **2/4** (израсходован r1 и r2, + оба жёлтые). Вердикт ниже — зелёный, цикла не образует, бюджет остаётся + **2/4**. +- Материал кода не менялся между r2 и r3: `git log --oneline ad2f9afd.. + b49721a0` даёт ровно один коммит — публикацию документа r2 + (`docs/reviews/SPEC-REVIEW-520-r2.md`, 203 добавленных строки, `git diff + --stat ad2f9afd..b49721a0 -- src/ demo/ scripts/ test/` пуст). Рабочая + копия сейчас на `b49721a0` (detached HEAD от + `origin/issue/520-first-frame`, подтверждено `git status`, `git rev-parse + HEAD`). Изменилось только тело issue на GitHub — SHA репозитория тут не + причём, доказательство ТЗ находится вне git. + +## Скоуп ревью + +Повторный заход ревью ТЗ (PROCESS.md §2.4, §2.9/§2.10) после жёлтого +вердикта r2. Предмет — **дельта тела issue между r2 и r3**: одна правка, +заявленная автором в комментарии `2026-09-10T15:33:29Z` +(«Исправление по ревью ТЗ r2 → заход r3»): AC4 расширен на +`firstStableRenderMs` изометрии, плюс отрицательный результат про +`cache.entries.cleanFloor` изометрии перенесён из документа r2 в текст +«Причины». Автор сам называет объём дельты: «одна строка AC4 и одна фраза +в «Причине»» — проверено построчно, а не принято на слово (см. «Как +проверялось»). + +Дельта локальна: правка текста ТЗ, ни ребейза, ни смены контракта, ни +новой подсистемы — код продукта между r2 и r3 не менялся вовсе (см. +«Якоря»). Полный разбор всей задачи заново не требуется. + +## Как проверялось + +1. Получено текущее тело issue #520 (`gh issue view 520 --json body`) и + комментарий автора после r2 (единственный новый комментарий, метка + `S4-spec-review` возвращена). +2. Прочитан `docs/reviews/SPEC-REVIEW-520-r2.md` целиком — вердикт, одна + находка Medium-1, материал (SHA `ad2f9afd2bfe`, дерево `cc8e3bd73eeb`, + тело `c5e79b63b531…`) — и `docs/reviews/SPEC-REVIEW-520-r1.md` для + раздела «Унаследовано». +3. **Находка Medium-1 r2 сверена построчно с текущим текстом**, а не + принята на слово автора. r2 требовал: строка AC4 должна покрывать + `firstStableRenderMs` изометрии тем же относительным пределом, что и + `modelReadyMs`, — либо явно исключить её одной фразой. Текущая строка + AC4 (тело issue, строка 59): + > «`timing.firstStableRenderMs.median` профиля взаимодействия ≤ 3000 + > (абсолютный потолок) **и обе метрики старта — `modelReadyMs` и + > `firstStableRenderMs` — внутри относительного лимита к базе + > `a44fbd37` на обоих профилях, взаимодействия и изометрии**», + «чем краснеет»: «превышение любого из **четырёх** относительных + пределов или абсолютного потолка — job красный». + Это буквально включает `firstStableRenderMs` изометрии — предмет + находки Medium-1. Закрытие полное, не частичное. +4. **Пересчитаны все четыре относительных предела**, которые теперь + называет AC4, по формуле `demo/performance/evaluate.mjs:73-118` + (`relativeLimit = max(base*(1+ratio), base+allowance)`, + `effective = min(hardMaxMs, relativeLimit)`), чтобы убедиться, что + счёт «четырёх» верен и не сбит на единицу: + - `modelReadyMs` interaction: база 1382, `budgets-large-house- + interaction.json` (`maxRegressionRatio: 0.3, noiseAllowanceMs: 200, + hardMaxMs: 2500`) → `max(1796.6, 1582) = 1796.6`; кандидат + **1930 > 1796.6** — нарушен. + - `firstStableRenderMs` interaction: база 2792.7, тот же файл + (`ratio 0.3, allowance 250, hardMax 3000`) → `max(3630.5, 3042.7) = + 3630.5`, `effective = min(3000, 3630.5) = 3000` — здесь относительный + предел поглощён абсолютным потолком, что и заявляет отдельная первая + половина AC4; двойного счёта нет. + - `modelReadyMs` isometric: база 2165.6, `budgets-large-house- + isometric.json` (`ratio 0.2, allowance 200, hardMax 3000`) → + `max(2598.72, 2365.6) = 2598.72`; кандидат **2626.3 > 2598.72** — + нарушен (установлено ещё в r1). + - `firstStableRenderMs` isometric: база 2227, тот же файл + (`ratio 0.2, allowance 250, hardMax 3500`) → `max(2672.4, 2477) = + 2672.4`; кандидат **2678.2 > 2672.4** — нарушен (предмет находки + Medium-1 r2, теперь покрыт текстом AC4). + Итого ровно четыре относительных предела, все они действительно сейчас + нарушены кандидатом — формулировка AC4 точна, не расширяет и не сужает + набор относительно того, что показывает «Симптом». +5. Проверено согласование «Контракта» (строка 46: «изометрия … чинится + тем же изменением и проверяется теми же метриками старта (AC4)») с + новым текстом AC4 — формулировка «тем же способом» из r2 заменена на + «теми же метриками старта», что точнее отражает: AC4 теперь буквально + покрывает обе метрики обоих профилей, а не «тот же способ» в общем + виде. Расхождений между «Контрактом» и таблицей AC больше нет. +6. Проверена вторая часть дельты — фраза в «Причине» (строка 24): «В + бюджетах изометрии этот потолок не поднимался и уже равен 100 — там + правка не нужна». Перепроверено чтением файлов (не принято на слово): + `grep -n cleanFloor demo/performance/budgets-{isometric-smoke,large- + house-isometric,interaction-smoke,large-house-interaction}.json` — + оба файла изометрии дают `100`, оба файла взаимодействия — `180`. + Совпадает с отрицательным результатом, который сам r2 получил + самостоятельно (не то же самое, что просто скопировать вывод r2, — + числа сверены заново на этом SHA, хоть код и не менялся). +7. `git log --oneline ad2f9afd..b49721a0` и `git diff --stat` — единственный + новый коммит это публикация документа r2; продуктовый код (`src/**`, + `demo/**`, `scripts/**`, `test/**`) не менялся между r2 и r3 — гейты + кода этому раунду не относятся (см. «Чего не проверял»), как и в r1/r2. +8. Проверены state/labels issue (`OPEN`, `bug`, `P1`, `S4-spec-review`) — + соответствуют ожиданиям заголовка задания (заход r3, 2/4 циклов). + +## Находки + +Нет находок, переживших проверку. Единственная находка r2 (Medium-1) +закрыта полностью — см. «Закрытие раунда r2» и п.3–5 «Как проверялось». +Новых находок делом дельты не выявлено: правка локальна (одна строка AC4, +одна фраза «Причины»), обе проверены построчно и не вводят новых +противоречий с остальным текстом ТЗ. + +## Закрытие раунда r2 + +| Находка r2 | Чем закрыта | Где это видно | +|---|---|---| +| Medium-1 — амендированный AC4 не покрывал `firstStableRenderMs` изометрии (второе число того же предложения «Симптома», что и уже покрытый `modelReadyMs`) | AC4 переписан: «обе метрики старта — `modelReadyMs` и `firstStableRenderMs` — внутри относительного лимита… на обоих профилях»; «чем краснеет» — «любого из **четырёх** относительных пределов»; «Контракт» синхронизирован формулировкой «теми же метриками старта (AC4)» | Тело issue, AC4 (строка 59) и «Контракт» (строка 46). Численно перепроверено в п.4 «Как проверялось» — все четыре предела названы верно, счёт не сбит | + +## Унаследовано из r2 (и, транзитивно, из r1) + +Приняты без повторной проверки — дельта r2→r3 их не задевает, код между +раундами не менялся: + +- **Причина регрессии измерена, не догадка**: три технических утверждения + о поведении `wrapped`-свойств Lit — r1 сверил дословно с исходником + `@lit/reactive-element` (`docs/reviews/SPEC-REVIEW-520-r1.md`, «Как + проверялось» п.4, материал SHA `27ea23c1ec25`, дерево `f80bcdd58b72`). +- **M1 (раздел «Риски») и M2 (AC3) из r1** — закрыты в r2 и сверены там + построчно (`docs/reviews/SPEC-REVIEW-520-r2.md`, «Как проверялось» п.3, + «Закрытие раунда r1»), текст этих разделов между r2 и r3 не менялся. +- **AC1** (source-scan тест по образцу `config-adoption-ownership. + test.mjs`) и **AC2** (юнит на `onBodyReplaced`) — доказуемы дёшево и + детерминированно, текст этих строк не менялся с r1 (сверено построчно + с текущим телом в этом раунде — строки идентичны цитатам r1/r2). +- **AC5** (`cache.entries.cleanFloor` ≤ 100 на двух interaction-файлах + бюджетов) — привязан к точным файлам, не менялся; числа перепроверены + заново в п.6 «Как проверялось» этого раунда (не просто унаследованы). +- **Откат** (revert двух строк `static properties` + потолка 180) и + **release-артефакты** (`User-Visible: no`, разблокирует + `v1.74.0-beta.1`) — просты, однозначны, текст не менялся с r1. +- **Продуктовая рамка** (администратор дома, поверхность — первый показ + большого дома) совпадает с `docs/SCOPE.md` J1 и фикстурой 60 комнат/200 + устройств — r1 сверил, текст не менялся. +- **Выбор полного трека** — верен, не оспаривается ни разу за три раунда. +- **Ни одного вопроса владельцу не требуется** — весь материал технический + (см. обоснование r1: «продуктовых развилок в задаче нет»), подтверждено + в r1 и не оспорено в r2; делта r3 тоже чисто техническая (числа, счёт + относительных пределов) — вывод не меняется. + +Документы-источники: `docs/reviews/SPEC-REVIEW-520-r1.md` (материал — +ветка `issue/520-first-frame` на `27ea23c1ec25`, дерево `f80bcdd58b72`), +`docs/reviews/SPEC-REVIEW-520-r2.md` (материал — та же ветка на +`ad2f9afd2bfe`, дерево `cc8e3bd73eeb`). + +## Что проверено и корректно (этот раунд) + +- Единственная правка фикс-комментария автора (`2026-09-10T15:33:29Z`) + подтверждена чтением текущего тела issue, а не принята по заявлению — + Medium-1 r2 закрыт полностью, не частично. +- Счёт «четырёх относительных пределов» в AC4 арифметически точен: + пересчитаны все четыре по опубликованной формуле и файлам бюджетов, + все четыре сейчас действительно нарушены кандидатом — AC4 не занижает + и не завышает то, что реально показывает «Симптом». +- «Контракт» (строка 46) и таблица AC4 больше не расходятся в формулировке + охвата изометрии — предыдущая асимметрия («тем же способом» против + буквально одной метрики) устранена. +- Фраза «Причины» про потолок `cache.entries.cleanFloor` изометрии + (100, правка не нужна) подтверждена чтением всех четырёх файлов + бюджетов заново на этом SHA, не просто перенесена из документа r2 на + слово. +- Код между r2 и r3 не менялся (`git log`, `git diff --stat` на + диапазоне `ad2f9afd..b49721a0`) — дешёвые гейты кода (`tsc`, `test`, + `build`) этому раунду не относятся по той же причине, что и в r1/r2: + предмет этапа — текст ТЗ, продуктовой правки в дереве ещё нет. +- Остальной текст ТЗ (унаследованное) не тронут дельтой — проверка + инвариантов и код-ревью инфраструктуры (мутант, тесты) остаются + предметом код-ревью после `S5-ready`. + +## Чего не проверял + +- **Гейты кода** (`tsc --noEmit`, `npm test`, `npm run build`, + `check-docs.mjs`) — не гонял: `src/**` не менялся ни разу за три + раунда, весь диапазон коммитов ветки — диагностика бенчмарка + (`test(perf):`). Предмет этапа spec — текст ТЗ. +- **`npm run invariants`** — не гонял: геометрия и её ссылки не тронуты + ни кодом, ни правкой ТЗ. +- **Реальный прогон `Full Performance`** — не запускал; числа «Симптома» + и пересчитанные относительные пределы взяты из текста issue и + файлов бюджетов в дереве, перепроверены арифметически по опубликованной + формуле, не переизмерены заново на живом рантайме. +- **Регистрацию мутанта `adoption-bodies-declared-reactive`** в + `scripts/mutation-gate.mjs` — предмет код-ревью, ещё не существует + (продуктовой правки AC1/AC2 в дереве ещё нет). +- **Полную сверку разделов §7.1, не тронутых дельтой** (Риски, откат, + release-артефакты, продуктовая рамка) — заново не перечитывал построчно, + только сверил, что их текст байт-в-байт совпадает с тем, что видели r1 + и r2 (см. «Унаследовано»). + +## Вердикт + +Зелёный. High: 0, Medium: 0. ТЗ готово к разработке (`S5-ready`). +Единственная находка r2 закрыта полностью и точно; новых находок дельта +не вносит. Бюджет циклов §4 не расходуется (зелёный вердикт цикла не +образует) и остаётся 2/4. + +--- + +--- + + + +## Материал раунда + +- Ветка: `issue/520-first-frame`, коммит `b49721a0bd98` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `50d8affd923b0a5e133ee1e453bb9388a5f44c78` + ``` + git log --all --format='%H %T' | grep 50d8affd923b + ``` +- Тело issue: `60492a3aec5ff7fed54f7b32c040b7a118526c209609d736ee68e116eb8f0aa6` +- Вердикт конвейера: `green` · High 0