mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
292 lines
25 KiB
Markdown
292 lines
25 KiB
Markdown
# SPEC-REVIEW-500-r2
|
||
|
||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/500
|
||
- **Этап:** ревью ТЗ (PROCESS.md §2.4)
|
||
- **Заход:** r2 · блокирующих циклов израсходовано 1 из 4 до этого раунда
|
||
- **Материал:** ветка `issue/500-config-adoption-boundary`, коммит
|
||
`c35ecf13206bb5037f86f1e55fc27b8ed46604ee` (docs-only поверх ревью-документа
|
||
r1 `565a0c4a`), файл ТЗ `docs/specs/500-config-adoption-boundary.md`,
|
||
дерево `9a354a9fa366fcca195773d22a15f7947caf5d14`,
|
||
блоб ТЗ `5cfc36e2614361d1e90d4d9a8a9a7358d4bec3b9`.
|
||
- **Ревьюер:** Claude (роль «ревьюер ТЗ», отдельно от автора).
|
||
- **Предыдущий раунд:** `docs/reviews/SPEC-REVIEW-500-r1.md`, вердикт жёлтый,
|
||
материал `ca07067386dee0ce2bfad64c169404c9284cd242` (докс-коммит поверх
|
||
`origin/dev` = `6b31e945`).
|
||
|
||
## Скоуп ревью (по §2.10 — объём по дельте)
|
||
|
||
Разбор по дельте: r1 нашёл две High-находки, обе — неполная инвентаризация
|
||
кода, на которой построены AC. Автор ответил коммитом `c35ecf13` (поверх
|
||
`ca070673`), диапазон изменений — только `docs/specs/500-config-adoption-boundary.md`.
|
||
Дельта не локальна в смысле «одна строка»: она вводит новое понятие (два
|
||
профиля adoption вместо одного) и переписывает §3, §4, §6.3, §6.4, AC1–AC4,
|
||
§11, §12 — то есть проверке подлежит вся дельта диффа `ca070673..c35ecf13`,
|
||
а не только формальное «закрыт ли пункт High-1/High-2». AC5–AC8, §7–10, §13–14
|
||
дельта не задевает — унаследованы из r1 без повторной проверки (раздел ниже).
|
||
|
||
Как и в r1, факт-чек количественных и структурных утверждений сделан прямым
|
||
чтением `src/**` на рабочей копии, соответствующей материалу (докс-only ветка
|
||
поверх текущего `dev`, код идентичен тому, что описывает ТЗ).
|
||
|
||
## Как проверялось
|
||
|
||
```
|
||
grep -rn "_adoptStructuralResponses" src/
|
||
grep -rn "_serverCfg\s*=[^=]" src/ --include=*.ts # исключая === сравнения
|
||
grep -rn "_layoutRev\s*=[^=]" src/ --include=*.ts
|
||
grep -rn "_cfgContentFingerprint\s*=[^=]" src/ --include=*.ts
|
||
grep -rn "_layoutContentFingerprint\s*=[^=]" src/ --include=*.ts
|
||
sed -n '85,115p' src/editors/vacuum-maps-section.ts src/vacuum-calibration-write.ts
|
||
sed -n '460,485p' src/houseplan-onboarding-runtime.ts
|
||
sed -n '8680,8700p;9560,9600p;9720,9760p' src/houseplan-editor-runtime.ts
|
||
grep -n "_adoptInitialSpace\|_resumePendingNavMode\|_restoreZoom" src/houseplan-editor-runtime.ts src/houseplan-onboarding-runtime.ts
|
||
```
|
||
|
||
Гейты кода не запускались — этап ревью ТЗ, продуктовый код не меняется этим
|
||
коммитом. Проверка гейтов относится к код-ревью (§2.7).
|
||
|
||
## Закрытие раунда r1
|
||
|
||
| Находка r1 | Чем закрыта в r2 | Где это видно |
|
||
|---|---|---|
|
||
| **High-1** — 4 названных вызывающих `_adoptStructuralResponses` против 7 реальных; дубликат `space/delete` в `houseplan-editor-runtime.ts:8693` с тем же дефектом M1 #490 не учтён AC2/AC4 | §3 п.2–3 переписаны: семь вызывающих в двух профилях (`reload` ×3, `post-write` ×4, включая оба `space/delete`); §6.3 вводит явный параметр `profile`; §6.4 добавляет host-интерфейс editor-runtime (`:842`); AC2 — «имя не встречается в `src/**` вне модуля» (было: «не существует как метод хоста», не видело второй `_adoptStructuralResponses` в `houseplan-editor-runtime.ts:842` как объявление интерфейса); AC4 расширен на все четыре post-write пути и оба входа `delete`, новый смок `demo/smoke_post_write_adoption.mjs` | Диф §3 п.2–3, §6.3, §6.4, AC2/AC4 в `docs/specs/500-config-adoption-boundary.md`. Построчно перепроверено чтением: все 7 вызовов (`houseplan-card.ts:4350,4527`; `summary-panel-runtime-loaded.ts:625`; `houseplan-onboarding-runtime.ts:476`; `houseplan-editor-runtime.ts:8693,9577,9736`) подтверждены на материале ревью |
|
||
| **High-2** — 16/6 заявлено, фактически 18 присваиваний `_serverCfg =` в 8 модулях; `vacuum-calibration-write.ts:108` и `editors/vacuum-maps-section.ts:107` не решены AC1 | §3 п.1 — «18 присваиваний в 8 модулях», оба vacuum-файла названы с номером строки; §6.4/AC1 — оба файла явно в объёме переноса на `beginOptimistic`/`stageLocalConfig`/`rollbackOptimistic` модуля, не в allowlist (AC1 требует ноль) | Диф §3 п.1, §6.4, §4 п.5, AC1. Построчно перепроверено: `grep -rn "_serverCfg\s*=[^=]" src/` даёт ровно 18 строк в 8 файлах, включая обе названные; оба vacuum-writer'а вызывают `optimisticAttempt`/`rollbackOptimistic` из `serialized-write-queue.ts`, что подтверждает применимость переноса |
|
||
|
||
Обе High-находки r1 закрыты содержательно, не декларативно: числа и строки
|
||
кода, названные в r2, совпадают с кодом на материале ревью (см. «Что
|
||
проверено и корректно» ниже — с деталями по каждому пересчитанному числу).
|
||
|
||
## Находки
|
||
|
||
### High-3 — новый текст r2 неверно утверждает, что `_adoptInitialSpace` сегодня не вызывается ни на одном post-write пути; фактически он вызывается в `import/apply` для фиксированного этажа, и именно там AC3/AC4/риск-таблица опираются на обратное
|
||
|
||
**Резюме.** Понятие «два профиля» и связанные с ним утверждения — целиком
|
||
новый текст r2, ранее не рецензировался. §6.3 (`docs/specs/500-config-adoption-boundary.md:213-216`)
|
||
формулирует это как факт о текущем коде:
|
||
|
||
> Пост-шаги профиля `post-write`: … `_adoptInitialSpace`/`_resumePendingNavMode`/
|
||
> `_restoreZoom` в этот профиль **не входят**: сегодня их там нет, а выбор
|
||
> пространства после удаления делает `_commitSpace` вызывающего.
|
||
|
||
Список «Особенности вызывающих» (`:226-227`) для `import/apply` называет
|
||
только `_dirtyPos`/`_sentPos`/`_defPos`, снапшоты устройств,
|
||
`_signer.invalidate`+`_resign`, `_cfgEpoch++` — без единого слова о выборе
|
||
пространства.
|
||
|
||
Чтение `src/houseplan-editor-runtime.ts` на материале ревью (обработчик
|
||
`import/apply`, окружает вызов `_adoptStructuralResponses` на строке 9736)
|
||
показывает обратное:
|
||
|
||
```
|
||
9751: const spaces = this.host._serverCfg?.spaces || [];
|
||
9752: const nextSpace = result.kind === 'space' && result.space_id
|
||
9753: ? result.space_id
|
||
9754: : spaces.some((space) => space.id === previousSpace)
|
||
9755: ? previousSpace : spaces[0]?.id || this.host._space;
|
||
9756: if (this.host._hasFixedFloor) this.host._adoptInitialSpace(this.host._model, true);
|
||
9757: else this.host._commitSpace(nextSpace);
|
||
```
|
||
|
||
`_adoptInitialSpace` (объявлен `houseplan-card.ts:3708`, делегат в
|
||
host-интерфейсе editor-runtime `:841`) — та же функция, что вызывают
|
||
«reload»-пути `_loadFromServer`/`_reloadConfigOnly` (`houseplan-card.ts:4352,4529`).
|
||
Она **не** сохраняет `previousSpace`: `_initialSpaceSelection` внутри неё
|
||
выбирает пространство по собственным правилам (hash/saved/default) и вызывает
|
||
`_commitSpace(selection.id, true)` независимо от вычисленного тут же
|
||
`nextSpace`. То есть сегодня после `import/apply` в конфигурации с
|
||
зафиксированным этажом видимое пространство определяет `_adoptInitialSpace`,
|
||
а не «попытка сохранить `previousSpace`», которую код вычисляет, но
|
||
отбрасывает в этой ветке.
|
||
|
||
**Почему это блокирует.** Три места ТЗ прямо опираются на утверждение
|
||
«`_adoptInitialSpace` не вызывается ни на одном post-write пути сегодня»:
|
||
|
||
1. §6.3 — сам факт, процитирован выше;
|
||
2. AC3 (`:283`) — «профиль `post-write` не вызывает
|
||
`_adoptInitialSpace`/`_resumePendingNavMode`/`_restoreZoom`» как часть
|
||
доказательства поведенческой нейтральности;
|
||
3. §12, строка риска (`:320`) — «Профиль `post-write` случайно получит шаги
|
||
`reload`… смок AC4 проверяет, что видимое пространство после Import не
|
||
меняется» — это неверно для конфигурации с зафиксированным этажом: там
|
||
пространство **должно** активно выбираться `_adoptInitialSpace`, а не
|
||
оставаться прежним.
|
||
|
||
Если реализовать буквально по тексту — разработчик, доверяя §6.3, вправе не
|
||
перенести вызов `_adoptInitialSpace` в обработчик `import/apply` внутри
|
||
модуля (ведь профиль его «не вызывает» по документу), и после рефакторинга
|
||
импорт в план с зафиксированным этажом перестанет корректно выбирать
|
||
пространство — новая регрессия ровно того класса, который issue #500 и
|
||
задуман закрыть (новый/переписанный путь пропускает правило adoption). Если
|
||
же разработчик решит буквально удовлетворить AC4/смок «пространство после
|
||
Import не меняется» — это будет расходиться с сегодняшним поведением
|
||
фикс-этажных планов, то есть сам смок и есть некорректный свидетель
|
||
поведенческой нейтральности для этого случая.
|
||
|
||
**Что не задето.** `_resumePendingNavMode` и `_restoreZoom` на post-write
|
||
путях действительно не встречаются (проверено тем же grep — оба совпадения в
|
||
`houseplan-editor-runtime.ts:2443,7534` относятся к geometry-undo и
|
||
inbox-навигации устройства, не к четырём post-write путям). Часть
|
||
утверждения §6.3 верна; неверна ровно доля про `_adoptInitialSpace`.
|
||
|
||
**Что нужно.** Скорректировать §6.3 (снять «сегодня их там нет» в части
|
||
`_adoptInitialSpace`), явно дописать в «Особенности вызывающих» для
|
||
`import/apply` ветку `_hasFixedFloor` (`_adoptInitialSpace` вместо
|
||
`_commitSpace(nextSpace)`) как caller-specific поведение, которое переносится
|
||
1:1 и не входит в общие пять шагов профиля `post-write`; исправить
|
||
формулировку AC3 и риск-строку §12, чтобы смок AC4 проверял неизменность
|
||
пространства только для не-фикс-этажного случая, а для фикс-этажного —
|
||
что `_adoptInitialSpace` по-прежнему вызывается после adoption (не
|
||
теряется).
|
||
|
||
## Что проверено и корректно (сверх унаследованного)
|
||
|
||
Все точечные числа, добавленные или изменённые в r2, перепроверены построчным
|
||
чтением и совпадают с кодом на материале ревью:
|
||
|
||
- **Семь вызывающих `_adoptStructuralResponses`** — подтверждено: 3 reload
|
||
(`houseplan-card.ts:4350,4527`; `summary-panel-runtime-loaded.ts:625`) + 4
|
||
post-write (`houseplan-onboarding-runtime.ts:476`;
|
||
`houseplan-editor-runtime.ts:8693,9577,9736`), плюс объявление метода
|
||
(`houseplan-card.ts:4231`) и два интерфейсных объявления
|
||
(`summary-panel-host.ts:87`, `houseplan-editor-runtime.ts:842`) — ровно как
|
||
в §3 п.2 и AC2.
|
||
- **Оба `space/delete` — байт-в-байт близкий дубликат с тем же дефектом**:
|
||
построчное сравнение `houseplan-onboarding-runtime.ts:466-478` и
|
||
`houseplan-editor-runtime.ts:8683-8695` подтверждает идентичную
|
||
структуру и идентичную перезапись `_cfgRev`/`_layoutRev` из ответа
|
||
`delete` после adoption в обеих копиях — ровно так, как описывает §3 п.3.
|
||
- **`_serverCfg =` — 18 присваиваний в 8 модулях.** Полный пересчёт (с
|
||
исключением ложных `===`/`!==` срабатываний, которые давал более грубый
|
||
паттерн) даёт: `houseplan-card.ts` 6, `houseplan-editor-runtime.ts` 6,
|
||
`plan-optimize-write.ts` 1, `serialized-write-queue.ts` 1,
|
||
`space-copy-runtime.ts` 1, `summary-panel-runtime-loaded.ts` 1,
|
||
`vacuum-calibration-write.ts` 1, `editors/vacuum-maps-section.ts` 1 — сумма
|
||
18, модулей 8. Совпадает с §3 п.1 дословно, включая номера строк обоих
|
||
vacuum-файлов (107/108).
|
||
Побочное наблюдение: r1 в скобках утверждал, что
|
||
`houseplan-editor-runtime.ts` «фактически даёт 8, не 6» — при пересчёте это
|
||
не подтвердилось: грубый `grep` без фильтра `!==`/`===` ловит сравнения
|
||
`this.host._serverCfg === cfg` (`:7521,8243`) как ложные совпадения; после
|
||
фильтра реальных присваиваний — ровно 6, как и было в исходном ТЗ и как
|
||
осталось в r2. Не находка (число в r2 верно), но стоит явно
|
||
зафиксировать: сам аргумент r1 в этой частности был артефактом паттерна,
|
||
а не второй скрытой находкой, оставшейся неисправленной.
|
||
- **`_layoutRev =` — 7 присваиваний в 4 модулях** (`houseplan-card.ts` 4,
|
||
`houseplan-onboarding-runtime.ts` 1, `houseplan-editor-runtime.ts` 1,
|
||
`plan-optimize-write.ts` 1, после исключения объявления поля) — совпадает
|
||
с §3 п.1.
|
||
- **fingerprint config — 10 мест в 5 модулях, fingerprint layout — 5 мест** —
|
||
оба числа пересчитаны (после исключения объявлений полей) и совпадают с
|
||
§3 п.1 дословно.
|
||
- **Оба vacuum-writer'а действительно используют `optimisticAttempt`/
|
||
`rollbackOptimistic`** из `serialized-write-queue.ts` и пишут
|
||
`host._serverCfg =` напрямую в обход какого-либо централизованного шага —
|
||
подтверждено чтением тел функций (`vacuum-maps-section.ts:89-113`,
|
||
`vacuum-calibration-write.ts:85-113`); включение их в перенос на
|
||
`beginOptimistic`/`stageLocalConfig` (§6.4/AC1) методологически корректно.
|
||
- Внутренняя согласованность новых разделов: `reason` в сигнатуре
|
||
`adoptAuthoritativeGated` расширен на `'optimize-undo'`/`'import-apply'`
|
||
синхронно с §3/§6.3; мутант `post-write-skips-asset-gate` (§11) корректно
|
||
заменяет прежний `space-delete-skips-asset-gate`, покрывая все четыре пути,
|
||
а не один.
|
||
- §15 п.7 — новый пункт «два профиля, а не один» — обоснование не
|
||
голословно: реальный набор пост-шагов у `reload` и `post-write` сегодня
|
||
действительно разный (подтверждено тем же чтением, что и выше), склейка
|
||
профилей была бы поведенческим изменением, а не только рефакторингом.
|
||
|
||
## Унаследовано из r1
|
||
|
||
Следующее принято без повторной проверки в r2, дельта их не касается:
|
||
|
||
- Обязательные разделы §7.1 присутствуют все — проверено в
|
||
`docs/reviews/SPEC-REVIEW-500-r1.md`, материал `ca070673`. Раздел «Статус
|
||
ТЗ» и структура файла в r2 не менялись содержательно (только пометка
|
||
«r2 — учтены High-1/High-2»).
|
||
- Отказ от `small` обоснован названными критериями §5 (сложность/риск 7/10,
|
||
7 модулей, state-контракт) — §4 ТЗ не менялся в r2, вывод r1 остаётся в силе.
|
||
- Продуктовых вопросов владельцу нет, блок §15 «принято предположительно»
|
||
использован по назначению — методология не изменилась, новый пункт §15.7
|
||
ей соответствует (проверен отдельно выше).
|
||
- `houseplan-card.ts` — 13699 строк при бюджете `core-file-budget` 13700
|
||
(§3.5/AC7) — число не затронуто дельтой r2, унаследовано из r1.
|
||
- Восемь методов `SummaryPanelHost` (`summary-panel-host.ts:84-94`),
|
||
сведение к одному — не затронуто дельтой (§6.4 в этой части не менялся),
|
||
унаследовано из r1.
|
||
- Смок `demo/smoke_summary_panel.mjs` содержит сценарий
|
||
`recoveryPreparesBackdropBeforeAdoption` — не затронуто дельтой,
|
||
унаследовано из r1.
|
||
- I2/I4 (ревизия только вместе с телом; ссылочная идентичность без
|
||
клонирования) корректно отражают текущее поведение — раздел §6.1-6.2 не
|
||
менялся дельтой r2 по существу, унаследовано из r1.
|
||
- Скоуп по `docs/SCOPE.md` (J6, техдолг, без новой пользовательской
|
||
поверхности) и корректность §5 «не входит» (lifecycle registry #493/#425,
|
||
umbrella-неизменяемость #34/#425) — не затронуты дельтой, унаследованы из
|
||
r1.
|
||
- AC5 (тёплый старт), AC6 (optimistic rollback), AC7 (бюджеты), AC8 —
|
||
дельта r2 их не редактировала (кроме синхронизации со счётчиками §3, что
|
||
проверено выше отдельно) — содержательно унаследованы из r1, где они уже
|
||
входили в «что проверено и корректно».
|
||
|
||
Материал для обеих ссылок — `docs/reviews/SPEC-REVIEW-500-r1.md`, SHA
|
||
`ca07067386dee0ce2bfad64c169404c9284cd242` (дерево
|
||
`183f1ddc6b6261a2345fcc84502883e6274a9b70`).
|
||
|
||
## Чего не проверял
|
||
|
||
- Достижимость самого рефакторинга (перенос кода) — кода ещё нет, ревью ТЗ
|
||
оценивает план.
|
||
- Автотесты/typecheck/build не запускались — класс изменения r2 докс-онли,
|
||
как и r1; гейты кода — предмет код-ревью (§2.7).
|
||
- Полный построчный аудит `plan-optimize-write.ts`/`space-copy-runtime.ts` на
|
||
предмет иных, не названных ни в r1, ни в r2 расхождений — не проводился;
|
||
фокус остался на утверждениях, которые дельта r2 реально вводит или
|
||
пересчитывает (профили, семь вызывающих, писатели identity), а не на
|
||
повторном полном аудите всех шести исходно названных модулей.
|
||
- Не проверялось, есть ли у `optimize_undo`/`import/apply` собственные
|
||
расхождения того же типа, что найден у `import/apply` (`_adoptInitialSpace`),
|
||
сверх этого одного — например, полная построчная сверка `_cfgEpoch++`/
|
||
`_signer.invalidate`/`_resign` списка на предмет пропущенных вызовов;
|
||
проверена только заявленная в находке High-3 область (выбор пространства).
|
||
Если High-3 будет чиниться правкой этого блока, стоит перечитать соседние
|
||
строки той же функции ещё раз в r3.
|
||
|
||
## Вердикт
|
||
|
||
High: 1 (High-3, в скоупе задачи — чинится автором тем же текстовым
|
||
изменением ТЗ, отдельный issue не заводится). Medium: 0.
|
||
|
||
Обе High-находки r1 закрыты содержательно и проверяемо. Но дельта r2 ввела
|
||
новое понятие («два профиля»), и именно в нём — в описании того, что
|
||
`import/apply` делает сегодня — обнаружена новая, ранее не рецензированная
|
||
фактическая неточность того же класса, что High-1/High-2: уверенное
|
||
утверждение о коде («сегодня их там нет»), которое не подтверждается чтением,
|
||
и на которое опирается конкретный AC (AC3) и конкретный смок (AC4). Задача не
|
||
может уйти в разработку, пока инвентаризация `import/apply` не учитывает
|
||
`_adoptInitialSpace` в ветке `_hasFixedFloor` — иначе рефакторинг рискует
|
||
тихо уронить выбор пространства после импорта в конфигурациях с
|
||
зафиксированным этажом, то есть повторить дефект класса M1 #490 внутри
|
||
задачи, которая создана его закрыть.
|
||
|
||
**Вердикт: жёлтый · заход r2 · блокирующих циклов 2/4 · High: 1 · Medium: 0 → в задаче**
|
||
|
||
---
|
||
|
||
<!-- material-anchors: заполняется конвейером публикации -->
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `issue/500-config-adoption-boundary`, коммит `c35ecf13206b` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||
- Дерево материала: `9a354a9fa366fcca195773d22a15f7947caf5d14`
|
||
```
|
||
git log --all --format='%H %T' | grep 9a354a9fa366
|
||
```
|
||
- ТЗ `docs/specs/500-config-adoption-boundary.md`, блоб `5cfc36e2614361d1e90d4d9a8a9a7358d4bec3b9`
|
||
```
|
||
git log --all --find-object=5cfc36e2614361d1e90d4d9a8a9a7358d4bec3b9 -- docs/specs/500-config-adoption-boundary.md
|
||
```
|
||
- Вердикт конвейера: `yellow` · High 1
|