docs: review document for #502

Issue: #502
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-09 19:35:30 +00:00
parent 72764cff90
commit 6b592c13c0
+184
View File
@@ -0,0 +1,184 @@
# SPEC-REVIEW-502-r1
Issue: #502 — «i18n-dead-keys: широкий паттерн `r.+` маскирует мёртвые ключи»
Этап: spec (PROCESS.md §2.4), лёгкий трек (`small`), ТЗ в теле issue.
Заход: r1 · блокирующих циклов израсходовано 0 из 2 (лимит §4 для лёгкого трека).
SHA рабочей копии на момент ревью: `72764cff906d8e27996e638ced8b708251e24657`.
## Скоуп
Класс B, один файл `test/i18n-dead-keys.test.mjs` (опционально новый
`test/helpers/i18n-consumers.mjs`). Продуктового кода, i18n-словарей, UX,
миграций задача не касается; `User-Visible: no`. Цель: сузить генератор
«динамических потребителей» в гейте мёртвых i18n-ключей так, чтобы паттерн вида
`^r.+$` (рождённый из `'r' + Date.now().toString(36)`) не маскировал реальные
мёртвые ключи семейства `radar.*`, `room.*`, `run.*` и т. п.
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` (§1–§10, включая §2.4,
§2.10, §5, §7.1).
2. Прочитано тело issue #502 целиком и оба комментария (S2-анализ, передача на
ревью). Нашедшая задачу цепочка — код-ревью #485 r2 — тоже сверена.
3. Прочитан текущий `test/i18n-dead-keys.test.mjs` на `dev` построчно —
подтверждено, что `expressionPattern` для `BinaryExpression` с `+`
действительно собирает регулярку без требования точки в статической части,
и что `'r' + Date.now().toString(36)` в `src/houseplan-editor-runtime.ts`
(id черновика) даёт ровно `^r.+$`, как написано в issue.
4. Проверено текущее состояние `radar.bad_references`: `grep` показал, что
ключ уже используется как строковый литерал в `src/radar-setup.ts:334`
(`... : message === 'bad_references' ? 'radar.bad_references' : ...`),
т.е. дефект из #485 r2, который и породил эту задачу, на `dev` уже устранён
(коммит `e9fb255a fix: close radar review gaps`), а #485 закрыт.
5. **Эмпирическая проверка выполнимости AC4** (гейт зелёный на реальном дереве
после сужения) — не полагаясь на заявление автора. Собран одноразовый
скрипт, реализующий текущий `expressionPattern` один в один плюс критерий
контракта п.1 («статическая часть содержит `.`») как фильтр
`isKeyShapedPattern`, прогнан на актуальном `src/**` и словарях
`src/i18n/en.json` + `src/i18n/support/en.json` (полный листинг результата
сохранён в сессии, не коммитился, репозиторий не менялся):
```
OLD unused count: 0 []
NEW unused count: 0 []
```
Узкий фильтр отбрасывает несколько десятков динамических паттернов (CSS/HTML
шаблоны в `src/day-cycle-render.ts`, `src/backdrop-pick.ts`,
`src/decor-image-editor.ts`, `src/color.ts`, `src/coincident-partitions.ts` и
др. — они никогда не были осмысленными i18n-потребителями, просто случайно
совпадали под старым широким критерием), но ни один из них не превращает ни
один ключ словаря в мёртвый: результат «0 unused» не меняется. Вывод: AC4 —
не догадка, а проверяемо достижимое требование уже на сегодняшнем дереве, при
разумной трактовке контракта п.1–2.
6. Дешёвые гейты (диф не тронут — smoke/golden/backend не запускались, они не
нужны для ревью ТЗ):
- `npx tsc --noEmit` — зелёный (0 ошибок), выполнен на `dev` без изменений.
Смысл: подтвердить, что рабочая копия сама по себе в порядке перед разбором
ТЗ; продуктовый код в этом раунде не менялся, полный `npm test`/`npm run
build` для ревью **спецификации** избыточны (диф — только тело issue).
## Находки
Находок High или Medium (в скоупе или вне него) не обнаружено.
### Low — снимается с записью
**L1. Раздел «Зависимости» описывает уже не актуальную ситуацию.**
Файл: тело issue #502 (раздел «Зависимости»).
Текст предполагает, что `radar.bad_references` может быть всё ещё мёртвым на
момент реализации и тогда сужённый гейт «покраснеет на нём», требуя ждать
слияния #485. Фактически #485 уже закрыт и слит, ключ уже используется в
`src/radar-setup.ts:334` — зависимость исполнена ещё до передачи ТЗ на ревью.
Раздел не создаёт риска (он написан как условие «если … то», а не как факт) и
не блокирует переход в `S5-ready`: реализатору достаточно перечитать текущее
состояние `dev` перед стартом, что он обязан сделать в любом случае. Снимаю без
правки текста — исправлять формулировку ради контроля версий issue не имеет
смысла, реализатор увидит актуальное состояние сам.
**L2. Место объявления `DYNAMIC_KEY_FAMILIES` не названо явно.**
Контракт п.3 вводит константу `DYNAMIC_KEY_FAMILIES`, «принятый предположительно»
блок разносит чистые функции в `test/helpers/i18n-consumers.mjs`, а сам гейт
остаётся в `test/i18n-dead-keys.test.mjs` — но не сказано, в каком из двух
файлов будет жить сама константа-список семей. Это техническая, не
продуктовая деталь (расположение файла, PROCESS §7.1: «место, где стоит
гвард» — решает исполнитель), и она не единообразна ни с чем, что требовало бы
одного конкретного ответа. Не блокирует; исполнитель решает свободно.
## Проверка контракта на непротиворечивость и проверяемость
- **П.1 (точка в статической части)** — проверяемо: AC1 и AC2 дают конкретные
примеры входов и ожидаемых `true`/`false`, юнит на синтетическом AST.
Убедился (см. «Как проверялось», п.6), что действующая реализация
`expressionPattern` уже собирает `source`/`dynamic` в форме, из которой этот
фильтр вычисляется механически (строка минус все `.+`-вставки содержит `.`).
Однозначно.
- **П.2 (открытый хвост допустим, если после точки)** — это уточнение к п.1,
не отдельный независимый фильтр; пример `.+\.title` корректно иллюстрирует
разрешённый случай, а `^r.+$` — прямое следствие того, что паттерн из п.1
уже отсеян (в нём нет точки вовсе), а не отдельно запрещённый случай «начала
с хвоста». Формулировка чуть избыточна («паттерн … невозможен по построению
п.1» — верно и прямо сказано в тексте, противоречия нет), Low не завожу.
- **П.3 (явный список семей + непустая причина + запись без покрытия — ошибка
теста)** — проверяемо AC4, оба условия (непустая причина, покрытие ≥1 ключа)
разнесены по разным ветвям мутации в третьем столбце таблицы AC.
- **П.4 (реально мёртвые ключи не маскируются, уходят отдельным issue Codex)**
— граница со скоупом задачи прочерчена верно: класс A (словари) не
затрагивается этим issue, что совпадает с заявленным классом B. Единственный
известный на момент ТЗ случай (`radar.bad_references`) уже не актуален (см.
L1); эмпирическая проверка (п.5 выше) показывает, что на сегодняшнем дереве
других случаев нет — контракт не вступает в противоречие с AC4 «зелёный гейт
на реальном дереве».
- **П.5 (форма сообщения об ошибке не меняется)** — тривиально проверяемо
визуальным сравнением текста в тесте, доказательство не требуется отдельным
AC (описательное требование к диагностике, не к защите).
Утверждений, поданных как факт о продуктовом поведении без пометки
«предположение», не найдено — блок «Принято предположительно» присутствует,
покрывает единственное содержательное техническое решение (критерий «похоже
на ключ» = наличие точки, и альтернатива, которую отвергли, названа явно).
Продуктовых вопросов владельцу в тексте нет и не требуется: гейт не наблюдаем
пользователем (персонами J1–J7 из `docs/SCOPE.md` этот тест не встречается
вовсе), задача не расширяет и не сужает видимое поведение продукта.
## Что проверено и корректно
- Класс изменений (B), `User-Visible: no`, отсутствие миграций/i18n/UX/perf/
touch-влияния — заявлено верно и подтверждено чтением диффа не требуется,
т.к. диффа кода ещё нет; сам факт, что единственный целевой файл лежит в
`test/**`, подтверждён структурой репозитория.
- Формат AC1–AC5: каждый несёт способ доказательства и (кроме AC5,
честно помеченного «не защита») мутацию, которой он красне́ет — это ровно
требование §2.7 на будущий код-ревью, заранее подготовленное автором,
снижает риск возврата на код-ревью.
- Откат — одним revert-коммитом, без затронутого продуктового кода;
минимально достаточен для класса B.
- Трек `small`: все пять критериев §5 действительно выполняются одновременно
(одна поверхность — один тестовый файл; ни миграции, ни compatibility-полей;
ни нового UX-контракта — потому что UX-контракта нет вовсе; ни perf/touch-
влияния). Полный трек не требовался бы ни по одному критерию.
- Эмпирическая проверка достижимости AC4 (раздел «Как проверялось», п.5) —
сильнее, чем «ревьюер поверил автору»: воспроизведена логика контракта на
реальном src-дереве, а не пересказана.
## Чего не проверял
- Полный `npm test` / `npm run build` / `npm run bundle:sync` — не нужны на
этапе ревью спецификации: продуктового и тестового кода ещё нет, диф —
только текст issue. Будут обязательны на код-ревью (§7.2), включая
дисциплину «тест умеет падать» по мутациям, которые сам автор уже назвал
в таблице AC.
- Браузерные смоки, golden, backend pytest, performance-профили — не
применимо: задача не трогает `src/**`, `custom_components/**/*.py` и не
меняет видимый результат.
- Точная реализация `isKeyShapedPattern`/`unusedKeys` как кода — не пишется на
этом этапе (ревьюер ТЗ не правит продуктовый/тестовый код); эмпирическая
симуляция в разделе «Как проверялось» использована только чтобы проверить
ВЫПОЛНИМОСТЬ контракта, а не чтобы заменить будущий код-ревью.
## Вердикт
Зелёный. Two Low-находки (L1, L2) сняты с записью выше, без правки текста
issue — они не мешают переходу в `S5-ready` и не создают риска для реализации.
High и Medium (в скоупе или вне него) не найдено.
---
## Материал раунда
- Issue: #502, тело + 2 комментария (S2-анализ и передача на `S4-spec-review`),
оба от `Matysh`, 2026-09-09.
- ТЗ живёт в теле issue (лёгкий трек), файла в `docs/specs/` нет и не должно
быть.
- SHA рабочей копии на момент вывода вердикта: `72764cff906d8e27996e638ced8b708251e24657`
(сверено непосредственно перед подведением итогов).
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `dev`, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Якоря снять не удалось: ветки задачи нет, материал читался по `dev`.
- Вердикт конвейера: `green` · High 0