Files
houseplan-card/docs/reviews/CODE-REVIEW-566-r2.md
T
2026-09-13 23:45:20 +00:00

18 KiB
Raw Blame History

CODE-REVIEW — issue #566, заход r2

Материал: 733cd5b86b301fd29c1eddebd88d6b09cd231071 (ветка issue/566-stale-layout-notes, один коммит поверх материала r1: 733cd5b8 на f724cca6). Рабочая копия уже стояла на этом SHA, git fetch/checkout на другой коммит не выполнялись.

Заход: r2 · блокирующих циклов израсходовано 1 из 4 (r1 был жёлтым и цикл потратил).

Трек: инфраструктурный (§1), тот же, что в r1. Коммит 733cd5b8 трогает только scripts/model-invariants.mjs, scripts/mutation-registry.mjs, test/geometry-corpus.test.mjs, test/model-invariants.test.mjs — те же четыре файла, что были в скоупе r1, новых файлов и подсистем дельта не касается. Ребейза на ушедший вперёд dev не было (материал лежит прямо на f724cca6, где его оставил r1), контракт поведения не менялся — менялась только точность одной уже введённой этим же issue диагностики. Разбор веду по дельте, а не заново.

Вердикт: жёлтый на входе r1 → зелёный на r2 · High: 0 · Medium: 0

Дельта раунда

