Files
houseplan-card/docs/reviews/SPEC-REVIEW-472-r2.md
T
2026-09-06 11:10:43 +00:00

230 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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=<id>`, красным гардам — их
команда как есть» — то есть `redGuards` обязаны попасть в тело отдельным
разделом.
- Третий абзац того же пункта, который буквально перечисляет содержимое
тела, называет только: «дата, ссылка на прогон, SHA `dev`, таблица
шардов, список сбежавших мутантов с их `guard` из реестра… и одна фраза
«что делать»… [`--id=<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 на момент
разбора.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `dev`, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Якоря снять не удалось: ветки задачи нет, материал читался по `dev`.