20 KiB
SPEC-REVIEW-707-r1
Issue: #707 · этап: spec · трек: ask · заход: r1 · блокирующих циклов израсходовано (после этого раунда): 1/4
Скоуп
#707 — часть разбиения семипунктовой аналитики 29.09 (#707/#726/#727/#728/#729). В этот
issue входят п.1, п.2, п.4 исходного объёма: единое правило риска по изменённым
участкам диффа (К1), единая функция трека/основания/лимита (К2), решение конвейера
на S7 и заметка ревьюеру show/ask (К3), новые разделы task-packet.mjs (К4),
правка канона и конспектов (К5). Продуктового кода задача не трогает (класс A не
затрагивается), User-Visible: no. ТЗ живёт в теле issue под ## ТЗ, комментарии
проверены (аналитика 29.09 и передача на ревью 30.09).
Как проверялось
Ревью текстовое (этап spec, ветки продуктового кода нет — п.12 ТЗ «этап spec, ветки нет, инфраструктурный дифф → риск пуст» сам это фиксирует). Проверка шла в три слоя:
- Обязательные разделы §7.1 и однозначность каждого AC — чтением тела issue.
- Проверка каждого технического утверждения по текущему коду
dev, а не на слово: все имена файлов, функций, регэкспов, строк конфигурации, упомянутых в ТЗ, сверены сgrep/node -eна рабочей копии. Список того, что именно проверено — ниже, в «Что проверено и корректно». Это сделано, потому что ТЗ содержит десятки конкретных технических утверждений о существующем поведении («в файле X на строке Y», «список Z даёт…», «сейчас такой проверки нет») — каждое из них либо доказуемо чтением кода, либо является непроверенной догадкой, которая по §7.1 — находка. - Прогон гейтов не требовался: задача не создаёт и не меняет ни одного файла —
только текст ТЗ в issue.
npx tsc --noEmit/npm test/npm run buildнечего было бы проверять на этом этапе (см. «Чего не проверял»).
Находки
Medium — К1/AC1: strings.json заявлен источником риска ux, но класс A его не видит
Файл: тело issue #707, раздел «6. Контракт поведения» → К1, строка таблицы
ux (столбец «Токен в изменённой строке»).
Воспроизведение. К1 открывается условием: «Судятся только файлы класса A
(classify из change-classes.mjs); test/**, scripts/**, demo/**, docs/**
риска не дают.» Строка ux того же К1 называет три источника нового ключа:
src/i18n/*.json, custom_components/**/translations/*.json и
strings.json. Проверка самой функции classify на рабочей копии:
$ node -e "import('./scripts/change-classes.mjs').then(({classify}) =>
console.log(classify('custom_components/houseplan/strings.json')))"
?
custom_components/houseplan/strings.json не подпадает ни под один паттерн
CLASS_A в scripts/change-classes.mjs (там только .py, manifest.json и
translations/-каталог) — classify() возвращает '?', не 'A'. Значит файл,
который К1 сам называет входом правила ux, будет отфильтрован ещё на первом
шаге («судятся только файлы класса A») и никогда не дойдёт до токен-правила.
Часть контракта AC1 описывает поведение, которое реализовать как написано
невозможно: либо strings.json тихо выпадает из правила ux (реализация
разойдётся с текстом ТЗ, и это не будет видно в тестах — ни один сценарий
AC1(а)–(и) не проверяет именно этот путь), либо реализатору придётся по своей
инициативе расширить CLASS_A в change-classes.mjs — файл, которого нет в
разделе «13. Затронутые файлы», и который используется не только этой задачей:
он же определяет shipLimitViolations (рамки ship) и признак «инфраструктура»
в resolveTrack/guard. Расширение класса A задним числом — это побочный эффект
за пределами скоупа, никак не описанный в контракте и не покрытый AC.
strings.json — не гипотетический путь: это реальный, вручную редактируемый файл
конфигурации HA-интеграции (custom_components/houseplan/strings.json, история
правок есть), т.е. случай практический, а не краевой теоретический.
Почему это находка, а не мелочь. AC1 — защитный/классифицирующий критерий: он обязан однозначно определять классы для указанных путей (DoR, §7.1 — «однозначность каждого AC»). Здесь для одного из трёх явно названных путей поведение не определено — оно противоречит собственному первому условию правила.
Что нужно от автора. Явно решить одно из двух и записать в ТЗ: (а) убрать
strings.json из строки ux К1 (ux-риск для HA config-flow строк не отслеживается
этим правилом), либо (б) явно включить путь custom_components/*/strings.json в
проверяемый список К1 отдельной оговоркой, не трогая change-classes.mjs (например,
нишевым исключением в самой риск-функции, а не в общем classify), и добавить
сценарий в план AC1. Любой из двух вариантов дёшев; выбор — продуктовое решение
о том, что RU/EN-строки конфиг-флоу интеграции достойны такого же сигнала риска,
что и src/i18n/*.json, — не то, что ревьюер решает за автора.
Без High в задаче это жёлтый вердикт, правка ТЗ и повторный цикл (§2.4, §4).
Что проверено и корректно
ТЗ необычно тщательно сверено с текущим кодом: я перепроверил около 30 отдельных технических утверждений, и, кроме находки выше, все подтвердились дословно:
- Существование и сигнатуры
resolveTrack,trackFromLabels,hasTrackLabel,shipLimitViolations,parseNumstat,parseNameStatus,SHIP_SRC_LINE_LIMITвscripts/process-track.mjs. - Реальное расхождение трека при двух метках, которое К2/AC2 называет
дефектом и чинит:
trackFromLabelsприtrack:ship+track:askвозвращает'ship'(проверяетtrack:shipпервым), тогда как bash guard в.github/workflows/_process.yml(строки 92–97) приtrack:askвсегда сбрасываетSMALL=false— т.е. сегодня guard даётask/4, аprocess-track.mjs—ship. Утверждение ТЗ подтвердилось построчно. task-packet.mjs:223— ровно та строка про «перед S7 ребейз» безусловно для любогоbehind > 0, которую К4/AC8 переписывает; устарелость с #696 подтверждается (строка не различает track).classify/CLASS_A/CLASS_B/CLASS_C/CLASS_Dвscripts/change-classes.mjsи их точное содержимое (кроме отсутствияstrings.json— см. находку).- Порядок шагов в
.github/workflows/_process.yml: шаг «Трек задачи и рамки ship (#696)» (строка 424) действительно идёт до шага «Привести ветку к dev» (строка 481), как того требует К3. - Существование и сигнатуры
SHIP_MERGE_MARKER_RE,renderShipBriefвscripts/ship-review.mjs; маркерhp:ship-merge material=...в конвейере (строка 1706). - Все четыре якоря реестра мутаций, которые ТЗ требует перенести, а не удалить:
guard-infra-keeps-ask-limit,packet-infra-track-ignores-show-default,pipeline-ship-ignores-limits,ship-review-ignores-merge-marker— все существуют вscripts/mutation-registry.mjs. - Отсутствие сегодня
bash -n-проверки шагов_process.yml(AC4) — подтверждено пустым результатомgrep -rn "bash -n" test/ scripts/. - Порог «300 файлов» в guard (
.github/workflows/_process.yml:239,files.length < 300) — дословное совпадение с описанием К2 «300 файлов и больше → инфраструктура не доказана». - Полный список файлов геометрии/touch/devices/perf/visual из таблицы К1 (src/.ts и custom_components/houseplan/.py) — каждое имя, которое я сверил построчно (physical-geometry, wall-, junction-limits, coincident-partitions, coordinate-canonicalization, opening-, partition-openings, open-spans, near-axis, align-grid, grid-scale, room-fit, resize*, stairs*, radar-geometry, zigbee-topology-geometry, device-marker-geometry, plan-geometry-preflight, plan-optimizer, zero-walls, iso-projection; pointer-modality, pointer-move-queue, touch-gesture-click-guard, live-interaction-runtime, live-viewport, viewport-transition, room-gear-drag; device-toggle, marker-toggle-entity, integration-provider, virtual-light-state, vacuum*, device-hit-owner; auth.py, http_api.py, websocket_api.py, virtual_lights.py, vacuum_routes.py, store.py, geometry_migration.py, import_export.py, validation.py, coordinate_canonicalization.py, junction_limits.py, wall_segment_model.py, radar_geometry.py, projection.py; houseplan-render-lifecycle, iso-scene-render, glow-*, day-cycle-render, initial-load, boot-soft-layout; paper-scene.ts; space-render, stairs-view, device-visual, device-face; styles.ts/styles/) — существует ровно как названо.
- Наличие
test/process-track.test.mjsс существующим паттерном извлечения текста шага_process.ymlрегэкспом (строка 120, 167) — подтверждает реалистичность плана автотестов АС4/АС6 «как существующие тесты #696». - Обязательные разделы §7.1 (сценарий, что увидит человек, проблема, скоуп и не-скоуп, контракт поведения, UX/данные/i18n/perf/touch, AC1–AC14 со способом доказательства, план автотестов, риски, откат, release-артефакты) — все присутствуют и в правильном порядке.
- «Принято предположительно» (§10) — 8 пунктов, каждый — реальная техническая развилка (где живёт код, эвристика К1, длина заметки и т.д.), не маскирует продуктовый вопрос под техническое решение.
- §10 п.8 (сужение «метка владельца окончательна» до «метка, подтверждённая строкой владельца») — автор прямо просит проверить его на ревью. Обоснование в тексте ТЗ внутренне непротиворечиво: текущий §5 PROCESS.md не различает метку, поставленную человеком-владельцем, от метки, поставленной аналитиком/агентом от того же аккаунта (о чём и сам §5 говорит: «Аналитик предлагает трек… и ставит метку»), и per §10 п.1 задачи по actor события их сегодня действительно не отличить. Уточнение закрывает реальный пробел, не меняет ничьих прав, которых не было прежде, и не требует отдельного продуктового вопроса владельцу — трек решается вердиктом по §7.1 («технический спор автора и ревьюера решается вердиктом»). Принимаю формулировку как есть.
- Лимиты циклов, таблица
ask=4/show,ship=2, «зелёный вердикт цикла не образует» — согласуются с §4 буквально. - Соответствие «Не входит» реальным границам:
smoke-select.mjs,process-reconcile,process-resume,merge-candidate, тонкийprocess.ymlвmain— задача их не трогает; список «затронутые файлы» (раздел 13) их действительно не включает. - Метки на самом issue:
track:ask,S4-spec-review,P2,infra,process,tech-debt— соответствуют заявленному в аналитике треку и статусу. - П.7 (черновая реализация до вердикта, меняющая правило №1) явно вынесен в #729 и
не входит в контракт #707; раздел «11. Вопрос владельцу» корректно помечен
«не блокирует #707,
blockedне ставится» — в #707 остаётся только справочная ссылка, лишнего продуктового вопроса в этой задаче нет.
Чего не проверял
- Гейты (
npx tsc --noEmit,npm test,npm run build) не прогонял — задача не меняет ни одного файла репозитория на этом этапе, проверять нечего; наS7-code-reviewони станут обязательны и будут применяться к реальному диффу. - Не проверял golden/смоки/бэкенд-pytest/invariants — задача класса A не
содержит,
User-Visible: no, визуальных и геометрических изменений продукта нет (сама ТЗ фиксирует это в разделе 7 и 16). - Не оценивал реальную эффективность эвристики К1 (долю ложных срабатываний/пропусков на исторических диффах) — это явно измеряемый, но не гейтируемый на этом этапе вопрос; ТЗ сама называет это «принято предположительно» (§10 п.2) и переносит измерение эффекта в #728.
- Не проверял, действительно ли
git diff --unified=0с настройками git по умолчанию в GitHub Actions runner надёжно распознаёт чистые переименования (сценарий AC1(и)) — это деталь реализации команды диффа, не зафиксированная в контракте явным флагом (-M/--find-renames); при стандартномdiff.renames=true(git ≥2.9) поведение ожидаемо корректно, а при сбое эффект безопасен (ложный риск, а не пропуск) — не поднимаю отдельной находкой, но это стоит держать в уме при написании AC1(и) как юнит-теста на встроенном диффе, а не на реальном git.
Вердикт
Один Medium в скоупе задачи (AC1/К1: strings.json заявлен источником риска
ux, но исключён условием «только класс A» того же правила) — без High это
жёлтый вердикт с возвратом автору по §2.4/§2.7. Всё остальное, вплоть до
мелких деталей вроде номеров строк и имён функций, подтвердилось построкой
сверкой с кодом dev; спор на ревью — только вокруг одной несостыковки К1.
Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 1 → в задаче
Материал раунда
- Этап: spec. Материал — тело issue #707 на момент комментария Matysh 2026-09-30T20:59:00Z («ТЗ написано… Передаю на ревью ТЗ»).
- Кода/ветки продукта не существует (инфраструктурная задача до реализации).
- Сверка кода
dev:git rev-parse HEADрабочей копии ревью =a5a73d1511ae4c09277e16a41c076bfd5eb1fd8e.
Материал раунда
- Ветка:
dev, коммитa5a73d1511ae— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
5a1bea4a8cb02a6fc04a3a8742cb8b57e57b7c51git log --all --format='%H %T' | grep 5a1bea4a8cb0 - Тело issue:
965899906f2230d8473b72e6304be7bb1f58c2045425912b69a9efa788025ff4 - Вердикт конвейера:
yellow· High 0