21 KiB
Код-ревью #556 · заход r2
Материал: 2642fdb446be65f5ebed16b9f892d6f7bd47715a (ветка issue/556-ci-trust-boundaries
на origin/dev = ad886b7f…; рабочая копия проверена на этом же коммите —
git rev-parse HEAD совпадает). Инфраструктурная задача (§1 PROCESS.md): полный
диапазон origin/dev...HEAD не содержит ни одного файла класса A — только
.github/workflows/**, scripts/**, test/**, docs/DEVELOPMENT.md.
Вердикт r1: красный, docs/reviews/CODE-REVIEW-556-r1.md, материал
954eeff45aeb187f74d460d35426ffec7299ca7b, H1 (блокирует).
Дельта r1 → r2
git diff 954eeff4..2642fdb4 — три содержательных коммита автора
(b3555188/954eeff4 уже были материалом r1; новые — e0098c8d, 2642fdb4;
717308f0 — публикация документа r1 конвейером, не автора):
.github/workflows/process.yml: у jobmodel_reviewpermissions:меняется с{contents: read, id-token: write}на{contents: read, issues: write}; шагуReview(uses: anthropics/claude-code-action@9cdae7f0…) добавленgithub_token: ${{ secrets.GITHUB_TOKEN }}.test/review-doc-guard.test.mjs: существующая проверка «права по job» обновлена под новый набор permissions; добавлен новый тест «модель работает job-scoped токеном, а не App-обменом (#556)», проверяющий и наличиеgithub_token, и отсутствиеadditional_permissions, и отсутствиеid-token: write.scripts/mutation-gate.mjs: два новых мутанта —review-model-gets-repository-write(снимаетcontents: read→write) иreview-job-trusts-the-app-token(снимает строкуgithub_token:).docs/DEVELOPMENT.md: новый раздел «What the review model is allowed to do (#556)», описывающий тот же механизм.
Больше ничего в делте нет: scripts/action-pins.mjs, scripts/review-result-gate.mjs
и их тесты в диапазоне 954eeff4..2642fdb4 не тронуты ни строкой — сверено
git diff по этим путям напрямую (пусто).
Закрытие раунда r1
| Находка r1 | Чем закрыта | Где видно |
|---|---|---|
H1 (High): permissions: {contents: read, id-token: write} у model_review не было потолком — claude-code-action без github_token меняет OIDC на собственный App-токен с дефолтом contents/issues/pull_requests: write, и ghs_… от claude[bot] лежал прямо в окружении Bash модели |
Шагу Review передан github_token: ${{ secrets.GITHUB_TOKEN }} (process.yml:892); id-token: write снят, permissions: job сведены к {contents: read, issues: write} (process.yml:762-764). С переданным github_token setupGitHubToken() (src/github/token.ts на том же закреплённом SHA) возвращает OVERRIDE_GITHUB_TOKEN и на обмен не идёт — обмен, из которого рождался App-токен с write, исчезает |
process.yml:748-764 (комментарий с разбором), process.yml:888-892; тест ревью: модель работает job-scoped токеном, а не App-обменом (#556) в test/review-doc-guard.test.mjs:920-933; мутант review-job-trusts-the-app-token — прогнан мной, «поймано 1 из 1» |
Строка env | grep -i GITHUB_TOKEN из r1 (живое доказательство ghs_…)
воспроизведена и в этой сессии (см. «Как проверялось» ниже) — это не
регресс фикса, а следствие того, что r2 всё ещё выполняется по СТАРОМУ
process.yml из main (см. «Что не проверял»/раздел ниже); автор
предупредил об этом отдельным комментарием после хендоффа r2, и наблюдение
ему соответствует буквально.
Унаследовано из r1 (без повторной проверки)
Документ docs/reviews/CODE-REVIEW-556-r1.md, материал
954eeff45aeb187f74d460d35426ffec7299ca7b — дельта r2 этих файлов не касается:
- SHA-пины. Все 116
uses:в 9 воркфлоу закреплены полным SHA;scripts/action-pins.mjs+test/action-pins.test.mjs(7/7); гейт стоит шагомaction_pinsв preflight Validate. Принято r1 как «реализовано корректно и доказано исполнением». - Граница artifact →
integrate.scripts/review-result-gate.mjs: точный набор файлов, контрольные суммы, все 17 полей паспорта, словарь вердикта; 15 враждебных фикстурtest/review-result-gate.test.mjs, 4 мутанта индивидуально прогнаны в r1 («поймано 1 из 1» по каждому).integrateпо-прежнему ходит на чистый checkoutdev(persist-credentials: false) и получает только запечатанный artifact — сверено чтением текущегоprocess.yml:1152-1237, строки не изменились относительно материала r1. - Трейлеры
b3555188/954eeff4(User-Visible: no) — приняты в r1.
Как проверялось
Дешёвые гейты этого захода уже подтверждены Validate на 2642fdb4
(run 34770378775,
conclusion: success) — typecheck/test/build/bundle-sync/hacs/hassfest/
diff-мутанты по всем 6 шардам покрыты этим прогоном; tsc --noEmit, npm test,
npm run build не перегонял.
Прогнано мной дополнительно, целенаправленно на дельту r2:
| Команда | Результат |
|---|---|
node --test test/review-doc-guard.test.mjs |
61/61 green, включая оба теста «по job» и «job-scoped» |
node scripts/mutation-gate.mjs --id=review-job-trusts-the-app-token |
«поймано 1 из 1» |
node scripts/mutation-gate.mjs --id=review-model-gets-repository-write |
«поймано 1 из 1» |
python3 -c yaml.safe_load(...) по .github/workflows/process.yml |
парсится без ошибок |
git diff 954eeff4..2642fdb4 -- scripts/action-pins.mjs scripts/review-result-gate.mjs test/action-pins.test.mjs test/review-result-gate.test.mjs |
пусто — r1-материал не тронут делтой |
Живое наблюдение в этой же сессии (model_review для #556, событие issues) |
`env |
node scripts/check-docs.mjs и сверка visualFingerprint(ROOT) с docs/images/screenshots.json вручную |
совпадают побайтово — фингерпринт НЕ устарел на этом материале (см. находку ниже) |
diff <(git show origin/main:.github/workflows/process.yml) <(git show origin/dev:.github/workflows/process.yml), то же для mutation-gate.yml |
main и dev пока идентичны — расхождение появится только ПОСЛЕ слияния этой ветки |
Почему живое наблюдение не опровергает фикс
Последнее наблюдение (ghs_…, claude[bot]) выглядит как повтор находки r1,
но это ожидаемо и не регресс: для события issues GitHub загружает
process.yml из ветки по умолчанию (main), а не из ветки задачи — фикс
физически не может подействовать на СОБСТВЕННУЮ ревью-сессию до слияния и
зеркалирования файла в main. Автор явно предупредил об этом отдельным
комментарием после хендоффа r2. Косвенное подтверждение того же вывода —
OVERRIDE_GITHUB_TOKEN в окружении этой сессии пуст: github_token: в
запущенный с action не передавался, то есть выполнялась именно старая
версия шага. Правильный признак после слияния и зеркалирования, по которому
это стоит проверить в журнале следующего живого прогона: строка
Using provided GITHUB_TOKEN for authentication вместо Requesting OIDC token… / Exchanging OIDC token for app token…, и токен в окружении
Bash-инструмента модели не начинается с ghs_ (или начинается, но
принадлежит не App, а ambient job-токену — различимо по gh auth status,
который для ambient токена покажет github-actions[bot], а не claude[bot]).
Не проверял и не мог проверить: реальный интерсект additional_permissions с
серверной стороной обмена токена (сервис недоступен для инспекции) — но фикс
этот путь не использует вовсе, поэтому вопрос снят самой формой решения, а не
предположением о ней.
Не запускал: golden:verify, demo/smoke_*, pytest tests_backend,
model-invariants, performance-профили — diff не касается src/**,
custom_components/**/*.py и геометрии ни одной строкой; в Validate эти job
skipped закономерно.
Находки
Блокирующих находок нет. Одно уточнение к двум замечаниям автора вне скоупа (не Medium-дефект и не повод заводить отдельный issue — см. обоснование ниже).
Уточнение к замечанию автора «нужно зеркало process.yml в main»
Автор дважды (r1 хендофф, r2 комментарий) отметил только process.yml.
Фактический охват шире и уже механизирован существующим (не новым) шагом
Validate "Процесс: process.yml и mutation-gate.yml идентичны в main и dev"
(validate.yml:94-113, continue-on-error: true, но входит в общий вердикт
preflight и красит его). Эта же задача SHA-пинами трогает mutation-gate.yml
(расписание еженедельного полного мутационного прогона грузится из main
ровно по той же причине, что и process.yml — комментарий на mutation-gate.yml:99-101
и issue #472). Сейчас main и dev по этим двум файлам идентичны (проверено
diff выше); после слияния этой ветки они разойдутся по обоим файлам сразу,
workflow_sync в Validate станет FAIL, и по правилу §8 («Гейт беты: CI
Validate зелёный на точном SHA») это заблокирует ближайший бета-кандидат, а не
только «сломает событие issues», как сформулировано в хендоффе.
Не завожу отдельный issue: это не дефект #556, а существующий (до-#556)
защитный гейт, который сработает штатно, и разовое действие, доступное только
владельцу (push в main — по AGENTS.md исключительно владельческая операция);
сам факт необходимости зеркалирования уже дважды зафиксирован автором в этом
issue, «оставили в тексте» здесь неприменимо — заводить второй issue про то же
самое разовое действие избыточно. Фиксирую только для точности: зеркалировать
нужно оба файла, и последствие при задержке — красный workflow_sync в
Validate на dev, а не абстрактное «расхождение».
Замечание автора «check-docs красный на dev» — больше не воспроизводится
Дважды повторённое в r1/r2 замечание («стух отпечаток скриншотов на чистом
dev») на материале r2 не подтвердилось: node scripts/check-docs.mjs и
node scripts/check-docs.mjs --screenshots=strict проходят чисто, без единого
предупреждения; сверка visualFingerprint(ROOT) с
docs/images/screenshots.json:sourceFingerprint вручную дала побайтовое
совпадение
(05e8facaeefbf66bd8a365ed55111ca0e583d2cb6edd188147c57953ba7bc1ec). Между
базой r1 (origin/dev = 8fa2568c) и базой r2 (origin/dev = ad886b7f)
в dev прошла отдельная задача (#563, судя по сообщениям коммитов
aa13e226/cc0b41ba), которая, по всей видимости, обновила скриншоты —
никакого действия по #556 это не требует, задача не в её скоупе и не задета
её диффом. Указывать отдельным issue нечего — состояние уже здоровое.
Что проверено и корректно
- Фикс H1 сам по себе. Логика верна и минимальна:
github_token:вwith:шагаReviewактивируетOVERRIDE_GITHUB_TOKENвsetupGitHubToken(), обмен OIDC→App-токен не происходит,permissions: {contents: read, issues: write}job становится реальным потолком для ambient-токена, который достаётся действию.id-token: writeкорректно снят как более не нужный — других потребителей OIDC в этой job нет (проверено чтением всех шаговmodel_review,process.yml:776-1105). issues: writeоставлено осознанно и обоснованно. Нужно для двух процессных обязанностей ревьюера — комментарий с вердиктом (§7.2) и отдельный issue на Medium вне скоупа (§12); снятие потребовало бы переноса обеих обязанностей вintegrate— отдельная правка конвейера, справедливо не делается в этой задаче. Записано и обосновано в коде (process.yml:756-761) и вdocs/DEVELOPMENT.md.- Свидетель проводки токена не полагается на порядок спреда чужого кода.
Автор сознательно выбрал
github_token:вместоadditional_permissions:именно по этой причине (сужение черезadditional_permissionsзависит от недокументированного порядка{...DEFAULT_PERMISSIONS, ...additional}в чужом исходнике) — разумный, консервативный выбор. - Мутанты ловят именно то, что заявлено.
review-job-trusts-the-app-tokenснимает ровно строкуgithub_token:— без свидетеля это тихо возвращает модели App-токен сcontents: write, зелёным прогоном; поймано.review-model-gets-repository-writeменяетcontents: read→write— тоже поймано. Оба--test-name-pattern("job-scoped","по job") проверены на уникальность совпадения по файлу тестов — каждый матчит ровно один тест. - Тест
test/review-doc-guard.test.mjsне даёт ложноположительного совпадения. Обновлённый тест «права по job» и новый «job-scoped» проверяют разные, но не противоречащие друг другу свойства одного и того же блока; 61/61 зелёных сборок. - Трейлеры.
e0098c8d,2642fdb4— обаIssue: #556,User-Visible: no, без изменений в changelog — верно для инфраструктурной, не User-Visible правки. - YAML синтаксис
process.ymlпарситсяyaml.safe_loadбез ошибок после правки.
Чего не проверял
- Полные
tsc/test/build— зачтены зелёным Validate на2642fdb4(см. выше), не перегонял. golden:verify, browser-смоки,pytest tests_backend,model-invariants, performance-профили — diff не касаетсяsrc/**,custom_components/**/*.pyни геометрии; в Validate соответствующие jobskipped.- Реальный постмерж-прогон
model_reviewпод новымprocess.yml— недоступен до слияния и зеркалирования вmain(см. раздел выше); признак, по которому это стоит проверить в журнале следующего запуска, зафиксирован там же. - Серверную сторону обмена токена
api.anthropic.com/api/github/github-app-token-exchange— недоступна для инспекции извне; вывод не опирается на предположение о ней, фикс обходит этот путь целиком.
Вердикт
Находка H1 закрыта по существу и доказана: чтением кода (github_token →
OVERRIDE_GITHUB_TOKEN → обмен не происходит), тестом, который ловит обе
регрессии индивидуально прогнанными мутантами, и документацией решения.
Delta r2 не касается унаследованных из r1 частей (SHA-пины, граница
artifact→integrate), проверка которых сохраняет силу. Блокирующих находок
нет; два замечания автора вне скоупа уточнены/сняты выше без отдельных issue.
Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0 → в задаче
Материал раунда: ветка issue/556-ci-trust-boundaries, HEAD
2642fdb446be65f5ebed16b9f892d6f7bd47715a (git rev-parse HEAD в рабочей
копии совпадает). Предыдущий раунд: docs/reviews/CODE-REVIEW-556-r1.md,
материал 954eeff45aeb187f74d460d35426ffec7299ca7b.
Материал раунда
- Ветка:
issue/556-ci-trust-boundaries, коммит2642fdb446be— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
a1d7cf9fa60261ba39a52335e21e7287199b293agit log --all --format='%H %T' | grep a1d7cf9fa602 - Тело issue:
0fae31d317a2b03af2c7f61185acc7eb55eebe03858995b3549aa28aa5ff487d - Вердикт конвейера:
green· High 0