chore(workflows)!: remove the six single-gate reusables, their wrappers and twins #1552

Merged
alex merged 1 commit from claude/1542-remove-single-gate-reusables into main 2026-10-05 23:50:32 +02:00
Member

Summary

  • What goes: the six single-gate reusables (manifest, fragment, changelog body, register, ac-closure, workflow parse), their six .gitea/ twins and their six toolkit-self wrappers. reusable-pr-gates.yml (v0.66.0, #1547) already runs all six gates from pr.yml.
  • What adopters do: delete the gate wrappers, add one pr.yml on reusable-pr-gates.yml with a gate 'on' for each wrapper they had, and require pr / gates (pull_request). Old tags keep serving the old reusables, so nothing breaks until an adopter moves its pin.
  • The swap, for Alex: remove 14 required contexts and add 1, right before merging and after you approve (list below). On this PR the 14 old contexts never post, because their wrappers are deleted, so the PR cannot go fully green under today's protection. #1540 had the same window.
  • The cut: this is a minor, v0.67.0 (rt decide --dry-run on this head prints bump downgraded major -> minor, next: version=0.67.0).

Refs #1542
Refs #1530
Refs #1548

Required-check swap (Alex, after approving, right before merging)

Read live on 2026-10-05 (GET /repos/frankenbit/release-toolkit/branch_protections): main requires 18 contexts.

Remove these 14:

  • ac-closure-check / ac-closure check (pull_request)
  • ac-closure-check / check (pull_request)
  • changelog-body-check / changelog body Cold-Read linter (pull_request)
  • changelog-body-check / check (pull_request)
  • fragment-check / check (pull_request)
  • fragment-check / coverage (pull_request)
  • fragment-check / density (pull_request)
  • manifest-check / check (pull_request)
  • manifest-check / manifest-vs-tag consistency (pull_request)
  • register-check / check (pull_request)
  • register-check / register-drift check (pull_request)
  • workflow-parse-check / check (pull_request)
  • workflow-parse-check / toolkit-self parse guard and controls (pull_request)
  • workflow-parse-check / workflow parse and schema (pull_request)

Add this 1: pr / gates (pull_request). It has posted on every PR since #1547 merged, and it posts on this one.

Result, 5 contexts: go-ci / lint + build + test, tests / bats, tests / checks, toolkit-self-gates / toolkit-self gates (PR's own rt) and pr / gates, each with (pull_request). That is under the "at most 6" in #1542's acceptance criteria, and it is the 5 that ADR-0013's Consequences predicts.

cmd/rt/pre_push.go already matches the post-swap set. Against the live pre-swap set, rt pre-push reports pr / gates as a dead declaration until the swap is made. Nothing in CI runs rt pre-push.

Decision tree

  • Delete the six now (ADR-0013 D7) is right while every adopter is under the operator's control. All five are. A deprecation release would be right once one is not. tmux-tell already runs pr-gates@v0.66.0 (tmux-tell#964), so the replacement is proven on the tag-pinned path.
  • secretsinherit: keep the six names in a RetiredNoSecrets set is right because tags up to v0.66.0 still serve them. An adopter that bumps release.yml before moving its gate wrappers would otherwise get "NeedsSecrets does not know whether that reusable needs a secret" from rt secrets-inherit-check (a continue-on-error warning in the release workflow). Deleting the entries outright is right only if no caller could still use an old tag. The derivation test refuses a retired name that still has a file or is also in NeedsSecrets.
  • Bats arms that graded a removed file: porting is right where the property lives on in reusable-pr-gates.yml or scripts/pr-gates.sh. Dropping is right where tests/pr-gates.bats already grades the same property. Each disposition is in the table below.
  • The fragment: the toolkit's changelog carries a fragment's summary line only (#854). So the summary itself names the migration and the new status. The body has the full list of removed reusables and renamed contexts, reachable through (#1542).
  • build-ref-check on an old tag now checks the 4 remaining canonical files, not 10. On a tag push it builds rt from the tagged tree, so new tags are unaffected. Only a workflow_dispatch grading an old tag with today's rt sees fewer files. Keeping the six names in the bake list is right only if that dispatch matters more than one source of truth.

Carry-forward (AGENTS.md section 1)

The wrappers. Each of the five small wrappers had only its check job, the call to its reusable. Their self jobs had already moved to toolkit-self-gates (#1253, #1540); the comments left behind them were orphans.

Wrapper Step or job Class Disposition
all six check: call the reusable (a) Replaced by pr / gates, every gate 'on', layout: combined (pr.yml)
all six on: pull_request (and edited on ac-closure, fragment) (b) pr.yml's types are the union, [opened, synchronize, reopened, edited], with no paths: filter (pinned by tests/pr-gates.bats)
register-check.yml check_commit_subjects: 'true' (c) Retired. The scan never ran inside a called reusable (#1548). This is the #1548 policy box's "remove the dead input and step in #1542" option, made real by deletion
workflow-parse-check.yml self: checkout (a) tests / checks checkout
workflow-parse-check.yml self: replay onto the landing base (#1195) (b) tests / checks has the same replay step, and pr / gates replays inside the reusable
workflow-parse-check.yml self: fetch the pinned validator (v12.8.2, sha256 7be27f32...) (b) tests / checks fetches the same version and hash; scripts/pr-gates.sh pins the same pair for the workflow_parse gate
workflow-parse-check.yml self: positive control, guard over this tree with a non-zero PARSED=TOTAL (b) tests / checks c1 runs scripts/workflow-parse-check.sh .. The script itself exits 1 on a zero or short count (lines 19-22 and 40-45; tests/workflow-parse-check.bats), and pr / gates' workflow_parse gate runs it too
workflow-parse-check.yml self: mutation control (fatal-block-scalar-comment.yml must fail) (b) tests / checks c13, moved there by #1541. Its comment no longer says the wrapper runs it too

The reusables. Every input of the six maps to a reusable-pr-gates.yml input of the same name: config_path, fragments_dir, valid_kinds, fragment_length_warn_chars, density, the five thresholds, changelog_path and register_patterns_override. The one exception is check_commit_subjects (above). Their steps (checkout, replay, resolve, checkout toolkit, bootstrap, run) are each a step of reusable-pr-gates.yml, and #1547 carried them.

The lists.

List Change
canonicalFiles (internal/bake/marker.go) and bakeRefFiles (cmd/rt/prep.go) 10 to 4
DefaultComposeScripts (internal/gates/selfboot_check.go) the six removed; scripts/workflow-parse-check.sh and scripts/pr-gates.sh stay
NeedsSecrets the six removed (its derivation test refuses an entry with no file); added to RetiredNoSecrets
pre_push.go the locallyUnreproducible rows for ac-closure, manifest and the parse self job removed; pr / gates (pull_request) added; register-check's two contexts dropped from its gate
tests/workflows.bats the arms in the next table
C3, arc42 05 and 02, docs/architecture.md, docs/integration.md, the fragment, parse-guard, coverage, Cold-Read, conventions and SECURITY docs, examples/, the README, AGENTS.md section 2 counts and the section 15 table updated to pr.yml and reusable-pr-gates.yml

Historical records are left as written: the ADRs, CHANGELOG.md, the test fixtures under testdata/ and the provenance markers in toolkit-self-gates.yml.

Bats arms that graded a removed file

Arm Disposition
all expected reusables are present floor 10 to 4
workflow parse guard has a self wrapper (#841) rewritten: the controls are in tests.yml (c1, c13, replay) and workflow_parse: 'on' in pr.yml
ac-closure wrapper executes (#921) rewritten: the wrapper lines go, the toolkit-self-gates assertions stay, ac_closure: 'on' in pr.yml
fragment-check pairs with its wrapper (#281) rewritten for pr.yml and scripts/pr-gates.sh
independently named coverage and density contexts (#1465) dropped: the matrix contexts are gone; pr-gates.bats "both modes run, and a red coverage still reports density"
changelog-body-check pairs with its wrapper (#290) dropped: pr-gates.bats pins changelog_body: 'on' and the trigger
bake-marker registration (#278/#704) floor 10 to 4
six adopter ref controls (#997/#1200) now the two adopter-wrapper reusables left
parse resolve-ref (#995), ac-closure resolve-ref (#997) dropped: pr-gates.bats drives every D6 resolve arm
wrapper-ref fallback (#1222), SHA pin (#336) resolver carrier floor 9 to 3; the dead fragment-check.yml name mapping removed
shared bootstrap artifact (#861/#606) now every gate-running job of reusable-pr-gates.yml
#513 key delivery reusable-pr-gates.yml and reusable-release.yml, every bootstrap call
fragment-check inputs wired (#621/#728) dropped: pr-gates.bats requires env transport for every input
caller inputs stay data (#654) ported to reusable-pr-gates.yml (job env) and scripts/pr-gates.sh (quoted path inputs)
density path provenance (#735) ported to the gates and fragments jobs and scripts/pr-gates.sh
BUILD arm (#728/#861) every gate-running job of reusable-pr-gates.yml
changed-fragment set (#1028), delete-only and rename (#1033), cut shape (#1051) the extracted lines now come from scripts/pr-gates.sh
consolidated trigger is the union (#1253) the union of the five is recorded as RETIRED_UNION

No re-pin

No re-pin: toolkit-self floats @main (AGENTS.md section 2.1). Adopters get the change from v0.67.0. On this head:

[check-self-bootstrap] pinned toolkit ref: main
[check-self-bootstrap] floating-pin (release-toolkit#456): main matches main by construction
{"ts":"","kind":"repin_check","result":"pass","pinned_ref":"main"}
rc=0

Evidence (local, on head 0de3876018392134d38dfda82ac1b35bcdcfb7be)

  • go vet ./... and go test -count=1 ./...: all packages ok.
  • golangci-lint run (forgejo-ci-go:latest, caps off): 0 issues.
  • bats tests/ (rt-bats:local, LF clone): 241 tests, 0 failures. There were 246; 5 arms were dropped, as listed above.
  • scripts/workflow-parse-check.sh . with the pinned validator: PARSED=26 TOTAL=26. Each example project: PASS: 2/2.
  • rt gitea-twin --check: PASS — 6 twin(s) match their source.
  • rt register-check: OK: 0 register-drift hits. rt changelog-body-check CHANGELOG.md: rc=0.
  • rt fragment-check (content mode, density on): density PASS, with the non-blocking 500-character warning on the new fragment.
  • rt prep --dry-run in a disposable clone: next_version=0.67.0, and every cut-time changelog check PASS on the composed section.
  • pre_push.go against both sets, in a scratch test that was not committed: post-swap stale=[] undeclared=[] dead=[]; live pre-swap dead=[pr / gates (pull_request)] and the 14 old contexts undeclared.

Mutants (each in a disposable copy, git diff --stat showing the change, then the named arm):

Mutant Arm that went red
RetiredNoSecrets arm removed from Check TestCheck_RetiredReusableIsSilentAtItsOldTag
pr-gates added to RetiredNoSecrets TestNeedsSecrets_MatchesWhatTheReusablesActuallyReference
RetiredNoSecrets emptied both of the above
--diff-filter=d dropped in pr-gates.sh both #1033 arms
three-dot diff made two-dot #1028
deleted-files line loses --diff-filter=D both #1051 arms
CHANGELOG line reads another path both #1051 arms
c13 step renamed in tests.yml #841/#1542
workflow_parse: 'off' in pr.yml #841/#1542
ac_closure: 'off' #921
fragments: 'off' #281
${{ inputs.layout }} in a run block #654
PR_GATES_CONFIG_PATH unbound #654
--config path unquoted in pr-gates.sh #654
PR_GATES_BASE_SHA bound to github.sha #735
token dropped from one bootstrap #861/#606
minisign-public-key dropped from one bootstrap #513
edited dropped from toolkit-self-gates.yml #1253 union
one job's bootstrap renamed #728/#861. It survived the first version of the arm, which only asked whether any job bootstraps; the arm now checks every gate-running job
example pr.yml with a short transport examples ABSOLUTE transport (#1402)
prepared-uncut wrapper probe renamed #997/#1200

Not verified here

  • The job and required-check counts per PR after the merge (#1542's fourth criterion). They need the merged main and its task timings.
  • Whether Forgejo still posts anything for the deleted wrappers on this PR. It should not; the CI comment will say what posted.
  • rt build-ref-check by workflow_dispatch on an old tag, which now grades 4 files (decision tree above).

Verify-at-source request

@alex, please verify at source (AGENTS.md section 6): the swap list against live protection, the carry-forward table against workflow-parse-check.yml at origin/main, and the CI job logs once they post. I will post the per-check log evidence in a comment.

🤖 Generated with Claude Code

## Summary - **What goes:** the six single-gate reusables (manifest, fragment, changelog body, register, ac-closure, workflow parse), their six `.gitea/` twins and their six toolkit-self wrappers. `reusable-pr-gates.yml` (v0.66.0, #1547) already runs all six gates from `pr.yml`. - **What adopters do:** delete the gate wrappers, add one `pr.yml` on `reusable-pr-gates.yml` with a gate `'on'` for each wrapper they had, and require `pr / gates (pull_request)`. Old tags keep serving the old reusables, so nothing breaks until an adopter moves its pin. - **The swap, for Alex:** remove 14 required contexts and add 1, right before merging and after you approve (list below). On this PR the 14 old contexts never post, because their wrappers are deleted, so the PR cannot go fully green under today's protection. #1540 had the same window. - **The cut:** this is a minor, v0.67.0 (`rt decide --dry-run` on this head prints `bump downgraded major -> minor`, `next: version=0.67.0`). Refs #1542 Refs #1530 Refs #1548 ## Required-check swap (Alex, after approving, right before merging) Read live on 2026-10-05 (`GET /repos/frankenbit/release-toolkit/branch_protections`): `main` requires 18 contexts. **Remove these 14:** - `ac-closure-check / ac-closure check (pull_request)` - `ac-closure-check / check (pull_request)` - `changelog-body-check / changelog body Cold-Read linter (pull_request)` - `changelog-body-check / check (pull_request)` - `fragment-check / check (pull_request)` - `fragment-check / coverage (pull_request)` - `fragment-check / density (pull_request)` - `manifest-check / check (pull_request)` - `manifest-check / manifest-vs-tag consistency (pull_request)` - `register-check / check (pull_request)` - `register-check / register-drift check (pull_request)` - `workflow-parse-check / check (pull_request)` - `workflow-parse-check / toolkit-self parse guard and controls (pull_request)` - `workflow-parse-check / workflow parse and schema (pull_request)` **Add this 1:** `pr / gates (pull_request)`. It has posted on every PR since #1547 merged, and it posts on this one. **Result, 5 contexts:** `go-ci / lint + build + test`, `tests / bats`, `tests / checks`, `toolkit-self-gates / toolkit-self gates (PR's own rt)` and `pr / gates`, each with ` (pull_request)`. That is under the "at most 6" in #1542's acceptance criteria, and it is the 5 that ADR-0013's Consequences predicts. `cmd/rt/pre_push.go` already matches the post-swap set. Against the live pre-swap set, `rt pre-push` reports `pr / gates` as a dead declaration until the swap is made. Nothing in CI runs `rt pre-push`. ## Decision tree - **Delete the six now (ADR-0013 D7) is right while every adopter is under the operator's control.** All five are. A deprecation release would be right once one is not. tmux-tell already runs `pr-gates@v0.66.0` (tmux-tell#964), so the replacement is proven on the tag-pinned path. - **`secretsinherit`: keep the six names in a `RetiredNoSecrets` set** is right because tags up to v0.66.0 still serve them. An adopter that bumps `release.yml` before moving its gate wrappers would otherwise get "NeedsSecrets does not know whether that reusable needs a secret" from `rt secrets-inherit-check` (a `continue-on-error` warning in the release workflow). Deleting the entries outright is right only if no caller could still use an old tag. The derivation test refuses a retired name that still has a file or is also in `NeedsSecrets`. - **Bats arms that graded a removed file:** porting is right where the property lives on in `reusable-pr-gates.yml` or `scripts/pr-gates.sh`. Dropping is right where `tests/pr-gates.bats` already grades the same property. Each disposition is in the table below. - **The fragment:** the toolkit's changelog carries a fragment's summary line only (#854). So the summary itself names the migration and the new status. The body has the full list of removed reusables and renamed contexts, reachable through `(#1542)`. - **`build-ref-check` on an old tag** now checks the 4 remaining canonical files, not 10. On a tag push it builds `rt` from the tagged tree, so new tags are unaffected. Only a `workflow_dispatch` grading an old tag with today's `rt` sees fewer files. Keeping the six names in the bake list is right only if that dispatch matters more than one source of truth. ## Carry-forward (AGENTS.md section 1) **The wrappers.** Each of the five small wrappers had only its `check` job, the call to its reusable. Their `self` jobs had already moved to `toolkit-self-gates` (#1253, #1540); the comments left behind them were orphans. | Wrapper | Step or job | Class | Disposition | |---|---|---|---| | all six | `check`: call the reusable | (a) | Replaced by `pr / gates`, every gate `'on'`, `layout: combined` (`pr.yml`) | | all six | `on: pull_request` (and `edited` on ac-closure, fragment) | (b) | `pr.yml`'s types are the union, `[opened, synchronize, reopened, edited]`, with no `paths:` filter (pinned by `tests/pr-gates.bats`) | | `register-check.yml` | `check_commit_subjects: 'true'` | (c) | **Retired.** The scan never ran inside a called reusable (#1548). This is the #1548 policy box's "remove the dead input and step in #1542" option, made real by deletion | | `workflow-parse-check.yml` | `self`: checkout | (a) | `tests / checks` checkout | | `workflow-parse-check.yml` | `self`: replay onto the landing base (#1195) | (b) | `tests / checks` has the same replay step, and `pr / gates` replays inside the reusable | | `workflow-parse-check.yml` | `self`: fetch the pinned validator (v12.8.2, sha256 `7be27f32...`) | (b) | `tests / checks` fetches the same version and hash; `scripts/pr-gates.sh` pins the same pair for the `workflow_parse` gate | | `workflow-parse-check.yml` | `self`: positive control, guard over this tree with a non-zero `PARSED=TOTAL` | (b) | `tests / checks` c1 runs `scripts/workflow-parse-check.sh .`. The script itself exits 1 on a zero or short count (lines 19-22 and 40-45; `tests/workflow-parse-check.bats`), and `pr / gates`' `workflow_parse` gate runs it too | | `workflow-parse-check.yml` | `self`: mutation control (`fatal-block-scalar-comment.yml` must fail) | (b) | `tests / checks` c13, moved there by #1541. Its comment no longer says the wrapper runs it too | **The reusables.** Every input of the six maps to a `reusable-pr-gates.yml` input of the same name: `config_path`, `fragments_dir`, `valid_kinds`, `fragment_length_warn_chars`, `density`, the five thresholds, `changelog_path` and `register_patterns_override`. The one exception is `check_commit_subjects` (above). Their steps (checkout, replay, resolve, checkout toolkit, bootstrap, run) are each a step of `reusable-pr-gates.yml`, and #1547 carried them. **The lists.** | List | Change | |---|---| | `canonicalFiles` (`internal/bake/marker.go`) and `bakeRefFiles` (`cmd/rt/prep.go`) | 10 to 4 | | `DefaultComposeScripts` (`internal/gates/selfboot_check.go`) | the six removed; `scripts/workflow-parse-check.sh` and `scripts/pr-gates.sh` stay | | `NeedsSecrets` | the six removed (its derivation test refuses an entry with no file); added to `RetiredNoSecrets` | | `pre_push.go` | the `locallyUnreproducible` rows for ac-closure, manifest and the parse self job removed; `pr / gates (pull_request)` added; `register-check`'s two contexts dropped from its gate | | `tests/workflows.bats` | the arms in the next table | | C3, arc42 05 and 02, `docs/architecture.md`, `docs/integration.md`, the fragment, parse-guard, coverage, Cold-Read, conventions and SECURITY docs, `examples/`, the README, AGENTS.md section 2 counts and the section 15 table | updated to `pr.yml` and `reusable-pr-gates.yml` | Historical records are left as written: the ADRs, `CHANGELOG.md`, the test fixtures under `testdata/` and the provenance markers in `toolkit-self-gates.yml`. ## Bats arms that graded a removed file | Arm | Disposition | |---|---| | all expected reusables are present | floor 10 to 4 | | workflow parse guard has a self wrapper (#841) | rewritten: the controls are in `tests.yml` (c1, c13, replay) and `workflow_parse: 'on'` in `pr.yml` | | ac-closure wrapper executes (#921) | rewritten: the wrapper lines go, the `toolkit-self-gates` assertions stay, `ac_closure: 'on'` in `pr.yml` | | fragment-check pairs with its wrapper (#281) | rewritten for `pr.yml` and `scripts/pr-gates.sh` | | independently named coverage and density contexts (#1465) | dropped: the matrix contexts are gone; `pr-gates.bats` "both modes run, and a red coverage still reports density" | | changelog-body-check pairs with its wrapper (#290) | dropped: `pr-gates.bats` pins `changelog_body: 'on'` and the trigger | | bake-marker registration (#278/#704) | floor 10 to 4 | | six adopter ref controls (#997/#1200) | now the two adopter-wrapper reusables left | | parse resolve-ref (#995), ac-closure resolve-ref (#997) | dropped: `pr-gates.bats` drives every D6 resolve arm | | wrapper-ref fallback (#1222), SHA pin (#336) | resolver carrier floor 9 to 3; the dead `fragment-check.yml` name mapping removed | | shared bootstrap artifact (#861/#606) | now every gate-running job of `reusable-pr-gates.yml` | | #513 key delivery | `reusable-pr-gates.yml` and `reusable-release.yml`, every bootstrap call | | fragment-check inputs wired (#621/#728) | dropped: `pr-gates.bats` requires env transport for every input | | caller inputs stay data (#654) | ported to `reusable-pr-gates.yml` (job env) and `scripts/pr-gates.sh` (quoted path inputs) | | density path provenance (#735) | ported to the `gates` and `fragments` jobs and `scripts/pr-gates.sh` | | BUILD arm (#728/#861) | every gate-running job of `reusable-pr-gates.yml` | | changed-fragment set (#1028), delete-only and rename (#1033), cut shape (#1051) | the extracted lines now come from `scripts/pr-gates.sh` | | consolidated trigger is the union (#1253) | the union of the five is recorded as `RETIRED_UNION` | ## No re-pin No re-pin: toolkit-self floats `@main` (AGENTS.md section 2.1). Adopters get the change from v0.67.0. On this head: ``` [check-self-bootstrap] pinned toolkit ref: main [check-self-bootstrap] floating-pin (release-toolkit#456): main matches main by construction {"ts":"","kind":"repin_check","result":"pass","pinned_ref":"main"} rc=0 ``` ## Evidence (local, on head `0de3876018392134d38dfda82ac1b35bcdcfb7be`) - `go vet ./...` and `go test -count=1 ./...`: all packages ok. - `golangci-lint run` (`forgejo-ci-go:latest`, caps off): `0 issues.` - `bats tests/` (`rt-bats:local`, LF clone): 241 tests, 0 failures. There were 246; 5 arms were dropped, as listed above. - `scripts/workflow-parse-check.sh .` with the pinned validator: `PARSED=26 TOTAL=26`. Each example project: `PASS: 2/2`. - `rt gitea-twin --check`: `PASS — 6 twin(s) match their source.` - `rt register-check`: `OK: 0 register-drift hits`. `rt changelog-body-check CHANGELOG.md`: rc=0. - `rt fragment-check` (content mode, density on): `density PASS`, with the non-blocking 500-character warning on the new fragment. - `rt prep --dry-run` in a disposable clone: `next_version=0.67.0`, and every cut-time changelog check PASS on the composed section. - `pre_push.go` against both sets, in a scratch test that was not committed: post-swap `stale=[] undeclared=[] dead=[]`; live pre-swap `dead=[pr / gates (pull_request)]` and the 14 old contexts undeclared. **Mutants** (each in a disposable copy, `git diff --stat` showing the change, then the named arm): | Mutant | Arm that went red | |---|---| | `RetiredNoSecrets` arm removed from `Check` | `TestCheck_RetiredReusableIsSilentAtItsOldTag` | | `pr-gates` added to `RetiredNoSecrets` | `TestNeedsSecrets_MatchesWhatTheReusablesActuallyReference` | | `RetiredNoSecrets` emptied | both of the above | | `--diff-filter=d` dropped in `pr-gates.sh` | both #1033 arms | | three-dot diff made two-dot | #1028 | | deleted-files line loses `--diff-filter=D` | both #1051 arms | | CHANGELOG line reads another path | both #1051 arms | | c13 step renamed in `tests.yml` | #841/#1542 | | `workflow_parse: 'off'` in `pr.yml` | #841/#1542 | | `ac_closure: 'off'` | #921 | | `fragments: 'off'` | #281 | | `${{ inputs.layout }}` in a run block | #654 | | `PR_GATES_CONFIG_PATH` unbound | #654 | | `--config` path unquoted in `pr-gates.sh` | #654 | | `PR_GATES_BASE_SHA` bound to `github.sha` | #735 | | `token` dropped from one bootstrap | #861/#606 | | `minisign-public-key` dropped from one bootstrap | #513 | | `edited` dropped from `toolkit-self-gates.yml` | #1253 union | | one job's bootstrap renamed | #728/#861. It survived the first version of the arm, which only asked whether any job bootstraps; the arm now checks every gate-running job | | example `pr.yml` with a short transport | examples ABSOLUTE transport (#1402) | | `prepared-uncut` wrapper probe renamed | #997/#1200 | ## Not verified here - The job and required-check counts per PR after the merge (#1542's fourth criterion). They need the merged `main` and its task timings. - Whether Forgejo still posts anything for the deleted wrappers on this PR. It should not; the CI comment will say what posted. - `rt build-ref-check` by `workflow_dispatch` on an old tag, which now grades 4 files (decision tree above). ## Verify-at-source request @alex, please verify at source (AGENTS.md section 6): the swap list against live protection, the carry-forward table against `workflow-parse-check.yml` at `origin/main`, and the CI job logs once they post. I will post the per-check log evidence in a comment. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chore(workflows)!: remove the six single-gate reusables, their wrappers and twins
All checks were successful
base-divergence-check / check (pull_request) Successful in 6s
go-ci / lint + build + test (pull_request) Successful in 33s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
pr / plan, and run every gate when combined (pull_request) Successful in 9s
readme-pin-check / mirror (pull_request) Successful in 7s
tests / bats (pull_request) Successful in 38s
tests / checks (pull_request) Successful in 7s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
go-ci / page landing-tree failure (pull_request) Has been skipped
pr / manifest (pull_request) Has been skipped
pr / fragments (pull_request) Has been skipped
pr / changelog_body (pull_request) Has been skipped
pr / register (pull_request) Has been skipped
pr / ac_closure (pull_request) Has been skipped
pr / workflow_parse (pull_request) Has been skipped
pr / split result (pull_request) Has been skipped
pr / gates (pull_request) Successful in 0s
check-self-bootstrap / check (push) Successful in 5s
gitea-twin-check / check (push) Successful in 4s
go-ci / lint + build + test (push) Successful in 33s
go-ci / record reviewed vs landed commit (push) Successful in 2s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 4s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 7s
prepared-uncut-check / check (push) Successful in 0s
release / decide + act (push) Successful in 9s
release / release (push) Successful in 0s
tests / bats (push) Successful in 37s
tests / checks (push) Successful in 7s
go-ci / page landing-tree failure (push) Has been skipped
release / fire-cut (push) Has been skipped
0de3876018
reusable-pr-gates.yml (v0.66.0, #1547) runs every PR gate from one pr.yml.
This removes what it replaces (ADR-0013 D7):

- the six gate reusables: reusable-manifest-check.yml,
  reusable-changelog-fragment-check.yml, reusable-changelog-body-check.yml,
  reusable-register-check.yml, reusable-ac-closure-check.yml and
  reusable-workflow-parse-check.yml;
- their six .gitea/ twins;
- the six toolkit-self wrappers: manifest-check.yml, fragment-check.yml,
  changelog-body-check.yml, register-check.yml, ac-closure-check.yml and
  workflow-parse-check.yml. The parse wrapper's self job is already carried
  by tests / checks (c1, c13 and its replay).

The bake, compose and C3 lists drop them. pre_push.go's tables match the
post-swap required set: pr / gates (pull_request) is declared locally
unreproducible, and register-check no longer claims two retired contexts.
secretsinherit keeps the six names in a RetiredNoSecrets set, so an adopter
still calling one at an old tag is not reported as unknown. The examples,
integration guide, C3, arc42 and the other docs move to pr.yml. The bats
arms that graded the removed files either follow their property into
reusable-pr-gates.yml and scripts/pr-gates.sh or are dropped where
tests/pr-gates.bats already grades it.

The register reusable's check_commit_subjects input and its --git-log step
go with it (#1548): the commit-subject scan never ran inside a called
reusable.

BREAKING CHANGE: the six single-gate reusables and their .gitea twins are
removed. Adopters wire one pr.yml calling reusable-pr-gates.yml with a gate
'on' for each wrapper they had (ADR-0013). The wrappers' status contexts
(manifest-check, fragment-check, changelog-body-check, register-check,
ac-closure-check and workflow-parse-check / ...) stop posting; the one caller
status pr / gates (pull_request) replaces them. Pinned tags up to v0.66.0
keep serving the old reusables.

Refs #1542
Refs #1530
Refs #1548

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude requested review from alex 2026-10-05 22:07:21 +02:00
Author
Member

CI on head 0de3876018392134d38dfda82ac1b35bcdcfb7be, read from the job logs in the browser (step logs fetched per step), not from the statuses.

18 contexts posted, all success. None of the 14 old wrapper contexts posted, as expected: their workflows are deleted. So under today's protection this PR waits on 14 contexts that never come, until the swap.

8 jobs ran steps; 9 posted as skipped (every step skipped, 0 s):

Job (run) What the log shows
pr / plan, and run every gate when combined (19873/1) layout=combined; on gates: manifest fragments changelog_body register ac_closure workflow_parse; resolved toolkit ref: main (source=toolkit-self local invocation (ADR-0013 D6)); landing-tree replay: PASS; manifest [manifest-check] all checks passed; fragment-coverage: PASS - fragment present: changelog.d/1542-remove-single-gate-reusables.removed.md, coverage PASS, density PASS; changelog-body-check: PASS (CHANGELOG.md); register-check: file scan only; the commit-subject scan is not part of this gate (#1548).; ac-closure no same-repo Closes-target found on either surface; PARSED=26 TOTAL=26; tree unchanged; attest: every expected gate ran (...)
pr / gates (19873/0) the caller entry: Set up job, Complete job
pr / manifest, fragments, changelog_body, register, ac_closure, workflow_parse, split result (19873/2-8) every step skipped (combined layout)
tests / checks (19875/1) landing-tree replay: PASS; c1 PARSED=26 TOTAL=26 and PASS: 26/26; c2 4 fatal fixture(s) rejected, 3 safe fixture(s) accepted; c13 PARSED=26 TOTAL=27 and negative control OK: mutation.yml made the executed guard fail (rc=1); attest: all 13 gate steps AND the shared-tree guard ran (14 of 14 ...)
tests / bats (19875/0) 1..241, 241 ok, 0 not ok
toolkit-self-gates (19876/0) ac-closure, body, fragment, manifest and register controls each control OK; check-self-bootstrap {"result":"pass","pinned_ref":"main"}; gitea-twin: PASS — 6 twin(s) match their source.; prep-order-check: PASS; tag-action-digest-check: PASS (rc=0)
go-ci / lint + build + test (19872/0) golangci-lint, build and test steps success; binary-size-check PASS: binary 10723490 B vs v0.66.0 baseline 10735778 B (delta -12288 B)
go-ci / page landing-tree failure, record reviewed vs landed commit (19872/1-2) skipped, as on every PR
readme-pin-check / mirror (19874/0) PASS: all 15 prescriptive pin(s) across 11 document(s) name v0.66.0 (the new example pr.yml pins included)
base-divergence-check / check (19871/0) PASS - graded against origin/main at 6172d9f26c1d8df0da979683ed420271a9419f4f
fork-pr-approval-notice (19870/0) ran (pull_request_target)

So, measured on this PR before the merge: 8 jobs with steps, and 5 required contexts after the swap. #1542's fourth criterion asks for the same measurement after the merge; that is still open.

CI on head `0de3876018392134d38dfda82ac1b35bcdcfb7be`, read from the job logs in the browser (step logs fetched per step), not from the statuses. **18 contexts posted, all `success`. None of the 14 old wrapper contexts posted**, as expected: their workflows are deleted. So under today's protection this PR waits on 14 contexts that never come, until the swap. **8 jobs ran steps; 9 posted as skipped** (every step `skipped`, 0 s): | Job (run) | What the log shows | |---|---| | `pr / plan, and run every gate when combined` (19873/1) | `layout=combined; on gates: manifest fragments changelog_body register ac_closure workflow_parse`; `resolved toolkit ref: main (source=toolkit-self local invocation (ADR-0013 D6))`; `landing-tree replay: PASS`; manifest `[manifest-check] all checks passed`; `fragment-coverage: PASS - fragment present: changelog.d/1542-remove-single-gate-reusables.removed.md`, coverage PASS, density PASS; `changelog-body-check: PASS (CHANGELOG.md)`; `register-check: file scan only; the commit-subject scan is not part of this gate (#1548).`; ac-closure `no same-repo Closes-target found on either surface`; `PARSED=26 TOTAL=26`; tree unchanged; `attest: every expected gate ran (...)` | | `pr / gates` (19873/0) | the caller entry: Set up job, Complete job | | `pr / manifest`, `fragments`, `changelog_body`, `register`, `ac_closure`, `workflow_parse`, `split result` (19873/2-8) | every step `skipped` (combined layout) | | `tests / checks` (19875/1) | `landing-tree replay: PASS`; c1 `PARSED=26 TOTAL=26` and `PASS: 26/26`; c2 `4 fatal fixture(s) rejected, 3 safe fixture(s) accepted`; c13 `PARSED=26 TOTAL=27` and `negative control OK: mutation.yml made the executed guard fail (rc=1)`; `attest: all 13 gate steps AND the shared-tree guard ran (14 of 14 ...)` | | `tests / bats` (19875/0) | `1..241`, 241 `ok`, 0 `not ok` | | `toolkit-self-gates` (19876/0) | ac-closure, body, fragment, manifest and register controls each `control OK`; `check-self-bootstrap` `{"result":"pass","pinned_ref":"main"}`; `gitea-twin: PASS — 6 twin(s) match their source.`; `prep-order-check: PASS`; `tag-action-digest-check: PASS (rc=0)` | | `go-ci / lint + build + test` (19872/0) | golangci-lint, build and test steps success; `binary-size-check PASS: binary 10723490 B vs v0.66.0 baseline 10735778 B (delta -12288 B)` | | `go-ci / page landing-tree failure`, `record reviewed vs landed commit` (19872/1-2) | skipped, as on every PR | | `readme-pin-check / mirror` (19874/0) | `PASS: all 15 prescriptive pin(s) across 11 document(s) name v0.66.0` (the new example `pr.yml` pins included) | | `base-divergence-check / check` (19871/0) | `PASS - graded against origin/main at 6172d9f26c1d8df0da979683ed420271a9419f4f` | | `fork-pr-approval-notice` (19870/0) | ran (`pull_request_target`) | So, measured on this PR before the merge: 8 jobs with steps, and 5 required contexts after the swap. #1542's fourth criterion asks for the same measurement after the merge; that is still open.
claude left a comment

Verdict: APPROVE at 0de3876018

Summary: the removal is complete and loses nothing. The parse wrapper's toolkit-self self job is carried in full: the replay, the pinned validator, the positive control's count rule and the mutation control. The bump resolves to v0.67.0, adopters on old tags get no false "unknown reusable" finding, and every dropped bats arm is covered elsewhere. Nothing is blocking.

Head pinned with rt review-submit-check (rc=0, READY) immediately before this review. One commit, 0de3876 chore(workflows)!, on base 6172d9f, which is live main.

The swap (read live just now)

main requires 18 contexts. The 14 to remove are all present:

  • ac-closure-check: check and ac-closure check;
  • changelog-body-check: check and changelog body Cold-Read linter;
  • fragment-check: check, coverage and density;
  • manifest-check: check and manifest-vs-tag consistency;
  • register-check: check and register-drift check;
  • workflow-parse-check: check, toolkit-self parse guard and controls and workflow parse and schema.

Each is (pull_request). Add pr / gates (pull_request). That leaves 5: go-ci / lint + build + test, tests / bats, tests / checks, toolkit-self-gates / ... and pr / gates. All five posted success on this head. Do the swap after approving, right before merging.

1. The self job's carry-forward, verified at source

The old workflow-parse-check.yml self job at 6172d9f had four parts.

self step Where it is now
replay onto the landing base tests / checks replay step; the log shows landing-tree replay: PASS
pinned validator 12.8.2, sha256 7be27f32... tests / checks setup step, and pr-gates.sh gate_workflow_parse, with the same version and hash
positive control: rc 0, a non-zero PARSED/TOTAL record, parsed == total c1 runs the guard under set -e
mutation control: rc≠0, names mutation.yml, a PARSED/TOTAL record c13, verbatim

The positive control's count rule is not lost. scripts/workflow-parse-check.sh itself exits 1 on expected == 0 and on parsed != expected ("A zero or short count is not a clean result"), so c1's rc carries it. Only the outer re-assertion of the emitted record is gone.

Mutant Mp: I deleted the script's parsed != expected block. tests/workflow-parse-check.bats goes red on rejects a short success count and rejects a silent zero-work validator. So the count rule is graded by its own tests, not only by the old control. The guard also runs a second time as pr / gates' workflow_parse gate (PARSED=26 TOTAL=26).

2. The bump

I built rt at the head in an LF clone and ran rt decide --dry-run: bump source: auto = major, pre_v1_breaking_to_minor: bump downgraded major -> minor, next: version=0.67.0 tag=v0.67.0. The fragment's summary line carries the migration ("wire one pr.yml on reusable-pr-gates.yml and require pr / gates (pull_request)"). Its body carries BREAKING CHANGE:, the six names, the migration and the renamed statuses.

3. secrets-inherit-check's retired names

RetiredNoSecrets holds the six names, and Check skips them only when they are not in NeedsSecrets. Mutants:

  • Adding pr-gates to RetiredNoSecrets: TestNeedsSecrets_MatchesWhatTheReusablesActuallyReference goes red twice: "still has reusable-pr-gates.yml" and "in both NeedsSecrets and RetiredNoSecrets".
  • Dropping manifest-check from it: TestCheck_RetiredReusableIsSilentAtItsOldTag goes red.

4. The five dropped bats arms are covered

Dropped Covered by
#1465, the independent coverage and density contexts pr-gates.bats "both modes run, and a red coverage still reports density"
#290, changelog-body-check pairs with its wrapper pr-gates.bats pr.yml arm: every gate 'on', trigger and wiring
#995, the parse resolve-ref the pr-gates.bats D6 arm, all 20 cases, both providers (mutants in review 7235)
#997, the ac-closure resolve-ref the same D6 arm
#621/#728, every fragment-check input wired pr-gates.bats "inputs, defaults, job wiring": refuses any input other than runs_on without env transport

The other removed arm headers were rewritten under new names, as the PR's table says.

5. Docs, examples, C3

  • C3 no longer lists the six. The three remaining mentions are the ADR-0013 amendment paragraph and the #1548 note.
  • Remaining mentions elsewhere are historical and labelled as such:
    • adopter-workflow-parse-guard.md, with its "The mechanism moved" note;
    • arc42 02 and 05;
    • a Go comment quoting this repo's own changelog;
    • the ADRs, CHANGELOG.md and testdata/.
  • examples/: both projects have pr.yml at @v0.66.0 with edited, layout: combined, and manifest and fragments 'on'; manifest-check.yml is gone. readme-pin-check / mirror logs PASS: all 15 prescriptive pin(s) across 11 document(s) name v0.66.0.
  • docs/integration.md has the pr.yml recipe (edited, no paths:, layout: combined) and "Require one status on main: ... pr / gates (pull_request)", gated on it having posted on the rolling release PR (#809), plus the migration.

6. #1548

check_commit_subjects goes with reusable-register-check.yml. C3 records it as not carried over.

CI, read in job logs (Axis C)

18 statuses, all success.

  • pr / plan, and run every gate when combined (run 19873, job 1, commit 0de3876018): it resolved the toolkit ref to main (source=toolkit-self local invocation, ADR-0013 D6). The replay passed against 6172d9f, and bootstrap rt built from source at main. All six gates passed, with workflow_parse at PARSED=26 TOTAL=26. The tree was unchanged and attest passed.
  • tests / checks (run 19875, job 1): c1 PARSED=26 TOTAL=26; c13 PARSED=26 TOTAL=27 and negative control OK: mutation.yml made the executed guard fail (rc=1); tree unchanged for c1-c13; attest 14 of 14.
  • tests / bats (job 0): 1..241, no not ok.
  • toolkit-self-gates (run 19876): attest 15 of 15.

Left open (as the coordinator says)

  • #1542's after-merge measurement of jobs and required counts.
  • A hand-dispatched rt build-ref-check against an old tag now grades the 4 canonical files, not 10. That is expected after the removal.
Verdict: APPROVE at 0de3876018392134d38dfda82ac1b35bcdcfb7be Summary: the removal is complete and loses nothing. The parse wrapper's toolkit-self `self` job is carried in full: the replay, the pinned validator, the positive control's count rule and the mutation control. The bump resolves to v0.67.0, adopters on old tags get no false "unknown reusable" finding, and every dropped bats arm is covered elsewhere. Nothing is blocking. Head pinned with `rt review-submit-check` (rc=0, READY) immediately before this review. One commit, `0de3876 chore(workflows)!`, on base `6172d9f`, which is live main. ## The swap (read live just now) `main` requires 18 contexts. The 14 to remove are all present: - ac-closure-check: `check` and `ac-closure check`; - changelog-body-check: `check` and `changelog body Cold-Read linter`; - fragment-check: `check`, `coverage` and `density`; - manifest-check: `check` and `manifest-vs-tag consistency`; - register-check: `check` and `register-drift check`; - workflow-parse-check: `check`, `toolkit-self parse guard and controls` and `workflow parse and schema`. Each is `(pull_request)`. Add `pr / gates (pull_request)`. That leaves 5: `go-ci / lint + build + test`, `tests / bats`, `tests / checks`, `toolkit-self-gates / ...` and `pr / gates`. All five posted success on this head. **Do the swap after approving, right before merging.** ## 1. The `self` job's carry-forward, verified at source The old `workflow-parse-check.yml` `self` job at 6172d9f had four parts. | `self` step | Where it is now | |---|---| | replay onto the landing base | `tests / checks` replay step; the log shows `landing-tree replay: PASS` | | pinned validator 12.8.2, sha256 `7be27f32...` | `tests / checks` setup step, and `pr-gates.sh gate_workflow_parse`, with the same version and hash | | positive control: rc 0, a non-zero `PARSED/TOTAL` record, `parsed == total` | `c1` runs the guard under `set -e` | | mutation control: rc≠0, names `mutation.yml`, a `PARSED/TOTAL` record | `c13`, verbatim | **The positive control's count rule is not lost.** `scripts/workflow-parse-check.sh` itself exits 1 on `expected == 0` and on `parsed != expected` ("A zero or short count is not a clean result"), so `c1`'s rc carries it. Only the outer re-assertion of the emitted record is gone. **Mutant Mp:** I deleted the script's `parsed != expected` block. `tests/workflow-parse-check.bats` goes **red** on `rejects a short success count` and `rejects a silent zero-work validator`. So the count rule is graded by its own tests, not only by the old control. The guard also runs a second time as `pr / gates`' `workflow_parse` gate (`PARSED=26 TOTAL=26`). ## 2. The bump I built `rt` at the head in an LF clone and ran `rt decide --dry-run`: `bump source: auto = major`, `pre_v1_breaking_to_minor: bump downgraded major -> minor`, `next: version=0.67.0 tag=v0.67.0`. The fragment's summary line carries the migration ("wire one `pr.yml` on `reusable-pr-gates.yml` and require `pr / gates (pull_request)`"). Its body carries `BREAKING CHANGE:`, the six names, the migration and the renamed statuses. ## 3. `secrets-inherit-check`'s retired names `RetiredNoSecrets` holds the six names, and `Check` skips them only when they are not in `NeedsSecrets`. Mutants: - **Adding `pr-gates` to `RetiredNoSecrets`:** `TestNeedsSecrets_MatchesWhatTheReusablesActuallyReference` goes **red** twice: "still has reusable-pr-gates.yml" and "in both NeedsSecrets and RetiredNoSecrets". - **Dropping `manifest-check` from it:** `TestCheck_RetiredReusableIsSilentAtItsOldTag` goes **red**. ## 4. The five dropped bats arms are covered | Dropped | Covered by | |---|---| | #1465, the independent coverage and density contexts | `pr-gates.bats` "both modes run, and a red coverage still reports density" | | #290, changelog-body-check pairs with its wrapper | `pr-gates.bats` `pr.yml` arm: every gate `'on'`, trigger and wiring | | #995, the parse resolve-ref | the `pr-gates.bats` D6 arm, all 20 cases, both providers (mutants in review 7235) | | #997, the ac-closure resolve-ref | the same D6 arm | | #621/#728, every fragment-check input wired | `pr-gates.bats` "inputs, defaults, job wiring": refuses any input other than `runs_on` without env transport | The other removed arm headers were rewritten under new names, as the PR's table says. ## 5. Docs, examples, C3 - **C3** no longer lists the six. The three remaining mentions are the ADR-0013 amendment paragraph and the #1548 note. - **Remaining mentions elsewhere** are historical and labelled as such: - `adopter-workflow-parse-guard.md`, with its "The mechanism moved" note; - arc42 02 and 05; - a Go comment quoting this repo's own changelog; - the ADRs, `CHANGELOG.md` and `testdata/`. - **`examples/`:** both projects have `pr.yml` at `@v0.66.0` with `edited`, `layout: combined`, and manifest and fragments `'on'`; `manifest-check.yml` is gone. `readme-pin-check / mirror` logs `PASS: all 15 prescriptive pin(s) across 11 document(s) name v0.66.0`. - **`docs/integration.md`** has the `pr.yml` recipe (`edited`, no `paths:`, `layout: combined`) and "Require one status on `main`: ... `pr / gates (pull_request)`", gated on it having posted on the rolling release PR (#809), plus the migration. ## 6. #1548 `check_commit_subjects` goes with `reusable-register-check.yml`. C3 records it as not carried over. ## CI, read in job logs (Axis C) 18 statuses, all success. - **`pr / plan, and run every gate when combined`** (run 19873, job 1, commit 0de3876018): it resolved the toolkit ref to `main` (source=toolkit-self local invocation, ADR-0013 D6). The replay passed against `6172d9f`, and `bootstrap rt` built from source at `main`. All six gates passed, with `workflow_parse` at `PARSED=26 TOTAL=26`. The tree was unchanged and attest passed. - **`tests / checks`** (run 19875, job 1): c1 `PARSED=26 TOTAL=26`; c13 `PARSED=26 TOTAL=27` and `negative control OK: mutation.yml made the executed guard fail (rc=1)`; tree unchanged for c1-c13; attest `14 of 14`. - **`tests / bats`** (job 0): `1..241`, no `not ok`. - **`toolkit-self-gates`** (run 19876): attest `15 of 15`. ## Left open (as the coordinator says) - #1542's after-merge measurement of jobs and required counts. - A hand-dispatched `rt build-ref-check` against an old tag now grades the 4 canonical files, not 10. That is expected after the removal.
alex approved these changes 2026-10-05 23:47:58 +02:00
alex merged commit 0de3876018 into main 2026-10-05 23:50:32 +02:00
alex deleted branch claude/1542-remove-single-gate-reusables 2026-10-05 23:50:32 +02:00

Landing identity record

  • PR: #1552
  • landed commit (server merge_commit_sha): 0de3876018392134d38dfda82ac1b35bcdcfb7be
  • effective official approval(s):
    • @alex, review #7257, stamped commit: 0de3876018392134d38dfda82ac1b35bcdcfb7be
  • replay comparison: no identity change (stamped SHA equals landed SHA)

This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.

<!-- release-toolkit:landing-review-record-v1 landed=0de3876018392134d38dfda82ac1b35bcdcfb7be --> ## Landing identity record - PR: #1552 - landed commit (server merge_commit_sha): `0de3876018392134d38dfda82ac1b35bcdcfb7be` - effective official approval(s): - @alex, review #7257, stamped commit: `0de3876018392134d38dfda82ac1b35bcdcfb7be` - replay comparison: no identity change (stamped SHA equals landed SHA) This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
frankenbit/release-toolkit!1552
No description provided.