Issue: #198 User-Visible: no
18 KiB
Код-ревью 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.