17 KiB
SPEC-REVIEW-162-r1
- Issue: #162
- ТЗ:
docs/specs/162-vacuum-map-space-routing.md, редакция 2, коммит086b3682(веткаissue/162-vacuum-map-space-routing, HEAD detached на момент ревью) - Трек: полный (P2 bug, сложность 8/10, риск 9/10 — критерии
small§5 PROCESS.md не выполнены: миграция конфига, несколько поверхностей, новый UX-контракт) - Заход: r1 (первый прогон
S4-spec-reviewдля этого issue — предыдущие редакции ТЗ его не проходили) - Вердикт: жёлтый
Скоуп
Многоэтажный робот-пылесос: House Plan хранит калибровку по map_id, но
marker несёт одно space, поэтому дока и live-оверлей фактически привязаны к
одному этажу. ТЗ вводит marker.vacuum.map_routes[] — явное сопоставление
(source, map_id) → target space, независимое от положения дока, с
resolution-контрактом, editor UI, backend trail routing, lifecycle/export и
release-артефактами. Продукт: J1 (достоверное местоположение), J6
(конфигурация остаётся корректной при нескольких пространствах) —
подтверждено в docs/SCOPE.md.
Задача проверялась целиком (заход r1, документ первый), раздел «Унаследовано из r0» не применим.
Как проверялось
- Прочитан issue #162 целиком (тело + 4 комментария аналитики/актуализации/ТЗ) через
gh issue view. - Прочитаны
docs/SCOPE.md,AGENTS.md,PROCESS.md(весь файл, включая §2.9/§2.10, §4, §7.1–7.2, §12) иdocs/USER-GUIDE.ru.md§16 (роботы-пылесосы). - Прочитан канонический документ подсистемы
docs/VACUUM.mdцеликом. - Прочитано ТЗ
docs/specs/162-vacuum-map-space-routing.mdцеликом (21 раздел). - Каждое фактическое утверждение §3.2 «Подтверждённая техническая база» проверено чтением кода на текущем HEAD (
086b3682,devслит):src/houseplan-card.ts:11334— фильтрdevsпоd.space === space.id— подтверждено буквально;src/houseplan-card.ts:12347— выбор матрицы поcalibration?.[mapNow]и silentcontinue— подтверждено буквально (строкаif (!matrix || matrix.length !== 6) continue;);src/types.ts— состав полейMarker.vacuum(семь полей, без ссылки на space) — подтверждено;src/vacuum.ts:310,322—vacMapIdFromAttrs/vacMapIdWithFallback, nullish-цепочка — подтверждено;custom_components/houseplan/trails.py— run как{"map_id", "started", "ended", "points"},can_resume_trail_run()сравнивает толькоmap_id, полеsourceв записи run отсутствует вовсе — подтверждено чтением всего файла;docs/USER-GUIDE.ru.md:1504— «Для каждогоmap_idхранится собственная калибровка, поэтому многоэтажный робот может работать с несколькими пространствами» — подтверждено, это и есть завышенное обещание, которое ТЗ обязано снять (§18 release-артефактов).
- Проверено существование каждого инструмента/модуля, на который ссылается ТЗ и не которые не выдумка:
scripts/mutation-gate.mjs,scripts/smoke-select.mjs,scripts/model-invariants.mjs,scripts/bundle-budget.mjs(поляinitialViewFiles/lazyEditorFilesв нём есть),src/space-deletion.ts(collectSpaceMarkerDependencies,createSpaceDeletionCandidate),src/space-reference-repair.ts(repairSpaceReferences, доктрина «не трогает вложенные calibration-данные» — подтверждена дословно в шапке файла),src/render-device-snapshot.ts. - Проверен «храповик»
test/core-file-budget.test.mjs: потолки 13659/14323 подтверждены; фактическая длина файлов на HEAD086b3682—wc -lдаёт 13544/14319 (в ТЗ 13545/14320 — расхождение на 1 объясняется разной трактовкой конечного перевода строки, не находка). - Проверен прецедент общей TS/Python JSON-фикстуры (
test/vacuum.test.mjs:446+tests_backend/test_trail_recorder.py:425+test/fixtures/vacuum-attrs/map-id.json) — план §16.2 «shared TS/Python route fixture» — не новый паттерн, а продолжение существующего. - Прогнаны дешёвые гейты (зелёного Validate на
086b3682нет):npx tsc --noEmit→ чисто, без ошибок;npm test→ 1819 pass / 0 fail / 1 skip — совпадает с базовой линией, заявленной в ТЗ §3.4 буквально;npm run build→ бандл собран без ошибок (16.1s).check-docs.mjs/model-invariants.mjs/смоки/golden не прогонялись — на этом коммите нет изменений вsrc/**(диф спец-ревью — толькоdocs/specs/162-*.md), эти гейты относятся к код-ревью после реализации, не к ревью ТЗ.
Находки
Medium (в скоупе) — M1: «compatible source» в §11.3.2 не определено и не проверяемо по факту схемы run
Файл: docs/specs/162-vacuum-map-space-routing.md:537 (§11.3, пункт 2)
Формулировка ТЗ: «Run без route_id: … 2. при explicit routes сопоставляется
только если ровно один route имеет тот же map_id и compatible source».
Почему это находка. Термин «compatible source» встречается в документе
ровно один раз и нигде не раскрыт — ни определением, ни примером, ни
блоком «принято предположительно». При этом текущая (и не меняемая этим ТЗ)
схема серверного run в custom_components/houseplan/trails.py —
{"map_id", "started", "ended", "points"} — не содержит поля source
вообще (TrailBook.on_point, can_resume_trail_run, весь файл прочитан
целиком). Легаси-run физически не хранит, с каким источником он записан —
только map_id. Значит по факту хранимых данных «совместимость источника»
нельзя вычислить сравнением полей run и route: это либо (а) требует ещё не
описанного косвенного механизма (например: «совместим», если единственный
кандидат-route на данный map_id в момент показа одновременно проходит
resolver §8.2 как ready/needs_calibration, то есть реально наблюдается на
этом source прямо сейчас), либо (б) реализация обязана будет угадать
конкретную трактовку самостоятельно — то есть ровно тот случай, который
раздел «Ambiguity is asked, not guessed» и §8.2 этого же ТЗ («Exact
source/map identity никогда не ретаргетится молча») запрещают в остальных
местах документа.
Это не отвлечённая формальность: от этого определения зависит AC14 («Legacy run без route id отображается только при unique match; ambiguity fail-closed | TS/backend unit») — как написано, эту проверку нельзя детерминированно закодировать unit-тестом, потому что неизвестно, что именно тест обязан признать «совместимым».
Почему Medium, не High. Вопрос не продуктовый (пользователь не видит слово «source» — он видит, показался путь или нет) и не требует владельца: это техническая формулировка алгоритма, которую автор ТЗ решает сам по §7.1 PROCESS.md («всё, чего пользователь не наблюдает, агенты решают сами»). Фикс — одна-две фразы в §11.3, например: «под "compatible source" понимается X» либо явная привязка к уже описанному в §8.2 механизму. Он не расширяет скоуп и не меняет ни одного другого раздела ТЗ.
Что нужно от автора при возврате: заменить «compatible source» точным правилом, вычислимым из данных, которые run реально хранит (или из результата resolver §8.2 на момент показа), и явно указать это правило рядом с AC14.
Что проверено и признано корректным
- Обязательные разделы §7.1 PROCESS.md присутствуют и в правильном порядке: сценарий/персоны (§2), что видно до/после (§2), проблема (§1), скоуп/не-скоуп (§5–6), контракт поведения (§4, §7–12), UX (§9), модель данных и миграция (§7, §7.3, §20), i18n (§13), AC1…AC20 с указанным способом доказательства (§15), план автотестов (§16), риски (§19), откат (§20), release-артефакты (§18).
- Все 20 AC пронумерованы, для каждого назван способ доказательства
(
unit/backend/smoke/golden/комбинации); ни один не оставлен без доказательства. - §16.5 «Мутанты защитных контрактов» заранее перечисляет 7 мутантов (M-A…M-G) с точным тестом-свидетелем на каждый — это ровно тот формат, который потребует код-ревью (#435/PROCESS §2.7), заложен уже на этапе ТЗ.
- Технический раздел §3.2 «Подтверждённая техническая база» — не голословен:
каждая строка кода, на которую ссылается ТЗ, проверена чтением и совпадает
дословно (см. «Как проверялось», п.5). Расхождений между заявленным и
фактическим поведением текущего
devне найдено. - §3.4 (ограничения после первой редакции — храповик core-file-budget,
ленивый граф/бюджет бандла, требование мутантов, взаимодействие с
space-deletion.ts/space-reference-repair.ts, снимок фактов) — все упомянутые файлы, скрипты и потолки существуют и имеют указанные значения; план реализации (§17) корректно распределяет новый код по отдельным модулям, не расширяяhouseplan-card.ts/houseplan-editor-runtime.ts, что обязательно при остатке бюджета в единицы строк. - Overpromise, который ТЗ обязуется снять (
docs/USER-GUIDE.ru.md:1504), подтверждён буквально — раздел §18 «Release-артефакты» верно называетdocs/USER-GUIDE.ru.mdдля правки. - Продуктовая рамка соответствует
docs/SCOPE.md: J1/J6, View/kiosk — блокирующие поверхности, editor touch — best effort (согласуется сdocs/TOUCH-SUPPORT.md), Static space card корректно исключена (§6, вне скоупа по существующей доктрине —docs/VACUUM.mdне описывает live-оверлей для неё). - Ни одного продуктового вопроса, вынесенного на автора вместо ревьюера, не обнаружено — все технические развилки (имена модулей, route id формат, раскладка файлов) явно помечены в §21 «assumed, change freely», как того требует §7.1 PROCESS.md.
- Downgrade/rollback (§20) явно называет небезопасный путь (полный откат backend после canonical-записи) и safe-путь — соответствует требованию «откат» из чек-листа DoR §2.5.
- Дешёвые гейты — тесты в разделе выше — зелёные и совпадают с
зафиксированной в ТЗ базовой линией
npm test(1819/0/1) один-в-один.
Чего не проверял
- Смоки (
demo/smoke_*.mjs),golden:verify,pytest tests_backend, performance-профили иcheck-docs.mjs— не прогонялись: на этом SHA нет изменений вsrc/**/custom_components/**/*.py(диф ревью — только текст ТЗ), эти гейты относятся к циклу код-ревью после реализации (PROCESS.md §8 прямо разделяет: «полные наборы — предрелизный/код-ревью гейт, не гейт ТЗ»). - Модельные инварианты (
npm run invariants) — геометрия комнат/стен этим ТЗ не затрагивается (маршруты пылесоса не являются рёбрами/толщиной), поэтому не запускались. - Не проверялась реализация — её ещё нет, ТЗ находится на входе в
S5-ready. - Мутанты M-A…M-G из §16.5 не прогонялись — они относятся к будущему коду, которого на этом коммите нет; на этапе ТЗ проверялось только то, что для каждого защитного контракта назван мутант и тест-свидетель (это выполнено).
Итог
Один Medium-дефект в скоупе задачи: термин «compatible source» в §11.3.2
не определён и, с учётом того что легаси-run вообще не хранит поле source,
не может быть проверен буквальным сравнением данных — AC14 в текущей
формулировке не поддаётся детерминированному тесту. High-находок нет,
остальные 19 AC однозначны и проверяемы, техническая база ТЗ подтверждена
чтением кода без расхождений. Вердикт — жёлтый: правка ограничивается одним
разделом ТЗ, повторный цикл может быть точечным.
Материал раунда
- Ветка:
issue/162-vacuum-map-space-routing, коммит086b3682e96e— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
2b5694be2febfde87f514b1261d14525b54a3c90git log --all --format='%H %T' | grep 2b5694be2feb - ТЗ
docs/specs/162-vacuum-map-space-routing.md, блоб22176219387e22288dcb3197806937edd7ad073cgit log --all --find-object=22176219387e22288dcb3197806937edd7ad073c -- docs/specs/162-vacuum-map-space-routing.md