mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
313 lines
30 KiB
Markdown
313 lines
30 KiB
Markdown
# CODE-REVIEW-282-r2
|
||
|
||
- **Issue:** #282 — стабильная идентичность сегментов стен (ADR Stage 1)
|
||
- **Ветка:** `issue/282-wall-geometry-model`, HEAD `3588284f` (после ребейза на `origin/dev` `30698d6e` — merge-base подтверждён, ветка полностью впереди `dev`)
|
||
- **ТЗ:** `docs/specs/282-stable-wall-segment-identity.md` (spec-review зелёный на r2, `2f30c481`)
|
||
- **Этап:** код-ревью (PROCESS.md §2.7)
|
||
- **Заход:** r2 · блокирующих циклов израсходовано 1 из 4 до этого вердикта
|
||
- **Предыдущий раунд:** `docs/reviews/CODE-REVIEW-282-r1.md`, красный, HEAD на тот момент `5adfdb5d` (SHA назван в документе — не находка). Этот SHA более не существует в истории ветки: после r1 ветка дважды уходила на ребейз (issue-комментарии `#282`, 19:33 и 21:12/21:21), причём второй ребейз (на `dev` `30698d6e`) потребовал реального разрешения конфликта — по словам автора, «post-rebase fixes also make direct backend Optimize cross the v7→v8 barrier, retain authored active-chain coordinates during conversion, and keep a rejected wall transaction atomic» — то есть новую продуктовую логику, а не только смену родителя.
|
||
- **Вердикт:** **красный**
|
||
|
||
## 1. Скоуп проверки
|
||
|
||
Раунд не локален по критериям PROCESS.md §2.10: ребейз на ушедший вперёд `dev`
|
||
с конфликтом, разрешённым новым кодом в самом identity-барьере — ровно та
|
||
подсистема, где r1 нашёл H1/H2. Поэтому разбор полный, но выполнен по факту
|
||
через дельту `9fc213c6..HEAD` (27 файлов, +1793/−1020: коммит `9fc213c6` — это
|
||
сам документ `CODE-REVIEW-282-r1.md`, его родитель `466a6765` — переименованный
|
||
рикбейзом эквивалент `5adfdb5d`, на котором получен вердикт r1). Дельта
|
||
покрывает: `src/houseplan-card.ts` (`_commitPhysicalGeometry`,
|
||
`_applyGeometryState`), `src/wall-segment-model.ts` (lineage hints, off-grid
|
||
guard), `src/plan-geometry-preflight.ts`, backend
|
||
`validation.py`/`wall_segment_model.py`/`websocket_api.py`, тесты и
|
||
mutation-gate. Остальные ~45 файлов исходного 62-файлового диффа r1 (import/export
|
||
remap, i18n, документация, backend-зеркало валидации помимо перечисленного)
|
||
дельтой не задеты — раздел «Унаследовано из r1» ниже.
|
||
|
||
## 2. Как проверялось — таблица гейтов
|
||
|
||
| Гейт | Команда | Результат |
|
||
|---|---|---|
|
||
| Typecheck | `npx tsc --noEmit` | зелёный |
|
||
| Unit/frontend | `npm test` | 1331 тестов, **1329 passed, 1 failed, 1 skipped** — см. H1 |
|
||
| Build + sync бандла | `npm run build && npm run bundle:sync` + `cmp` трёх копий | зелёный, `dist/`, `custom_components/houseplan/frontend/`, `demo/srv/assets/` побайтово идентичны |
|
||
| Docs fingerprint | `node scripts/check-docs.mjs` | зелёный (7 файлов, 10 внешних ссылок) — обязателен, дельта трогает `src/**` |
|
||
| Model invariants (синтетика) | часть `npm test` (`test/model-invariants.test.mjs`, `test/wall-segment-model.test.mjs`) | зелёный |
|
||
| Model invariants (реальная нагрузка) | `node scripts/model-invariants.mjs --config <large-house.mjs, до и после `commitWallSegmentModel`>` | зелёный до миграции («ссылки разрешимы, записи толщины находятся»), зелёный после; `--lattice`: 0.00% шума у узла |
|
||
| Smoke selection | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | 31 прямое совпадение, 5 зарегистрированных связей, 7 слабых — вывод см. §4 |
|
||
| Golden | `npm run golden:verify` | **зелёный**, 129/129 сценариев, в т.ч. оба сценария из M1(r1) |
|
||
| Mutation gate | `node scripts/mutation-gate.mjs --check` | **зелёный**, 226/226 мутантов пойманы, включая 3 мутанта #282 (editor-commit/history-restore/optimize barrier) |
|
||
| Named/registered smokes | 31 прямых + 5 зарегистрированных = 36 запущено | **1 красный** (`smoke_open_passage.mjs`) — см. H2, остальные 35 зелёные |
|
||
| Backend pure (`tests_backend`) | не прогонялся мной — среда без `homeassistant`/`voluptuous`/`.venv-backend` (см. §6) | проверено чтением диффа `validation.py`/`wall_segment_model.py` + нового backend-теста, не исполнением |
|
||
| `pytest tests_backend` (полный, Linux CI) | не прогонялся | пре-релизный гейт, вне объёма код-ревью (§8) |
|
||
| Performance-профили (полный `performance_smoke`) | не прогонялся | пре-релизный гейт; целевой `npm run benchmark:wall-model` дельтой не задет (файл вне диффа) |
|
||
|
||
Дешёвые гейты (`tsc`, `build`, `check-docs`) зелёные. Но `npm test` сам по себе
|
||
**красный** — это не пре-релизный, а обязательный на каждом раунде гейт (§8,
|
||
§2.10), и он не проходит на предъявленном HEAD. Расширенные гейты (invariants,
|
||
smoke-select, golden, mutation-gate, целевые smoke) прогнаны, потому что дельта
|
||
меняет геометрию/ссылки на неё и барьер идентичности — именно то сочетание,
|
||
для которого §8 требует их «по необходимости». Golden и mutation-gate теперь
|
||
полностью зелёные (закрывают M1(r1) и M2(r1) — см. §5 «Закрытие раунда r1»); но
|
||
один из 36 запущенных смоков нашёл новую, не связанную с r1 регрессию (H2).
|
||
|
||
## 3. High — блокирующие находки
|
||
|
||
### H1. `npm test` красный: диагностика больше не соответствует контракту, который сама же проверяет
|
||
|
||
**Файл:** `test/open-passage-contract.test.mjs:70-74`, тест «all write/import
|
||
paths invoke the semantic passage validator».
|
||
|
||
**Воспроизведение:**
|
||
|
||
```
|
||
npm test
|
||
# ...
|
||
not ok 688 - all write/import paths invoke the semantic passage validator
|
||
Expected values to be strictly equal:
|
||
3 !== 2
|
||
location: test/open-passage-contract.test.mjs:71
|
||
```
|
||
|
||
Тест жёстко ожидает ровно 2 вхождения `validate_opening_passages(` в
|
||
`custom_components/houseplan/websocket_api.py`. Последний коммит дельты,
|
||
`3588284f` («fix: preserve passage validation during wall migration»),
|
||
осознанно добавил третий вызов — `validate_opening_passages(candidate_config,
|
||
config_data.get("config"))` в `ws_plan_optimize` **до** барьера
|
||
`commit_wall_segment_model`, рядом с уже существующим вызовом **после** барьера
|
||
(строки 1655 и 1672; третий, старый вызов на 1327 — в `ws_config_set`). Это
|
||
осмысленное и корректное по существу исправление (см. §5, closure M-out
|
||
comment #16 в issue): без него Optimize возвращал общий
|
||
`wall_model_migration_blocked` вместо конкретного
|
||
`invalid_passage_fields`-кода на конфиге с некорректным passage ещё до миграции.
|
||
Но тест, фиксирующий количество мест вызова, не обновлён вместе с фиксом — он
|
||
писался под контракт «ровно 2 вызова» и не отражает намеренно добавленный
|
||
третий.
|
||
|
||
Это прямое нарушение дисциплины: `npm test` — гейт, обязательный **в каждом
|
||
раунде** (§2.10, §8), и он сейчас красный на предъявленном коде. Хендофф-
|
||
комментарий автора к `3588284f` (issue, 22:04:14) называет только узкий прогон
|
||
14 pure-passage-кейсов и process-gate, не полный `npm test` — расхождение
|
||
между «verified» и фактическим состоянием gate.
|
||
|
||
**Доказательство:** тест уже красный на HEAD, механически (`node --test
|
||
test/open-passage-contract.test.mjs`); падение детерминировано, не флуктуация.
|
||
|
||
### H2. AC7 нарушен: opening на обычной стене исчезает со Static-карточки после миграции в v8
|
||
|
||
**Файл:** `demo/smoke_open_passage.mjs` — не тронут этой дельтой (0 строк в
|
||
`git diff --stat origin/dev...HEAD`). Корень — `src/space-render.ts:217-278`
|
||
(`renderSpaceStatic`, единственный потребитель — `space-card.ts:770`, т. е.
|
||
`houseplan-space-card`, документированная в `ARCHITECTURE.md` «Second card»);
|
||
файл не тронут диффом #282 вовсе (`git diff --stat origin/dev...HEAD --
|
||
src/space-render.ts` → пусто).
|
||
|
||
**Воспроизведение:** на `origin/dev` (`30698d6e`, отдельный git-worktree, тот
|
||
же `npm run bundle:sync`):
|
||
|
||
```
|
||
node demo/smoke_open_passage.mjs → OK (все 13 проверок true)
|
||
```
|
||
|
||
На HEAD (`3588284f`), дважды подряд, детерминированно:
|
||
|
||
```
|
||
"staticCutsAndFillsPassage": false
|
||
FAILED (1)
|
||
```
|
||
|
||
Целевая диагностика (см. приложенный debug-прогон) показывает точный механизм:
|
||
после того как `_saveOpening()` проводит объект через
|
||
`_commitPhysicalGeometry` (v7→v8 baseline-replay из `c694f6ea`), опция
|
||
`passage-main` корректно получает `host: {kind:'wall', id:'wall-…', t:0.5}` —
|
||
барьер `hostRoomOpenings` (`src/wall-segment-model.ts:530-556`) тегирует
|
||
**любой** незакреплённый за partition opening (door/window/gate/passage
|
||
одинаково, без разбора по типу) таким `host` при миграции. Но
|
||
`renderSpaceStatic` (`space-render.ts:217-222`, `resolvedHosted`) резолвит
|
||
`opening.host` только через `resolvePartitionOpeningCompat` — функцию,
|
||
понимающую исключительно `host.kind === 'partition'`. Для нашего
|
||
`host.kind === 'wall'` резолюция не находится, `resolvedHosted` пуст для этой
|
||
записи, и `resolvedRawOpenings` (строки 274-278) на строке `return resolved ?
|
||
[materializePartitionOpening(...)] : []` **молча роняет** opening из всего
|
||
дальнейшего рендера Static-карточки — не только из tunnel-заливки прохода, а
|
||
из любой отрисовки (символ, вырез в стене, hit-box).
|
||
|
||
Сам код `src/houseplan-card.ts` (полный/Plan/View card) везде корректно
|
||
различает `host?.kind === 'partition'` от прочих случаев (8295, 8325, 8480,
|
||
12766, 12804, 12958) — регрессия локализована именно в `space-render.ts`,
|
||
единственном месте, которое ADR Stage 1 должен был обновить и не обновил, хотя
|
||
сам ТЗ прямо обещает это в AC7: «v8 → legacy… projection даёт текущим full,
|
||
**static**… consumers прежние inputs» с доказательством «projection snapshots
|
||
+ **existing geometry suite**» — а именно этот existing-geometry смок
|
||
(`smoke_open_passage.mjs`, часть suite с #157) и ловит нарушение AC7.
|
||
|
||
Blast radius не ограничен синтетическим passage из смока: `hostRoomOpenings`
|
||
единообразно тегирует **все** типы openings без partition-host при миграции,
|
||
значит после Optimize/любой структурной правки, переводящей пространство в
|
||
v8, **любой** door/window/gate/passage на обычной (не-partition) стене
|
||
перестаёт отображаться на `houseplan-space-card` — символ, вырез, hit-box.
|
||
Это прямой конфликт с J1/J2 из `docs/SCOPE.md` (план должен показывать, что
|
||
происходит и где) на реальной, документированной с v1.16.0 поверхности
|
||
продукта.
|
||
|
||
**Доказательство:** тест умеет падать — зафиксирована пара «зелёный на
|
||
`origin/dev` `30698d6e` / красный на HEAD `3588284f`» на байт-идентичном файле
|
||
теста, дважды воспроизведено. Причина локализована до конкретных строк (в
|
||
отличие от недиагностированных H1/H2 из r1) через целевой debug-прогон,
|
||
приложенный к разбору находки, а не только «зелёный/красный».
|
||
|
||
**Итог по H1+H2:** оба блокируют. H1 — процессный, лечится синхронизацией
|
||
числа в тесте с фактическим количеством вызовов (тривиально, но не сделано).
|
||
H2 — продуктовый: явное нарушение собственного AC7 задачи, широкий blast
|
||
radius на документированной поверхности продукта, которую эта дельта не
|
||
касалась и не тестировала.
|
||
|
||
## 4. Medium — по существу задачи
|
||
|
||
Новых находок Medium в этом раунде нет. Все четыре Medium из r1 закрыты — см.
|
||
§5 «Закрытие раунда r1». Отдельных находок вне скоупа нет — новый issue не
|
||
заводился.
|
||
|
||
## 5. Закрытие раунда r1
|
||
|
||
| Находка r1 | Чем закрыта | Где это видно |
|
||
|---|---|---|
|
||
| **H1** — guard #278 не отклоняет правку деградированного пространства | `_commitPhysicalGeometry` (`c694f6ea`, `src/houseplan-card.ts:7296-7313`) теперь вызывает `_checkSpacePhysicalGeometry` на `liveCandidate` **до** прохождения identity-барьера (`legacySafe`), а не после — восстановлен исходный порядок guard-а #278 | `node demo/smoke_wall_union_isolation.mjs` → OK, все 11 проверок `true` (перепроверено мной на HEAD, включая три ранее красные: `degradedPhysicalEditRejected`, `rejectedEditHasNoHistoryOrWrite`, `rejectedEditHasLocalizedToast`) |
|
||
| **H2** — 8 именованных structural-smoke красные | Переработан `_commitPhysicalGeometry`: для v7-пространства identity выводится из pre-edit baseline (`baselineSource`/`historyBefore`), а не из уже отредактированной геометрии, плюс lineage hints (`fixedTopologyWallLineageHints`) для сохранения ID при чистом split | Все 8 смоков (`smoke_resize_pointer_real_plan`, `smoke_wall_chain_thickness`, `smoke_unified_wall_tool`, `smoke_wall_face_overlap`, `smoke_edit_walk`, `smoke_editor_tabs`, `smoke_plan_snap_overlay`, `smoke_partition_openings`) перепрогнаны мной на HEAD — все зелёные |
|
||
| **M1** — golden baseline не обновлён под AC15-строку | Baseline не потребовался: фикстура `optimize-orphan-references-*` (`demo/golden/matrix.mjs:695-720`) уже v8/canonical, `Optimize` на ней не мигрирует ни одного сегмента, `r.wallSegmentsMigrated` = 0 → строка `gs.wall_segments_migrated` не рендерится, визуального различия нет | `npm run golden:verify` → 129/129 passed, включая `optimize-orphan-references-dark-en`/`-light-ru`; `demo/golden/baselines/**` не тронут диффом (подтверждено `git log` — пусто), что согласуется, а не противоречит |
|
||
| **M2** — AST/choke-point guard уже, чем заявлено в AC5/`TESTING.md` | Добавлены два новых именованных мутанта: `wall-identity-history-restore-barrier-bypassed` (Undo/Redo restore) и `wall-identity-optimize-barrier-bypassed` (`plan-optimizer.ts`), плюс переименован исходный в `wall-identity-editor-commit-barrier-bypassed` — все три известных writer-пути теперь поимённо покрыты | `node scripts/mutation-gate.mjs --check` → 226/226 `ok`, включая все три `wall-identity-*` мутанта (перепрогнано мной) |
|
||
| **M3** — `wall_model_client_outdated` не срабатывает на реалистичном echo-сценарии | `validate_wall_model_transition` (`custom_components/houseplan/validation.py:185-217`) получил новую ветку `old_model >= 8 and new_model >= 8 and geometry_changed`, сверяющую `_wall_catalog_projection` (только `wall_segments`) — расхождение поднимает `WallModelClientOutdatedError` с текстом «unchanged wall catalogue» | Новый тест `tests_backend/test_wall_segment_model.py::test_stale_client_echoing_v8_catalog_gets_the_named_error` — прочитан, логика соответствует описанному сценарию; **не исполнен** (см. §6, нет `homeassistant`/`voluptuous` в этой среде) — проверено чтением, не исполнением |
|
||
| **M4** — регрессионный тест #248 ослаблен без обновления фикстуры | `test/fixtures/optimize-storage-roundtrip.json` обновлена под полный v8-выход, `test/plan-optimizer.test.mjs:366` вернул строгий `assert.deepEqual(first.config, storageRoundtripFixture.expected.config)` вместо частичного сравнения только `poly` | Тест прогнан мной в составе `npm test` — проходит (часть 1329 passed); дифф `test/plan-optimizer.test.mjs` (`9fc213c6..HEAD`) показывает возврат полного `deepEqual` |
|
||
|
||
Все шесть находок r1 (H1, H2, M1-M4) закрыты предметно, не декларативно. Ни
|
||
одна из них не осталась открытой или не переоткрылась при моей перепроверке.
|
||
|
||
## 6. Унаследовано из r1
|
||
|
||
Из полного 62-файлового диффа r1 (`git diff origin/dev...5adfdb5d` — уже
|
||
недоступен напрямую как SHA, но содержательно эквивалентен диапазону, который
|
||
описывает `docs/reviews/CODE-REVIEW-282-r1.md`), эта дельта не касается и
|
||
поэтому **не переразобрано**, а принято на основании выводов r1 (документ
|
||
`CODE-REVIEW-282-r1.md`, SHA на котором получен вывод — `5adfdb5d`/содержательно
|
||
`466a6765` после ребейза):
|
||
|
||
- **Migration determinism/idempotence** (`deterministicWallSegmentId`,
|
||
суффиксы при коллизии) — r1 §5, не задето дельтой (`test/wall-segment-model.test.mjs`
|
||
дельта затрагивает только новые тест-кейсы, не эту логику).
|
||
- **Backend-зеркало валидации** — unique-id, owner-count, dangling refs,
|
||
edge-geometry parity кроме нового `_wall_catalog_projection`-блока (см. §5
|
||
M3) — r1 §5, файл `wall_segment_model.py` в дельте затронут (+48/-х строк),
|
||
но это тот же lineage-код, что и H1/H2-фикс на фронтенде; backend-инварианты
|
||
(owner-count/dangling refs) не изменены текстуально в дельте.
|
||
- **Import/export remap** (`import_export.py`, `secrets.token_hex(4)`) — не в
|
||
дельте (`git diff --stat 9fc213c6..HEAD` не содержит `import_export.py`),
|
||
наследуется из r1 §5 без переразбора.
|
||
- **i18n-ключи** (`toast.wall_model_migration_blocked`,
|
||
`toast.wall_model_client_outdated`, `gs.wall_segments_migrated`,
|
||
`wall_model.reason.*`) — не в дельте, наследуется из r1 §5.
|
||
- **Документация** (`WALL-THICKNESS.md`, `CANVAS.md`, `USER-GUIDE.{ru,}.md` —
|
||
кроме `TESTING.md`, который в дельте, см. §5 M2) — наследуется из r1 §5.
|
||
- **`config-field-registry.mjs`** — не в дельте, наследуется.
|
||
- **Трейлеры и changelog структуры коммитов r1** — наследуется; трейлеры
|
||
каждого нового коммита дельты проверены заново мной отдельно (см. §7).
|
||
|
||
Обоснование, почему это не требует переразбора несмотря на общий полный охват
|
||
раунда (§1): дельта физически не касается перечисленных файлов
|
||
(`git diff --stat 9fc213c6..HEAD`, приведён в §1), а находки этого раунда (H1,
|
||
H2) обнаружены и локализованы именно в затронутых дельтой файлах
|
||
(`test/open-passage-contract.test.mjs` тестирует код `websocket_api.py`,
|
||
который в дельте; `space-render.ts` хоть и не в дельте, но находка H2 —
|
||
следствие данных, которые генерирует именно delta-код `wall-segment-model.ts`,
|
||
т.е. вызвана дельтой, а не унаследованным кодом).
|
||
|
||
## 7. Что проверено и корректно
|
||
|
||
- **Трейлеры** — все 6 коммитов дельты (`c694f6ea`, `41d7a63f`, `c24792b6`,
|
||
`35fce64a`, `38b03549`, `3588284f`) несут `Issue: #282`; четыре
|
||
`User-Visible: yes` (`c694f6ea`, `35fce64a`, `38b03549`, `3588284f`) —
|
||
каждый правит оба changelog в том же коммите (проверено `git show --stat`
|
||
на каждом).
|
||
- **Backend-барьер Optimize** (`35fce64a`) — `ws_plan_optimize` теперь ведёт
|
||
тот же v7→v8-барьер, что и frontend `_commitPhysicalGeometry`
|
||
(`commit_wall_segment_model` + `too_large`-проверка после миграции +
|
||
`WallSegmentMigrationError` в списке перехватываемых) — прочитано полностью,
|
||
логика симметрична фронтенду.
|
||
- **Guard-переупорядочивание #278** (H1(r1)) — `legacySafe`-проверка на
|
||
`liveCandidate` до барьера, restore на отказ — прочитано и подтверждено
|
||
прогоном (см. §5).
|
||
- **Lineage hints** (`fixedTopologyWallLineageHints`,
|
||
`adoptWallSegmentModelCandidateInPlace`) — прочитаны; логика различения
|
||
`preferredIds`/`positionalIds` через `collinearOverlap` (защита от
|
||
«застарелого ординала» при сплите) выглядит осмысленной и подтверждена всем
|
||
доступным smoke/mutation набором.
|
||
- **Off-grid guard с authored path** (`38b03549`) — `wallModelOffGridValueCount`
|
||
теперь учитывает `_path` (активная рисуемая цепочка) как базовую линию
|
||
роста, не как новое отклонение — юнит-тест на это добавлен и проходит.
|
||
- **Три мутанта #282** (editor-commit/history-restore/optimize barrier) —
|
||
реально пойманы (§2, §5 M2).
|
||
- **`npm run invariants`-эквивалент на реальной нагрузке** — прогнан на
|
||
`demo/fixtures/large-house.mjs` (3 пространства, 1788 записей по прошлым
|
||
измерениям Stage 0) до и после `commitWallSegmentModel`: ссылки разрешимы,
|
||
записи толщины находятся, 0% шума у узла.
|
||
- **35 из 36 запущенных смоков** зелёные (см. §2, §4).
|
||
|
||
## 8. Чего не проверял и почему
|
||
|
||
- **`pytest tests_backend`, даже pure-subset** — среда исполнения не содержит
|
||
`homeassistant`, а `custom_components/houseplan/__init__.py` безусловно
|
||
импортирует `homeassistant.components.frontend`, поэтому даже
|
||
не-`test_ha_*.py` модули (`test_validation.py`, `test_wall_segment_model.py`)
|
||
не собираются без полного HA-пакета — это не селективный пропуск
|
||
`conftest.py`, а полный отказ коллекции. `.venv-backend` в этой среде не
|
||
существует (в отличие от cloud-agent окружения, описанного в `AGENTS.md`).
|
||
M3 (§5) и общая корректность `validation.py`/`wall_segment_model.py`-диффа
|
||
проверены **чтением**, не исполнением — честно назвать это ограничением, не
|
||
«verified».
|
||
- **Полный `performance_smoke`** — пре-релизный гейт (§8); целевой
|
||
`benchmark:wall-model` дельтой не затронут (файл вне диффа), поэтому не
|
||
перепрогонялся отдельно в этом раунде — цифры из r1/хендоффа автора (167-284
|
||
мс на p95 при бюджете 500 мс) остаются в силе, так как измеряемый код не в
|
||
дельте.
|
||
- **7 «слабая связь» смоков** (`smoke_align_guides`, `smoke_card_tool_conflict`,
|
||
`smoke_editor_gestures`, `smoke_esc_dialogs`, `smoke_island_rooms`,
|
||
`smoke_optional_space_model`, `smoke_pan_any_zoom` — связь только по
|
||
`_path`) — не прогонялись: методология идентична r1 (§6 её документа) — уже
|
||
найденных блокирующих находок достаточно для возврата, и один из 36 более
|
||
сильно связанных смоков (`open_passage`) уже красный. Автору стоит
|
||
прогнать все 7 вместе с полным набором перед следующей сдачей.
|
||
- **Глубокий line-by-line аудит `buildAtoms`/lineage-логики
|
||
(`src/wall-segment-model.ts`, весь диапазон 218 изменённых строк)** — не
|
||
выполнен formal-proof, только чтение ключевых веток плюс полное
|
||
black-box-покрытие (36 смоков, 226 мутантов, 129 golden, полный `npm test`
|
||
кроме H1). Смок `smoke_open_passage.mjs` — прямое доказательство, что
|
||
black-box-покрытие не исчерпывающее; глубже эту логику не разбирал, так как
|
||
найденных H1/H2 достаточно, чтобы вернуть задачу автору (тот же принцип, что
|
||
в r1 §3 «проверять глубже уже не требуется по методологии»).
|
||
|
||
## 9. Резюме
|
||
|
||
Красный вердикт, второй подряд, но по существу другим находкам, чем r1: все
|
||
шесть находок r1 (2 High + 4 Medium) закрыты предметно и перепроверены
|
||
исполнением (кроме M3 — чтением, backend-харнесс недоступен). Однако именно
|
||
дельта, закрывшая r1, внесла две новые блокирующие находки:
|
||
|
||
- **H1** — `npm test` красный прямо сейчас: тест на количество вызовов
|
||
`validate_opening_passages(` не обновлён под намеренно добавленный третий
|
||
вызов из последнего коммита дельты. Механически тривиально, но нарушает
|
||
обязательный на каждом раунде гейт.
|
||
- **H2** — нарушение собственного AC7 задачи: `houseplan-space-card` теряет
|
||
**любой** wall-hosted opening (не только passage) после миграции
|
||
пространства в v8, потому что `src/space-render.ts` — единственный
|
||
нетронутый диффом #282 потребитель `opening.host` — не различает
|
||
`host.kind === 'wall'` от `host.kind === 'partition'` и молча роняет
|
||
нерезолвленные openings из рендера. Реальный blast radius на
|
||
документированной поверхности продукта, конфликтующий с J1/J2
|
||
`docs/SCOPE.md`.
|
||
|
||
Задача возвращается в «В разработке». Оба фикса локальны и не должны требовать
|
||
повторного полного разбора всей подсистемы в r3, если дельта останется
|
||
локальной к `websocket_api.py`-тесту и `space-render.ts`; но `space-render.ts`
|
||
до сих пор не в диффе #282 вовсе — стоит проверить на этом же файле и другие
|
||
пути, потребляющие `opening.host` (hidden-isometric, если он существует
|
||
отдельно), а не только `houseplan-space-card`.
|