Files
houseplan-card/docs/reviews/SPEC-REVIEW-593-r2.md
T
2026-09-19 06:03:36 +00:00

90 lines
25 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-593-r2 — «Обновить встроенную библиотеку мебели до houseplan-0.4.0»
Issue: [#593](https://github.com/Matysh/houseplan-card/issues/593)
Этап: spec (полный трек, унаследовано из r1 — критерий «одна поверхность» нарушен явно)
Заход: r2 · блокирующих циклов израсходовано на входе в раунд: 0 из 4 (дано конвейером); после публикации этого вердикта — 1 из 4 (вердикт ниже не зелёный)
## Вердикт
**Красный.** High: 1. Medium в скоупе: 0. Medium вне скоупа: 0. Low: 0.
Разбор по дельте (§2.10): найдены обе закрытые находки r1 (H1, M1) — закрытие подтверждено по существу, а не по заявлению автора. Но закрытие H1 расширило раздел §3 ТЗ третьим вопросом (Q3, авторство/лицензия), которого r1 не проверял по содержанию (в редакции r1 AC10 требовал будущего подтверждения и был признан корректным именно как требование). Проверка того, что фактически произошло по Q3/AC10 в этом раунде, вскрыла новую находку той же серьёзности, что и закрытая H1.
## Материал раунда (входные данные разбора)
- Тело issue #593 на момент разбора: `gh issue view 593 --json body` (редакция 3 от 19.09.2026, раздел `## ТЗ`), сохранённое предложение автора выше него.
- Все 5 комментариев issue: `gh issue view 593 --json comments`.
- Полная временная шкала: `gh api repos/Matysh/houseplan-card/issues/593/timeline`.
- Предыдущий раунд: [`docs/reviews/SPEC-REVIEW-593-r1.md`](../../docs/reviews/SPEC-REVIEW-593-r1.md), вердикт красный, материал зафиксирован в его собственном машинном блоке якорей: ветка `dev`, коммит `073c45b04308cd613182c700f30c75ebfa8d2ca7`, дерево `393fc8925392ed600ae675587da91f34ba6b3372`, тело issue sha256 `8be001d5a0b2efa4aeb3f2bcd347a9253252dd47c38b091473e6686ca24f91d6`. Текущее тело issue (редакция 3) очевидно отличается от этого текста — задача прошла через редакции 2 и 3 в ответ на вердикт r1; байтового совпадения с r1 не проверял, поскольку различие заведомо есть (сам факт правки — предмет этого раунда).
- Прецедент [#159](https://github.com/Matysh/houseplan-card/issues/159) — та же подсистема, предыдущее внедрение библиотеки мебели (0.3.0): его r1-ревью, его находка High-1, его remediation-комментарии, финальный код (`test/furniture-assets.test.mjs`, `assets/furniture/houseplan-0.3.0/README.md`). Читал заново специально для этого раунда, поскольку именно на него ссылается текущий ТЗ (AC10) как на образец, и именно его конкретный текст стал мерилом находки H1(new) ниже.
- Код: `test/furniture-assets.test.mjs` (тест `'release provenance is normalized to the repository MIT grant'`), `assets/furniture/houseplan-0.3.0/README.md`, `src/furniture-art-runtime.ts`, `src/houseplan-card.ts:2693`, `src/i18n/{ru,en,fr,de}.json` (ключ `toast.furniture_art_load_failed`) — прочитаны, чтобы проверить два конкретных утверждения редакции 3 (см. «Как проверялось»).
- Ветка/код продукта: не создавались. `git log` подтверждает отсутствие любых коммитов или веток `issue/593-*`; рабочая копия на `HEAD = dcd6657581ba205edb63713e011a498b51983a20` (последний коммит — публикация документа r1), продуктовый код не менялся с r1. Гейты `typecheck`/`test`/`build`/`bundle:budget` не запускал — на этапе ревью ТЗ они не относятся к предмету раунда (см. «Чего не проверял», и это же практика самого r1).
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| **H1** — Р1 (`cactus → plant`) и Р2 (поведение при отказе чанка) помечены «Принято» без независимого голоса владельца, `blocked` не применялась | Автор признал находку текстуально (`#issuecomment-5741…`, 05:46:43): «молчание — согласие... я подменил одно другим». §3 ТЗ переписан: оба варианта стали «ОТКРЫТЫ, ждём владельца» с ценой каждой ветки; `blocked` поставлена поверх `S3-spec`. Владелец ответил отдельным, более поздним и стилистически иным комментарием (`#issuecomment-5739749394`, 05:48:51, через ≈2 минуты и после смены статуса на `blocked`): «1. Принимаю default · 2. Принимаю default · 3. Подтверждаю собственное авторство иконок». `blocked` снята комментарием 05:51:27 после получения ответа. | Таймлайн issue (`gh api .../timeline`): `commented(Matysh,05:46:43)` → `labeled(blocked? — см. ниже)` → `commented(Matysh,05:48:51)` → `commented(Matysh,05:51:27)`. Текст §3 ТЗ, редакция 3: «Ревью r1 (H1) справедливо забраковало прежнюю редакцию... Вопросы были заданы заново, issue нёс `blocked`, ответы получены отдельным комментарием владельца». Формат (отдельный комментарий, задержка, `blocked` между вопросом и ответом) совпадает с протоколом §7.1 и с прецедентом, который сам r1 привёл (SPEC-REVIEW-588-r1). Считаю закрытым: структурная часть находки (самопровозглашённое согласие в одном сообщении без паузы) не повторилась. Проверил содержательно оба ответа (Q1, Q2) отдельно — оба воспроизведены корректно в §3, §4 п.2/4, §6, §10 (AC3/AC8) без остаточных противоречий со старой формулировкой «33 непустые категории» (расхождение с предложением автора названо явно). |
| **M1** — AC9 заявляет защиту трёх бюджетов, а `assertBundleBudget` реально ограничивает потолком только `initialViewGzipBytes`; два ленивых графа только печатаются | §7 ТЗ переписан: в скоуп добавлена чистая функция `lazyGraphCeilingViolation(bytes, {ceiling, label})` по образцу `initialViewCeilingViolation`, две новые константы `LAZY_FURNITURE_ART_GZIP_CEILING`/`LAZY_EDITOR_GZIP_CEILING` (полоса 2000 B, зазор >500 B), обе сверки — в `assertBundleBudget`. AC9 переписан: «защищены **гейтом**, а не печатью», третий столбец таблицы AC называет мутант `bundle-budget-lazy-ceiling-never-fires`. | §4 п.5, §7, §8 «Мутанты» (таблица), §10 AC9 (таблица «AC · чем доказан · чем краснеет») редакции 3. Прочитал текст: описанный механизм (чистая функция + throw в `assertBundleBudget`) — реалистичная, проверяемая надстройка над кодом, который сам же r1 прочитал построчно (`scripts/bundle-budget.mjs:399-450`); ничего в редакции 3 не противоречит структуре существующего кода. Числа ценностей (полоса/зазор) корректно вынесены в §12 п.5 как техническое предположение, а не как факт — ревьюер вправе оспорить, но оспаривать нечего: значения снимаются с реального замера после сборки, что и должно быть так. Закрыто полностью. |
## Новая находка (delta r1→r2): Q3/AC10 не достигает собственной планки ТЗ
### High (блокирует)
**H1(new). Раздел §3 Q3 и AC10 заявляют, что владелец «публично подтвердил... авторство и MIT для всех 93 SVG» по образцу прецедента #159, но фактически данный владельцем комментарий заметно уже, чем требует и сам этот прецедент, и собственная формулировка AC10; при этом ТЗ не планирует восстановление механизма провенанса (README + SHA-256 + ссылки на комментарии), на который сейчас опирается исполняемый тест той же подсистемы.**
Воспроизведение:
1. Issue #593 подан аккаунтом `nikitaevfz-commits`, `author_association: NONE` (`gh api repos/Matysh/houseplan-card/issues/593 --jq .author_association` → `NONE`) — сторонний контрибьютор без каких-либо прав в репозитории, приложивший архив `houseplan-furniture-0.4.0.zip` прямо в теле issue. Это **тот же самый аккаунт**, что подавал архив для предыдущего внедрения библиотеки: в #159 r1-ревью цитирует «комментарий `nikitaevfz-commits` (`authorAssociation: NONE`)» как источник архива 0.3.0 (`gh api repos/Matysh/houseplan-card/issues/159 --jq .author_association` → `OWNER`, потому что issue #159 создал сам владелец — но приложенный архив внутри неё дал именно `nikitaevfz-commits`). Это не гипотетический риск «а вдруг сторонний» — это второй раз подряд один и тот же посторонний аккаунт поставляет «готовый» набор иконок с заявленными правами.
2. Планка, которую сам этот ТЗ называет образцом (AC10: «АС10 корректно учитывает урок #159»), — дословный текст, который #159 признал необходимым и достаточным ([issuecomment-5454085168](https://github.com/Matysh/houseplan-card/issues/159#issuecomment-5454085168)):
> «Я являюсь автором всех 77 SVG из архива `houseplan-furniture-custom-0.3.0` и разрешаю House Plan использовать, изменять и распространять их на условиях MIT License репозитория без обязательной отдельной атрибуции в интерфейсе».
Три обязательных элемента: (а) явное заявление об авторстве, (б) явное разрешение на использование/изменение/распространение, (в) именование конкретного архива/версии.
3. Фактический ответ владельца на Q3 в #593 ([issuecomment-5739749394](https://github.com/Matysh/houseplan-card/issues/593#issuecomment-5739749394), 2026-09-19T05:48:51Z), пункт 3 из трёх: **«Подтверждаю собственное авторство иконок»**. Из трёх элементов присутствует только (а). Нет разрешения на использование/изменение/распространение под MIT — ни слова про лицензию или условия использования. Нет привязки к конкретному архиву/версии (`houseplan-furniture-0.4.0.zip`) — фраза говорит об «иконках» вообще.
4. Это не мелочь формулировки: авторство и разрешение на использование — разные юридические факты. Автор произведения не обязан автоматически разрешать его использование под конкретной лицензией; #159 явно развёл эти два факта и потребовал оба, поэтому текущий ответ закрывает Q3 только частично относительно того, что сам ТЗ обещает читателю в AC10.
5. Механизм, ранее закрывавший этот же риск исполняемо, в новом ТЗ не продолжен. `test/furniture-assets.test.mjs` содержит тест `'release provenance is normalized to the repository MIT grant'`, который **не просто** сверяет `pack.json.author`/`pack.json.license` со строками — он проверяет, что `assets/furniture/houseplan-0.3.0/README.md` содержит конкретные ссылки на `issuecomment-5454085168` (полный грант) и `issuecomment-5449707137` (комментарий с архивом) и точный SHA-256 архива `9E969016EE3B4B4E3DB776FEC53C8B387B91368B118EB5E39911483DEF1B0953`. Этот README — не файл из присланного пакета, а документ, специально написанный при внедрении 0.3.0 (закрытие Medium-1 из `CODE-REVIEW-159-r1`, коммит `b688714f`): в нём House Plan фиксирует **свою собственную** цепочку доказательства права на использование картинок.
Редакция 3 §4 п.1 ТЗ #593 говорит: «Вендорить пакет целиком: `pack.json`, `LICENSE.md`, `README.md`, `svg/menu/*` (33), `svg/plan/*` (60). Каталог `houseplan-0.3.0` **удаляется**». Это стирает файл-носитель провенанса 0.3.0 (тест на него ссылается по фиксированному пути `assets/furniture/houseplan-0.3.0/pack.json`/`README.md` — путь тоже исчезнет), а на его место ставится «README.md» пакета — неясно, из архива это файл, специально написанный вендором-художником про сам рисунок, или это должен быть новый провенанс-документ по образцу 0.3.0. Ни то, ни другое явно не названо. Ни AC10, ни §8 «Доказательства», ни §9 «Release-артефакты» не упоминают ни SHA-256 нового архива, ни ссылку на нужный комментарий, ни необходимость переписать/воссоздать тест провенанса с новыми значениями.
6. Следствие для код-ревью: если это не поправить сейчас, у исполнителя есть ровно два пути и оба плохие — (а) отредактировать тест провенанса так, чтобы он снова сверял что-то конкретное, но **выдумав**, на какие ссылки/хеш ссылаться, поскольку ТЗ этого не говорит (ровно тот класс дефекта — «догадка, выданная за факт» — который сам процесс называет «худшим видом»); либо (б) тихо удалить или ослабить этот тест вместе с директорией 0.3.0, и тогда единственная исполняемая защита от повторения истории #159 пропадает молча, а «чем краснеет» у AC10 (сейчас: «подмена автора или лицензии **в манифесте**») продолжает проверять только внутреннюю согласованность строк `pack.json`, а не то, откуда взялось само право на эти 93 файла.
Почему это продуктовый/юридический вопрос, а не то, что ревьюер вправе решить сам: ровно как и H1(r1) — это факт, который может подтвердить только владелец лично, и вердикт ревью не может выдать чтение кода за такое подтверждение (`PROCESS.md` §7.1: «Технический спор... решается вердиктом... продуктовое — нет»). Отличие от закрытой H1 в том, что здесь ответ уже дан, но не покрывает то, что заявляет AC10.
Чем закрывается: один явный публичный комментарий владельца, называющий архив по имени/версии (`houseplan-furniture-0.4.0.zip`, версия пакета `0.4.0`) и содержащий разрешение на использование, изменение и распространение под MIT License репозитория — по образцу текста #159, приведённого выше. Плюс явный пункт в скоупе/AC10/§8, требующий воссоздать провенанс-документ (README нового пакета либо `docs/`-заметку) с этим комментарием и SHA-256 архива, и обновить (а не молча снести) тест `'release provenance is normalized...'` под новый путь и новые значения — так же, как это было сделано в #159 для Medium-1.
## Что проверено и корректно (унаследовано + новое)
- Закрытие H1(r1) и M1(r1) — подтверждено по существу, детали в таблице выше.
- Числовые и структурные факты редакции 3, которые дельта не изменила по сравнению с r1 (состав `RETAINED_IDS`, default-размеры 12 retained-символов, число ключей i18n 29/56 в исходном состоянии, ловушка двойного объединения `FURNITURE`/`BY_ID`, существование golden-сцен и npm-скриптов, отсутствие коллизий id мутантов) — не проверял заново, см. «Унаследовано из r1».
- Механизм `subscribeFurnitureArtLoadFailures`/тост `toast.furniture_art_load_failed`, на который опирается новое поведение Q2 (§3, AC8), — проверил в коде (`src/furniture-art-runtime.ts:212-235`, `src/houseplan-card.ts:2693`, ключ уже существует в `src/i18n/{ru,en,fr,de}.json:798`) — это существующая с #474 инфраструктура «один тост на страницу при уходе арта в fallback», её распространение на все 60 предметов вместо нынешних 48 (44 designer) корректно описано в ТЗ и **не** требует нового i18n-ключа, вопреки моему первоначальному опасению при чтении §6 — список из 7 новых ключей в §6 полон и точен.
- §3 Q1/Q2 корректно и последовательно проведены через весь документ: §4 п.2/4, §5, §6, §10 (AC3/AC8), §11 — расхождение со старым тезисом «33 непустые категории» названо явно везде, где он встречается в сохранённом предложении автора.
- §7/AC9 (закрытие M1) технически состоятельно при чтении существующего `scripts/bundle-budget.mjs`/`bundle-manifest.mjs` — сама структура (чистая функция + throw) реализуема без противоречий с текущим кодом.
## Чего не проверял
- `npm run typecheck`, `npm test`, `npm run build`, `npm run bundle:budget`, `golden:verify`, любые смоки — этап ревью ТЗ, продуктовый код не менялся с r1 (нет ни одного коммита в `issue/593-*`, `HEAD` — публикация документа r1). Эти гейты относятся к код-ревью, не к этому раунду; так же поступил и r1.
- Байтовое содержимое присланного `houseplan-furniture-0.4.0.zip` — как и в r1, полагаюсь на прогон генератора аналитикой S2 (`OK: 60 plan symbols, 33 menu icons`); фактическая проверка — предмет код-ревью, когда пакет окажется в дереве.
- Не пересчитывал SHA-256 самого архива `houseplan-furniture-0.4.0.zip` — H1(new) не о том, что хеш неверный, а о том, что ни хеш, ни ссылка на него нигде не зафиксированы как требование ТЗ.
- Не проверял французский и немецкий переводы новых строк на смысл — они появятся при реализации.
- Не проверял, применялась ли метка `blocked` физически (в `timeline` она видна как `unlabeled`/`labeled` без явного текста имени метки в JSON-выводе `--json`); полагался на текстовые подтверждения обоих комментариев автора («ставлю `blocked`», «`blocked` снят») и на факт, что между вопросом и ответом действительно прошло отдельное, самостоятельное сообщение — это и есть контролируемый по процессу факт, а не сама метка.
## Материал раунда (для конвейера)
- Issue #593, тело редакции 3 на момент разбора (`gh issue view 593 --json body`), все 5 комментариев (`gh issue view 593 --json comments`), полная временная шкала (`gh api repos/Matysh/houseplan-card/issues/593/timeline`).
- Заход r2, циклов ревью ТЗ израсходовано до этого раунда: 0 из 4 (значение дано конвейером на входе в раунд); лимит для полного трека — 4 (§4). Текущий красный вердикт израсходует цикл после публикации.
- Код сверялся на рабочей копии в состоянии `HEAD = dcd6657581ba205edb63713e011a498b51983a20` — продуктовый код под задачу не менялся, все ссылки на строки относятся к текущему `dev`.
<!-- pipeline appends its own material-anchors block below on publish -->
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `dev`, коммит `dcd6657581ba` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `0b48707268e99ee38221c8e74fb1c48ce6793e35`
```
git log --all --format='%H %T' | grep 0b48707268e9
```
- Тело issue: `d3303755f955b90dcdf74279c7b76a98de9814103c65cfeb2a5455a49201d457`
- Вердикт конвейера: `red` · High 1