mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 13:18:58 +00:00
175 lines
17 KiB
Markdown
175 lines
17 KiB
Markdown
# SPEC-REVIEW-162-r1
|
||
|
||
- Issue: [#162](https://github.com/Matysh/houseplan-card/issues/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» не применим.
|
||
|
||
## Как проверялось
|
||
|
||
1. Прочитан issue #162 целиком (тело + 4 комментария аналитики/актуализации/ТЗ) через `gh issue view`.
|
||
2. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` (весь файл, включая §2.9/§2.10, §4, §7.1–7.2, §12) и `docs/USER-GUIDE.ru.md` §16 (роботы-пылесосы).
|
||
3. Прочитан канонический документ подсистемы `docs/VACUUM.md` целиком.
|
||
4. Прочитано ТЗ `docs/specs/162-vacuum-map-space-routing.md` целиком (21 раздел).
|
||
5. **Каждое фактическое утверждение §3.2 «Подтверждённая техническая база» проверено чтением кода на текущем HEAD (`086b3682`, `dev` слит):**
|
||
- `src/houseplan-card.ts:11334` — фильтр `devs` по `d.space === space.id` — подтверждено буквально;
|
||
- `src/houseplan-card.ts:12347` — выбор матрицы по `calibration?.[mapNow]` и silent `continue` — подтверждено буквально (строка `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-артефактов).
|
||
6. Проверено существование каждого инструмента/модуля, на который ссылается ТЗ и не которые не выдумка: `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`.
|
||
7. Проверен «храповик» `test/core-file-budget.test.mjs`: потолки 13659/14323 подтверждены; фактическая длина файлов на HEAD `086b3682` — `wc -l` даёт 13544/14319 (в ТЗ 13545/14320 — расхождение на 1 объясняется разной трактовкой конечного перевода строки, не находка).
|
||
8. Проверен прецедент общей 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» — не новый паттерн, а продолжение существующего.
|
||
9. Прогнаны дешёвые гейты (зелёного 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 однозначны и проверяемы, техническая база ТЗ подтверждена
|
||
чтением кода без расхождений. Вердикт — жёлтый: правка ограничивается одним
|
||
разделом ТЗ, повторный цикл может быть точечным.
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `issue/162-vacuum-map-space-routing`, коммит `086b3682e96e` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||
- Дерево материала: `2b5694be2febfde87f514b1261d14525b54a3c90`
|
||
```
|
||
git log --all --format='%H %T' | grep 2b5694be2feb
|
||
```
|
||
- ТЗ `docs/specs/162-vacuum-map-space-routing.md`, блоб `22176219387e22288dcb3197806937edd7ad073c`
|
||
```
|
||
git log --all --find-object=22176219387e22288dcb3197806937edd7ad073c -- docs/specs/162-vacuum-map-space-routing.md
|
||
```
|