Ревью #682 r1, Medium: перенос добавляет документу уровень вложенности (`docs/reviews/X.md` → `legacy/reviews/<тег>/X.md`, `docs/specs/` → `legacy/specs/`), а относительные ссылки внутри перенесённых документов и в соседях, ссылавшихся на них, никто не пересчитывал — на `97d19268` 53 битые ссылки в 46 файлах (заявление «все 26 резолвятся» в `7feb6177` было верно только до переноса документов ревью). Гейты архив не смотрят. `reviews-archive.mjs`: `repairLinks` пересчитывает ссылку, если она не резолвится от нового места, а цель находится от нового или старого места через карту переносов; битая и до переноса ссылка не трогается. `--apply` делает это само, `--repair-links=<rev>` — для всех переименований `<rev>..HEAD`, `--check-links` печатает битые. Этим коммитом `--repair-links=origin/dev` переписал ровно 53 ссылки в 46 файлах; остались две прежние «...»-заглушки в CODE-REVIEW-448-r2 (битые и на dev). Тесты: перенесённый документ, сосед со ссылкой в архив, ТЗ со ссылкой на позже перенесённое ревью, битая-до-переноса не трогается, в `legacy/` битых нет; мутант `reviews-archive-links-from-new-place-only`. PROCESS §2.10 и DEVELOPMENT › Release называют переписывание и `--check-links`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
25 KiB
SPEC-REVIEW-593-r2 — «Обновить встроенную библиотеку мебели до houseplan-0.4.0»
Issue: #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, вердикт красный, материал зафиксирован в его собственном машинном блоке якорей: веткаdev, коммит073c45b04308cd613182c700f30c75ebfa8d2ca7, дерево393fc8925392ed600ae675587da91f34ba6b3372, тело issue sha2568be001d5a0b2efa4aeb3f2bcd347a9253252dd47c38b091473e6686ca24f91d6. Текущее тело issue (редакция 3) очевидно отличается от этого текста — задача прошла через редакции 2 и 3 в ответ на вердикт r1; байтового совпадения с r1 не проверял, поскольку различие заведомо есть (сам факт правки — предмет этого раунда). - Прецедент #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 + ссылки на комментарии), на который сейчас опирается исполняемый тест той же подсистемы.
Воспроизведение:
-
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). Это не гипотетический риск «а вдруг сторонний» — это второй раз подряд один и тот же посторонний аккаунт поставляет «готовый» набор иконок с заявленными правами. -
Планка, которую сам этот ТЗ называет образцом (AC10: «АС10 корректно учитывает урок #159»), — дословный текст, который #159 признал необходимым и достаточным (issuecomment-5454085168):
«Я являюсь автором всех 77 SVG из архива
houseplan-furniture-custom-0.3.0и разрешаю House Plan использовать, изменять и распространять их на условиях MIT License репозитория без обязательной отдельной атрибуции в интерфейсе».Три обязательных элемента: (а) явное заявление об авторстве, (б) явное разрешение на использование/изменение/распространение, (в) именование конкретного архива/версии.
-
Фактический ответ владельца на Q3 в #593 (issuecomment-5739749394, 2026-09-19T05:48:51Z), пункт 3 из трёх: «Подтверждаю собственное авторство иконок». Из трёх элементов присутствует только (а). Нет разрешения на использование/изменение/распространение под MIT — ни слова про лицензию или условия использования. Нет привязки к конкретному архиву/версии (
houseplan-furniture-0.4.0.zip) — фраза говорит об «иконках» вообще. -
Это не мелочь формулировки: авторство и разрешение на использование — разные юридические факты. Автор произведения не обязан автоматически разрешать его использование под конкретной лицензией; #159 явно развёл эти два факта и потребовал оба, поэтому текущий ответ закрывает Q3 только частично относительно того, что сам ТЗ обещает читателю в AC10.
-
Механизм, ранее закрывавший этот же риск исполняемо, в новом ТЗ не продолжен.
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 нового архива, ни ссылку на нужный комментарий, ни необходимость переписать/воссоздать тест провенанса с новыми значениями. -
Следствие для код-ревью: если это не поправить сейчас, у исполнителя есть ровно два пути и оба плохие — (а) отредактировать тест провенанса так, чтобы он снова сверял что-то конкретное, но выдумав, на какие ссылки/хеш ссылаться, поскольку ТЗ этого не говорит (ровно тот класс дефекта — «догадка, выданная за факт» — который сам процесс называет «худшим видом»); либо (б) тихо удалить или ослабить этот тест вместе с директорией 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.
Материал раунда
- Ветка:
dev, коммитdcd6657581ba— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
0b48707268e99ee38221c8e74fb1c48ce6793e35git log --all --format='%H %T' | grep 0b48707268e9 - Тело issue:
d3303755f955b90dcdf74279c7b76a98de9814103c65cfeb2a5455a49201d457 - Вердикт конвейера:
red· High 1