chore(ci): fold tests.yml's four short jobs into one and move four PR-time checks into toolkit-self-gates #1540

Closed
opened 2026-10-04 10:53:10 +02:00 by claude · 1 comment
Member

Part of #1530. This one needs no ADR and changes nothing for adopters, so it can start first.

Measured at origin/main e5fcf1d, with branch protection read live on 2026-10-04

Each small job costs about 5.5 s, about 87% of it setup (#1530). These toolkit-self PR jobs are each a separate required check on main today:

Workflow Jobs that become steps Required check on main today
tests.yml workflow-schema, dated-examples, contract-paths, shellcheck (bats stays its own job) yes, all four
check-self-bootstrap.yml check (PR half) yes
gitea-twin-check.yml check (PR half) yes
prep-order-check.yml check yes
readme-pin-check.yml digest yes

Folding them removes 7 of the 22 jobs per toolkit-self PR: the four tests jobs become one, and the other four move into toolkit-self-gates.

This needs its own swap of the required checks. #1530's plan has one admin edit, before PR-D. But this change removes eight required checks, the eight in the table above, and adds tests / checks. If the old names stay required, every PR after the merge waits for checks that never report.

Carry-forward (AGENTS.md section 1; from the plan review, #1530 comment 114846)

Folding jobs drops per-job preconditions unless each one is carried over. These are load-bearing today:

  • The release-prep/ skip. check-self-bootstrap.yml skips release-prep/ heads with a job-level if:. toolkit-self-gates has no such skip.
  • fetch-depth: 0. It is needed by dated-examples (a shallow clone gets an empty population and refuses), by contract-paths and by check-self-bootstrap. workflow-schema and shellcheck use depth 1.
  • Tokens. prep-order-check needs pull-requests: read and FORGEJO_TOKEN. readme-pin-check / digest deliberately runs with no token. In one job the permissions become the union, so the token-less property must hold at step level.
  • Triggers. toolkit-self-gates fires on edited, so the moved checks start re-running on body edits. digest loses readme-pin-check's workflow_dispatch if it moves.
  • The check name. The context is tests / checks only if the job id is checks with no other name:.

Plan

  • tests.yml: one checks job runs workflow-schema, dated-examples, contract-paths and shellcheck as steps. It takes the build, tree-state and attest guards from toolkit-self-gates (#1323). bats stays a separate job, because it is the critical path (about 34 s).
  • toolkit-self-gates.yml: its gates job also runs the PR halves of check-self-bootstrap and gitea-twin-check, plus prep-order-check and readme-pin digest.
  • Every gate writes its verdict file in its own namespace, and the attest step requires the exact expected set. That covers #1304's residual risk: gates in one job share one checkout and one $RUNNER_TEMP.
  • The push and schedule halves of those workflows stay as they are. readme-pin-check / mirror and base-divergence-check stay their own jobs, because advisory checks need their own checks (#1177).

Sequencing with #1541: #1541 moves the parse-self mutation control into tests / workflow-schema. If this issue lands first, that job no longer exists, so the control goes into whichever job runs the workflow-schema step.

Acceptance criteria

  • tests.yml runs bats plus one checks job holding the four short checks as steps, with the build, tree-state and attest guards (#1323). — DONE @ #1546 (9968ed4, reviewed as 9968ed4). The first push:main run, 19697 job 1, logs attest: all 12 gate steps AND the shared-tree guard ran (13 of 13 ...).
  • toolkit-self-gates runs the four PR-time checks above as steps, each with a namespaced verdict file. — DONE @ #1546 (9968ed4, reviewed as 9968ed4). On the PR head, run 19651 attested 15 of 15.
  • The PR body carries a carry-forward table covering every item above. Each is preserved, reimplemented or retired, with the reason. — DONE @ #1546 (9968ed4, reviewed as 9968ed4). Review 7222 diffed every moved step against its source.
  • Attest is mutation-verified: deleting one gate's verdict, or one gate's step, turns the job red. — DONE @ #1546 (9968ed4, reviewed as 9968ed4). The mutants ran through a local step emulator and the bats arms, not the forge runner. The reviewer reproduced two kills independently (review 7222).
  • The PR body lists the eight required checks to remove and tests / checks to add. The operator swaps them at merge, and no PR is left waiting on a check that no longer reports. — DONE @ #1546 (9968ed4, reviewed as 9968ed4). Read back live after the merge: main requires 18 contexts, including tests / checks (pull_request) and none of the eight.
  • Unchanged: the push and schedule halves, readme-pin-check / mirror and base-divergence-check. — DONE @ #1546 (9968ed4, reviewed as 9968ed4). The push halves of check-self-bootstrap and gitea-twin-check ran green on 9968ed4 (runs 19692 and 19693).
  • Measured after the merge, from task timings on the live forge: jobs per toolkit-self PR and the wall clock, compared with #1530's 22 jobs and about 200 s. — DONE @ #1546 (9968ed4, reviewed as 9968ed4). Measured from /actions/tasks, counting jobs that ran at least 1 s and leaving out the 0 s caller rows, as #1530 did. Before, on #1544's first head 3bf6fee on the old main: 22 jobs, 181 s summed, 207 s wall clock. After, on #1544's rebased head 5cc3b8c on main 9968ed4: 15 jobs, 156 s summed, 173 s wall clock. #1546's own head 9968ed4: 15 jobs, 152 s, 167 s. That is 7 jobs fewer, as planned, and about 34 s less wall clock per PR.

The PR needs a no-changelog line: this is toolkit-self CI only, and no adopter sees it.

Part of #1530. This one needs no ADR and changes nothing for adopters, so it can start first. ## Measured at origin/main `e5fcf1d`, with branch protection read live on 2026-10-04 Each small job costs about 5.5 s, about 87% of it setup (#1530). These toolkit-self PR jobs are each a separate required check on `main` today: | Workflow | Jobs that become steps | Required check on `main` today | |---|---|---| | `tests.yml` | `workflow-schema`, `dated-examples`, `contract-paths`, `shellcheck` (`bats` stays its own job) | yes, all four | | `check-self-bootstrap.yml` | `check` (PR half) | yes | | `gitea-twin-check.yml` | `check` (PR half) | yes | | `prep-order-check.yml` | `check` | yes | | `readme-pin-check.yml` | `digest` | yes | Folding them removes 7 of the 22 jobs per toolkit-self PR: the four `tests` jobs become one, and the other four move into `toolkit-self-gates`. **This needs its own swap of the required checks.** #1530's plan has one admin edit, before PR-D. But this change removes eight required checks, the eight in the table above, and adds `tests / checks`. If the old names stay required, every PR after the merge waits for checks that never report. ## Carry-forward (AGENTS.md section 1; from the plan review, #1530 comment 114846) Folding jobs drops per-job preconditions unless each one is carried over. These are load-bearing today: - **The `release-prep/` skip.** `check-self-bootstrap.yml` skips `release-prep/` heads with a job-level `if:`. `toolkit-self-gates` has no such skip. - **`fetch-depth: 0`.** It is needed by `dated-examples` (a shallow clone gets an empty population and refuses), by `contract-paths` and by `check-self-bootstrap`. `workflow-schema` and `shellcheck` use depth 1. - **Tokens.** `prep-order-check` needs `pull-requests: read` and `FORGEJO_TOKEN`. `readme-pin-check / digest` deliberately runs with no token. In one job the permissions become the union, so the token-less property must hold at step level. - **Triggers.** `toolkit-self-gates` fires on `edited`, so the moved checks start re-running on body edits. `digest` loses `readme-pin-check`'s `workflow_dispatch` if it moves. - **The check name.** The context is `tests / checks` only if the job id is `checks` with no other `name:`. ## Plan - `tests.yml`: one `checks` job runs `workflow-schema`, `dated-examples`, `contract-paths` and `shellcheck` as steps. It takes the build, tree-state and attest guards from `toolkit-self-gates` (#1323). `bats` stays a separate job, because it is the critical path (about 34 s). - `toolkit-self-gates.yml`: its `gates` job also runs the PR halves of check-self-bootstrap and gitea-twin-check, plus prep-order-check and readme-pin `digest`. - Every gate writes its verdict file in its own namespace, and the attest step requires the exact expected set. That covers #1304's residual risk: gates in one job share one checkout and one `$RUNNER_TEMP`. - The push and schedule halves of those workflows stay as they are. `readme-pin-check / mirror` and `base-divergence-check` stay their own jobs, because advisory checks need their own checks (#1177). **Sequencing with #1541:** #1541 moves the parse-self mutation control into `tests / workflow-schema`. If this issue lands first, that job no longer exists, so the control goes into whichever job runs the workflow-schema step. ## Acceptance criteria - [x] `tests.yml` runs `bats` plus one `checks` job holding the four short checks as steps, with the build, tree-state and attest guards (#1323). — DONE @ #1546 (`9968ed4`, reviewed as `9968ed4`). The first push:main run, 19697 job 1, logs `attest: all 12 gate steps AND the shared-tree guard ran (13 of 13 ...)`. - [x] `toolkit-self-gates` runs the four PR-time checks above as steps, each with a namespaced verdict file. — DONE @ #1546 (`9968ed4`, reviewed as `9968ed4`). On the PR head, run 19651 attested 15 of 15. - [x] The PR body carries a carry-forward table covering every item above. Each is preserved, reimplemented or retired, with the reason. — DONE @ #1546 (`9968ed4`, reviewed as `9968ed4`). Review 7222 diffed every moved step against its source. - [x] Attest is mutation-verified: deleting one gate's verdict, or one gate's step, turns the job red. — DONE @ #1546 (`9968ed4`, reviewed as `9968ed4`). The mutants ran through a local step emulator and the bats arms, not the forge runner. The reviewer reproduced two kills independently (review 7222). - [x] The PR body lists the eight required checks to remove and `tests / checks` to add. The operator swaps them at merge, and no PR is left waiting on a check that no longer reports. — DONE @ #1546 (`9968ed4`, reviewed as `9968ed4`). Read back live after the merge: `main` requires 18 contexts, including `tests / checks (pull_request)` and none of the eight. - [x] Unchanged: the push and schedule halves, `readme-pin-check / mirror` and `base-divergence-check`. — DONE @ #1546 (`9968ed4`, reviewed as `9968ed4`). The push halves of check-self-bootstrap and gitea-twin-check ran green on `9968ed4` (runs 19692 and 19693). - [x] Measured after the merge, from task timings on the live forge: jobs per toolkit-self PR and the wall clock, compared with #1530's 22 jobs and about 200 s. — DONE @ #1546 (`9968ed4`, reviewed as `9968ed4`). Measured from `/actions/tasks`, counting jobs that ran at least 1 s and leaving out the 0 s caller rows, as #1530 did. Before, on #1544's first head `3bf6fee` on the old main: 22 jobs, 181 s summed, 207 s wall clock. After, on #1544's rebased head `5cc3b8c` on main `9968ed4`: **15 jobs, 156 s summed, 173 s wall clock**. #1546's own head `9968ed4`: 15 jobs, 152 s, 167 s. That is 7 jobs fewer, as planned, and about 34 s less wall clock per PR. The PR needs a `no-changelog` line: this is toolkit-self CI only, and no adopter sees it.
Author
Member

Closing this by hand, now that all seven acceptance criteria are ticked.

  • Landed: #1546 as a fast-forward to 9968ed4b9e97e4b6b7e361261763520fec82a5d0, exactly the reviewed head. Alex merged it on 2026-10-04 at 11:52. The head branch is deleted.
  • Review: 7222 from the reviewer session (verdict APPROVE at 9968ed4, posted as a COMMENT), and Alex's approval on the forge.
  • Required checks: read back after the swap, main requires 18 contexts, including tests / checks (pull_request) and none of the eight removed ones.
  • CI:
    • The first push:main run on 9968ed4 is green. tests / checks (run 19697 job 1) logs PARSED=36 TOTAL=36, the tree unchanged across c1-c12, and attest 13 of 13. rt decide gave mode=noop.
    • The push halves of check-self-bootstrap and gitea-twin-check are green (runs 19692 and 19693).
    • Two skipped jobs still show as pending: release / fire-cut and go-ci / page landing-tree failure. That is the known skipped-job display.
  • Measured: 22 to 15 jobs per toolkit-self PR, and 207 s to 173 s wall clock. The details are on the last criterion above.

A follow-up for the old g7/g9 command not found output, and for a stale comment in mirror-release.yml, was offered as a separate task. It is not part of this issue.

Closing this by hand, now that all seven acceptance criteria are ticked. - **Landed:** #1546 as a fast-forward to `9968ed4b9e97e4b6b7e361261763520fec82a5d0`, exactly the reviewed head. Alex merged it on 2026-10-04 at 11:52. The head branch is deleted. - **Review:** 7222 from the reviewer session (verdict APPROVE at `9968ed4`, posted as a COMMENT), and Alex's approval on the forge. - **Required checks:** read back after the swap, `main` requires 18 contexts, including `tests / checks (pull_request)` and none of the eight removed ones. - **CI:** - The first push:main run on `9968ed4` is green. `tests / checks` (run 19697 job 1) logs `PARSED=36 TOTAL=36`, the tree unchanged across c1-c12, and attest 13 of 13. `rt decide` gave `mode=noop`. - The push halves of check-self-bootstrap and gitea-twin-check are green (runs 19692 and 19693). - Two skipped jobs still show as pending: `release / fire-cut` and `go-ci / page landing-tree failure`. That is the known skipped-job display. - **Measured:** 22 to 15 jobs per toolkit-self PR, and 207 s to 173 s wall clock. The details are on the last criterion above. A follow-up for the old g7/g9 `command not found` output, and for a stale comment in `mirror-release.yml`, was offered as a separate task. It is not part of this issue.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#1540
No description provided.