ci: thin default-branch callers invoke reusable bodies at @dev (#623)

Six workflows run from the default branch (issues, schedule, workflow_run):
process, process-resume, process-reconcile, mutation-gate, nightly,
process-metrics. Their bodies move to _<name>.yml (on: workflow_call); the
original files keep only triggers, run-name, permissions, concurrency and one
job `uses: Matysh/houseplan-card/.github/workflows/_<name>.yml@dev` with
`secrets: inherit`. A pipeline change becomes one commit to dev.

- caller job permissions = union of body job permissions (#556 minimum kept
  per job inside the body); caller `if` repeats the body guard for process and
  process-resume so unrelated events stay skipped;
- dispatch inputs forwarded via workflow_call inputs of the same names;
- _mutation-gate.yml keys evidence/marker on job.workflow_sha (the body SHA):
  in a called workflow github.workflow_sha belongs to the caller in main;
- action-pins: narrow exception for this repo's _*.yml at @dev with a reason;
- preflight workflow_sync compares all six thin callers (was 3 of 6);
  performance.yml excluded: its schedule judges main with main's own body;
- tests read bodies from _*.yml; new test/default-branch-workflows.test.mjs;
  six mutants; PROCESS.md §10.4, AGENTS.md, REVIEWER.md updated.

Issue: #623
User-Visible: no
This commit is contained in:
Claude
2026-09-24 10:23:28 +03:00
parent 72acafd7eb
commit baf283c50f
30 changed files with 2805 additions and 2196 deletions
+24 -3
View File
@@ -990,7 +990,7 @@ SHA. Manifest публикуется и входит в `SHA256SUMS`; свежа
### 10.4 Событийный конвейер: метка как триггер
`.github/workflows/process.yml`, issue #114. Смена статусной метки — не запись в
`.github/workflows/process.yml` (тело — `_process.yml`, #623), issue #114. Смена статусной метки — не запись в
журнал, а **сообщение**: она порождает событие, событие запускает следующий шаг.
```
@@ -1012,13 +1012,34 @@ Medium-находки вне скоупа задачи (#202), кладёт до
1. метки переставляет **PAT**, а не `GITHUB_TOKEN`: GitHub намеренно не порождает
события от `GITHUB_TOKEN`, чтобы не было циклов, и цепочка обрывалась бы после
первого шага без ошибок в логах;
2. `process.yml` обязан лежать в **ветке по умолчанию**: для события `issues`
GitHub берёт workflow только оттуда, независимо от содержимого `dev`;
2. для события `issues` GitHub берёт workflow только из **ветки по умолчанию**
(`main`), независимо от содержимого `dev`. Поэтому в `main` лежит тонкий
`process.yml` — триггер, run-name, потолок прав, — а тело `_process.yml` он
вызывает по ссылке `@dev` (#623). Правило ниже, «Workflow из ветки по
умолчанию», — общее для всех таких файлов;
3. слияние в `dev` происходит **до** простановки `S8-merged`, иначе метка врёт в
промежутке — она утверждает, что код в `dev`;
4. многострочный текст внутри `run:` — только через heredoc: строка с нулевым
отступом обрывает блок YAML, и скрипт обрезается без ошибки парсера.
**Workflow из ветки по умолчанию: тонкий файл и тело из `dev`** (#623). Для
событий `issues`, `schedule` и `workflow_run` GitHub исполняет workflow из
`main`. Таких файлов шесть: `process.yml`, `process-resume.yml`,
`process-reconcile.yml`, `mutation-gate.yml`, `nightly.yml`,
`process-metrics.yml`. Каждый — тонкий вызывающий: триггеры, run-name,
права, concurrency и одна job `uses:
Matysh/houseplan-card/.github/workflows/_<имя>.yml@dev` с `secrets: inherit`.
Тело `_<имя>.yml` читается из `dev` в момент запуска, поэтому **правка
конвейера — один коммит в `dev`**, зеркало в `main` и возврат `main` в `dev`
перед промоушеном не нужны. Потолок прав вызывающей job равен объединению
прав job тела: вызываемый workflow права только сужает, и каждая job тела
получает прежний минимум (#556). Тонкий файл меняется, только когда меняются
триггеры, входы ручного запуска или потолок прав; тогда он зеркалится в
`main`, и preflight `workflow_sync` в `validate.yml` держит копии равными —
сверяются ровно эти шесть файлов, список держит
`test/default-branch-workflows.test.mjs`. `performance.yml` в список не входит:
по расписанию он судит `main` собственным телом из `main`.
**Ревью не начинается на красном коде** (#510). После фиксации материала конвейер
запускает Validate с мутантами по диффу на этом SHA (`scripts/validate-gate.mjs`:
`workflow_dispatch validate.yml -f mutants=true`). **Ждёт его не раннер, а событие**