mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 12:49:56 +00:00
167 lines
14 KiB
Markdown
167 lines
14 KiB
Markdown
# Спек-ревью #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).
|
|
|
|
---
|
|
|
|
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
|
|
|
## Материал раунда
|
|
|
|
- Ветка: `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
|
|
```
|