From a48bde0b1d437d7ca9687da3ea97233a4222325c Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 15:33:08 +0000 Subject: [PATCH] docs: review document for #162 Issue: #162 User-Visible: no --- docs/reviews/SPEC-REVIEW-162-r2.md | 166 +++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 docs/reviews/SPEC-REVIEW-162-r2.md diff --git a/docs/reviews/SPEC-REVIEW-162-r2.md b/docs/reviews/SPEC-REVIEW-162-r2.md new file mode 100644 index 00000000..60a6ad22 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-162-r2.md @@ -0,0 +1,166 @@ +# Спек-ревью #162 — заход r2 + +- Issue: [#162](https://github.com/Matysh/houseplan-card/issues/162) — «Многоэтажный робот: сопоставление карт пространствам отдельно от базы» +- Этап: spec (PROCESS.md §2.4) +- Документ ТЗ: `docs/specs/162-vacuum-map-space-routing.md` +- Заход: r2, SHA `4c56b389` +- Заход r1: SHA `086b3682`, вердикт жёлтый, документ `docs/reviews/SPEC-REVIEW-162-r1.md` (закоммичен на `dd2b5d74`) +- Блокирующих циклов израсходовано: 1 из 4 (жёлтые/красные тратят бюджет; этот заход зелёный — бюджет не меняется, PROCESS.md §4, #227) + +## Скоуп разбора + +Правка r1→r2 — точечный ответ на единственную находку прошлого раунда (Medium, +§11.3 «compatible source»), без ребейза на ушедший вперёд dev и без смены +контракта поведения где-либо ещё. Дельта локальна: + +``` +git diff 086b3682..4c56b389 -- docs/specs/162-vacuum-map-space-routing.md +``` + +затрагивает только: +- заголовок статуса документа (строка 7); +- §11.3 «Legacy trails» (переписан, добавлен §11.3.1 «Правило усыновления»); +- таблицу AC — строка AC14; +- таблицу §16.5 — новый мутант M-H и итоговое число мутантов (7→8); +- changelog в конце документа — новый bullet. + +Ни один другой раздел, AC или мутант диффом не затронут. Это укладывается в +критерий локальности из инструкции (не ребейз, не новая подсистема, объём +несопоставим с исходной задачей) — разбор ПОЛНЫЙ по дельте, остальное +наследуется из r1 без повторной проверки. + +## Как проверялось + +1. Прочитан вердикт r1 и весь тред комментариев issue #162, включая ответ + автора на Medium (комментарий 2026-09-03T15:28:32Z) и уточнение SHA + (2026-09-03T15:29:15Z: правка перебазирована с `3ed21af5` на `4c56b389` + после коммита артефакта ревью `dd2b5d74`). +2. Сверен факт: `git log --oneline 086b3682..4c56b389` → ровно два коммита + (`dd2b5d74` документ ревью, `4c56b389` правка ТЗ). Рабочее дерево на + `4c56b389` соответствует HEAD ветки — ревьюется именно эта версия. +3. Перечитан переписанный §11.3 и новый §11.3.1 целиком, сверены все + перекрёстные ссылки внутри документа (§8.1 `VacuumMapRoute.source`/`map_id`, + §8.2 nullish-контракт, §7.3 explicit conversion, AC13, AC14, M-H, §12). +4. Фактическая посылка нового текста — «серверная запись run физически несёт + только `{map_id, started, ended, points}`, и recorder всегда пишет её по + единственному текущему `marker.vacuum.source`» — перепроверена чтением + `custom_components/houseplan/trails.py` заново (не унаследовано из r1, + так как это ядро самой правки): + - `TrailBook.on_point` создаёт `cur = {"map_id": map_id, "started": now, + "ended": None, "points": []}` (`trails.py:98`) — поля `source` нет, + утверждение верно; + - `async_refresh` строит `pairs: source → [(marker_id, vac)]` из + `v.get("source")` — ровно один root source на marker в момент подписки + (`trails.py:167-184`); `_sample` вызывает `book.on_point(marker, ...)` + для источника, на который сейчас подписан marker (`trails.py:350-384`). + Это подтверждает, что «единственный свидетель» — текущий + `marker.vacuum.source`, а не историческая величина, восстановить которую + неоткуда — с этим ограничением текст ТЗ прямо работает (fail-closed при + несовпадении/пустом source), а не скрывает его. +5. Проверено, что старый неопределённый термин «compatible source» пропал из + нормативного текста (остался только в заголовке статуса документа как + ссылка на закрытую находку — `grep` подтверждает единственное вхождение). +6. Проверена согласованность схемы: `VacuumMapRoute.map_id`/`source` — + обязательные `string` (§7.1, docs/specs/162-vacuum-map-space-routing.md:254-260), + `MarkerVacuumCfg.source` — `string | null` (строка 267), поэтому проверка + «`rootSource` пуст/удалён» в §11.3.1 корректно покрывает `null`/`undefined`/`""`. +7. Пересчитано число мутантов в таблице §16.5 (7 строк M-A…M-G + новая + M-H = 8) против итоговой фразы «каждый из восьми» — совпадает. +8. Дешёвые гейты кода не запускались: дельта раунда — правка одного .md-файла + в `docs/specs/`, `src/**` и `custom_components/**/*.py` не затронуты + (`git diff --stat 086b3682..4c56b389` — единственный файл, + `docs/specs/162-vacuum-map-space-routing.md`). `npx tsc --noEmit`, + `npm test`, `npm run build`, `node scripts/check-docs.mjs`, инварианты + модели и смоки к этой правке неприменимы: они гоняют код, а код не менялся. + Значения из r1 (tsc чисто, build собирается, `npm test` 1819/0/1, + идентично базовой линии ТЗ) наследуются без повторного прогона — правка их + не затрагивает. + +## Находки + +Нет. Единственная находка r1 (Medium, §11.3, в скоупе) закрыта содержательно, +новых находок делтой не внесено. + +## Закрытие раунда r1 + +| Находка r1 | Чем закрыта | Где это видно | +|---|---|---| +| §11.3 п.2: термин «compatible source» не определён; серверная схема run не хранит `source`, поэтому «совместимость источника» нельзя вычислить сравнением с содержимым run буквально; AC14 не сводится к детерминированному unit-тесту | §11.3 переписан: явно назван единственный свидетель — корневой `marker.vacuum.source` в момент записи, а не поле run. Введена чистая функция `adoptLegacyRun(run, routes, rootSource)` с явным двухусловным отбором кандидатов (`map_id` строго, `source` — только если `rootSource` непустой) и ровно тремя исходами (unique / `orphan_run` / `ambiguous_run`), все три fail-closed кроме unique. AC14 переформулирован под эти три исхода со ссылкой на §11.3.1; добавлен мутант M-H на конкретную регрессию («при двух кандидатах берётся первый») | `docs/specs/162-vacuum-map-space-routing.md` §11.3 и §11.3.1 (строки ~529–566 после правки), таблица AC строка AC14, таблица §16.5 строка M-H, changelog-bullet в конце файла | + +Факт закрытия подтверждён не заявлением автора, а перечитыванием переписанного +текста и независимой перепроверкой его фактической посылки в +`custom_components/houseplan/trails.py` (см. «Как проверялось», п.4) — текст +не просто переименовал термин, а свёл его к вычислимому по сохранённым данным +правилу. + +## Унаследовано из r1 + +Без повторной проверки в этом раунде — дельта их не задевает: + +- Все факты §3.2 «Подтверждённая техническая база», сверенные в r1 чтением + `src/houseplan-card.ts:11334,12347`, `src/types.ts`, `src/vacuum.ts:310,322`, + `src/space-deletion.ts`, `src/space-reference-repair.ts`, + `test/core-file-budget.test.mjs` — правка их не касается. +- Полнота обязательных разделов §7.1 PROCESS.md (21 раздел) и то, что все 20 + AC пронумерованы и снабжены способом доказательства — делта меняет + формулировку только одной строки (AC14), остальные 19 не тронуты. +- Overpromise в `docs/USER-GUIDE.ru.md:1504`, зафиксированный как часть + продуктового обоснования задачи. +- Результаты дешёвых гейтов на коде: `npx tsc --noEmit` чисто, `npm run build` + собирается, `npm test` → 1819 pass / 0 fail / 1 skip (совпадает с базовой + линией ТЗ). +- Продуктовая рамка (docs/SCOPE.md, J1/J6) и оценка сложности/риска — не + предмет этого раунда, изменений в §1–§6, §9–§10, §13–§15, §17 нет. + +Источник наследования: `docs/reviews/SPEC-REVIEW-162-r1.md`, SHA `086b3682`. + +## Что проверено и корректно (по дельте r2) + +- §11.3.1 определяет `adoptLegacyRun` как чистую функцию от только сохранённых + данных — тестируема unit-тестом без HA, как и заявлено. +- Оба поля, на которые опирается правило (`route.map_id`, `route.source`), + обязательные строки в схеме `VacuumMapRoute` (§7.1) — отбор кандидатов не + зависит от опциональных полей, кроме сознательно опционального `rootSource`. +- Три исхода (unique/`orphan_run`/`ambiguous_run`) исчерпывающие и + fail-closed по обоим отрицательным веткам: ничего не рисуется, `route_id` + не дописывается, запись не переписывается и не удаляется — согласуется с + общей fail-closed доктриной документа (§8, §12). +- AC13 («byte-for-byte until routing edit») не сломан: §11.3 явно оставляет + поведение при отсутствии explicit routes прежним «байт в байт». +- Число мутантов в тексте и таблице согласовано (8). +- Никаких новых недоказанных допущений (незаявленных как предположение) + правка не вносит — единственное новое фактическое утверждение (структура + serverной записи run и то, что recorder пишет её по текущему root source) + проверено чтением кода, а не принято на слово. + +## Чего не проверял + +- Реализацию — её нет, этап spec. Соответствие `adoptLegacyRun`/`trails.py` + зеркалированию будет предметом code-review. +- Остальные 19 AC и разделы вне §11.3/§11.3.1/AC14/§16.5 — не проверялись + повторно, приняты по наследованию из r1 (см. выше), т.к. дельта их не + касается. +- Гейты кода (tsc/test/build/check-docs/invariants/smoke) в этом раунде не + запускал: дельта не трогает `src/**` и `custom_components/**/*.py`, значения + из r1 остаются в силе без изменений входных файлов. + +## Вердикт + +Зелёный. Находка r1 закрыта по существу и проверяемо, новых находок нет, +бюджет циклов не расходуется (PROCESS.md §4, #227). + +--- + + + +## Материал раунда + +- Ветка: `issue/162-vacuum-map-space-routing`, коммит `4c56b389ca9e` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `ec0c63dac8b0e34afb2f04aa30708ee5b8b3216e` + ``` + git log --all --format='%H %T' | grep ec0c63dac8b0 + ``` +- ТЗ `docs/specs/162-vacuum-map-space-routing.md`, блоб `a8edcfbe6c37700095f6f8582258eac4fcb58ad9` + ``` + git log --all --find-object=a8edcfbe6c37700095f6f8582258eac4fcb58ad9 -- docs/specs/162-vacuum-map-space-routing.md + ```