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

20 KiB
Raw Blame History

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 на момент разбора.

Материал раунда

  • Ветка: dev, коммит `` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Якоря снять не удалось: ветки задачи нет, материал читался по dev.