Files
houseplan-card/docs/reviews/SPEC-REVIEW-162-r2.md
T
2026-09-03 15:33:08 +00:00

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 без повторной проверки.

Как проверялось

  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