14 KiB
Спек-ревью #162 — заход r2
- Issue: #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 без повторной проверки.
Как проверялось
- Прочитан вердикт r1 и весь тред комментариев issue #162, включая ответ
автора на Medium (комментарий 2026-09-03T15:28:32Z) и уточнение SHA
(2026-09-03T15:29:15Z: правка перебазирована с
3ed21af5на4c56b389после коммита артефакта ревьюdd2b5d74). - Сверен факт:
git log --oneline 086b3682..4c56b389→ ровно два коммита (dd2b5d74документ ревью,4c56b389правка ТЗ). Рабочее дерево на4c56b389соответствует HEAD ветки — ревьюется именно эта версия. - Перечитан переписанный §11.3 и новый §11.3.1 целиком, сверены все
перекрёстные ссылки внутри документа (§8.1
VacuumMapRoute.source/map_id, §8.2 nullish-контракт, §7.3 explicit conversion, AC13, AC14, M-H, §12). - Фактическая посылка нового текста — «серверная запись 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), а не скрывает его.
- Проверено, что старый неопределённый термин «compatible source» пропал из
нормативного текста (остался только в заголовке статуса документа как
ссылка на закрытую находку —
grepподтверждает единственное вхождение). - Проверена согласованность схемы:
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/"". - Пересчитано число мутантов в таблице §16.5 (7 строк M-A…M-G + новая M-H = 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 test1819/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— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
ec0c63dac8b0e34afb2f04aa30708ee5b8b3216egit log --all --format='%H %T' | grep ec0c63dac8b0 - ТЗ
docs/specs/162-vacuum-map-space-routing.md, блобa8edcfbe6c37700095f6f8582258eac4fcb58ad9git log --all --find-object=a8edcfbe6c37700095f6f8582258eac4fcb58ad9 -- docs/specs/162-vacuum-map-space-routing.md