22 KiB
SPEC-REVIEW-727-r1
Issue: #727 · этап: spec · трек: ask · заход: r1 · блокирующих циклов израсходовано (после этого раунда): 1/4
Скоуп
#727 — первая из двух независимых частей, выделенных из #707 п.3: ночное
пакетное ship-ревью на dev и переиспользование его результата в гейте беты
(ship-review.mjs check). Контракт — К1 (патч-набор задачи по git patch-id --stable), К2 (ночной режим tag=nightly, имя документа
SHIP-REVIEW-<база>-dev-<sha12>.md), К3 (shipCoverage: clean/high/
stale/none), К4 (гейт check по покрытию), К5 (ревью беты читает только
дельту none/stale), К6 (job в _nightly.yml, dispatch ship-review.yml с
tag=nightly), К7 (индекс и архив узнают ночное имя), К8 (комментарий в
задачу при High), К9 (канон — PROCESS.md §10.4/§11.7, REVIEWER.md). Красная
ночь (комментарий по задачам, слитым после последней зелёной) — отдельный
issue E, в #727 не входит. Продуктового кода задача не трогает (класс A
файлов нет), User-Visible: no. ТЗ живёт в теле issue под ## ТЗ; комментарий
владельца 2026-09-30 передаёт на ревью с треком ask, обоснование которого —
первая строка раздела «ТЗ» (сложность/риск >3: четыре поверхности; публичный
контракт: правило гейта беты §11.7 меняется с «документ тега покрывает все
ship-задачи» на «задачи покрыты документами диапазона с тем же патч-набором»)
— соответствует критериям §5, трек ask обоснован корректно.
Как проверялось
Ревью текстовое (этап spec, продуктовой ветки нет). Три слоя:
- Обязательные разделы §7.1 и однозначность каждого AC — чтением тела issue.
- Проверка каждого технического утверждения по текущему коду на материале
ревью (
HEADрабочей копии40607aa37f13819c9db37a392a1d99f30382b3c0, совпадает сorigin/dev): ТЗ называет конкретные функции, регэкспы, имена файлов, мутанты и исторические коммиты — каждое утверждение либо подтверждается чтением, либо остаётся непроверенной догадкой (находка по §7.1). Список проверенного — ниже. - Гейты не прогонялись: задача не меняет ни одного файла репозитория на этом этапе (см. «Чего не проверял»).
Находки
Medium — К7/AC7: архивирование ночного документа с базой-стабильным-тегом не имеет ни одного проверяемого примера, а текущий код его не реализует
Файл: тело issue #727, раздел «Контракт поведения» → К7, второй подпункт («база — стабильный тег → первая архивируемая стабильная линия новее базы»); AC7 в таблице «Критерии приёмки».
Воспроизведение. archivePlan (scripts/reviews-archive.mjs:91-97) сегодня
для doc.stage === 'ship' вычисляет единственную целевую линию так:
const line = doc.tag.replace(/-beta\.\d+$/, '');
if (tags.has(line)) moves.push({ ... to: `${ARCHIVE_DIR}/${line}/${name}` ... });
else kept.push({ name, reason: `ревью линии ${line} не входит в архивируемые линии` });
Это находит ТОЧНОЕ совпадение (после снятия -beta.N) между базой документа и
одной из архивируемых стабильных линий. Для сегодняшнего единственного формата
имени (SHIP-REVIEW-<тег-беты>.md) это корректно: база всегда сама и есть та
линия, в которую документ должен уйти.
К7 вводит новый случай — ночной документ, чья база (doc.tag после парсинга
нового формата К2/AC7) может быть стабильным тегом (ночь, прошедшая после
стабильного релиза и до первого beta.1 следующей линии — это не редкий
случай, а обычное окно между двумя циклами). Для него К7 явно требует: «первая
архивируемая линия новее базы», то есть не точное совпадение, а поиск
следующего по порядку тега. Текущая реализация archivePlan такого поиска не
делает вообще: если doc.tag (стабильный) не входит дословно в tags
(например, база v1.78.0, а архивируется уже v1.79.0), ветка попадёт в
else и документ останется в docs/reviews/ с причиной «линия не входит в
архивируемые», а не переедет в v1.79.0, как того хочет К7. Это значит: правило
К7 для этой ветки требует новой логики в archivePlan (поиск ближайшего
большего тега, не поиск по равенству), а не простого использования
существующей функции.
AC7 при этом даёт ровно три конкретных примера — parseDocName(...) на новое
имя, renderIndex его перечисляет, archivePlan уносит документ с базой-бетой
в v1.79.0 (это старая, уже поддержанная ветка кода). Для ветки «база —
стабильный тег» AC7 ограничивается пересказом самого правила («База v1.79.0
→ первая архивируемая линия новее базы») без единого конкретного входа и
ожидаемого выхода: какая база, какой набор архивируемых тегов, какой
результат. Без такого примера реализатор и ревьюер кода не смогут отличить
осмысленную имплементацию нового алгоритма от случайной: нет фиксированной
пары «вход → выход», по которой можно требовать тест, а «способ доказательства»
AC7 называет только файл и раздел теста, не конкретный случай.
Почему это находка, а не мелочь. Правило К7 в этой части меняет публичный
контракт архивирования (куда физически переедет документ, на который будущие
ревью ссылаются как на «унаследовано из r», §2.10) — ошибка здесь тихая:
archivePlan возвращает план, который применяется git-мультивом в --apply, и
неверный путь обнаружится не тестом, а человеком, читающим архив postfactum.
Ветка реально достижима (первое же окно между релизом и следующей бетой), не
теоретический край.
Что нужно от автора. Добавить в AC7 (или в само К7) один конкретный пример
для ветки «база — стабильный тег»: например, «архивируемые линии
[v1.78.0, v1.79.0], документ с базой v1.78.0 (стабильный) → переезжает в
v1.79.0» и отрицательный случай «архивируемых линий новее базы ещё нет →
документ остаётся (kept), как сегодня для тега, которого нет в tags». Это
дёшево (одна строка в таблице AC) и снимает риск, что «первая линия новее
базы» останется нереализованной или реализованной с ошибкой в граничном
случае (нет более новой линии, несколько линий новее одновременно).
Без High в задаче это жёлтый вердикт, правка ТЗ и повторный цикл (§2.4, §4).
Что проверено и корректно
ТЗ необычно тщательно сверено с текущим кодом и историей; кроме находки выше, все проверенные утверждения подтвердились дословно:
- Существование и сигнатуры
readCandidateHistory,issueTrailers(scripts/release-membership.mjs, импортируются вscripts/ship-review.mjs:28),shipReviewDocPath,isShipIssue,specSection,shipIssuesInRange,renderShipBrief,anchorBlock,parseAnchorBlock,shipReviewProblems,readShipDocвscripts/ship-review.mjs— все существуют как названо. RELEASE_TAG_RE(scripts/ship-review.mjs:33) иshipReviewDocPath(падает на нерелизном теге) — подтверждают, что литеральноеtag=nightlyне может случайно совпасть с реальным тегом релиза (риск «неявный вход», который ТЗ сам называет и откладывает).- Текущий машинный блок документа (
docs/reviews/SHIP-REVIEW-v1.79.0-beta.1.md, якорь<!-- hp-ship-review-anchors -->, поляtag/candidate/base/issues/ high/medium/low/run) совпадает построчно с тем, что генерируетanchorBlock— К2's «прежние строки блока не меняются», «дополняется mode/patches» корректно описывает расширение, а не переписывание существующего формата. - Существующий тег
v1.79.0-beta.1и документSHIP-REVIEW-v1.79.0-beta.1.mdреальны (нет фантомных примеров); пример имени ночного документа в К2/AC7 (SHIP-REVIEW-v1.79.0-beta.1-dev-108427dc1234.md) корректно использует реальный тег как базу. - Коммит
dca0fd28(«Release v1.79.0-beta.1 candidate») реально несёт трейлерыIssue: #661/#692/#693/#711/#713иRelease: v1.79.0-beta.1— обоснование исключенияRelease:-коммитов из патч-набора (К1) фактически точное, не гипотетическое. SHIP_DOC_NAMEвscripts/reviews-index.mjs:32иparseDocName(scripts/reviews-index.mjs:45-58) сегодня действительно узнают толькоSHIP-REVIEW-<тег>.mdбез суффикса — К7's требование расширить регэксп под-dev-<sha12>сnightly: trueкорректно описывает необходимое изменение, а не несуществующий пробел.parseDocName/archivePlanвscripts/reviews-archive.mjsимпортируются и используются именно так, как описывает К7 (кроме разобранного пробела).- Существующий тест
test/nightly-workflow.test.mjs(29 строк) уже содержитtest('nightly ждёт запущенный Validate и падает вместе с ним (#492 §7)', …)и тест про русское имя job — ровно те тесты, которые АC6 требует оставить зелёными; файл не новый, «Затронутые файлы» его называет верно. - Текущий
_nightly.yml— одна jobdispatch, делаетgh workflow run validate.yml --repo "$REPO" --ref dev -f full=trueтокеномGH_TOKEN: ${{ github.token }}, ждёт появления прогона до 3 минут (18×10с) — это ровно прецедент, на который К6 ссылается («как у dispatch Validate», «до трёх минут, как у Validate»); новая job для ship-review по аналогии технически реализуема без смены токена или прав. ship-review.yml(тонкий,main) сегодня действительно объявляет входыtag(required),candidate(optional, default""),force(optional boolean) и праваcontents: read/ jobdev:contents: read, issues: read— К6's «тонкие файлы не меняются: входы... и права... уже есть в main» подтверждается дословно;tag=nightlyне требует нового входа.HP_PROCESS_TOKEN— существующий секрет, уже используется для публикации комментариев/меток конвейером (_process.yml,_process-resume.yml,_beta-derived.ymlи др.) — К8's «Токен — HP_PROCESS_TOKEN, как у публикации» ссылается на реальный, а не придуманный прецедент; в_ship-review.ymlсегодня комментариев в issue нет — это действительно новая часть работы, и ТЗ её как новую не маскирует.- Мутант
ship-review-ignores-merge-markerсуществует вscripts/mutation-registry.mjs:13732(AC10). node scripts/entry-cost.mjs --check— реальная команда (scripts/entry-cost.mjs:12,75), AC9 её не выдумывает.test/process-digests.test.mjs,test/ship-review.test.mjsсуществуют — план автотестов ссылается на реальные файлы, не на планируемые с нуля (кромеtest/nightly-workflow.test.mjs, который тоже существует).- Обязательные разделы §7.1 (сценарий, что человек увидит, проблема, скоуп и не-скоуп, контракт поведения, UX/данные/миграция/i18n/perf/touch, критерии приёмки AC1–AC10 со способом доказательства, план автотестов, риски, откат, release-артефакты) — присутствуют, в правильном порядке.
- «Принято предположительно» — 6 пунктов, все — реальные технические развилки (способ dispatch, гранулярность severity по документу vs по задаче, критерий «последний документ», имя файла), ни один не маскирует продуктовый вопрос: гранулярность «High снимает покрытие со всех задач документа» совпадает с уже действующим сегодня правилом гейта §11.7 («документ обязан покрывать их все и не нести High» — это уже документ-уровневый, а не задаче-уровневый гейт), так что это не новый прецедент, а перенос старого.
- Факты сверены автором с
origin/dev 108427dc— коммиты между этим SHA и материалом ревью (git diff 108427dc..40607aa3 --stat) не затрагивают ни один из файлов, которые называет ТЗ (scripts/ship-review.mjs,scripts/reviews-index.mjs,scripts/reviews-archive.mjs,.github/workflows/_nightly.yml,.github/workflows/_ship-review.yml,.github/workflows/ship-review.yml) — материал не устарел за эти 4 коммита. - Продуктовых вопросов владельцу в ТЗ нет; единственный открытый пункт, который я бы мог счесть техническим спором (гранулярность severity), оказался уже действующим прецедентом — оспаривать нечего.
Чего не проверял
- Гейты (
npx tsc --noEmit,npm test,npm run build) не прогонял — задача не меняет ни одного файла репозитория на этом этапе; они станут обязательны наS7-code-reviewк реальному диффу. - Не проверял golden/смоки/бэкенд-pytest/инварианты модели — задача класса A
не содержит,
User-Visible: no, геометрии и визуала не касается (сама ТЗ это явно фиксирует в разделе «UX · данные · i18n · миграция · perf · touch» и «Release-артефакты»). - Не проверял корректность
git patch-id --stableна реальных cherry-pick сценариях (АС1 «тот же дифф в другом коммите даёт тот же patch-id») — это задокументированное поведение самой команды git, не специфика этого кода; проверка на встроенном временном git-репозитории — задача автора теста, не спецификации. - Не оценивал реальную стоимость job
prepareship-ревью в ночи («около минуты, без npm ci») — измеримо только на первом живом прогоне; ТЗ сама называет первый живой прогон «наблюдением, не AC» (раздел «Риски»). - Не проверял, действительно ли GitHub допускает
workflow_dispatchчерезgh workflow runсGITHUB_TOKENдля другого workflow-файла (ship-review.yml) из job с правомactions: write, отличным от той же операции дляvalidate.ymlв существующем коде, — по документированному поведению GitHub Actions разницы по целевому workflow нет (workflow_dispatchAPI не различает вызываемый файл), и это тот же механизм, что уже работает в_nightly.yml; отдельно на реальном runner не проверял.
Вердикт
Один Medium в скоупе задачи (К7/AC7: правило архивирования ночного
документа с базой-стабильным-тегом не реализуемо существующим archivePlan и
не имеет ни одного конкретного примера входа/выхода в AC) — без High это
жёлтый вердикт с возвратом автору по §2.4. Все прочие технические утверждения
ТЗ — имена функций, регэкспы, номера строк, реальные теги и коммиты — сверены
построчно с dev и подтвердились; трек ask обоснован верно; продуктовых
вопросов владельцу нет.
Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 1 → в задаче
Материал раунда
- Этап: spec. Материал — тело issue #727 на момент комментария Matysh 2026-09-30T23:25:21Z («Оценка и ТЗ: трек ask… Передаю на ревью ТЗ»).
- Кода/ветки продукта не существует (инфраструктурная задача до реализации);
факты сверены с рабочей копией на
HEAD=origin/dev=40607aa37f13819c9db37a392a1d99f30382b3c0.
Материал раунда
- Ветка:
dev, коммит40607aa37f13— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
901e6cbd1964ef2dcd9a0e08e8ec01a5c47dcac7git log --all --format='%H %T' | grep 901e6cbd1964 - Тело issue:
1c9e15704953110b6c7e68babaa7500b8d3e2331e4583fcfff52d41462321e7a - Вердикт конвейера:
yellow· High 0