mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -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=<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`.
|
||||
Reference in New Issue
Block a user