Issue: #33 User-Visible: no
18 KiB
CODE-REVIEW-33-r2
- Issue: https://github.com/Matysh/houseplan-card/issues/33
- ТЗ: docs/specs/033-config-schema-lifecycle.md (ревизия 3, зелёное ревью SPEC-REVIEW-33-r2)
- Ветка:
issue/33-config-schema-lifecycle - SHA материала ревью:
15fa33b9d965ee463a8e61e8de2c13dec41a1832(git rev-parse HEAD) - Заход: r2 · блокирующих циклов израсходовано 1 из 4
- Предыдущий раунд: CODE-REVIEW-33-r1, жёлтый, на SHA
572cf928969fd8849299170ba3d42ff61d7ef423 - Дельта этого раунда:
git diff 572cf928..15fa33b9— один коммит15fa33b9(«fix: close CODE-REVIEW-33-r1 M1-M3»), трейлерыIssue: #33,User-Visible: no
Скоуп раунда
Разбор ПО ДЕЛЬТЕ, не заново — согласно PROCESS.md §2.9/§2.7 и правилу этого цикла. Дельта
локальна: 6 файлов, ни один не src/**, ни один не геометрия/схема:
| Файл | Изменение |
|---|---|
scripts/config-audit.mjs |
сужение MIGRATION_STATUSES (закрывает M3) |
test/config-schema-parity.test.mjs |
переписан AC7-тест: весь src/**, актуальное имя дампа (закрывает M1) |
docs/CHANGELOG.md, docs/CHANGELOG.ru.md, docs/ARCHITECTURE.md |
удалён задвоенный абзац/раздел (закрывает M2) |
docs/reviews/CODE-REVIEW-33-r1.md |
публикация документа предыдущего раунда (артефакт конвейера, не код) |
Рантайм-поведение конфига не менялось (не менялось и в r1); дельта — это ровно три точечные правки по числу находок r1, без побочных изменений скоупа. Условие «разбор остаётся полным» (ребейз, новый контракт поведения, новая подсистема, объём дельты сопоставим с задачей) не выполняется ни по одному пункту — сокращаю объём разбора до дельты и наследую остальное.
Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
M1 — AC7-тест искал строку 'config-schema-manifest', которой нет нигде после переименования файла в f4318721; тест не мог падать. |
Тест переписан: рекурсивный обход всего src/** (readdirSync с withFileTypes, фильтр по .ts/.js/.mjs/.json), поиск актуальной строки 'config-schema.json'. |
test/config-schema-parity.test.mjs:96-106. Проверено исполнением заново в этом раунде: дописал в src/houseplan-card.ts строку fetch('./scripts/config-schema.json') (тот же приём, что в r1) и перезапустил тест — теперь not ok 3, тест красный с точным указанием файла; после отката файла — снова зелёный (node --test test/config-schema-parity.test.mjs → 3/3). |
M2 — абзац/раздел #33 задвоен в CHANGELOG.md, CHANGELOG.ru.md, ARCHITECTURE.md (второй feat-коммит повторил правки первого). |
Вторая копия удалена в каждом из трёх файлов. | Диф 15fa33b9: docs/ARCHITECTURE.md −24 строки, docs/CHANGELOG.md −7, docs/CHANGELOG.ru.md −7. Проверено чтением и подсчётом в этом раунде: grep -c на характерную фразу в каждом файле даёт 1 (было 2). Заголовок ## Schema as the source of truth (#33, 2026-08-30) встречается в ARCHITECTURE.md один раз (docs/ARCHITECTURE.md:1503). |
M3 — config-audit.mjs расширил exit-код 3 на drop-on-validation и decision-required; для decision-required (group_lights/exclude_integrations — живые поддерживаемые поля, ждущие решения #44) это семантически неверно и вводит в заблуждение. |
MIGRATION_STATUSES сужен ровно до контракта ТЗ Блока 3: migrate-on-write, migrate-on-settings-save, deprecated-read. Новый комментарий объясняет, почему оба статуса исключены. |
scripts/config-audit.mjs:80-88. Проверено исполнением заново, своими фикстурами (тест задачи это не покрывает — см. ниже): {settings:{group_lights:true}} → exit=0 (было бы 3 до фикса); {spaces:[{...,aspect:1.2}],...} → exit=0. Контракт спеки (docs/specs/033-config-schema-lifecycle.md:102-105, «3 — migration available (найдены поля со статусом migrate-*/deprecated-read)») теперь воспроизведён буквально. |
Ни одна находка не закрыта «на словах»: по каждой — либо перегон теста с воспроизведением
регрессии (M1), либо построчная сверка файла (M2), либо собственный ad hoc прогон,
раз штатный test/config-audit.test.mjs фикстуры с decision-required/drop-on-validation
не содержит и не мог бы отличить старое поведение от нового (M3 — см. «Находки» ниже,
это Low, не блокирует раунд).
Унаследовано из r1
Из CODE-REVIEW-33-r1 (SHA 572cf928) принимается без повторной проверки — дельта этого
раунда не касается доказательной базы этих пунктов:
- AC1 (свежесть/детерминизм/полнота манифеста, 265 путей) — доказано в r1
исполнением (
dump-config-schema.py --check, pytest freshness). Файлы манифеста и дамп-скрипт не менялись между572cf928и15fa33b9. - AC2/AC3 (parity 8 пар + анти-гниение allow-list) — доказано в r1 мутантом
schema-manifest-enum-drift(1/1).scripts/schema-compat-allowlist.mjsиvalidation.pyне менялись в дельте; сам parity-блок теста (строки 1-92) не тронут — правка коснулась только AC7-блока в конце файла. - AC4 (полнота registry) — доказано в r1 мутантом
registry-selector-dead-decision(1/1).scripts/config-field-registry.mjsне менялся в дельте. - AC5 (lossless-фикстуры, round-trip будущих полей) — доказано в r1 pytest на трёх
фикстурах
test/fixtures/config-lifecycle/. Фикстуры не менялись в дельте. - enforcedBy для 4 реализованных механизмов (show_all, weather_entity, ripple, aspect/segments) — подтверждено чтением кода в r1, код этих механизмов не менялся.
- Трейлеры и разнесение классов (build-коммит
572cf928отдельно отfeat) — оценено в r1, не относится к дельте этого раунда (у неё свой коммит со своими трейлерами, проверен отдельно выше). - Не-скоуп (поведение конфига, судьба group_lights/exclude_integrations как таковая — не паспорт, а решение #44) — подтверждено в r1, дельта его не расширяет и не сужает.
Что проверено в этом раунде дополнительно к таблице закрытия
- AC6 (exit-коды 0/3/2) пересмотрен целиком, не только по факту закрытия M3:
штатный
test/config-audit.test.mjs(не менялся в дельте) по-прежнему зелёный —current.json→ 0,oldest-supported.json→ 3 (за счётshow_all/weather_entity/ripple, которые остались вMIGRATION_STATUSES;aspectв той же фикстуре имеет статусdrop-on-validationи больше не участвует, но фикстура всё равно даёт 3 по другим находкам — тест не мог бы заметить сужение сам по себе),broken.json→ 2. Отсутствие штатного теста именно наdecision-required/drop-on-validationв изоляции — Low-разрыв в покрытии AC6, не блокирует раунд (см. «Находки»). - AC7 пересмотрен целиком (это и есть предмет M1): тест теперь читает весь
src/**(102 файла верхнего уровня плюс поддиректорииeditors/,render/,i18n/— итого 115 файлов поfind src -type f), а не 2 файла, как раньше. Проверил, что обход не пропускает поддиректории (реализация рекурсивна,entry.isDirectory()идёт вwalkснова) и что фильтр расширений (.ts/.js/.mjs/.json) не даёт ложноположительных срабатываний на бинарные/генерируемые артефакты.
Гейты, прогнанные в этом раунде
Зелёного Validate на SHA 15fa33b9 нет — прогнал дешёвые гейты лично.
| Гейт | Команда | Результат |
|---|---|---|
| typecheck | npx tsc --noEmit |
чисто |
| unit (полный) | npm test |
1608 pass / 0 fail / 1 skipped — то же число, что в r1, дельта не сдвинула счётчик |
| docs fingerprint | node scripts/check-docs.mjs |
«Documentation checks passed (7 files, 10 external links)» — не обязателен (дельта не трогает src/**), прогнан из осторожности, т.к. дёшев |
| AC7-тест изолированно, с воспроизведением регрессии | node --test test/config-schema-parity.test.mjs (дважды: с посаженным fetch('./scripts/config-schema.json') в src/houseplan-card.ts, и после отката) |
красный с посаженной регрессией → зелёный (3/3) после отката — тест умеет падать, доказано в этом раунде заново |
| M3 руками, две ad hoc фикстуры | node scripts/config-audit.mjs <config с только group_lights> и <config с только aspect> |
оба exit=0 (было бы 3 до фикса) |
Не прогонял и почему:
npm run build+ сверка трёх копий бандла — дельта не трогает ни один файлsrc/**и ни один бандлируемый скрипт;dist/**/custom_components/.../frontend/**не менялись (git diff --stat 572cf928..15fa33b9подтверждает — их нет в списке). Билд не мог измениться, гонять его — тратить время без нового сигнала.npm run bundle:budget— по той же причине (нет изменений в бандлируемом коде).npm run invariants— дельта не трогает геометрию/layout/marker.space/open_spans; повторный прогон унаследован из r1 (фикстуры lifecycle не менялись).- Мутанты
schema-manifest-enum-drift/registry-selector-dead-decision— код, который они бьют (validation.py,config-field-registry.mjs, allowlist), не менялся в дельте; результат унаследован из r1. python -m pytest tests_backend -q— дельта не трогаетcustom_components/**/*.pyи не трогаетtests_backend/**; ноль оснований перегонять.- Браузерные смоки /
smoke-select.mjs— дельта не содержит ни одного изменённого символаsrc/**(там вообще нет правокsrc/**, кроме временной ad hoc-мутации, откаченной после проверки); инструмент нечего сравнивать, не запускал. golden:verify— дельта не меняет рендер/геометрию/стили.
Находки
Три находки r1 закрыты состоятельно (см. таблицу выше). Новых Medium/High дельта не принесла. Одна Low-находка, заводимая как заметка, а не блокирующая:
L1 — AC6 не имеет штатного регрессионного теста на изолированные decision-required/drop-on-validation
test/config-audit.test.mjs не менялся в этой правке и по-прежнему проверяет только три
lifecycle-фикстуры Блока 3, ни одна из которых не изолирует «только decision-required»
или «только drop-on-validation». Штатный прогон npm test зелёный и до, и (гипотетически)
после отката M3-фикса — потому что oldest-supported.json всё равно даёт exit 3 через
show_all/weather_entity/ripple. То есть M3 закрыта фактически (проверено вручную
выше), но не закреплена тестом задачи — будущая правка может случайно вернуть
decision-required в MIGRATION_STATUSES, и штатный набор этого не заметит.
Не блокирует: серьёзность Low, находка технической полноты покрытия, а не дефект
поведения на HEAD. Можно снять правкой (добавить кейс с изолированным
decision-required-конфигом в test/config-audit.test.mjs) либо принять с записью —
решение автора.
Что проверено и корректно (сводно, r1 + r2)
- Все три Medium из r1 закрыты и перепроверены исполнением в этом раунде (таблица «Закрытие раунда r1»).
- AC1-AC5 унаследованы из r1 без повторной проверки (см. «Унаследовано из r1») — доказательная база не затронута дельтой.
- AC6 пересмотрен целиком в контексте M3-фикса: контракт exit-кодов теперь буквально
соответствует ТЗ (
docs/specs/033-config-schema-lifecycle.md:102-105). - AC7 пересмотрен целиком в контексте M1-фикса: тест теперь способен поймать регрессию,
которую называет своей целью, и покрывает весь
src/**, а не 2 файла. - Дублирование документации (M2) устранено без остатка в трёх файлах.
- Трейлеры коммита
15fa33b9корректны:Issue: #33,User-Visible: no— правки не вводят новое видимое поведение (дедуп уже анонсированного changelog-абзаца плюс внутренняя коррекция dev-инструмента и теста), отдельного изменения в changelog не требуется. - Один источник числа: у этой дельты нет ни одной новой пользовательской величины (правки — тест, CLI dev-инструмент, дедуп документации) — правило не применяется.
Чего не проверял
- Полный
tests_backendс реальной Home Assistant — не установлена в этой песочнице; дельта в любом случае не трогаетcustom_components/**/*.py. - Полный набор мутантов и golden/браузерные смоки — не относятся к дельте (см. таблицу гейтов выше с обоснованием по каждому пункту).
- Судьба
group_lights/exclude_integrationsкак продуктовое решение (#44) — вне скоупа #33, не судил.
Вердикт
Зелёный. High: 0, Medium: 0. Все три Medium-находки r1 закрыты состоятельно и
перепроверены исполнением заново в этом раунде (не только чтением диффа): M1 — тест
подтверждённо умеет падать на реальном воспроизведении старой регрессии; M2 —
дублирующиеся абзацы отсутствуют во всех трёх файлах; M3 — контракт exit-кодов
воспроизведён буквально по ТЗ и проверен на изолированных ad hoc конфигурациях, которых
штатный набор задачи не покрывает (отсюда одна Low-заметка о неполноте покрытия AC6,
не блокирует). Дешёвые гейты (tsc, npm test 1608/0/1, check-docs) зелёные на HEAD;
остальные гейты обоснованно унаследованы из r1 или признаны неприменимыми к локальной
дельте.