docs: review document for #39

Issue: #39
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-29 06:28:19 +00:00
parent 9431a5cea2
commit f6da1ff57c
+173
View File
@@ -0,0 +1,173 @@
# 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**