From 90b185f603a49faf2d6012b112349a397dd8a07f Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:10:43 +0000 Subject: [PATCH] docs: review document for #472 Issue: #472 User-Visible: no --- docs/reviews/SPEC-REVIEW-472-r2.md | 229 +++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 docs/reviews/SPEC-REVIEW-472-r2.md diff --git a/docs/reviews/SPEC-REVIEW-472-r2.md b/docs/reviews/SPEC-REVIEW-472-r2.md new file mode 100644 index 00000000..d8d4af06 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-472-r2.md @@ -0,0 +1,229 @@ +# SPEC-REVIEW-472-r2 + +Этап: spec (PROCESS.md §2.4) · Трек: лёгкий (`small`) · Заход r2 · +блокирующих циклов 2/2 (лимит лёгкого трека — 2, §4) +SHA на момент ревью: `f52b2449eaadaa2f604634780a0da926779ad9c3` (dev) +Ревьюер ≠ автор ТЗ. + +## Скоуп повторного раунда + +Issue #472: еженедельный полный мутационный прогон (`mutation-gate.yml`) может +падать/отменяться без адресата. ТЗ в теле issue (лёгкий трек), контракт из 6 +пунктов, AC1–AC8, теперь три мутанта на будущий `scripts/mutation-gate.mjs`. +Продукт не затронут — чистая CI/process-задача, кода ещё нет (ветки +`issue/472-*` не существует, см. «Как проверялось»). + +Раунд r1 (жёлтый, `docs/reviews/SPEC-REVIEW-472-r1.md`, SHA `615045cb`) нашёл +M1 (AC3 не различает «сбежавший мутант» и «уже красный guard»), M2 (AC5 не +даёт job `report` прав на скачивание артефактов) и L1 (неверный номер строки). +Автор ответил комментарием «Оба Medium исправлены в теле ТЗ, Low тоже» и +отредактировал тело issue. **Предмет этого раунда — дельта тела issue**: +правки контракта п.3 (AC3, «Мутанты»), п.4 (AC5) и абзаца «Проблема» (ссылка +на строку). Полный разбор не требуется: dev не сдвинулся в затронутых файлах +между `615045cb` и текущим HEAD (см. ниже), новая подсистема не появилась, +контракт поведения не сменился — только текст ТЗ. + +## Как проверялось + +- Перечитано тело issue #472 целиком (текущая редакция) и все четыре + комментария (S2, «ТЗ готово», вердикт r1, «Оба Medium исправлены»). +- `git log --oneline 615045cb..HEAD -- .github/workflows/mutation-gate.yml + .github/workflows/validate.yml .github/workflows/announce.yml + scripts/mutation-gate.mjs test/mutation-gate.test.mjs` → пусто: ни один из + файлов, на которые опирается контракт, не менялся между материалом r1 и + текущим HEAD (`f52b2449`, второй коммит после `615045cb` — не по этой + задаче, документ ревью #475). Значит фактическое состояние репозитория, + описанное в r1, актуально и сейчас; переоткрывать сверку с файлами вне + делты незачем. +- `scripts/mutation-gate.mjs`: `runMutant` (строка 6759 — `FAIL ${mutant.id}: + тест остался зелёным на сломанном коде`), `runCleanGuards` (строки + 6771–6793 — `FAIL чистый прогон: ${guard} красный без мутанта`), `main` + (строка 6897, вызов `runCleanGuards` до цикла мутантов) — перечитаны заново, + подтверждают, что обе формы `FAIL` печатаются в один и тот же лог именно + так, как описывает исправленный п.3. +- `grep -oP "id: '\K[^']+" scripts/mutation-gate.mjs | grep -vE + '^[a-z0-9-]+$'` → пусто (509 id, `grep -c "id: '"` подтверждает то же + число, что и в r1) — техническая посылка M1 «все id — `[a-z0-9-]+`, различить + формы дёшево» не сломалась. +- `.github/workflows/mutation-gate.yml`: `permissions: contents: read` — + строка 31; `concurrency.group: mutation-gate` / `cancel-in-progress: true` + — строки 34–36; `checkout ref: ... || 'dev'` — строка 52. L1 (ссылка была + на строку 33) подтверждена закрытой: текст issue сейчас ссылается на 52. +- `.github/workflows/validate.yml`: job `preflight.permissions` — строки + 38–41 (`contents: read`, `actions: read`, `issues: read`) — прецедент, + на который ссылается M2-фикс в контракте п.4, подтверждён построчно. +- `git status --short`, `git branch -a | grep 472`, `git log --all --oneline + | grep 472` — рабочее дерево чистое, ветки `issue/472-*` нет, кода по этой + задаче в истории нет: контракт всё ещё чисто в стадии ТЗ, преждевременного + кода (§12) не обнаружено. +- Gates (`typecheck`/`test`/`build`) не запускал: для #472 ещё нет ни строки + продуктового или тестового кода (проверено выше), это ревью ТЗ, а не + код-ревью — гейты неприменимы, ровно как в r1. `docs/SCOPE.md`, + `docs/USER-GUIDE.ru.md`, канонические документы подсистем — не + применимы: задача не трогает продукт и видимое пользователю поведение + (чистый CI/process, как и установил r1). + +## Закрытие раунда r1 + +| Находка r1 | Чем закрыта | Где это видно | +|---|---|---| +| **M1** — AC3 не отличал «сбежавший мутант» от строки «guard уже красный без мутанта»; наивный парсер получил бы фиктивный id | Контракт п.3 теперь явно вводит `escaped` (id обязан существовать в реестре, иначе — `unparsed`) и `redGuards` (для `FAIL чистый прогон: …`) как разные ведра; AC3 переписан с перечислением всех четырёх случаев; добавлен третий мутант, целящийся именно в эту путаницу | Тело issue, «Контракт» п.3 (абзац «две формы `FAIL`…»), таблица AC/строка AC3, «Мутанты»/строка `mutation-report-red-guard-as-mutant` | +| **M2** — AC5 называл для job `report` только `permissions: issues: write`, job не смог бы скачать артефакты шардов (`actions: read` отсутствует) | Контракт п.4 и AC5 перечисляют `contents: read, actions: read, issues: write` с обоснованием каждого права (checkout, `download-artifact`, `gh issue`) | Тело issue, «Контракт» п.4 (первый абзац), таблица AC/строка AC5 | +| **L1** — ссылка на строку `mutation-gate.yml:33` для checkout `ref: dev` неверна (фактически 52) | Ссылка исправлена на `mutation-gate.yml:52`, подтверждено `grep -n` на HEAD `f52b2449` | Тело issue, «Проблема», последний абзац | + +Все три закрыты по существу (строка контракта/AC, не только заявление автора +в комментарии). + +## Унаследовано из r1 + +Не проверялось заново — дельта этого раунда их не задевает, а файлы, от +которых зависит их доказательство, не менялись с `615045cb`: + +- Контракт п.1 (раздельные concurrency-группы по `github.event_name`) и AC1; +- Контракт п.2 (артефакт лога на шард, `if: always()`) и AC2; +- Контракт п.5 (`workflow_sync` расширяется на `mutation-gate.yml`) и AC8; +- Контракт п.6 («что не меняется» — реестр, гварды, шардирование, `--changed`, + права `mutants`); +- AC6 (повторный отказ дописывает комментарий, не создаёт issue), AC7 + (отсутствие Telegram-секретов не роняет job); +- «Откат» (один коммит, без миграции данных); +- Критерии лёгкого трека §5 (все пять выполнены, отказа от трека не + требуется). + +Источник: `docs/reviews/SPEC-REVIEW-472-r1.md`, материал — SHA `615045cb1882 +fa36164a55d544dc6520f2f58618` (dev). Проверено, что `git log --oneline +615045cb..HEAD` для `.github/workflows/mutation-gate.yml`, +`.github/workflows/validate.yml`, `.github/workflows/announce.yml`, +`scripts/mutation-gate.mjs`, `test/mutation-gate.test.mjs` пуст — основание +для наследования не устарело. + +## Находки + +### Medium (в скоупе задачи) — 1 + +**M3 (новая, введена самим фиксом M1). Контракт п.3 противоречит себе насчёт +того, попадают ли `redGuards`/`unparsed` в тело письма, которое видит +владелец, — а AC4 проверяет только `escaped`-часть тела.** + +- Файл: тело issue #472, раздел «Контракт», п.3 «Репортёр — отдельный + тестируемый скрипт», и таблица AC, строка AC4. +- Первый абзац п.3 (тот самый, что закрывает M1) прямо обещает: «В теле они + идут отдельными разделами: сбежавшим — `--id=`, красным гардам — их + команда как есть» — то есть `redGuards` обязаны попасть в тело отдельным + разделом. +- Третий абзац того же пункта, который буквально перечисляет содержимое + тела, называет только: «дата, ссылка на прогон, SHA `dev`, таблица + шардов, список сбежавших мутантов с их `guard` из реестра… и одна фраза + «что делать»… [`--id=`] на каждый» [сбежавшего] — ни `redGuards`, ни + `unparsed` здесь не упомянуты вовсе. +- AC4 — единственный критерий, доказательство которого касается содержимого + `title`/`body`, — описывает то же самое узкое перечисление: «тело — + ссылку на прогон, SHA и по одной команде `--id=` на сбежавшего». Про + `redGuards`/`unparsed` в теле AC4 не говорит ничего. +- Симметричный, более мелкий след того же недосмотра: первый bullet п.3 + объявляет форму возврата `mutationGateReport` как `{ title, body, escaped, + redGuards, shards }` — без поля `unparsed`, хотя тот же абзац требует + отдельный список `unparsed` для строк с id вне реестра, и AC3 явно + включает `unparsed` в перечень того, что репортёр обязан произвести. +- Как это проявляется (разобрано по тексту — реализации ещё нет, «проверено + чтением, не исполнением» неприменимо, применяю «проверено по контракту»): + реализация, которая берёт AC4 буквально и пишет unit ровно на «заголовок + + дата + ссылка + SHA + таблица шардов + список escaped с `--id=`», проходит + все восемь AC — и при этом `redGuards`/`unparsed` никогда не попадают в + письмо, которое читает владелец. Это тот же класс дефекта, ради устранения + которого заведена вся задача («у отказа нет адресата»): «guard был красным + ещё до мутанта» или «встретилась нераспознанная `FAIL`-строка» — оба + случая реального отказа шарда — снова тихо не долетают до issue/Telegram, + просто на один уровень глубже (не сам факт падения шарда, а его причина). +- Почему это находка именно r2, а не пропуск r1: до фикса M1 контракт вообще + не различал `redGuards`/`unparsed` как отдельные сущности — само это + разделение и обещание «отдельных разделов» появилось в тексте только + сейчас, вместе с ответом на M1. Дельта этого раунда его и порождает. +- Что чинит: одна фраза в AC4 (или новый AC4b) — тело обязано содержать + `redGuards` отдельным разделом с командой гарда «как есть», и явно сказать, + что происходит с `unparsed` в теле письма (свой раздел — либо, если решение + «в тело не выводить, только в служебное поле», написать это прямо, а не + оставлять молчание, которое ревьюер обязан трактовать как недосказанность, + а не как решение); плюс добавить `unparsed` в объявленную форму возврата + `mutationGateReport`. + +### Low — 1 (не блокирует, на усмотрение автора) + +**L2.** Раздел «Затронутые файлы» всё ещё говорит «`scripts/mutation-gate.mjs` +— два мутанта», хотя после фикса M1 в разделе «Мутанты» их три (добавлен +`mutation-report-red-guard-as-mutant`). Реализацию не путает — список мутантов +в разделе «Мутанты» исчерпывающий и однозначный, реализатор будет читать его, +а не считать по итоговой сводке, — но счётчик стоит поправить для +согласованности документа. + +## Что проверено и корректно + +- M1: закрытие сверено с фактическим кодом `runMutant`/`runCleanGuards`/ + `main` на HEAD — обе формы `FAIL` действительно печатаются в один лог в + точности так, как теперь описывает контракт; техническая посылка (все id — + `[a-z0-9-]+`, различение дёшево) реверифицирована и держится. +- M2: закрытие сверено с прецедентом `validate.yml` (`preflight.permissions`, + строки 38–41) — набор прав `contents: read, actions: read, issues: write` + и обоснование каждого совпадают с моделью GitHub Actions (job-level + `permissions` заменяет, а не дополняет). +- L1: номер строки `mutation-gate.yml:52` подтверждён `grep -n` на HEAD. +- AC1, AC2, AC6–AC8, «Откат», критерии лёгкого трека §5 — не тронуты дельтой, + файлы, на которых основано их доказательство, не менялись с `615045cb`; + наследуются без переисполнения (раздел выше). +- Кода по issue #472 нет нигде в дереве и истории — задача честно остаётся в + стадии ТЗ, преждевременного кода (§12) не обнаружено. + +## Чего не проверял + +- `npx tsc --noEmit` / `npm test` / `npm run build` — не запускал: для #472 + нет ни строки кода (проверено `git status`/`git branch`/`git log --all`), + гейты неприменимы на этапе ревью ТЗ. +- `node scripts/mutation-gate.mjs` живьём, эмуляция `--shard` со сломанным + guard — не запускал: `scripts/mutation-gate-report.mjs` и + `test/mutation-gate-report.test.mjs` ещё не существуют (подтверждено `ls`), + исполнять нечего; вывод по M1/M3 получен чтением контракта и существующего + `scripts/mutation-gate.mjs`, а не исполнением. +- golden / браузерные смоки / инварианты модели / performance-профили — не + применимы: diff не существует, задача не трогает продукт, фронтенд или + геометрию. +- Контракт п.1, п.2, п.5, п.6, AC1, AC2, AC6–AC8, «Откат» — не переразбирал + по существу сверх сверки «файлы не менялись» (см. «Унаследовано из r1»); + если у ревьюера были к ним вопросы, они должны были прозвучать в r1. +- Точность фразы «паттерн [пропуска при отсутствующих секретах] уже есть в + `announce.yml`» (AC7, контракт п.4, абзац про Telegram) — эта часть текста + не менялась между r1 и r2 и не входит в дельту; r1 принял AC7 как + доказанный, переоткрывать вне делты по правилам §2.10 не стал. + +## Вердикт + +Одна находка Medium **в скоупе задачи** (M3), High нет. По §2.4/§7.2 это +жёлтый вердикт: автор правит текст ТЗ (тело/AC4, при необходимости — форму +возврата в п.3), фикс проходит третий заход. Бюджет циклов лёгкого трека — +**2** (§4); эта находка использует второй из них, то есть лимит на этом +раунде исчерпан. Дальнейшая правка возможна («заход» и «цикл» — разные +величины, заходов может быть больше двух), но при новой блокирующей находке +в r3 решение по §4 переходит владельцу (разделить/отклонить/арбитраж), а не +автоматически продолжает цикл. + +--- + +## Материал раунда + +- Ветка: `dev`, коммит `f52b2449eaadaa2f604634780a0da926779ad9c3`. +- Файлы, от которых зависит контракт, не менялись с материала r1 + (`615045cb1882fa36164a55d544dc6520f2f58618`): `git log --oneline + 615045cb..HEAD -- .github/workflows/mutation-gate.yml + .github/workflows/validate.yml .github/workflows/announce.yml + scripts/mutation-gate.mjs test/mutation-gate.test.mjs` → пусто. +- Ветки задачи `issue/472-*` не существует — ТЗ живёт в теле issue (лёгкий + трек), материал этого раунда — текущая редакция тела issue #472 на момент + разбора. + +--- + + + +## Материал раунда + +- Ветка: `dev`, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Якоря снять не удалось: ветки задачи нет, материал читался по `dev`.