19 KiB
CODE-REVIEW-727-r1
Issue: #727 · этап code · трек ask · заход r1 · блокирующих циклов 0/4
Материал: git log --oneline origin/dev..HEAD / git diff origin/dev...HEAD,
ровно 93be52ebe8c3f7860eeb955c5dda2a6d50bdbabe (рабочая копия уже на нём)
Скоуп
Один коммит поверх dev (52dc08a0): ночной режим пакетного ревью ship и
переиспользование его результата гейтом беты по патч-набору (К1–К9, AC1–AC10
из ТЗ в теле #727). Классы изменений — B (scripts/**, .github/workflows/**,
test/**) и C (PROCESS.md, docs/process/REVIEWER.md); файлов класса A нет.
User-Visible: no — изменений в обоих CHANGELOG не требуется и нет
(проверено: git diff --stat по docs/CHANGELOG*.md пуст).
Файлы: scripts/ship-review.mjs (+428/-…), scripts/reviews-archive.mjs,
scripts/reviews-index.mjs, .github/workflows/_nightly.yml,
.github/workflows/_ship-review.yml, PROCESS.md (§10.4, §11.7),
docs/process/REVIEWER.md, test/ship-review.test.mjs,
test/nightly-workflow.test.mjs, test/process-digests.test.mjs.
Спец-ревью пройдено за r1→r2 (единственная Medium-находка r1 по К7/АС7 —
нереализуемость правила архивации для стабильной базы без примера в АС —
закрыта в r2, вердикт зелёный, docs/reviews/SPEC-REVIEW-727-r2.md). Трек
ask подтверждён верно: сложность/риск >3 (четыре поверхности) и публичный
контракт гейта беты меняется.
Как проверялось
Прочитано построчно: scripts/ship-review.mjs целиком (591 строка, не только
дифф), scripts/reviews-archive.mjs/reviews-index.mjs диффы с контекстом
вызывающих функций (compareStable, stableTagsThrough, ordered),
.github/workflows/_nightly.yml и _ship-review.yml целиком, PROCESS.md
§10.4/§11.7 и docs/process/REVIEWER.md диффы, весь новый текст
test/ship-review.test.mjs (580 новых строк) и test/nightly-workflow.test.mjs
(137 новых строк).
Каждый AC1–AC9 сверен построчно с кодом (оракул AC → конкретная функция/шаг → тест, который его проверяет). AC10 — гейты, ниже.
Исполнено, не только прочитано:
node --test test/ship-review.test.mjs test/nightly-workflow.test.mjs test/process-digests.test.mjs test/default-branch-workflows.test.mjs— 29 + … тестов, все зелёные (включая тесты на настоящем bash/git во временных репозиториях —shipSandbox,tempRepo,runStep).node scripts/mutation-gate.mjs --check— exit 0, 3 предупреждения (те же три имени, что называет автор как «как на dev»:corpus-loses-its-short-edge,optimize-reports-work-it-did-not-do,nightly-reuse-accepts-stale-marker— все три про геометрический корпус/#650, не про этот дифф). Якорьship-review-ignores-merge-markerнайден и зелёный, новые мутантыship-review-accepts-partial-coverage/ship-review-accepts-highв реестре есть и зелёные.node scripts/entry-cost.mjs --check— зелёный.node scripts/reviews-index.mjs --check— зелёный («INDEX.md свеж»).node scripts/smoke-select.mjs --base origin/dev --head HEAD— «исполняемого frontend-диффа нет, browser-smoke не выбираются»:src/**/*.tsне тронут, смоки объективно нечего выбирать, а не решение их пропустить.- Мутационная проверка вручную (дисциплина «тест умеет падать») на двух
участках с наибольшим риском тихой порчи:
samePatchSet→return true(покрытие никогда не станетstale): упал AC3, AC4, AC5 и интеграционный AC2/AC5-тест — 4 из 19 тестов файла.archivePlan:ordered.find(...)→[...ordered].reverse().find(...)(брать не ближайшую линию новее базы, а самую дальнюю — тестовый случай АС7 (в),[v1.78.0, v1.78.1, v1.79.0]должен датьv1.78.1): упал ровно тест АС7. Файл восстановленcpиз бэкапа после обеих проверок,git diffпосле — пуст.
Не прогонял и почему: actionlint — бинарь недоступен в этой песочнице;
автор заявляет «чистый по изменённым файлам», не перепроверено независимо —
риск низкий: YAML run:-блоки этого диффа уже проверяются bash -n в
исполняемых тестах (test/ship-review.test.mjs,
test/nightly-workflow.test.mjs), а структурные инварианты (поля job, порядок
шагов, needs/if/permissions) — текстовыми assert'ами на реальном файле.
npx tsc --noEmit / npm test (полный) / npm run build — не перегонял:
Validate зелёный на этом SHA (ссылка в промпте ревью), дешёвые гейты закрыты
им. Python/pytest tests_backend — не трогается, нет правки
custom_components/**/*.py. npm run golden:verify и invariants — нет
ci:golden, нет правки геометрии/рендера. Performance — не назван в AC.
Находки
Нет High. Нет Medium в скоупе задачи.
Low (наблюдение, не блокирует, не правлю и не снимаю — требует решения
владельца о сроке). ТЗ (раздел «Разбиение») инструктирует: «Новый issue E
(завести в S1-new, ссылка на #727): «Красная ночь: комментарий в задачи,
слитые после последней зелёной»». Поиском по заголовку/метке S1-new такой
issue в репозитории не найден — похоже, не заведён. Это явно вне скоупа
самого #727 (раздел «Не-скоуп»: «Красная ночь — issue E»), поэтому не влияет
ни на один AC1–AC10 и не блокирует это ревью; но без заведённой задачи
инструкция ТЗ технически не выполнена полностью, и решение, которое владелец
принял при развилке #707, рискует потеряться. Не завожу отдельным issue сам:
это не найденный попутный дефект кода (критерий §12 «вне скоупа»), а
административный шаг, порядок исполнения которого (сейчас / при следующей
правке конвейера) решает не ревьюер.
Low (наблюдение). readRangeDocs — новая, нетривиальная функция (сортировка
по глубине коммита через rev-list --count, дедупликация документа с одним
именем между candidate и devRef в пользу devRef, отсечение чужой базы
для ночных имён по regex). Прямого unit-теста на саму функцию нет: она
проверяется только через интеграционный shipSandbox-тест, где сценарий
«один и тот же документ с разным содержимым одновременно в дереве кандидата и
в origin/dev» не встречается (там документ либо ещё не существует, либо уже
запушен в оба места с одним и тем же содержимым). Логика — простая перезапись
Map по порядку обхода [candidate, devRef], поведение прочитано и совпадает
с докстрингом; риск низкий, повторной сессии не прошу.
Проверка критериев приёмки
| AC | Что | Доказательство в коде | Тест | Вердикт |
|---|---|---|---|---|
| AC1 К1 | Патч-набор: git patch-id --stable, без Release:, без коммитов только docs/reviews/**, порядок не важен, cherry-pick даёт тот же id |
hasReleaseTrailer, countsForPatchSet, commitPatchIds (явные опции диффа — PATCH_DIFF), issuePatchSets |
#727 AC1 — временный git-репозиторий, реальный git patch-id |
доказано исполнением |
| AC2 К2 | tag=nightly требует candidate; имя документа по базе+SHA12; prepare отдаёт только none/stale; блок несёт mode/patches в хвосте |
shipReviewMode, nightlyDocPath, shipDocPath, anchorBlock/parseAnchorBlock |
#727 AC2 (unit) + #727 AC2/AC5 и #727 AC2 _ship-review.yml (реальный bash, реальный workflow-файл через stepRun) |
доказано исполнением |
| AC3 К3 | Четыре статуса покрытия; «последний документ главнее»; чужая база не в счёт; документ без patches — по номеру |
shipCoverage |
#727 AC3 — все переходы статусов, включая «high отменяет ранний clean» и наоборот |
доказано исполнением; мутация samePatchSet→true ловится тестом |
| AC4 К4 | Гейт: всё clean без документа тега проходит; none/stale/high — отказ с командой (force только когда нужен) |
shipReviewProblems |
#727 AC4 |
доказано исполнением |
| AC5 К5 | Бета читает дельту; бриф называет прочитанное ночью; force=true — всё; пустая дельта — без модели |
planShipReview, renderShipBrief |
#727 AC5 (unit) + #727 AC2/AC5 (сквозной сценарий на реальном workflow) |
доказано исполнением |
| AC6 К6 | Новая job после Validate, if: always(), не красит ночь, ждёт только появления прогона (18×10с) |
.github/workflows/_nightly.yml (ship_review job) |
#727 AC6 (структура YAML) + два теста на реальном bash (runStep) — вывод SHA до ожидания, красный Validate красит именно job ожидания, dispatch/appearance таймауты — предупреждение, не красная ночь |
доказано исполнением |
| AC7 К7 | parseDocName узнаёт ночное имя; archivePlan: база-бета → своя линия, стабильная база → ближайшая новее, без линии новее — kept |
parseDocName (SHIP_DOC_NAME с опциональной -dev-sha12 группой), archivePlan (новая ветка doc.nightly && STABLE_TAG_RE), renderIndex (сортировка ночь/небета, подпись «ночь после») |
#727 AC7 — все четыре примера (а)-(г) из АС дословно |
доказано исполнением; мутация find→reverse().find ловится тестом (в) |
| AC8 К8 | Строка о High только ночью, с меткой документа, без повтора на тот же документ | highCommentBody, highCommentTargets, шаг «High ночью» в _ship-review.yml (if: needs.prepare.outputs.mode == 'nightly') |
#727 AC8 (unit) + #727 AC8 _ship-review.yml (реальный bash: beta не пишет, nightly без High не пишет Medium/Low, nightly с High — только в задачу без метки) |
доказано исполнением |
| AC9 К9 | Канон — PROCESS.md §11.7/§10.4, REVIEWER.md, ключевое правило в process-digests |
диффы PROCESS.md/REVIEWER.md прочитаны целиком; test/process-digests.test.mjs |
node --test test/process-digests.test.mjs зелёный; entry-cost --check зелёный |
доказано исполнением |
| AC10 | Гейты | — | gate:small (заявлен автором, зелёный Validate подтверждает дешёвую часть), mutation-gate --check, reviews-index --check — перепрогнаны мной, зелёные; якорь найден |
доказано исполнением (частично — моим прогоном) |
Что проверено и корректно
- Патч-набор исключает ровно то, что нужно:
Release:-коммит кандидата беты (несущийIssue:всей линии) и чисто-доковые коммиты — иначе ночной набор никогда бы не совпал с набором на кандидате беты (тест это явно демонстрирует: один и тот же набор до и послеRelease:-коммита). shipReviewProblemsкорректно различает «документа тега нет вовсе» (нет force в команде) от «документ тега есть, но не всё покрывает» (force обязателен) — оба случая сведены к конкретным regex-проверкам в тестах.archivePlan: ветвление «база-бета» (точное совпадение, прежняя логика) и «стабильная база» (поиск ближайшей линии строго новее, новая логика через уже существующийcompareStable) не пересекаются и не регрессируют старыйdeepEqual-тест#696(проверено прогоном)._nightly.yml: SHA прогона Validate выводится шагомid: validateдо шага ожидания (gh run watch --exit-status), поэтому красный Validate не теряет кандидата для ночного ship-ревью — ключевое свойство АС6, прочитано и подтверждено порядком шагов иoutputs:на уровне job.ship_reviewjob зависит только отdispatch(не от его успеха —if: always()) и несётcontinue-on-error: true, так что сбой диспетчеризации ship-ревью не красит ночь — ровно то поведение, которое требует АС6 («предупреждение, не красная ночь»)._ship-review.yml:mode/patchesпубликуемого блока берутся из выходовprepare(детерминированный скрипт), а не из JSON-результата модели — единственный источник числа patch-id не раздваивается между моделью и шагом публикации (§8, «одно число — один источник»); явно проверено тестом с «подложным»patchesв результате модели, который публикация игнорирует.- Трейлеры коммита корректны:
Issue: #727,User-Visible: no,Co-Authored-By/Claude-Session— на месте.
Унаследовано из спец-ревью (без повторной проверки)
Продуктовых вопросов владельцу по ТЗ не было (спец-ревью r2, зелёный,
docs/reviews/SPEC-REVIEW-727-r2.md, материал a49f7095). Код-ревью принимает
формулировки К1–К9 и границы скоупа/не-скоупа как данность и проверяет только
соответствие кода этим формулировкам — что и сделано выше.
Чего не проверял
actionlint— бинарь недоступен в песочнице ревью; заявление автора не перепроверено независимо (риск низкий, см. «Как проверялось»).- Полные
npx tsc --noEmit/npm test/npm run build— не перегонял: зелёный Validate на93be52eb(ссылка в промпте) закрывает дешёвые гейты для этого SHA. - Живой ночной прогон (
_nightly.yml→ship-review.yml -f tag=nightlyна реальном GitHub Actions, включаяgit describeс настоящими тегами в checkout публикации) — автор сам называет это риском и предлагает понаблюдать первую живую ночь; контрактные тесты на реальном bash/git это закрывают настолько, насколько возможно до прод-прогона, но не заменяют его. - Browser-smoke, golden, performance, HA-pytest — объективно не применимы к этому диффу (обоснование — «Как проверялось»).
Вердикт
Все AC1–AC10 доказаны исполнением (не только чтением), два defensive-правила (stale-покрытие, выбор ближайшей архивной линии) проверены мутацией вручную и тест их ловит. High и Medium в скоупе нет. Одно Low-наблюдение (issue E из ТЗ не заведён) — вне скоупа #727, не блокирует.
Зелёный.
Материал раунда
- Ветка:
issue/727-nightly-ship-review, коммит93be52ebe8c3— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
990dd76b955a9fc9d630dd09709eaa5d4f052e05git log --all --format='%H %T' | grep 990dd76b955a - Тело issue:
8ce2942ca915bc938c8c5b4a720bb71d5a06d18c13db0692ee802f6a55662b7a - Вердикт конвейера:
green· High 0