Files
houseplan-card/legacy/reviews/v1.65.0/CODE-REVIEW-198-r3.md
T
Claudeandclaude[bot] 0991c45374 fix(tools): архив переписывает относительные ссылки перенесённых документов (#682)
Ревью #682 r1, Medium: перенос добавляет документу уровень вложенности
(`docs/reviews/X.md` → `legacy/reviews/<тег>/X.md`, `docs/specs/` →
`legacy/specs/`), а относительные ссылки внутри перенесённых документов и в
соседях, ссылавшихся на них, никто не пересчитывал — на `97d19268` 53 битые
ссылки в 46 файлах (заявление «все 26 резолвятся» в `7feb6177` было верно
только до переноса документов ревью). Гейты архив не смотрят.

`reviews-archive.mjs`: `repairLinks` пересчитывает ссылку, если она не
резолвится от нового места, а цель находится от нового или старого места
через карту переносов; битая и до переноса ссылка не трогается. `--apply`
делает это само, `--repair-links=<rev>` — для всех переименований
`<rev>..HEAD`, `--check-links` печатает битые. Этим коммитом
`--repair-links=origin/dev` переписал ровно 53 ссылки в 46 файлах; остались
две прежние «...»-заглушки в CODE-REVIEW-448-r2 (битые и на dev). Тесты:
перенесённый документ, сосед со ссылкой в архив, ТЗ со ссылкой на позже
перенесённое ревью, битая-до-переноса не трогается, в `legacy/` битых нет;
мутант `reviews-archive-links-from-new-place-only`. PROCESS §2.10 и
DEVELOPMENT › Release называют переписывание и `--check-links`.

Issue: #682
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 22:10:47 +00:00

18 KiB
Raw Blame History

Код-ревью issue #198 — очистка изолированного микро-интервала толщины через Optimize (цикл r3)

  • Цикл: r3/4
  • Вердикт: зелёный · High: 0 · Medium: 0
  • Ветка: issue/198-optimize-micro-interval
  • Диапазон коммитов: git log --oneline origin/dev..HEAD = 3a68efa (ТЗ), cd17a0b (ревью ТЗ r1), 9b05dd5 (реализация), 833e8e5 (ревью-документ r1), a797752 (follow-up тесты на замечания r1), ef22b23 (ревью-документ r2), 5ce3cee (исправление находки r2 — изоляция AC5-фикстуры), 2d5fec0 (обновление screenshot provenance после технического ребейза)
  • Диапазон диффа: git diff origin/dev...HEAD
  • ТЗ: docs/specs/198-optimize-micro-interval.md (ревью ТЗ зелёное, r1)
  • Предыдущий цикл: docs/reviews/CODE-REVIEW-198-r2.md (жёлтый · Medium-1 — новый AC5-тест не хермитичен к сценарию входной мутации, который он должен доказывать)

Скоуп

Тот же, что в r1/r2: явное действие «Общие настройки → Оптимизировать планы» получает строго ограниченный lossy-шаг — схлопывание одиночного изолированного интервала толщины стены короче половины шага сетки между двумя одинаковыми коллинеарными соседями, при отсутствии room-vertex/ opening-узла на его концах (контракт §6 ТЗ). Runtime и редактор остаются lossless.

Этот цикл закрывает единственную оставшуюся находку r2 (Medium-1, AC5): тест «micro-interval cleanup is endpoint, input-order and room-order invariant without mutation» (test/plan-optimizer.test.mjs) строил baseline-вызов на непроклонированном fixture, а затем читал fixture.walls[1] для reversedMiddle уже после этого вызова — если бы collapseIsolatedWallThicknessIslands начала мутировать входной массив на месте, baseline-вызов сам испортил бы источник для второго сценария раньше, чем снимался снимок «до», и тест прошёл бы тривиально.

Продуктовый код (src/plan-optimizer.ts, src/wall-thickness.ts) в этом цикле не менялся — git diff ef22b23..HEAD -- src/ пуст. Единственная содержательная правка — 5ce3cee (3 строки в test/plan-optimizer.test.mjs): baseline теперь строится из независимого baselineFixture = microIntervalFixture(), а detached для этого вызова клонируется через structuredClone, так что fixture.walls[1], используемый дальше для reversedMiddle, никаким предыдущим вызовом не затрагивается.

Коммит 2d5fec0 — provenance-обновление docs/images/screenshots.json после того, как ветка была технически перебазирована ещё раз (см. «Как проверялось»): git diff содержимого #198 между старым и новым срезом ребейза пуст, изменился только sourceFingerprint/скриншот вслед за уже слитыми в dev посторонними issue. Класс C, User-Visible: no, вне продуктового поведения #198.

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

Инцидент с устаревшим срезом ветки. Сессия ревью изначально была развёрнута на локальном HEAD=b411a58 (после git fetch origin dev в начале сессии). Перед вынесением вердикта повторный git fetch origin issue/198-optimize-micro-interval показал, что удалённая ветка уже продвинулась до 2d5fec0 — тем же набором из восьми коммитов #198, но перебазированным поверх более нового origin/dev, куда за время ревью успел влиться не связанный с #198 issue #205 (vacuum trail resume grace). Сверил git diff b411a58 2d5fec0 — единственная разница это файлы #205 (уже находящиеся в dev, значит не относящиеся к диапазону #198) плюс 1-байтовый шум provenance-скриншота; для всех файлов, содержательных для #198 (src/plan-optimizer.ts, оба тестовых файла, ТЗ, три предыдущих ревью-документа, docs/WALL-THICKNESS.md, targeted smoke), diff между двумя срезами ребейза пуст. Переключился на актуальный 2d5fec0 (git checkout 2d5fec0) и весь гейт-прогон ниже выполнен именно на нём — иначе вердикт судил бы уже не тот код, что лежит на удалённой ветке.

Гейт Результат Команда
typecheck green npx tsc --noEmit
unit green, 924/924 (было 917/917 в r2 на предыдущем ребейзе; рост числа тестов — от слияния #204/#205 в dev, не от этого issue) npm test
build + 3 копии бандла green, побайтово идентичны npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js && cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js
targeted smoke (AC7) green, все 10 подпроверок node demo/smoke_optimize_micro_interval.mjs
mutation-gate, целевой мутант (AC9) мутант пойман 1/1 node scripts/mutation-gate.mjs --id=optimizer-micro-interval-cleanup-disabled
mutation-gate, полный self-check green, 37/37 node scripts/mutation-gate.mjs --check
docs-gate (затронут 2d5fec0) green node scripts/check-docs.mjs --external
whitespace чисто git diff --check origin/dev...HEAD
process-gate (офлайн) green, предупреждений 0 node scripts/process-gate.mjs (диапазон origin/dev..HEAD, 8 коммитов)
трейлеры всех 8 коммитов диапазона Issue: #198 на каждом; ровно один User-Visible: yes — на 9b05dd5 (реализация), и именно в нём оба changelog правятся тем же коммитом git log, git show 9b05dd5 --stat
адверсариальная проверка исправленного AC5-теста тест ловит ровно ту мутацию, из-за которой в r2 был выставлен Medium-1 см. «Проверка дисциплины „тест должен уметь падать“» ниже

Не прогонялось и почему (без изменений относительно r1/r2 — диапазон по продуктовому коду и UI-поверхности не менялся с 9b05dd5):

  • npm run golden:verify и смежные smoke_grid_snap/smoke_align_guides — этот цикл не трогает src/plan-optimizer.ts, рендер, геометрию, стили или UI-пайплайн Optimize; изменился только один тестовый файл на 3 строки плюс provenance двух docs-артефактов. Тот же код уже прогонялся против той же UI-поверхности зелёным в r1.
  • python -m pytest tests_backend -q — ни один файл custom_components/**/*.py не входит в диапазон #198 ни в одном из трёх циклов (подтверждено: git diff origin/dev...HEAD --name-only | grep '\.py$' — пусто).
  • Производительность/бенчмарки — не названы в AC; изменения этого цикла — три строки теста и docs-provenance, никаких perf-путей не затронуто.
  • Полный набор из 127+ browser-смоков — диапазон r3 не касается ни одной новой UI-поверхности; единственный релевантный (smoke_optimize_micro_interval) прогнан и зелёный.

Проверка дисциплины «тест должен уметь падать» для исправленного AC5-теста

Не ограничился чтением диффа 5ce3cee — воспроизвёл ровно ту адверсариальную мутацию, которой в r2 было доказано отсутствие защиты, и подтвердил, что теперь тест её ловит.

В src/plan-optimizer.ts временно заменил иммутабельный

out = out.map((wall) => {
  if (!exactMatches(wall, candidate)) return wall;
  exactMatch = true;
  return wall.cm === target ? wall : { ...wall, cm: target };
});

на мутирующий цикл

for (const wall of out) {
  if (!exactMatches(wall, candidate)) continue;
  exactMatch = true;
  wall.cm = target;
}

(тот же самый объект, на который ссылаются и out, и исходный walls, поскольку out = walls.slice() — поверхностная копия массива). Пересобрал test-build (npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs) и прогнал изолированно только целевой тест: node --test --test-name-pattern="invariant without mutation" test/plan-optimizer.test.mjs.

Результат: 1 fail — assert.deepEqual(walls, beforeWalls, 'helper must not mutate the walls array or entries') красный, walls[2].cm меняется с 15 на 22. Это ровно тот класс регрессии, для которого r2 показал тривиальное прохождение теста. Откатил мутацию (git checkout -- src/plan-optimizer.ts), пересобрал test-build заново и подтвердил: тот же тест снова зелёный (1/1), а npm test на чистом дереве — 924/924, git status --short пуст.

Правка 5ce3cee действительно устраняет корень находки r2: baseline теперь строится из отдельного вызова microIntervalFixture() (baselineFixture), а detached-комната для этого вызова клонируется, так что второй сценарий (reversedMiddle, walls, rooms) строится из fixture, который первый вызов никогда не видел — «до»-снимок снимается до единственного вызова над этим конкретным входом, ровно как просил r2.

Находки

Не выявлено. Обе Medium-находки r1 (AC5, AC8) и оставшаяся Medium-находка r2 (нехерметичность AC5-теста) закрыты и подтверждены исполнением, а не только чтением диффа.

Что проверено и корректно

  • Находка r2 (Medium-1, AC5) закрыта полностью и доказана заявленным способом. См. адверсариальную проверку выше — тест теперь дискриминативен именно для сценария мутации входа на месте, который он обязан ловить.
  • Продуктовый код не менялся с r1/r2. git diff ef22b23..HEAD -- src/ пуст: контракт §6 ТЗ (строгий порог 0.5 × pitch, коллинеарность через roomWallProfile, topology-guard по room vertex/open cut, non-cascading snapshot через targets.size === 1, разрешение неоднозначности) не требовал повторной проверки по существу в r2 и в этом цикле — он был предметно проверен чтением и тестами в r1 (см. docs/reviews/CODE-REVIEW-198-r1.md) и с тех пор не тронут ни одним коммитом. Перепроверены только пересобранные гейты на актуальном коде — все зелёные.
  • Ребейз безопасен. Технический ребейз перед r3 (упомянутый владельцем в issue-комментарии «Технический ребейз перед r3») не изменил ни одной строки, относящейся к #198: единственная разница между срезом ребейза, на котором сессия стартовала (b411a58), и актуальным (2d5fec0) — это уже слитый в dev посторонний issue #205 и провенанс-обновление скриншота. Конфликт при ребейзе (упомянутый в комментарии «объединением changelog/STATUS») не оставил следов расхождения: docs/CHANGELOG.md, docs/CHANGELOG.ru.md, docs/STATUS.md содержат обе записи (#198 и соседние issue), пересечения или потери контента не найдено.
  • AC1–AC4, AC6, AC7, AC9, AC10 — доказаны так же, как в r1/r2 (перечисление unit/negative-matrix/smoke/mutation-gate), код не менялся, гейты перепрогнаны и зелёные на актуальном коде.
  • AC8 — закрыт в r1-follow-up, подтверждён адверсариальной мутацией в r2 (degradeWalls() мутация walls[0].cm = 999 ловится новым unit), код и тест с тех пор не менялись.
  • Трейлеры и changelog. Все 8 коммитов диапазона несут Issue: #198; ровно один User-Visible: yes (9b05dd5), и именно в нём правятся оба changelog в том же коммите — подтверждено git show 9b05dd5 --stat. Ветка называется issue/198-optimize-micro-interval, соответствует трейлерам.
  • Три копии бандла синхронны. cmp дал побайтовое совпадение dist/, custom_components/houseplan/frontend/ и demo/srv/assets/ после npm run build на актуальном HEAD=2d5fec0.
  • Docs-provenance корректен. node scripts/check-docs.mjs --external зелёный (7 файлов, 10 внешних ссылок) на актуальном срезе после второго ребейза.

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

  • Полный HA backend harness (tests_backend под настоящим Home Assistant) — диапазон #198 не содержит правок в custom_components/**/*.py ни в одном цикле.
  • npm run golden:verify и полный browser-smoke набор (127+ файлов) — диапазон r3 не трогает рендер/геометрию/UI-пайплайн сверх уже проверенного в r1; продуктовый код не менялся.
  • Производительность/бенчмарки — не названы в AC, изменения r3 — три строки теста и provenance.
  • Повторное чтение контракта §6 в src/plan-optimizer.ts построчно — выполнено предметно в r1 (см. CODE-REVIEW-198-r1.md) и не повторялось заново построчно в r2/r3, так как git diff подтверждает отсутствие изменений в этом файле с тех пор; вместо повторного чтения перепрогнаны все относящиеся гейты и целевая мутация на актуальном коде.

Вывод

High-находок нет, Medium-находок нет. Обе находки r1 (AC5, AC8) и оставшаяся находка r2 (нехерметичность AC5-теста) закрыты и лично перепроверены исполнением — включая независимое воспроизведение той же адверсариальной мутации, которой r2 доказал дефект. Продуктовый код с r1 не менялся; все обязательные и относящиеся к диапазону гейты зелёные на актуальном срезе удалённой ветки (2d5fec0), включая процессный process-gate.mjs. Вердикт: зелёный, задача готова к слиянию в dev.