git diff f724cca6..733cd5b8 — 260 строк, 5 файлов (из них 201 — сам документ ревью r1, которым эта дельта была вызвана, содержательных 59):

  • scripts/model-invariants.mjs:213-244 — ветка stale_layout_space стала трёхзначной: owner теперь 'absent' | 'live' | 'unverified' вместо булева ownerGone. unverified (ключ не резолвится ни как rl_, ни как grp_, ни как маркер) получил собственный kind: 'unknown_owner' с той же формулировкой, что уже использует ветка unknown_owner живого пространства пятью строками ниже (src/model-invariants.mjs:269-270).
  • scripts/mutation-registry.mjs — новый мутант invariants-claim-proof-for-an-unknown-owner (сводит unverified к live), плюс перепривязка find-якорей двух прежних мутантов #566 к новым строкам (owner === 'absent', removedMarkerIds.has(key) ? 'absent').
  • test/model-invariants.test.mjs — тест #566 теперь сверяет owner по каждому из трёх kind отдельно (stale_layout_space/unknown_owner) и, главное, сверяет ТЕКСТ detail для каждого, включая отрицательное утверждение assert.doesNotMatch(note.detail, /владелец жив/) для unknown_owner — именно эта проверка отсутствовала в r1 и пропустила находку.
  • test/geometry-corpus.test.mjs — фикстура c6-stale-layout меняет ожидание с ['references/stale_layout_space', 'references/stale_layout_space'] на ['references/unknown_owner', 'references/unknown_owner'].

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
Medium: наблюдение stale_layout_space утверждало «владелец жив» и для ключа, который не резолвится ни во что (только removedMarkerIds.has(key) проверялся, обратное трактовалось как live) Введено третье состояние unverified: removedMarkerIds.has(key) ? 'absent' : activeMarkerIds.has(key) ? 'live' : 'unverified'. unverified получает kind: 'unknown_owner' с формулировкой «владелец не найден в конфигурации (возможно устройство HA); пространства тоже нет» — не утверждает то, чего не знает scripts/model-invariants.mjs:222-243; воспроизведённый в r1 пример (unknown_device_id_1234 на мёртвом пространстве) при повторной проверке ниже даёт unknown_owner, а не stale_layout_space
Указанный в r1 механизм пропуска: тест сверял только kind, не текст detail, поэтому асимметрия (первая по ключу запись случайно была живой комнатой) не ловилась Тест переписан: assert.deepEqual по owner для каждого kind отдельно, assert.match/assert.doesNotMatch на detail внутри цикла по всем нотам своего вида, а не по первой найденной test/model-invariants.test.mjs:150-172, дважды прогнан лично, зелёный
Предписанная r1 правка: различить live/unverified «по образцу уже существующего unknown_owner», используя activeMarkerIds Использован именно activeMarkerIds (тот же Set, что вычислен в начале функции для живого пространства), формулировка detail дословно перекликается с формулировкой соседней ветки unknown_owner scripts/model-invariants.mjs:227,241-243 vs scripts/model-invariants.mjs:269-270
Побочный эффект находки: фикстура c6-stale-layout (реальный кейс из корпуса #560) — оба «сиротских» ключа там на самом деле незарегистрированные владельцы, а не подтверждённо живые маркеры, то есть сама фикстура иллюстрировала баг expectedNotes фикстуры пересмотрены на unknown_owner вместо stale_layout_space — не подогнаны молча, тест явно проверяет новую классификацию на реальных данных test/geometry-corpus.test.mjs:253-256; сквозной CLI-прогон ниже подтверждает

Мутант находки (invariants-claim-proof-for-an-unknown-owner) прогнан лично, «поймано 1 из 1» — не принято на слово автора.

Унаследовано из r1

Без повторной проверки приняты пункты раздела «Проверено и не вызывает вопросов» документа docs/reviews/CODE-REVIEW-566-r1.md (материал f724cca6), поскольку дельта r2 их не касается:

  • классификация rl_/grp_ по allRoomIds/allAreas, собранным по всем пространствам, и сравнение с приёмом space-reference-repair.ts:148 — дельта не трогает эту ветку;
  • сами мутанты #566 (invariants-blame-every-stale-position, invariants-forgive-a-vanished-position-owner) как факт существования и назначения — но их привязка к коду перепроверена заново в этом раунде (см. «Как проверялось»), поскольку рефакторинг сдвинул их якоря;
  • три реанимированных мутанта #568-приёма (inner-span-reads-whole-edge-thickness, safe-resize-legacy-midpoint-fail-open, optimizer-micro-interval-cleanup-disabled) в src/wall-thickness.ts/src/plan-optimizer.ts — файлы вне дельты r2, не тронуты;
  • трейлеры коммитов dedcedc8/7ecdafef/f724cca6 (Issue: #566, User-Visible: no) — не переоцениваю, дельта их не меняет;
  • сквозной CLI-прогон на корпусе #560 как метод проверки (не только юнит-тест) — метод унаследован, но результат перепрогнан на новом коде (см. ниже), так как дельта прямо меняет вывод этой команды;
  • отсутствие golden/check-docs/pytest/perf — src/** и custom_components/**/*.py дельтой r2 тоже не тронуты, вывод не изменился.

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

Дешёвый Validate (tsc/test/build/bundle-ratchet, mutation-gate по шардам) на 733cd5b8 подтверждён зелёным CI-прогоном (ссылка дана автором в issue), поэтому не перегонялся целиком. Сверх этого лично прогнано в песочнице ревью — то, что относится к точечной дельте r2, плюс сквозная проверка на реальных данных:

Гейт Команда Результат
Новый мутант находки node scripts/mutation-gate.mjs --id=invariants-claim-proof-for-an-unknown-owner поймано 1 из 1
Мутант «обвинить всех» (перепривязка) node scripts/mutation-gate.mjs --id=invariants-blame-every-stale-position поймано 1 из 1
Мутант «простить пропавшего» (перепривязка) node scripts/mutation-gate.mjs --id=invariants-forgive-a-vanished-position-owner поймано 1 из 1
Структура реестра node scripts/mutation-gate.mjs --check exit 0, 733 записи ok, find-якоря уникальны
Юнит-тесты дельты tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test --test-name-pattern="#566|#252|#254" test/model-invariants.test.mjs test/geometry-corpus.test.mjs 8 + 26 тестов, всё зелёное
Полный test/geometry-corpus.test.mjs node --test test/geometry-corpus.test.mjs 26/26 зелёных
Инварианты сквозным CLI на фикстуре #560 node scripts/model-invariants.mjs --config test/fixtures/560-corpus/c6-stale-layout.json «Инварианты выполнены… Наблюдений (не нарушения): 2. 2 — позиции без записи маркера» (было «2 — позиции живых владельцев на удалённых пространствах» до правки)
Дословное совпадение find-строк трёх мутантов с текущим кодом точечная проверка через String.includes на содержимом файла все три совпали
Сводка по kind в CLI-выводе не хардкодит текст помимо словаря чтение scripts/model-invariants.mjs:790-798 (noteSummary) подтверждено чтением: titles[kind] общий словарь, переклассификация транслируется без отдельной правки

check-docs, golden, backend pytest, performance-профили, полный отбор мутантов (selectForDiff) не запускались — те же основания, что в r1: src/** и custom_components/**/*.py не тронуты дельтой r2, рендер и Python-бэкенд не задеты, ничего перфочувствительного в диффе нет; полный отбор для дельты избыточен — все затронутые дельтой мутанты (3 из отбора) прогнаны поштучно, остальные 91 из ранее выбранных 94 не касаются изменённых строк и уже подтверждены зелёными шардами Validate в предыдущих заходах.

Находки

Нет. Правка 733cd5b8 делает ровно то, что предписал r1: вводит третье состояние unverified, использует уже вычисленный activeMarkerIds, дословно перекликается формулировкой с соседней веткой unknown_owner, и закрывает механизм пропуска (тест теперь сверяет текст причины, а не только вид). Побочная проверка (noteSummary, реальная фикстура #560) подтверждает, что переклассификация корректно доходит до пользователя CLI без скрытых мест, где старое двухзначное деление могло остаться зашитым.

Проверено и не вызывает вопросов

  • Мутант находки r1 (invariants-claim-proof-for-an-unknown-owner) — прогнан лично, «поймано 1 из 1», а не принят на слово.
  • Перепривязка двух прежних мутантов #566 к сдвинутым строкам — прогнаны лично после рефакторинга, оба всё ещё «поймано 1 из 1»; --check подтверждает уникальность и валидность новых find-якорей.
  • Тест test/model-invariants.test.mjs — теперь различает три состояния по owner и по тексту detail для каждого, включая отрицательную проверку для unknown_owner; асимметрия из r1 таким тестом уже не проходит.
  • Фикстура c6-stale-layout — пересмотрена не формально: два «сиротских» ключа в ней (c6-orphan-1, c6-orphan-2) не зарегистрированы как маркеры вообще, то есть по существу это и есть unverified, а не live — старое ожидание фикстуры само было артефактом найденного в r1 дефекта.
  • Комментарии в коде (scripts/model-invariants.mjs:215-219, 237-240) называют находку r1 по существу («сказать «владелец жив» про ключ, который ни во что не резолвится, значит заявить доказанность там, где её нет») — не общие слова, а прямая привязка к причине правки.
  • Трейлеры коммита 733cd5b8 — Issue: #566, User-Visible: no; верно, правка меняет только текст диагностики CLI-скрипта, не продукт.
  • Один коммит-заход, как требует процесс после возврата на доработку.

Чего не проверял

  • tsc --noEmit, npm test целиком, npm run build со сверкой бандла — не перегонял: зелёный Validate на 733cd5b8 уже есть, дельта r2 не расширяет класс изменений (по-прежнему только инфраструктурные файлы). Полный npm test не гонял отдельно от полного tsc -p tsconfig.test.json, который выполнил для сборки тестов — это тот же шаг, что и в CI.
  • check-docs, golden, pytest tests_backend, performance — не запускал: src/** и custom_components/**/*.py дельтой r2 не тронуты.
  • Полный отбор 94 мутантов, которые дифф затягивает в CI (selectForDiff) — как и в r1, проверил только те, что относятся к содержанию дельты этого раунда (3: новый плюс два перепривязанных); на остальные полагаюсь на зелёный Validate CI по всем шести шардам.
  • Полевой конфиг владельца issue (4 записи на 2 пространствах) по-прежнему недоступен ревьюеру — как и в r1, использовал фикстуру c6-stale-layout из репозитория и подтвердил сквозным CLI-прогоном, а не только юнит-тестом.

Что дальше

Задача закрыта: единственная Medium-находка r1 устранена по существу, новый код доказан мутантом, тест больше не пропускает асимметрию, которая привела к находке. High нет, Medium нет. Возврат в S7-code-review не требуется — вердикт зелёный, к автору не возвращается.


Материал раунда

  • Ветка: issue/566-stale-layout-notes, коммит 733cd5b86b30 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 7ce15af6d80e3c0746cc4399044e7e9e79da35de
    git log --all --format='%H %T' | grep 7ce15af6d80e
    
  • Тело issue: b30fafcfa79bb1bfe11625206ac445bb83a257f0b821202f064d1c4cfdcb2dd7
  • Вердикт конвейера: green · High 0