diff --git a/docs/reviews/SPEC-REVIEW-282-r2.md b/docs/reviews/SPEC-REVIEW-282-r2.md new file mode 100644 index 00000000..3057e914 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-282-r2.md @@ -0,0 +1,74 @@ +# SPEC-REVIEW-282-r2 + +- **Issue:** [#282 — Геометрия стен: сменить представление, а не чинить последствия](https://github.com/Matysh/houseplan-card/issues/282) +- **Этап:** spec (PROCESS.md §2.4) +- **Заход:** r2 · блокирующих циклов израсходовано 1 из 4 (§2.10, §4: зелёный вердикт r1 не был — r1 был жёлтым и потратил цикл 1; если этот заход зелёный, он сам цикла не образует) +- **Артефакт ТЗ:** [`docs/specs/282-stable-wall-segment-identity.md`](../specs/282-stable-wall-segment-identity.md) +- **SHA ТЗ на момент этого ревью:** `2f30c481` (`docs: address wall identity spec review`) +- **SHA предыдущего ревью (r1):** `8856fbda` (`docs: specify stable wall segment identity`) +- **Документ r1:** [`docs/reviews/SPEC-REVIEW-282-r1.md`](./SPEC-REVIEW-282-r1.md), вердикт жёлтый, High: 0, Medium: 2 (обе в скоупе) + +## Скоуп ревью r2 — по дельте (PROCESS.md §2.10) + +Между `8856fbda` и `2f30c481` изменён **только** `docs/specs/282-stable-wall-segment-identity.md` +(`git diff 8856fbda..HEAD --stat`: 37 строк в одном файле; второй файл в diffstat — +сам `docs/reviews/SPEC-REVIEW-282-r1.md`, зафиксированный шагом публикации, не автором ТЗ). +Продуктовый код (`src/**`, `custom_components/**`) не тронут — его для #282 всё ещё нет. +Изменение целиком является прямым ответом на M1/M2 из r1: не рёбейз, не смена контракта, +не новая подсистема, объём дельты (37 строк из 663) несопоставим с объёмом задачи. Полный +разбор ТЗ заново не требуется — разбирается дельта плюс AC, которых она касается (AC1), +остальное наследуется из r1 без повторной проверки. + +## Как проверялось + +1. Найден вердикт r1 и SHA, на котором он получен (`8856fbda`, указан в шапке `SPEC-REVIEW-282-r1.md` — в отличие от типового риска этого пункта, здесь SHA был назван явно, задача «найти SHA» тривиальна). +2. Объявлена дельта: `git diff 8856fbda..HEAD` — построчно прочитан весь diff `docs/specs/282-stable-wall-segment-identity.md` (7 хансов). +3. По каждой находке r1 (M1, M2) проверено, чем именно она закрыта — конкретной строкой изменённого текста, а не комментарием автора «исправлено» (комментарий issue #282 от Matysh, `2f30c48`, только заявляет закрытие; настоящая проверка — ниже). +4. Прочитан целиком раздел «§7 Атомизация и deterministic migration v7 → v8» (включая нетронутый §7.2) и «§9 Writers и единый identity barrier» в текущей редакции — чтобы убедиться, что новое определение `exact` в §7.1 корректно распространяется на §7.2, который сам не редактировался. +5. Перечитан AC1 целиком (единственный AC, чей текст изменён) на предмет однозначности и проверяемости после правки. +6. Проверена терминология нового i18n/UX-текста против источника интерфейса (PROCESS.md требует брать термины из `docs/USER-GUIDE.ru.md`, не изобретать): `grep` по `src/i18n/ru.json` подтвердил `gs.align_all`/`gs.align_title` = «Оптимизировать планы» — тот же лейбл, что и в новом тексте тоста и в §2; `docs/USER-GUIDE.ru.md:998` (`backup.preserved_unresolved_hint`) уже использует ровно ту же конструкцию «запустите «Оптимизировать планы»» — формулировка не придумана, а повторяет существующий паттерн продукта. +7. Проверено, что дельта не расширяет скоуп и не меняет контракт вне того, что закрывают M1/M2 (§4/§5/§6/§8/§10/§13/§14 кроме AC1/§15–§19 кроме п.5 не тронуты). + +## Закрытие раунда r1 + +| Находка r1 | Чем закрыта | Где это видно | +|---|---|---| +| **M1** (Medium) — не специфицирован порядок атомизации Stage 1 относительно lattice-барьера #291, и какой из двух существующих допусков (`1e-4` vs `max(pitch·10⁻⁶,10⁻⁹)`) применяется | §7.1 получил абзац: канонизация #291 запускается **до** breakpoint-вычисления и до любых v8 ID/ref; порог принадлежит только #291 (`LATTICE_NOISE_STEPS = 1e-4`); слово `exact` везде в §7 означает побитовое равенство **уже канонизированных** координат; третий допуск не вводится. §9 добавил тот же порядок в описание barrier-пайплайна («сначала… `canonicalizeConfigGeometry` #291, затем… `commitWallSegmentModel`», «одна атомарная commit-транзакция»). §19 п.5 повторяет то же для момента миграции. AC1 расширен явным требованием: fixtures с шумом по обе стороны порога дают byte-equivalent v8 candidate, шум внутри порога канонизируется и не создаёт micro-segments, координата вне порога не схлопывается молча. | `docs/specs/282-stable-wall-segment-identity.md:199-205` (§7.1), `:333-338` (§9), `:655-657` (§19 п.5), `:439-445` (AC1) | +| **M2** (Medium) — §2/§11 обещают тексту тоста конкретный совет («Optimize либо исправление»), но `toast.wall_model_migration_blocked` в §12 не содержал никакого действия | §2 переформулирован под фактический текст: «предлагает сначала запустить «Оптимизировать планы», а при повторном отказе — исправить конфликтующую геометрию стен». §12 переписан в обе стороны (RU/EN): `toast.wall_model_migration_blocked` теперь буквально содержит «Запустите «Оптимизировать планы»; если ошибка повторится, исправьте конфликтующую геометрию стен.» / «Run "Optimize plans"; if the error repeats, fix the conflicting wall geometry.» — текст §2 и текст ключа теперь описывают одно и то же действие в одном порядке. | `docs/specs/282-stable-wall-segment-identity.md:32-34` (§2), `:412` (§12, таблица i18n) | + +Обе находки закрыты текстом, а не заявлением: конкретные строки процитированы выше и прочитаны целиком в контексте (см. «Как проверялось», пп. 3–4). + +### Дополнительная проверка терминологии (не отдельная находка, часть проверки закрытия M2) + +Новая формулировка «Оптимизировать планы» — не изобретённый ревьюером или автором термин: это точное название существующей кнопки (`gs.align_all`/`gs.align_title`, `src/i18n/ru.json:807-808`) и уже употреблённый в `docs/USER-GUIDE.ru.md:998` паттерн «запустите «Оптимизировать планы»» для родственного случая (нерешённые ссылки после импорта). Правка M2 не только закрывает находку, но и делает это в терминах, которые уже существуют в продукте — соответствует требованию PROCESS.md брать терминологию интерфейса из `USER-GUIDE.ru.md`, а не изобретать. + +## Что переразобрано в этом раунде (AC, которых касается дельта) + +- **AC1** — единственный AC с изменённым текстом. Новая формулировка добавляет случай `lattice-noise по обе стороны порога #291` к уже существующему списку fixture-классов (outer/shared/partial-overlap/T/X/diagonal/open span/key-only/exact walls). Критерий остаётся однозначным и проверяемым тем же способом доказательства (`TS migration unit matrix + golden/static path comparison + backend fixture parity`): для шума ниже `1e-4` ожидается, что после канонизации #291 атомизация не порождает лишних микросегментов; для расхождения выше порога — coordinate не схлопывается. Оба утверждения формулируются как конкретные fixture-проверки (сравнение catalog до/после на данных внутри/вне порога), а не как качественное пожелание — критерий выполнимости не потерян правкой. +- Остальные AC2–AC17 текстуально не изменились дельтой; их доказательство не зависит от порядка canonicalization/atomization (эта деталь относится только к самому механизму миграции, не к результату, который проверяют AC2 и далее) — повторный разбор не требуется, см. «Унаследовано» ниже. + +## Унаследовано из r1 (без повторной проверки) + +Всё нижеперечисленное принято из `docs/reviews/SPEC-REVIEW-282-r1.md` на SHA `8856fbda`, так как дельта r1→r2 их текста и логики не касается: + +- Соответствие `docs/SCOPE.md`/J6, отсутствие расширения UX/новой функциональности — §4/§5 не изменены дельтой. +- Продуктовые разделы §1 «Сценарий» и §2 «Что человек увидит» присутствуют и различимы (PROCESS.md §7.1); §2 в этом раунде получил только точечную правку текста тоста-совета (см. M2 выше), структура и персона не менялись. +- Построчная сверка фактических утверждений ТЗ о текущей модели (v7, `MAX_ROOMS/MAX_POLY_POINTS=200000`, `host?: {kind:'partition',...}`, `wallKey`/`rekeyWallsAfterMove`/`exactCoveringWall`/`edgeKinds`/`sharedSegsOf`/`atomicPolyForRoom`, существование `src/coordinate-canonicalization.ts`/`canonicalizeConfigGeometry`/`LATTICE_NOISE_STEPS`, существование скриптов `config-field-registry.mjs`/`model-invariants.mjs`/`mutation-gate.mjs`/`smoke-select.mjs`, i18n-префиксы `toast.*`/`gs.*`) — код в этих частях не менялся между r1 и r2, повторная сверка не требуется. +- AC2–AC17 однозначны и проверяемы, каждый называет способ доказательства — текст этих AC не изменён дельтой. +- Не-скоуп (§5) корректно ограничивает риск, держит границу Stage 1 против Stage 2–4 — не изменён. +- Откат и release-артефакты (§17–18) реалистичны — не изменены. +- Ссылки на существующий тулинг и гейты (§10.4, §17) верны — не изменены. +- «Чего не проверял» из r1 (ARCHITECTURE.md/CANVAS.md/UX-MODES.md/TOUCH-SUPPORT.md целиком; история регрессионных issues из AC12 построчно; typecheck/test/build — кода нет; вероятность hash-коллизии §7.3 математически) — основания этих пропусков не изменились дельтой, пропуски наследуются на тех же условиях. + +## Чего не проверял в r2 + +- Не перечитывал целиком ADR `docs/adr/282-wall-geometry-representation.md` заново — дельта его не касается, а r1 уже подтвердил соответствие ТЗ этому ADR. +- Не гонял `npx tsc --noEmit`/`npm test`/`npm run build` — дельта не касается ни одного файла класса A/B (только `docs/specs/*.md`, класс C), продуктового кода для #282 по-прежнему нет; гонять эти гейты в этом раунде нечего. +- Не проверял математически вероятность hash-коллизии §7.3 — не тронуто дельтой, унаследовано из r1. +- Не проверял согласованность нового текста тоста (M2) с фактическими лимитами длины UI-тоста/типографикой — это находится за пределами ТЗ-ревью (вопрос реализации/визуального QA на этапе код-ревью), а не критерий выполнимости ТЗ. + +## Вердикт + +Обе Medium-находки r1 закрыты точечными, проверяемыми правками текста: M1 — явным порядком «канонизация #291 → атомизация» и запретом третьего допуска, с расширением AC1 под fixture с шумом по обе стороны порога; M2 — синхронизацией текста §2/§11 с фактическим i18n-ключом, использующей существующую терминологию интерфейса, а не изобретённую. Новых High- или Medium-находок дельта не создала. Дельта локальна (только текст ТЗ, 37 строк), полного повторного разбора не требует. + +`Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0 · Документ: docs/reviews/SPEC-REVIEW-282-r2.md`