16 KiB
SPEC-REVIEW-262-r1
- Issue: #262 — Deleting entities prevents them from being added again later
- ТЗ:
docs/specs/262-readd-child-entity-after-device-delete.md - Ветка:
issue/262-readd-child-entity, коммит на ревью:7b3ea37 - Этап:
S4-spec-review(ревью ТЗ, PROCESS.md §2.4) - Заход: r1 · блокирующих циклов израсходовано 0 из 4 (зелёный вердикт бюджет не тратит, #227)
- Диф ревью:
git diff origin/dev...origin/issue/262-readd-child-entity— толькоdocs/specs/262-readd-child-entity-after-device-delete.md(новый файл) и одна строка вdocs/specs/README.md. Продуктового кода нет — соответствует стадииS3→S4(Rule #1: код трогают только сS5-ready).
Скоуп
Bug-репорт: после удаления HA-устройства с плана его отдельные дочерние
сущности невозможно добавить обратно даже при включённом флаге «Показывать
сущности» — можно вернуть только всё устройство целиком. Аналитика владельца
(комментарий в issue) разложила репорт на три сценария и подтвердила, что
именно третий («удалить device, добавить одну entity того же device») — баг,
остальные два уже работают штатно. ТЗ фиксирует контракт на этот третий
сценарий: exact live entity:X должна перевешивать exact/parent tombstone
только для себя самой, не воскрешая ни родительское устройство, ни соседние
сущности.
Соответствие docs/SCOPE.md: закрывает J6 («Keep the plan true as the home
evolves») — часть про «поддерживать план в актуальном состоянии» и явный выбор
нужной HA-привязки после удаления. Продуктовой рамке не противоречит, новых
поверхностей не открывает.
Как проверялось
Ревью технической части ТЗ построено не на доверии тексту, а на сверке каждого
фактического утверждения (§3, §4, §6, §8) с кодом на origin/dev — здесь
именно тот случай, где «утверждение о поведении не в документах и не
помеченное как предположение» было бы находкой, если бы не подтвердилось.
- §4.1 (picker разрешает только точный tombstone binding). Прочитан
_bindingCandidates()вsrc/houseplan-card.ts(ветка «Individual entities of devices — behind the show entities checkbox»):removedBindings— Set точных строк binding сm.removed; исключение!removedBindings.has(v)сравнивается сv = 'entity:'+eid, а не с'device:'+parentId. Приdevice:DtombstoneisRemovedPlanEntity(h, eid, removed)возвращаетtrue(поremoved.devices.has(deviceId)),removedBindings.has('entity:'+eid)—false⇒ строкаcontinueотбрасывает X из списка даже при включённом чекбоксе. Ровно то, что описывает ТЗ. Подтверждено чтением. - §4.2 (фикса списка недостаточно). Прочитан
_saveMarker():replacedRemovedIds = markers.filter(m => m.removed && m.binding === dlg.binding)— снимает tombstone только с тем же exact binding (entity:X), родительскийdevice:Dtombstone переживает сохранение. ПрочитанbuildDevices(), веткаkind === 'entity':if (isRemovedPlanEntity(fullHass, ref, removed)) continue;— уже сохранённый живой explicit markerentity:Xвсё равно отбрасывается, потому чтоremovedстроится из того жеmarkers[], где всё ещё лежитdevice:Dtombstone. Оба утверждения ТЗ подтверждены чтением, не домыслены. - §8 (schema-valid combination). Прочитан
validation.py: схема маркера не запрещает сосуществование двух записей с разнымиbindingи независимымremoved; дляdevice:D removed:true+ живойentity:Xнет constraint, который бы это отклонил. Утверждение «уже schema-valid» — не гипотеза. - §3 (воспроизведение). Прочитан
demo/smoke_binding_picker.mjs(добавлен в #263): блок 3 буквально помечаетo.knownDefect262ChildEntityBlocked = !(await offered(true)).includes('entity:'+childEntity)с комментарием «почитают #262, смок покраснеет, потребовав перевернуть проверку» — соответствует плану автотестов ТЗ (AC1: «проверка known defect перевёрнута в положительную»). - Согласованность с #226. Прочитан
docs/specs/226-entity-parent-dedup.md(решения 4–5: «явная конфигурация сильнее автоматической», «tombstone не владеет сущностью») — контракт §6.4 ТЗ #262 («явная D не подавляет явную X») этому не противоречит, использует уже принятую модель, не переопределяет её. - Согласованность с #104.
docs/specs/104-opening-ha-reference-after-marker-delete.mdсам ссылается наisRemovedPlanEntity/isRemovedPlanSource— подтверждает заявление ТЗ, что точные ссылки opening contact/lock уже независимы от marker tombstones и не задеты этим исправлением. - Регистрация smoke-связи.
scripts/smoke-links.mjsуже связывает символыremovedPlanBindings,isRemovedPlanEntity,deletePlanMarkerRecordsсоsmoke_binding_picker.mjs(внесено в #263) — заявление §11 ТЗ («имя_bindingCandidatesостаётся прямым совпадением, дублировать не требуется») корректно, связь уже есть, ничего добавлять не нужно. - Терминология UI. Флаг называется в ТЗ «Показывать сущности» —
совпадает с формулировкой из
docs/USER-GUIDE.ru.md(строки 657, 1587), а не изобретён. (Сам i18n-ключmarker.show_entitiesв коде переведён как «Отображать сущности» — расхождение междуUSER-GUIDE.ru.mdиru.jsonсуществует независимо от #262, автор ТЗ корректно взял термин из пользовательского гайда, как требует AGENTS.md; фиксирую это отдельно ниже как находку вне скоупа, не блокирующую этот заход.) - Обязательные разделы ТЗ (PROCESS.md §7.1). Сверены построчно: сценарий и персона (§1) — есть, включая поверхность и момент; что человек видит до/после одной фразой без терминов реализации (§2) — есть, без упоминаний функций/tombstone; проблема (§3–4); скоуп и не-скоуп (§5); контракт поведения (§6); UX/touch/a11y (§7); данные/compatibility/миграция (§8); i18n/security/performance (§9); AC1…AC6 с доказательством (§10); план автотестов (§11); файлы (§12); release-артефакты (§13); риски (§14); откат (§15); блок принятых предположений (§16). Ничего не пропущено.
- Проверка «не выдана ли догадка за факт». Все технические утверждения о текущем поведении (§3, §4, §8) проверены чтением реального кода (пункты 1–3 выше), а не приняты на слово. Пять пунктов §16 корректно помечены как предположения и являются техническими (форма реализации, позиция нового маркера, отсутствие whitelist для registry-hidden), а не спрятанными продуктовыми решениями — ни один не требовал вопроса владельцу.
- AC на проверяемость и доказательства. AC1–AC6 однозначны, у каждого
указан способ доказательства (
smoke/unit/«существующие unit/smoke #104/#161/#226»/gates), совпадающий с §11 и с уже зарегистрированными файлами/тестами.
Находки
Блокирующих (High) и находок Medium в скоупе — нет.
Medium — вне скоупа (не блокирует, заводится отдельным issue по §12 PROCESS.md)
Расхождение термина чекбокса между docs/USER-GUIDE.ru.md («Показывать
сущности», строки 657 и 1587) и фактическим i18n-ключом marker.show_entities
в src/i18n/ru.json («Отображать сущности»). Автор ТЗ #262 действовал верно —
взял формулировку из канонического пользовательского гайда, как требует
AGENTS.md, — но само расхождение живёт в репозитории независимо от #262 и не
имеет отношения к его сценарию. Правка в этой ветке была бы посторонним
скоупом (диалог маркера правит другой файл — houseplan-card.ts/ru.json, не
затронутый ТЗ #262). Заведён отдельным issue — см. решение ниже.
Низких (Low) находок нет.
Что проверено и корректно
- Диагноз двухчастного дефекта (picker exact-binding whitelist + runtime
isRemovedPlanEntityвbuildDevices) — точное соответствие кодуdev, не гипотеза. - Контракт §6 (матрица Add picker, транзакция сохранения, runtime-приоритет, что остаётся удалённым) — самосогласован: список Add специально шире, чем runtime-построение (все дети D становятся видимыми в Add, но строится/рендерится только явно сохранённый marker) — это осмысленное разделение, а не противоречие.
- Совместимость с уже принятыми контрактами #226, #161, #104 подтверждена по их собственным спекам и коду, не только заявлена.
- Данные/миграция: комбинация
device:D removed:true+ живойentity:Xуже schema-valid, новых полей и model bump нет — подтверждено чтениемvalidation.py. - Воспроизведение (§3) подтверждено существующим browser smoke
demo/smoke_binding_picker.mjs(#263), который уже фиксируетknownDefect262ChildEntityBlocked: trueи явно рассчитан на переворот проверки этим исправлением. - Все обязательные разделы ТЗ (PROCESS.md §7.1) присутствуют и по существу, не формально.
- Продуктовых вопросов владельцу нет, и это оправдано: ни одна неопределённость в ТЗ не задевает то, что видит или делает пользователь сверх уже описанного в §1–§2; сценарий закрыт исходным репортом и аналитическим комментарием владельца.
- i18n-раздел корректен: новых ключей нет, использованная терминология взята
из
docs/USER-GUIDE.ru.md, а не изобретена.
Чего не проверял
- Гейты
typecheck/test/build/check-docsне гонялись: этап — ревью ТЗ (S3→S4), продуктового и тестового кода в дифе нет (только новыйdocs/specs/*.mdи строка вdocs/specs/README.md), эти гейты относятся к этапу код-ревью (PROCESS.md §2.7, §8) и будут прогнаны там. - Не проверялась работоспособность будущей реализации — её ещё нет; предмет этого ревью — выполнимость и однозначность ТЗ, а не код.
- Не запускал browser smoke
demo/smoke_binding_picker.mjs— код на этой ветке не менялся относительноdev, поведение смока не могло измениться; чтения исходника было достаточно, чтобы подтвердить, что смок уже воспроизводит описанный в ТЗ дефект. - Не проверял golden/performance/mutation-gate — на этой стадии нет ни визуальных, ни мутационных изменений: диф ограничен документацией.
Решение
Найдена ровно одна находка — Medium, вне скоупа задачи (терминологическое
расхождение чекбокса, не создано и не усугублено этим ТЗ). Она не блокирует
переход в S5-ready и заводится отдельным issue со ссылкой на #262, а не
чинится в этой ветке.
Вердикт: зелёный. ТЗ выполнимо, однозначно, каждый AC проверяем и снабжён способом доказательства, технические утверждения о текущем поведении подтверждены чтением кода, а не приняты на веру, продуктовых вопросов владельцу нет.