Files
houseplan-card/docs/reviews/SPEC-REVIEW-39-r3.md
T
2026-08-29 06:28:19 +00:00

174 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-39-r3
- Issue: https://github.com/Matysh/houseplan-card/issues/39 — «[HP-UX-09] большие подложки»
- Этап: ТЗ на ревью (PROCESS.md §2.4)
- Заход: r3 · блокирующих циклов израсходовано 2/4 (зелёные вердикты цикл не образуют, #227)
- ТЗ: `docs/specs/039-large-backdrops.md`, ревизия 4, SHA `9431a5ce`
- Предыдущий вердикт: жёлтый, r2, SHA `f787de99` (`docs/reviews/SPEC-REVIEW-39-r2.md`) — 0 High, 2 Medium (r2-M1, r2-M2)
- Трек: полный (владелец, 2026-08-15: «P3, polish/tech-debt, обычный трек») — без изменений
## Скоуп проверки (дельта r2→r3)
Дельта локальна и полностью укладывается в границы одной правки одного файла:
`git diff f787de99..9431a5ce -- docs/specs/039-large-backdrops.md` — 22
добавленных / 7 удалённых строк, ничего больше (`git diff --stat f787de99..9431a5ce`
показывает только этот файл плюс публикацию `docs/reviews/SPEC-REVIEW-39-r2.md`,
которую я не оцениваю как предмет ревью — это артефакт прошлого раунда).
Единственный коммит в диапазоне — `9431a5ce docs: #39 spec revision 4 per
SPEC-REVIEW-39-r2` (`Issue: #39`, `User-Visible: no`). Ребейза на ушедший вперёд
`dev` не было (`f787de99` — прямой родитель по линии этого файла). Контракт
поведения не менялся: ревизия 4 отвечает точечно на r2-M1 и r2-M2, новых AC не
добавляла, новой подсистемы не задела. Объём дельты не сопоставим с исходной
задачей — веду по дельте согласно PROCESS.md §2.9/§2.10, полный разбор с нуля
не требуется.
## Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
| **r2-M1** — AC4б без названного способа доказательства: не назван триггер decode-fail/таймаута в смоке, не проверяется ключ тоста и сброс инпута, ни один мутант не ловит регресс «автофолбэк на оригинал» | Раздел «Тесты и мутанты» получил абзац «Hard фаза 2 в смоке»: `window.createImageBitmap` подменяется на (а) reject — путь decode-fail, (б) вечно висящий Promise — путь таймаута; названы 4 ассерта (тост содержит `_t('backdrop.downscale_failed')`, `<input type=file>.value` сброшен, `_spaceDialog.planFile === null`, повторный выбор открывает диалог заново) плюс счётчик обращений к upload/planFile со старым оригиналом; в реестр мутантов добавлен пункт (5) — подмена catch фазы 2 продолжением старого пути ловится смоком по этому счётчику | `docs/specs/039-large-backdrops.md:176–186` |
| **r2-M2** — «Release-артефакты» не называет security даже отрицательно, хотя вводится ручной парсер заголовков от пользователя | Добавлен пункт «security (явное решение)»: парсер читает только фиксированные оффсеты/длины уже полученного `ArrayBuffer`, ни одно поле файла не становится размером аллокации, `w×h` — арифметика с защитой от переполнения через `Number`-границы и отказом `unknown` на неправдоподобных значениях, любые исключения → `unknown`; юнит-таблица получает fuzz-набор враждебных заголовков (нулевые/гигантские длины чанков, обрезанный VP8X, SOF без длины); SVG-путь парсером не тронут | `docs/specs/039-large-backdrops.md:214–221` |
Обе находки r2 закрыты полностью, а не частично: r2-M1 требовал по своей
формулировке «правки» назвать способ форсировать decode-fail/таймаут — «мок
`window.createImageBitmap`… на усмотрение автора, но названный» — ревизия 4
называет ровно этот способ и для decode-fail, и для таймаута, плюс закрывает
все перечисленные в r2 пробелы (тост, сброс инпута, мутант на автофолбэк).
r2-M2 требовал одну строку с явным решением по security — ревизия 4 даёт
больше запрошенного минимума (перечисляет конкретные защитные инварианты
парсера и связывает их с юнит-фикстурами), но это не выход за скоуп: те же
фикстуры уже были обещаны разделом «Тесты и мутанты» ревизии 3, здесь их
только называют явно под заголовком security.
## Унаследовано из r2 (и транзитивно из r1)
Без повторной проверки — дельта их не касается:
- Технические анкеры кода (`_pickPlanFile`, ручной base64-цикл, aspect через
`<img>`, `MAX_FILE_BYTES`, квота, транзакция staging-до-save) — сверены
построчно в r1 (SHA `156be645`), текст этих мест дельта r2→r3 не меняла.
- Бенчмарк-матрица (4–165 МП, headless Chromium) и константы
`src/backdrop-probe.ts` (`WARN_DECODED_BYTES`, `HARD_DIMENSION`,
`DOWNSCALE_TARGET_PX`) — не менялись со ревизии 2/3.
- AC1–AC3, AC5–AC9, AC4/AC4б по своей формулировке (не по способу
доказательства) — не менялись в этой дельте, признаны проверяемыми в r1/r2.
- Раздел «Сценарий» и «До/После» (закрытие M1 из r1) и полный список i18n-ключей
en/ru (закрытие M2 из r1) — не менялись, проверены в r2 (`SPEC-REVIEW-39-r2.md`,
SHA `f787de99`).
- Тост как технически достижимый механизм из `_pickPlanFile` →
`this.host._showToast` — сверено в r2 по коду `houseplan-editor-runtime.ts:8252`
и `houseplan-card.ts:2495`; дельта r3 не меняла ни вызывающий код, ни
описание тоста, только добавила новый ключ в уже существующий канал.
- `docs/SCOPE.md` J4 и standing rules (never-delete-a-file, lock invariant) —
нерелевантны/не задеты, сверено в r1 и r2, дельта r3 текст сценария и скоупа
не трогала.
- Терминология («подложка», диалоги `hp-dialog`) соответствует
`docs/USER-GUIDE.ru.md` — сверено в r1, новых терминов в дельте r3 нет.
- Low-находки r2 («чего не проверял»): возможное несоответствие
`backdrop.unknown_title`/`large_title` и наличие ли у `hp-dialog` готового
сценария с тремя кнопками действия — оставлены открытыми решением
ревьюера r2 (не блокируют DoR), дельта r3 их не касалась, повторно не
поднимаю.
## Как проверялось в этом раунде
1. Нашёл вердикт r2 и его SHA в опубликованном документе
`docs/reviews/SPEC-REVIEW-39-r2.md:6` (`f787de99`, назван явно) и в
комментарии issue от 2026-08-29T06:22:04Z — совпадают.
2. `git diff f787de99..9431a5ce -- docs/specs/039-large-backdrops.md`
построчно (полный вывод см. выше в «Скоуп проверки»).
3. `git diff --stat f787de99..9431a5ce` — подтвердил, что дельта не тронула
ничего вне `docs/specs/039-large-backdrops.md` (второй файл в диапазоне —
публикация прошлого документа ревью, не предмет этого раунда).
4. Прочитал итоговый файл целиком (232 строки), не только diff-хунки — сверил
новый абзац «Hard фаза 2 в смоке» и мутант (5) против UX-контракта фазы 2
(`:99–104`) и AC4б (`:157–159`): триггер, тост, сброс инпута и «нет
автофолбэка» совпадают дословно с тем, что описано в UX и AC.
5. Сверил новый security-абзац (`:214–221`) с разделом «Диагностика: заголовки,
не decode» (`:75–85`) и «Риски» (`:194–197`, «Зоопарк заголовков») —
противоречий нет, детали (фиксированные оффсеты, отказ в `unknown`)
согласуются, а не дублируют другим числом.
6. `gh issue view 39 --json comments` — прочитал все 6 комментариев целиком,
включая комментарий владельца о ревизии 4 (совпадает с diff, новых устных
решений или продуктовых вопросов не содержит).
7. `docs/specs/README.md` (список обязательных release-артефактов) — сверил,
что после правки перечень «changelog / docs / i18n / golden / performance /
security» закрыт по всем пунктам буквально, а не молчанием.
8. Проверил метки issue (`gh issue view 39 --json labels`): `S4-spec-review`,
`P3`, `polish`, `tech-debt` — трек по-прежнему полный, файл ТЗ обязателен
(не `small`), что и наблюдается.
## Гейты
Diff — только `docs/specs/039-large-backdrops.md` (плюс публикация прошлого
review-документа), 0 файлов в `src/**`/`custom_components/**`/`test/**`/
`demo/**`. Как в r1 и r2:
| Гейт | Применимо? | Причина |
|---|---|---|
| `npx tsc --noEmit` / `npm test` / `npm run build` | нет | диапазон не содержит кода |
| `node scripts/check-docs.mjs` | нет | `src/**` не тронут |
| `npm run invariants` | нет | геометрия/`layout`/толщина стен не затронуты |
| browser-смоки, `golden:verify`, `pytest tests_backend` | нет | реализации по-прежнему нет; смоки/юниты из ТЗ (`test/backdrop-probe.test.mjs`, `demo/smoke_backdrop_guard.mjs`) не существуют |
Намеренно ничего не прогонял: предмет этапа `spec` — текст ТЗ, а не код, и
прогон гейтов над docs-only диапазоном не проверил бы ничего по существу.
## Находки
Нет. High: 0. Medium: 0. Обе находки r2 закрыты полностью (см. таблицу выше),
новых Medium/High дельта не вносит.
## Что проверено и корректно
- r2-M1 закрыта: способ форсирования decode-fail (`reject`) и таймаута
(вечно висящий `Promise`) назван явно через мок `window.createImageBitmap`;
все 4 ранее не названных ассерта (ключ тоста, сброс инпута, чистый staging,
повторный выбор) теперь в тексте; мутант (5) ловит именно ту регрессию,
ради которой AC4б вводился («молча грузить оригинал, от которого
отказались, — нечестно»).
- r2-M2 закрыта: security-решение явное, содержательное (fixed-offset parsing,
без file-controlled allocation size, fail-closed в `unknown`, fuzz-таблица)
и не противоречит уже описанной диагностике и рискам.
- Новый текст согласован с остальным документом: UX-контракт фазы 2
(`:99–104`), AC4б (`:157–159`) и обновлённые «Тесты и мутанты» (`:176–186`)
описывают одно и то же поведение одними и теми же терминами (тост,
сброс инпута, staging), расхождений в формулировках нет.
- «Откат», «Вне скоупа», i18n-список и «Риски» дельтой не менялись и остаются
согласованными с новым текстом.
- Владельцу вопросов не задавалось ни в этом, ни в предыдущих раундах — обе
находки r2 были техническими и решены автором ТЗ в своей компетенции.
- DoR §2.5 по совокупности трёх ревизий (r1→r4) закрыт буквально: сценарий и
«до/после» есть, AC1–AC9(+4б) пронумерованы с указанием способа
доказательства, i18n-ключи en/ru перечислены явно, риски и release-артефакты
(включая security) названы, откат — один revert без миграции конфига.
## Чего не проверял
- Реальное поведение в браузере — реализации по-прежнему нет, `test/backdrop-probe.test.mjs`
и `demo/smoke_backdrop_guard.mjs` не существуют; их «умение падать» —
предмет код-ревью (AC4б, мутант 5 в частности).
- Точна ли оценка «10 с таймаут» будет действительно проверяться смоком без
реального 10-секундного ожидания в CI: текст называет механизм (мок
глобала на вечно висящий `Promise`), но не говорит, укорачивается ли сам
таймер реализации под тестовым флагом. Это не было отдельно
сформулировано как обязательное требование в «Правке» r2-M1 (там был
предложен выбор «мок глобала **или** ускоренный таймер — на усмотрение
автора, но названный»), и названный вариант формально закрывает находку.
Оставляю Low, не блокирует: это решение исполнителя о скорости прогона
смока, не о корректности AC4б, и он вправе сам выбрать — ускорить таймер
под тестовым флагом или мириться с 10-секундным ожиданием в смоке.
- Low-находки, унаследованные из r2 (`backdrop.unknown_title` vs переиспользование
`large_title`; поддержка `hp-dialog` трёх кнопок действия) — не переоценивал,
дельта их не касалась, решение реализации остаётся открытым по тем же
причинам, что в r2.
- Точность чисел бенчмарка (4–165 МП) — не пересчитывал, как и в r1/r2.
## Вердикт
0 High, 0 Medium. Обе Medium-находки r2 (r2-M1, r2-M2) закрыты полностью и
без остатка, новых блокирующих находок дельта r2→r3 не вносит. ТЗ (ревизия 4)
удовлетворяет §7.1 и DoR §2.5 по совокупности всех четырёх ревизий.
**Вердикт: зелёный · заход r3 · блокирующих циклов 2/4 · High: 0 · Medium: 0**