chore(ci): fold four tests jobs into tests / checks and four PR checks into toolkit-self-gates #1546

Merged
alex merged 4 commits from claude/1540-fold-toolkit-self-jobs into main 2026-10-04 11:52:11 +02:00
Member

Summary

This removes 7 of the 22 jobs a toolkit-self PR runs. The runner has one slot, and a small job spends about 87% of its time on setup (#1530).

  • Before: tests ran 5 jobs (workflow-schema, bats, dated-examples, contract-paths, shellcheck). check-self-bootstrap / check, gitea-twin-check / check, prep-order-check / check and readme-pin-check / digest were 4 more jobs on every PR.
  • After: tests runs 2 jobs, bats and checks. The four short jobs are 12 steps of checks. toolkit-self-gates grows from 9 gate steps to 14, picking up the PR halves of the other four.
  • Both consolidated jobs now have the guards from #1253 and #1323, plus a verdict half. Each gate writes $RUNNER_TEMP/verdicts/<id> as its last command, and attest requires the exact GATE_IDS set.
  • The push halves of check-self-bootstrap and gitea-twin-check do not change. Neither do readme-pin-check / mirror and base-divergence-check.

What Alex must do: swap the required checks on main (list below). Do it after you approve, right before you merge. Until the swap, this PR cannot be fully green: the 8 old required contexts should never post on its head (checked on the run; see "CI on this PR").

Refs #1540
Refs #1530

Required-check swap on main (read live 2026-10-04, 25 contexts)

Remove these 8:

tests / workflow-schema (pull_request)
tests / dated-examples (pull_request)
tests / contract-paths (pull_request)
tests / shellcheck (pull_request)
check-self-bootstrap / check (pull_request)
gitea-twin-check / check (pull_request)
prep-order-check / check (pull_request)
readme-pin-check / digest (pull_request)

Add this 1:

tests / checks (pull_request)

That leaves 18 required contexts. toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) stays required and now covers the four moved checks. The exact posted name of the new context is confirmed against this PR's run in the CI comment below.

rt pre-push reads the live required set, and this PR updates its tables to the set after the swap. Between the push and the swap, a local rt pre-push reports tests / checks as a dead declaration and the 8 old names as undeclared. Once the swap is done, main and the table agree.

Decision tree

  • Where does a moved PR check go? Into one of the two consolidated jobs when it is a required, pull_request-only check whose trigger set fits that job. Into a job of its own when it is advisory: an advisory check needs its own context (#1177), so readme-pin-check / mirror stays. Its own job also fits a trust boundary or a different event; base-divergence-check and the push halves stay where they are.
  • Should a PR half be removed by trigger or by job? Remove the pull_request: trigger when the workflow keeps a push half: check-self-bootstrap.yml and gitea-twin-check.yml. Their dead pull_request-only steps went too: the replay step, and check-self-bootstrap's job-level if:, which was always true on push. Delete the file when the PR half was the whole workflow, as with prep-order-check.yml. Delete the job when the workflow keeps another job, as with the digest job in readme-pin-check.yml.
  • Should bats be folded too? No. It is the critical path (about 34 s). Folding it in adds the other steps to the wall clock and saves only one setup prefix.
  • What should attest grade? Step outcome alone is right when the only failure is "the step did not run". The outcome plus a verdict file is right once gates share a checkout and $RUNNER_TEMP (#1304's residual risk). A gate that exits 0 before its end, a deleted step, or a verdict nobody declared then each turns the job red instead of reading as a pass.
  • How should the release-prep/ exemption (#304) work? A step-level if: is right only when attest does not grade the step. Here attest does grade it, and a skipped step reads as NOT RUN, which would turn every rolling PR red. So g10 runs and records exempt. Attest accepts exempt only for ids in EXEMPTABLE_GATE_IDS (just g10).

Carry-forward (AGENTS.md section 1)

Classes: (a) scaffolding, (b) load-bearing setup, (c) system-behaviour workaround. Every gate body was moved verbatim, apart from the verdict line and the changes named below.

Into tests / checks

From Step Class Disposition
all 4 jobs checkout (a) Preserved once. fetch-depth: 0 is the deepest of the four. It is load-bearing for dated-examples (#926: a shallow clone gives an empty retired-script population and the check refuses, rc 2) and for contract-paths (#903: provenance needs history). Both "DO NOT lower" comments are kept. workflow-schema and shellcheck used depth 1 and do not care.
all 4 jobs replay onto the landing base (#1195) (b) Preserved once, verbatim.
dated-examples, shellcheck verify standard go runner tools (b) Preserved as their union: python3 git shellcheck. The #863 bats arm now executes the checks preflight with an empty PATH.
workflow-schema fetch the pinned Forgejo Actions validator (b) Preserved, with the same checksum. It is now the last setup step and writes ready=true, which every gate's guard reads.
workflow-schema schema gate, fixture self-test gate Preserved as c1 and c2.
shellcheck run shellcheck on scripts/ gate Preserved as c3.
contract-paths real gate gate Preserved as c4.
dated-examples real gate, anchor gate (#1451), anchor controls gate Preserved as c5, c6 and c7. c6 ends in exit "$rc", so it writes its verdict only when rc is 0.
contract-paths 2 mutating controls (c) Reimplemented as c8 and c9. Each restores its file from an EXIT trap. Before, each control had a private checkout and restored only on success.
dated-examples 3 mutating controls (c) Reimplemented as c10, c11 and c12, with the same EXIT-trap restore. The comment saying "the controls are free to mutate, no restore has to be trusted" is retired, because it is false in a shared checkout. Every read-only gate (c1-c7) now runs before any mutating control. anchor-check.py reads all of docs/, including both mutated files.
dated-examples anchor step env FORGEJO_TOKEN: secrets.GITHUB_TOKEN (b) Preserved at step level on c6 and c7 only. There is no job-level token.
(new) tree state + empty verdict dir, shared-tree guard, attest Ported from toolkit-self-gates (#1253, #1323), with the generic GATE_IDS form.

Into toolkit-self-gates

From Step / setting Class Disposition
check-self-bootstrap job if: skipping release-prep/ heads (#304) (c) Reimplemented at step level. g10 runs; on a release-prep/ head it records exempt and does not call rt. Attest accepts that only for EXEMPTABLE_GATE_IDS: g10. The #304 rationale moved into g10's comment. The #304 bats arm now executes g10 against a stub rt.
check-self-bootstrap checkout fetch-depth: 0, fetch-tags: true (b) Preserved on the gates checkout. The replay already unshallowed every PR checkout, so it adds no cost. Under @main the check exits before reading a tag. Under a tag pin it needs them (AGENTS.md section 2.3).
check-self-bootstrap, gitea-twin-check, prep-order-check, readme-pin digest build rt from THIS PR, replay (a)/(b) Preserved once, already in the job.
check-self-bootstrap run check-self-bootstrap gate Preserved as g10.
gitea-twin-check twin --check gate Preserved as g11. --check writes nothing to the tree.
prep-order-check job permissions: contents: read, pull-requests: read (b) Preserved as the job's permissions. This is the union: the #1253 gates use no token, and the digest job held only contents: read.
prep-order-check step env FORGEJO_BASE_URL, FORGEJO_TOKEN: secrets.GITHUB_TOKEN (b) Preserved at step level on g12 and g13 only.
prep-order-check positive control (#1157), grade this PR gate Preserved as g12 and g13. g12's log moved from $RUNNER_TEMP/control.log to prep-order-control.log, its own name in the shared temp dir. g12's step name is now quoted: its unquoted #1157 was a YAML comment, which truncated the name in the old workflow too (AGENTS.md section 3a; seen in the emulated run). g13 writes its verdict only when rc is 0.
prep-order-check types: [opened, synchronize, reopened] trigger Widened to the job's union, which adds edited. So g12 and g13 (and g10, g11, g14) now re-run on every PR-body edit. That costs two forge API reads plus three local checks, all inside a job that already re-ran on edited. It adds no job.
readme-pin digest fetch release tags (git fetch --force --tags) (b) Preserved as a setup step before the replay. It moves refs only, never the working tree.
readme-pin digest grade digests gate Preserved as g14. No token: g14 has no step env:, as the digest job had none. rt tag-action-digest-check reads local tags through git show (cmd/rt/tag_action_digest_check.go). The no-token comment at readme-pin-check.yml ~105 belongs to the mirror step, which stays where it is. The PASS label changed from readme-pin-check / digest: to tag-action-digest-check:, because that context no longer exists.
readme-pin-check workflow_dispatch for digest trigger Retired for digest. Dispatch remains for mirror. That is the half graded against gitea.com, which a person publishes by hand. The digest grades this repo's own docs and tags. Docs change only through a PR, which runs g14. A new tag's digest is graded by mirror-release.yml's set-adopter-pin before it pushes the pin. A one-off check runs locally with rt tag-action-digest-check --doc README.md --doc docs/integration.md --doc examples.
check-self-bootstrap.yml, gitea-twin-check.yml pull_request trigger, PR-only replay step Removed. The push halves keep their checkout, build and gate step byte for byte.
prep-order-check.yml whole file Deleted.

Also in this PR

  • ASCII. All five touched workflows are now ASCII-only (AGENTS.md section 11). The first commit, 3ea3e9f, is a mechanical transliteration only: em-dash to -, the emoji markers to (!)/KEY:/NOTE:, and § to section. The content changes read on their own after it.
  • cmd/rt/pre_push.go. Updated to the post-swap required set. check-self-bootstrap no longer claims a context of its own: its PR half is one of 14 gates behind one context, and claiming that context would be partial coverage.
  • Docs. cli-surface.md, integration.md, AGENTS.md section 2.3 and the contract-paths-check.sh header now name where each verb runs.
  • Not changed: a comment in mirror-release.yml (~289) still says "both verdicts readme-pin-check.yml would post on a PR". That file is baked and twinned, so I left it out of this change. Its statement is now stale for the digest half.

Tests and mutation evidence

Everything below ran in an LF clone at head 9968ed4.

  • go test -count=1 ./... (native): all ok.
  • golangci-lint run in forgejo-ci-go:latest: 0 issues.
  • bats --print-output-on-failure tests/ in docker rt-bats:local: 1..222, 222 ok.
  • rt check-self-bootstrap: [check-self-bootstrap] floating-pin (release-toolkit#456): main matches main by construction / {"kind":"repin_check","result":"pass","pinned_ref":"main"}, rc 0.
  • rt gitea-twin --check: gitea-twin: PASS - 11 twin(s) match their source.

Runtime: both jobs through a step emulator

No local forgejo-runner is available to run a job. So emulate_job.py (scratch, not committed) executes each job's real YAML steps:

  • each step runs as bash --noprofile --norc -eo pipefail, with per-step $GITHUB_OUTPUT, a shared $RUNNER_TEMP, and steps.*.outcome substituted into env;
  • it supports only the if: forms these two jobs use, and refuses any other;
  • checkout is a clone of the committed head.

It downloaded and checksum-verified the real validator, so the c1 schema step ran the real engine. g12 and g13 used the live API, with github.event.number=1531 standing in for this PR. Each mutant is a copy of the workflow file. make_mutants.py asserts each one applied and prints its diff.

Run Mutation Result
tests / checks baseline (pull_request) none all 12 gates, tree guard and attest success. attest: all 12 gate steps AND the shared-tree guard ran (13 of 13 ...). JOB success
tests M1 delete c4's verdict line NO VERDICT: steps.c4 succeeded but never wrote verdicts/c4. attest failure, JOB failure
tests M2 delete step c3 COULD-NOT-GRADE: steps.c3.outcome did not resolve. attest failure, JOB failure
tests M3 c4 runs false (a gate fails) c4 failure. The 11 other gates still ran (13 of 13) and attest passed, so there is no double red. JOB failure
tests M4 c5 leaves leaked-by-c5.txt REFUSED: all 12 gates passed, so the tree was left dirty. tree guard failure, JOB failure
toolkit-self-gates baseline none 14 gates success. attest: all 14 gate steps AND the shared-tree guard ran (15 of 15 ...). JOB success
head release-prep/rolling none EXEMPT: g10 recorded an exemption, which EXEMPTABLE_GATE_IDS allows. JOB success
tsg M1 delete g9's verdict line NO VERDICT: steps.g9 .... JOB failure
tsg M2 delete step g11 COULD-NOT-GRADE: steps.g11.outcome did not resolve. JOB failure
tsg M3 g11 runs false g11 failure, attest passed (no double red). JOB failure
tsg M5 g9 also writes verdicts/g15 UNEXPECTED VERDICT: verdicts/g15 names no gate in GATE_IDS. JOB failure

The emulator does not prove how Forgejo resolves steps.<missing>.outcome; it substitutes an empty string. The #1253 arms already assumed empty, so M2 on the real runner is unmeasured.

Static: the bats arms against the same mutants (Axis B)

Baseline: bats -f '1253|1540|304|1463|1453' tests/workflows.bats gives 11 of 11 ok. Each mutant was copied over a disposable copy of the clone, and git diff --stat showed it landed.

Mutant Arms that went red
tests M1 (no c4 verdict) temp-path / verdict-namespace arm
tests M2 (no c3 step) attest arm, guard-wiring arm, verdict-namespace arm
tests M4 (c5 leaks) none. That is expected: a runtime leak is the tree guard's job (M4 above).
tsg M1 (no g9 verdict) verdict-namespace arm
tsg M2 (no g11 step) drift arm (14-gate count), #1540 PR-halves arm, attest, wiring, namespace
tsg M5 (undeclared g15 verdict) verdict-namespace arm
tsg M6 (release-prep case gutted) #304 arm
tsg M7 (g14 writes its verdict before grading) verdict-namespace arm (runs [...] after writing its verdict)

scripts/workflow-parse-check.sh needs forgejo-runner. Here it ran only inside the emulator, through the downloaded validator (c1 green). CI's workflow-parse-check and tests / checks grade it for real.

CI on this PR

Workflows run from the PR head, so the old jobs are gone here and their 8 required contexts will never post. I'll read the job logs for tests / checks and the grown toolkit-self-gates on this head, and post the posted context names and log evidence as a comment. I'm not editing this body for that, because an edit re-fires the edited gates.

Self-bootstrap (AGENTS.md section 2)

No re-pin: toolkit-self floats @main (AGENTS.md section 2.1). Nothing here touches a reusable or gates.DefaultComposeScripts. Adopters see nothing: these are toolkit-self workflows only. rt check-self-bootstrap at 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"}

no-changelog: toolkit-self CI only; no adopter sees it

Verify at source (@alex)

Please verify against the tree rather than this body. In particular:

  1. The 14 gate bodies in toolkit-self-gates.yml and the 12 in tests.yml against their sources at 6b5d554. The only intended differences are those in the carry-forward tables.
  2. The swap list, against the live status_check_contexts.
  3. That tests / checks is the context name this PR's run posted (see the CI comment).

🤖 Generated with Claude Code

## Summary This removes 7 of the 22 jobs a toolkit-self PR runs. The runner has one slot, and a small job spends about 87% of its time on setup (#1530). - **Before:** `tests` ran 5 jobs (`workflow-schema`, `bats`, `dated-examples`, `contract-paths`, `shellcheck`). `check-self-bootstrap / check`, `gitea-twin-check / check`, `prep-order-check / check` and `readme-pin-check / digest` were 4 more jobs on every PR. - **After:** `tests` runs 2 jobs, `bats` and `checks`. The four short jobs are 12 steps of `checks`. `toolkit-self-gates` grows from 9 gate steps to 14, picking up the PR halves of the other four. - Both consolidated jobs now have the guards from #1253 and #1323, plus a verdict half. Each gate writes `$RUNNER_TEMP/verdicts/<id>` as its last command, and attest requires the exact `GATE_IDS` set. - The push halves of `check-self-bootstrap` and `gitea-twin-check` do not change. Neither do `readme-pin-check / mirror` and `base-divergence-check`. **What Alex must do:** swap the required checks on `main` (list below). Do it **after you approve, right before you merge**. Until the swap, this PR cannot be fully green: the 8 old required contexts should never post on its head (checked on the run; see "CI on this PR"). Refs #1540 Refs #1530 ## Required-check swap on `main` (read live 2026-10-04, 25 contexts) Remove these 8: ``` tests / workflow-schema (pull_request) tests / dated-examples (pull_request) tests / contract-paths (pull_request) tests / shellcheck (pull_request) check-self-bootstrap / check (pull_request) gitea-twin-check / check (pull_request) prep-order-check / check (pull_request) readme-pin-check / digest (pull_request) ``` Add this 1: ``` tests / checks (pull_request) ``` That leaves 18 required contexts. `toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request)` stays required and now covers the four moved checks. The exact posted name of the new context is confirmed against this PR's run in the CI comment below. `rt pre-push` reads the live required set, and this PR updates its tables to the set *after* the swap. Between the push and the swap, a local `rt pre-push` reports `tests / checks` as a dead declaration and the 8 old names as undeclared. Once the swap is done, `main` and the table agree. ## Decision tree - **Where does a moved PR check go?** Into one of the two consolidated jobs when it is a required, `pull_request`-only check whose trigger set fits that job. Into a job of its own when it is advisory: an advisory check needs its own context (#1177), so `readme-pin-check / mirror` stays. Its own job also fits a trust boundary or a different event; `base-divergence-check` and the push halves stay where they are. - **Should a PR half be removed by trigger or by job?** Remove the `pull_request:` trigger when the workflow keeps a push half: `check-self-bootstrap.yml` and `gitea-twin-check.yml`. Their dead `pull_request`-only steps went too: the replay step, and `check-self-bootstrap`'s job-level `if:`, which was always true on push. Delete the file when the PR half was the whole workflow, as with `prep-order-check.yml`. Delete the job when the workflow keeps another job, as with the `digest` job in `readme-pin-check.yml`. - **Should `bats` be folded too?** No. It is the critical path (about 34 s). Folding it in adds the other steps to the wall clock and saves only one setup prefix. - **What should attest grade?** Step outcome alone is right when the only failure is "the step did not run". The outcome plus a verdict file is right once gates share a checkout and `$RUNNER_TEMP` (#1304's residual risk). A gate that exits 0 before its end, a deleted step, or a verdict nobody declared then each turns the job red instead of reading as a pass. - **How should the `release-prep/` exemption (#304) work?** A step-level `if:` is right only when attest does not grade the step. Here attest does grade it, and a skipped step reads as NOT RUN, which would turn every rolling PR red. So g10 runs and records `exempt`. Attest accepts `exempt` only for ids in `EXEMPTABLE_GATE_IDS` (just `g10`). ## Carry-forward (AGENTS.md section 1) Classes: (a) scaffolding, (b) load-bearing setup, (c) system-behaviour workaround. Every gate body was moved verbatim, apart from the verdict line and the changes named below. ### Into `tests / checks` | From | Step | Class | Disposition | |---|---|---|---| | all 4 jobs | `checkout` | (a) | **Preserved** once. `fetch-depth: 0` is the deepest of the four. It is load-bearing for `dated-examples` (#926: a shallow clone gives an empty retired-script population and the check refuses, rc 2) and for `contract-paths` (#903: provenance needs history). Both "DO NOT lower" comments are kept. `workflow-schema` and `shellcheck` used depth 1 and do not care. | | all 4 jobs | `replay onto the landing base (#1195)` | (b) | **Preserved** once, verbatim. | | dated-examples, shellcheck | `verify standard go runner tools` | (b) | **Preserved** as their union: `python3 git shellcheck`. The #863 bats arm now executes the `checks` preflight with an empty PATH. | | workflow-schema | `fetch the pinned Forgejo Actions validator` | (b) | **Preserved**, with the same checksum. It is now the last setup step and writes `ready=true`, which every gate's guard reads. | | workflow-schema | schema gate, fixture self-test | gate | **Preserved** as c1 and c2. | | shellcheck | `run shellcheck on scripts/` | gate | **Preserved** as c3. | | contract-paths | real gate | gate | **Preserved** as c4. | | dated-examples | real gate, anchor gate (#1451), anchor controls | gate | **Preserved** as c5, c6 and c7. c6 ends in `exit "$rc"`, so it writes its verdict only when rc is 0. | | contract-paths | 2 mutating controls | (c) | **Reimplemented** as c8 and c9. Each restores its file from an `EXIT` trap. Before, each control had a private checkout and restored only on success. | | dated-examples | 3 mutating controls | (c) | **Reimplemented** as c10, c11 and c12, with the same `EXIT`-trap restore. The comment saying "the controls are free to mutate, no restore has to be trusted" is **retired**, because it is false in a shared checkout. Every read-only gate (c1-c7) now runs before any mutating control. `anchor-check.py` reads all of `docs/`, including both mutated files. | | dated-examples | anchor step env `FORGEJO_TOKEN: secrets.GITHUB_TOKEN` | (b) | **Preserved** at step level on c6 and c7 only. There is no job-level token. | | (new) | tree state + empty verdict dir, shared-tree guard, attest | | **Ported** from `toolkit-self-gates` (#1253, #1323), with the generic `GATE_IDS` form. | ### Into `toolkit-self-gates` | From | Step / setting | Class | Disposition | |---|---|---|---| | check-self-bootstrap | job `if:` skipping `release-prep/` heads (#304) | (c) | **Reimplemented at step level.** g10 runs; on a `release-prep/` head it records `exempt` and does not call rt. Attest accepts that only for `EXEMPTABLE_GATE_IDS: g10`. The #304 rationale moved into g10's comment. The #304 bats arm now *executes* g10 against a stub rt. | | check-self-bootstrap | checkout `fetch-depth: 0`, `fetch-tags: true` | (b) | **Preserved** on the `gates` checkout. The replay already unshallowed every PR checkout, so it adds no cost. Under `@main` the check exits before reading a tag. Under a tag pin it needs them (AGENTS.md section 2.3). | | check-self-bootstrap, gitea-twin-check, prep-order-check, readme-pin digest | `build rt from THIS PR`, replay | (a)/(b) | **Preserved** once, already in the job. | | check-self-bootstrap | `run check-self-bootstrap` | gate | **Preserved** as g10. | | gitea-twin-check | twin `--check` | gate | **Preserved** as g11. `--check` writes nothing to the tree. | | prep-order-check | job `permissions: contents: read, pull-requests: read` | (b) | **Preserved** as the job's permissions. This is the union: the #1253 gates use no token, and the digest job held only `contents: read`. | | prep-order-check | step env `FORGEJO_BASE_URL`, `FORGEJO_TOKEN: secrets.GITHUB_TOKEN` | (b) | **Preserved** at step level on g12 and g13 only. | | prep-order-check | positive control (#1157), grade this PR | gate | **Preserved** as g12 and g13. g12's log moved from `$RUNNER_TEMP/control.log` to `prep-order-control.log`, its own name in the shared temp dir. g12's step name is now quoted: its unquoted ` #1157` was a YAML comment, which truncated the name in the old workflow too (AGENTS.md section 3a; seen in the emulated run). g13 writes its verdict only when rc is 0. | | prep-order-check | `types: [opened, synchronize, reopened]` | trigger | **Widened** to the job's union, which adds `edited`. So g12 and g13 (and g10, g11, g14) now re-run on every PR-body edit. That costs two forge API reads plus three local checks, all inside a job that already re-ran on `edited`. It adds no job. | | readme-pin digest | `fetch release tags` (`git fetch --force --tags`) | (b) | **Preserved** as a setup step before the replay. It moves refs only, never the working tree. | | readme-pin digest | grade digests | gate | **Preserved** as g14. **No token:** g14 has no step `env:`, as the digest job had none. `rt tag-action-digest-check` reads local tags through `git show` (`cmd/rt/tag_action_digest_check.go`). The no-token comment at `readme-pin-check.yml` ~105 belongs to the `mirror` step, which stays where it is. The PASS label changed from `readme-pin-check / digest:` to `tag-action-digest-check:`, because that context no longer exists. | | readme-pin-check | `workflow_dispatch` for digest | trigger | **Retired** for digest. Dispatch remains for `mirror`. That is the half graded against gitea.com, which a person publishes by hand. The digest grades this repo's own docs and tags. Docs change only through a PR, which runs g14. A new tag's digest is graded by `mirror-release.yml`'s `set-adopter-pin` before it pushes the pin. A one-off check runs locally with `rt tag-action-digest-check --doc README.md --doc docs/integration.md --doc examples`. | | check-self-bootstrap.yml, gitea-twin-check.yml | `pull_request` trigger, PR-only replay step | | **Removed.** The push halves keep their checkout, build and gate step byte for byte. | | prep-order-check.yml | whole file | | **Deleted.** | ### Also in this PR - **ASCII.** All five touched workflows are now ASCII-only (AGENTS.md section 11). The first commit, `3ea3e9f`, is a mechanical transliteration only: em-dash to `-`, the emoji markers to `(!)`/`KEY:`/`NOTE:`, and `§` to `section`. The content changes read on their own after it. - **`cmd/rt/pre_push.go`.** Updated to the post-swap required set. `check-self-bootstrap` no longer claims a context of its own: its PR half is one of 14 gates behind one context, and claiming that context would be partial coverage. - **Docs.** `cli-surface.md`, `integration.md`, AGENTS.md section 2.3 and the `contract-paths-check.sh` header now name where each verb runs. - **Not changed:** a comment in `mirror-release.yml` (~289) still says "both verdicts readme-pin-check.yml would post on a PR". That file is baked and twinned, so I left it out of this change. Its statement is now stale for the digest half. ## Tests and mutation evidence Everything below ran in an LF clone at head `9968ed4`. - `go test -count=1 ./...` (native): all ok. - `golangci-lint run` in `forgejo-ci-go:latest`: `0 issues.` - `bats --print-output-on-failure tests/` in docker `rt-bats:local`: `1..222`, 222 ok. - `rt check-self-bootstrap`: `[check-self-bootstrap] floating-pin (release-toolkit#456): main matches main by construction` / `{"kind":"repin_check","result":"pass","pinned_ref":"main"}`, rc 0. - `rt gitea-twin --check`: `gitea-twin: PASS - 11 twin(s) match their source.` ### Runtime: both jobs through a step emulator No local `forgejo-runner` is available to run a job. So `emulate_job.py` (scratch, not committed) executes each job's real YAML steps: - each step runs as `bash --noprofile --norc -eo pipefail`, with per-step `$GITHUB_OUTPUT`, a shared `$RUNNER_TEMP`, and `steps.*.outcome` substituted into env; - it supports only the `if:` forms these two jobs use, and refuses any other; - checkout is a clone of the committed head. It downloaded and checksum-verified the real validator, so the c1 schema step ran the real engine. g12 and g13 used the live API, with `github.event.number=1531` standing in for this PR. Each mutant is a copy of the workflow file. `make_mutants.py` asserts each one applied and prints its diff. | Run | Mutation | Result | |---|---|---| | `tests / checks` baseline (pull_request) | none | all 12 gates, tree guard and attest success. `attest: all 12 gate steps AND the shared-tree guard ran (13 of 13 ...)`. JOB success | | tests M1 | delete c4's verdict line | `NO VERDICT: steps.c4 succeeded but never wrote verdicts/c4`. attest failure, JOB failure | | tests M2 | delete step c3 | `COULD-NOT-GRADE: steps.c3.outcome did not resolve`. attest failure, JOB failure | | tests M3 | c4 runs `false` (a gate fails) | c4 failure. The 11 other gates still ran (`13 of 13`) and attest passed, so there is no double red. JOB failure | | tests M4 | c5 leaves `leaked-by-c5.txt` | `REFUSED: all 12 gates passed, so the tree was left dirty`. tree guard failure, JOB failure | | `toolkit-self-gates` baseline | none | 14 gates success. `attest: all 14 gate steps AND the shared-tree guard ran (15 of 15 ...)`. JOB success | | head `release-prep/rolling` | none | `EXEMPT: g10 recorded an exemption, which EXEMPTABLE_GATE_IDS allows`. JOB success | | tsg M1 | delete g9's verdict line | `NO VERDICT: steps.g9 ...`. JOB failure | | tsg M2 | delete step g11 | `COULD-NOT-GRADE: steps.g11.outcome did not resolve`. JOB failure | | tsg M3 | g11 runs `false` | g11 failure, attest passed (no double red). JOB failure | | tsg M5 | g9 also writes `verdicts/g15` | `UNEXPECTED VERDICT: verdicts/g15 names no gate in GATE_IDS`. JOB failure | The emulator does not prove how Forgejo resolves `steps.<missing>.outcome`; it substitutes an empty string. The #1253 arms already assumed empty, so M2 on the real runner is unmeasured. ### Static: the bats arms against the same mutants (Axis B) Baseline: `bats -f '1253|1540|304|1463|1453' tests/workflows.bats` gives 11 of 11 ok. Each mutant was copied over a disposable copy of the clone, and `git diff --stat` showed it landed. | Mutant | Arms that went red | |---|---| | tests M1 (no c4 verdict) | temp-path / verdict-namespace arm | | tests M2 (no c3 step) | attest arm, guard-wiring arm, verdict-namespace arm | | tests M4 (c5 leaks) | none. That is expected: a runtime leak is the tree guard's job (M4 above). | | tsg M1 (no g9 verdict) | verdict-namespace arm | | tsg M2 (no g11 step) | drift arm (14-gate count), #1540 PR-halves arm, attest, wiring, namespace | | tsg M5 (undeclared g15 verdict) | verdict-namespace arm | | tsg M6 (release-prep `case` gutted) | #304 arm | | tsg M7 (g14 writes its verdict before grading) | verdict-namespace arm (`runs [...] after writing its verdict`) | `scripts/workflow-parse-check.sh` needs `forgejo-runner`. Here it ran only inside the emulator, through the downloaded validator (c1 green). CI's `workflow-parse-check` and `tests / checks` grade it for real. ## CI on this PR Workflows run from the PR head, so the old jobs are gone here and their 8 required contexts will never post. I'll read the job logs for `tests / checks` and the grown `toolkit-self-gates` on this head, and post the posted context names and log evidence as a comment. I'm not editing this body for that, because an edit re-fires the `edited` gates. ## Self-bootstrap (AGENTS.md section 2) No re-pin: toolkit-self floats `@main` (AGENTS.md section 2.1). Nothing here touches a reusable or `gates.DefaultComposeScripts`. Adopters see nothing: these are toolkit-self workflows only. `rt check-self-bootstrap` at 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"} ``` no-changelog: toolkit-self CI only; no adopter sees it ## Verify at source (@alex) Please verify against the tree rather than this body. In particular: 1. The 14 gate bodies in `toolkit-self-gates.yml` and the 12 in `tests.yml` against their sources at `6b5d554`. The only intended differences are those in the carry-forward tables. 2. The swap list, against the live `status_check_contexts`. 3. That `tests / checks` is the context name this PR's run posted (see the CI comment). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
AGENTS.md section 11 requires workflow YAML to be ASCII-only. The five
files this change edits carried em-dashes and emoji markers in comments,
step names and echo strings. Mechanical mapping only, so the content
edits that follow read on their own:
  em-dash -> -, arrows -> ->, red-circle/warning -> (!), key -> KEY:,
  pushpin -> NOTE:, section sign -> section.

Refs #1540

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runner has one slot, and a small job spends about 87% of its ~5.5 s
on setup (#1530). This removes 7 jobs from every toolkit-self PR.

tests.yml: workflow-schema, dated-examples, contract-paths and
shellcheck run as twelve steps of one `checks` job; bats stays its own
job (the critical path). The guards from toolkit-self-gates come with
them: a setup-keyed `if:` that survives an earlier gate's failure, the
shared-tree guard, and attest. Read-only gates run before the
tree-mutating controls, and each control now restores its files from an
EXIT trap.

toolkit-self-gates.yml grows from 9 gates to 14: the pull_request halves
of check-self-bootstrap (g10, carrying the release-prep/ exemption as an
`exempt` verdict) and gitea-twin-check (g11), prep-order-check (g12,
g13) and readme-pin-check's digest job (g14). The push halves stay in
their workflows; prep-order-check.yml is retired; readme-pin-check keeps
its advisory mirror job.

Both jobs: every gate writes $RUNNER_TEMP/verdicts/<id> as its last
command, and attest requires the exact GATE_IDS set. A green gate with no
verdict, an undeclared verdict, an exempt from a non-exemptable gate and
a failed gate carrying a verdict are each refused.

tests/workflows.bats: the #1253 arms now grade both jobs and the verdict
half; the #304, #863, #1451, #1453 and #1463 arms follow their content;
a new arm checks each moved PR half runs once and the push halves stay.

Refs #1540

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rt pre-push reads the live required set, so its tables must match the
set after the #1540 swap: tests / checks replaces the four folded tests
contexts and the gitea-twin-check declaration, and check-self-bootstrap
stops claiming a context of its own (its PR half is one of fourteen
gates behind the toolkit-self-gates context).

cli-surface.md, integration.md, AGENTS.md section 2.3 and the
contract-paths-check.sh header now name where each verb runs.

Refs #1540

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chore(ci): quote the g12 step name so #1157 is not read as a YAML comment
All checks were successful
fragment-check / coverage (pull_request) Successful in 7s
fragment-check / density (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
go-ci / lint + build + test (pull_request) Successful in 33s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
readme-pin-check / mirror (pull_request) Successful in 7s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 36s
tests / checks (pull_request) Successful in 8s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 8s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 5s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 4s
workflow-parse-check / check (pull_request) Successful in 0s
go-ci / page landing-tree failure (pull_request) Has been skipped
check-self-bootstrap / check (push) Successful in 5s
gitea-twin-check / check (push) Successful in 5s
go-ci / lint + build + test (push) Successful in 33s
go-ci / record reviewed vs landed commit (push) Successful in 3s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 5s
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 8s
release / release (push) Successful in 0s
tests / bats (push) Successful in 34s
tests / checks (push) Successful in 7s
go-ci / page landing-tree failure (push) Has been skipped
release / fire-cut (push) Has been skipped
9968ed4b9e
An unquoted ' #1157' in a plain scalar starts a comment (AGENTS.md
section 3a). The emulated run of toolkit-self-gates showed the step as
'... must still refuse', the same truncation prep-order-check.yml's step
name had carried.

Refs #1540

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude requested review from alex 2026-10-04 11:35:35 +02:00
Author
Member

CI on head 9968ed4b9e97e4b6b7e361261763520fec82a5d0, read from the commit statuses and from the job logs (not just the statuses).

Context names. 23 contexts posted, all success. The new one posted as exactly tests / checks (pull_request) (run 19650, job 1), so the swap list in the body is the right spelling. None of the 8 contexts to remove posted on this head, which confirms that pull_request workflows are read from the PR head here. Under the current protection this PR therefore waits on 8 required contexts that will never come, until the swap.

tests / checks (run 19650/jobs/1, 8 s; bats 36 s), from its log:

  • checkout ran at full depth, and the replay passed: this head already contains origin/main at 6b5d554...
  • validator: forgejo-runner version v12.8.2, sha256 verified
  • PARSED=36 TOTAL=36 / PASS: 36/36 workflow files conform to the Forgejo Actions schema.
  • PASS: 4 fatal fixture(s) rejected, 3 safe fixture(s) accepted.
  • anchor-check: PASS - all selected in-document anchors resolve., plus both anchor controls OK
  • each of the five tree-mutating controls printed its control OK line
  • tree unchanged across every gate step (c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12).
  • attest: all 12 gate steps AND the shared-tree guard ran (13 of 13 reported success or failure), and every green gate wrote its own verdict and no other.

toolkit-self-gates (run 19651/jobs/0, 8 s), from its log:

  • g10: [check-self-bootstrap] floating-pin (release-toolkit#456): main matches main by construction
  • g11: gitea-twin: PASS - 11 twin(s) match their source.
  • g12: positive control OK: #1157 still refused (exit 1). The step name now shows in full, ending in #1157.
  • g13: [prep-order-check] PASS #1546 - no prepare commit on this PR / prep-order-check: PASS
  • g14: [rt tag-action-digest-check] PASS (rc=0): graded 1 distinct docs-pinned tag(s)
  • attest: all 14 gate steps AND the shared-tree guard ran (15 of 15 reported success or failure), and every green gate wrote its own verdict and no other.

Seen in passing, and not changed here: g7 and g9 print check: command not found. Their NOTE lines put `check` inside a double-quoted echo, so bash runs it as command substitution. The step still passes. This predates the PR, and the old per-workflow jobs had the same lines.

The release-prep exemption (g10) and the mutants are graded only by the bats arms and the emulator, as the body says. This run is an ordinary feature head.

CI on head `9968ed4b9e97e4b6b7e361261763520fec82a5d0`, read from the commit statuses and from the job logs (not just the statuses). **Context names.** 23 contexts posted, all `success`. The new one posted as exactly `tests / checks (pull_request)` (run 19650, job 1), so the swap list in the body is the right spelling. None of the 8 contexts to remove posted on this head, which confirms that `pull_request` workflows are read from the PR head here. Under the current protection this PR therefore waits on 8 required contexts that will never come, until the swap. **`tests / checks`** (run 19650/jobs/1, 8 s; `bats` 36 s), from its log: - checkout ran at full depth, and the replay passed: `this head already contains origin/main at 6b5d554...` - `validator: forgejo-runner version v12.8.2, sha256 verified` - `PARSED=36 TOTAL=36` / `PASS: 36/36 workflow files conform to the Forgejo Actions schema.` - `PASS: 4 fatal fixture(s) rejected, 3 safe fixture(s) accepted.` - `anchor-check: PASS - all selected in-document anchors resolve.`, plus both anchor controls OK - each of the five tree-mutating controls printed its `control OK` line - `tree unchanged across every gate step (c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12).` - `attest: all 12 gate steps AND the shared-tree guard ran (13 of 13 reported success or failure), and every green gate wrote its own verdict and no other.` **`toolkit-self-gates`** (run 19651/jobs/0, 8 s), from its log: - g10: `[check-self-bootstrap] floating-pin (release-toolkit#456): main matches main by construction` - g11: `gitea-twin: PASS - 11 twin(s) match their source.` - g12: `positive control OK: #1157 still refused (exit 1)`. The step name now shows in full, ending in `#1157`. - g13: `[prep-order-check] PASS #1546 - no prepare commit on this PR` / `prep-order-check: PASS` - g14: `[rt tag-action-digest-check] PASS (rc=0): graded 1 distinct docs-pinned tag(s)` - `attest: all 14 gate steps AND the shared-tree guard ran (15 of 15 reported success or failure), and every green gate wrote its own verdict and no other.` Seen in passing, and not changed here: g7 and g9 print `check: command not found`. Their NOTE lines put `` `check` `` inside a double-quoted `echo`, so bash runs it as command substitution. The step still passes. This predates the PR, and the old per-workflow jobs had the same lines. The release-prep exemption (g10) and the mutants are graded only by the bats arms and the emulator, as the body says. This run is an ordinary feature head.
claude left a comment

Verdict: APPROVE at 9968ed4b9e

Summary: the consolidation does what it says. Every moved gate body matches its source apart from the changes the carry-forward tables name. Both consolidated jobs ran green on the forge with attest at 13 of 13 and 15 of 15. The swap list matches main's live required set exactly. I found nothing blocking. Below are two small optional items and one thing to read after the merge.

Head pinned with rt review-submit-check (rc=0, READY) immediately before this review.

Swap list (read live just now)

main has 25 required contexts. All 8 to remove are present, and tests / checks (pull_request) is not yet required. After the swap there are 18. On this head, tests / checks (pull_request) posted with exactly that name (run 19650, job 1), and none of the 8 old contexts posted. Do the swap after approving, right before merging.

Verified at source

  • 3ea3e9f is transliteration only. A word-level diff of the five files gives 205 swaps: em-dash 155, warning emoji 18, red marker 15, key marker 13, § 2, ellipsis 1, check mark 1. Nothing else changed, and no ASCII-only token changed.
  • Moved gate bodies, 3ea3e9f to head. I diffed every step of the old workflow-schema, dated-examples, contract-paths, shellcheck, check-self-bootstrap, gitea-twin-check, prep-order-check and digest jobs against the new checks and gates steps (run, env, if, with). The only differences are the ones the tables name:
    • the verdict lines and rc==0 wrappers;
    • id: and the guard if:;
    • the EXIT-trap restores on c8-c12;
    • the tool preflight union;
    • ready=true and built=true;
    • the digest PASS label;
    • g12's quoted name;
    • fetch-depth: 0 on both checkouts;
    • g10's case exemption in place of the job-level if:.
      The nine #1253 gate steps are byte-identical.
  • Push halves. In check-self-bootstrap.yml and gitea-twin-check.yml, the only non-comment lines that change are deletions: the pull_request trigger, the PR-only replay step and the always-true job if:. The checkout, build and gate steps are untouched.
  • Carry-forward points. These match my plan-review points:
    • the release-prep/ exemption (g10 writes exempt, accepted only for EXEMPTABLE_GATE_IDS: g10);
    • full depth on both checkouts;
    • pull-requests: read plus a step-level FORGEJO_TOKEN on g12 and g13 only;
    • g14 has no env:;
    • edited is accepted;
    • digest's workflow_dispatch is retired with a reason.
  • No stale references. git grep at head finds none of the 8 contexts or prep-order-check.yml outside comments, the retirement note in cli-surface.md and the bats arm that asserts the file is gone. pre_push.go drops the four tests / rows and the gitea-twin-check row, adds tests / checks, and makes check-self-bootstrap claim nothing.
  • tests / checks on push: main. The only event-conditional step is the replay, which attest does not grade. So the job should behave the same on push, but that has not run yet (see below).

CI, read in job logs (Axis C)

  • tests / checks (run 19650, job 1, commit 9968ed4b9e): replay PASS against 6b5d554; preflight found python3 git shellcheck; validator v12.8.2, sha256 verified. Every gate passed:
    • c1: PASSED=36 TOTAL=36;
    • c2: 4 fatal and 3 safe fixtures;
    • c4/c8/c9: contract-paths and its two controls;
    • c5: 5 blocks;
    • c6: anchor gate, 0 unresolved;
    • c7 and c10-c12: controls, all OK.
      Then tree unchanged across every gate step (c1 ... c12), and attest reads 13 of 13. c3 shellcheck prints nothing, the same as main's own tests / shellcheck job on run 19638.
  • toolkit-self-gates (run 19651, job 0, commit 9968ed4b9e):
    • g10: floating-pin (release-toolkit#456): main matches main by construction;
    • g11: 11 twin(s) match;
    • g12: #1157 still refused (exit 1);
    • g13: prep-order-check: PASS over this PR's 4 commits;
    • g14: tag-action-digest-check: PASS (rc=0);
    • attest: 15 of 15.
  • g7/g9 check: command not found is pre-existing. It appears in the same two steps of #1544's toolkit-self-gates run (19639), whose workflows are main's.

Mutants (Axis B), in an LF clone at head, bats in rt-bats:local

The baseline -f '304|1540|1253' gives 8 of 8 ok.

  • A. g10's release-prep/*) changed to release-prepX/*): the #304 arm goes red.
  • B. The G12 outcome binding is removed from attest's env: only: the attest arm goes red.
  • C. EXEMPTABLE_GATE_IDS: g10 widened to g10 g13: survives, 8 of 8 ok. This is inert on its own, since only g10 ever writes exempt. But it is the one fail-open lever no arm pins. Optional: assert the exact value g10 in the #1540 arm, and '' for tests / checks.

On the unmeasured M2 (how the real runner renders steps.<deleted>.outcome): whatever it renders, attest fails closed. Only success and failure count as ran. Empty is COULD-NOT-GRADE, and every other value is NOT RUN. The only way it could pass is if a missing step rendered as success. The #1253 attest has relied on the same property since it landed.

Optional

  • mirror-release.yml ~289 is twinned (.gitea/workflows/mirror-release.yml) but not baked: it is not in canonicalFiles. A comment fix plus rt gitea-twin regeneration is small. Fine as a follow-up, but the body's "baked" reason is inaccurate.

After the merge

Read the first push: main run of tests / checks. It is the job's first run on push: the replay is skipped and everything else should be identical. Then measure the jobs per PR and the wall clock on the next PR, for the last AC.

Verdict: APPROVE at 9968ed4b9e97e4b6b7e361261763520fec82a5d0 Summary: the consolidation does what it says. Every moved gate body matches its source apart from the changes the carry-forward tables name. Both consolidated jobs ran green on the forge with attest at 13 of 13 and 15 of 15. The swap list matches main's live required set exactly. I found nothing blocking. Below are two small optional items and one thing to read after the merge. Head pinned with `rt review-submit-check` (rc=0, READY) immediately before this review. ## Swap list (read live just now) `main` has 25 required contexts. All 8 to remove are present, and `tests / checks (pull_request)` is not yet required. After the swap there are 18. On this head, `tests / checks (pull_request)` posted with exactly that name (run 19650, job 1), and none of the 8 old contexts posted. **Do the swap after approving, right before merging.** ## Verified at source - **`3ea3e9f` is transliteration only.** A word-level diff of the five files gives 205 swaps: em-dash 155, warning emoji 18, red marker 15, key marker 13, `§` 2, ellipsis 1, check mark 1. Nothing else changed, and no ASCII-only token changed. - **Moved gate bodies, `3ea3e9f` to head.** I diffed every step of the old `workflow-schema`, `dated-examples`, `contract-paths`, `shellcheck`, `check-self-bootstrap`, `gitea-twin-check`, `prep-order-check` and `digest` jobs against the new `checks` and `gates` steps (run, env, if, with). The only differences are the ones the tables name: - the verdict lines and `rc==0` wrappers; - `id:` and the guard `if:`; - the `EXIT`-trap restores on c8-c12; - the tool preflight union; - `ready=true` and `built=true`; - the digest PASS label; - g12's quoted name; - `fetch-depth: 0` on both checkouts; - g10's `case` exemption in place of the job-level `if:`. The nine #1253 gate steps are byte-identical. - **Push halves.** In `check-self-bootstrap.yml` and `gitea-twin-check.yml`, the only non-comment lines that change are deletions: the `pull_request` trigger, the PR-only replay step and the always-true job `if:`. The checkout, build and gate steps are untouched. - **Carry-forward points.** These match my plan-review points: - the `release-prep/` exemption (g10 writes `exempt`, accepted only for `EXEMPTABLE_GATE_IDS: g10`); - full depth on both checkouts; - `pull-requests: read` plus a step-level `FORGEJO_TOKEN` on g12 and g13 only; - g14 has no `env:`; - `edited` is accepted; - digest's `workflow_dispatch` is retired with a reason. - **No stale references.** `git grep` at head finds none of the 8 contexts or `prep-order-check.yml` outside comments, the retirement note in cli-surface.md and the bats arm that asserts the file is gone. `pre_push.go` drops the four `tests /` rows and the `gitea-twin-check` row, adds `tests / checks`, and makes `check-self-bootstrap` claim nothing. - **`tests / checks` on `push: main`.** The only event-conditional step is the replay, which attest does not grade. So the job should behave the same on push, but that has not run yet (see below). ## CI, read in job logs (Axis C) - **`tests / checks`** (run 19650, job 1, commit 9968ed4b9e): replay PASS against 6b5d554; preflight found `python3 git shellcheck`; validator v12.8.2, sha256 verified. Every gate passed: - c1: `PASSED=36 TOTAL=36`; - c2: 4 fatal and 3 safe fixtures; - c4/c8/c9: contract-paths and its two controls; - c5: 5 blocks; - c6: anchor gate, `0 unresolved`; - c7 and c10-c12: controls, all OK. Then `tree unchanged across every gate step (c1 ... c12)`, and attest reads `13 of 13`. c3 shellcheck prints nothing, the same as main's own `tests / shellcheck` job on run 19638. - **`toolkit-self-gates`** (run 19651, job 0, commit 9968ed4b9e): - g10: `floating-pin (release-toolkit#456): main matches main by construction`; - g11: `11 twin(s) match`; - g12: `#1157 still refused (exit 1)`; - g13: `prep-order-check: PASS` over this PR's 4 commits; - g14: `tag-action-digest-check: PASS (rc=0)`; - attest: `15 of 15`. - **g7/g9 `check: command not found` is pre-existing.** It appears in the same two steps of #1544's `toolkit-self-gates` run (19639), whose workflows are main's. ## Mutants (Axis B), in an LF clone at head, bats in `rt-bats:local` The baseline `-f '304|1540|1253'` gives 8 of 8 ok. - **A.** g10's `release-prep/*)` changed to `release-prepX/*)`: the #304 arm goes **red**. - **B.** The `G12` outcome binding is removed from attest's `env:` only: the attest arm goes **red**. - **C.** `EXEMPTABLE_GATE_IDS: g10` widened to `g10 g13`: **survives**, 8 of 8 ok. This is inert on its own, since only g10 ever writes `exempt`. But it is the one fail-open lever no arm pins. Optional: assert the exact value `g10` in the #1540 arm, and `''` for `tests / checks`. **On the unmeasured M2** (how the real runner renders `steps.<deleted>.outcome`): whatever it renders, attest fails closed. Only `success` and `failure` count as ran. Empty is COULD-NOT-GRADE, and every other value is NOT RUN. The only way it could pass is if a missing step rendered as `success`. The #1253 attest has relied on the same property since it landed. ## Optional - **`mirror-release.yml` ~289 is twinned** (`.gitea/workflows/mirror-release.yml`) **but not baked**: it is not in `canonicalFiles`. A comment fix plus `rt gitea-twin` regeneration is small. Fine as a follow-up, but the body's "baked" reason is inaccurate. ## After the merge Read the first `push: main` run of `tests / checks`. It is the job's first run on push: the replay is skipped and everything else should be identical. Then measure the jobs per PR and the wall clock on the next PR, for the last AC.
alex approved these changes 2026-10-04 11:49:51 +02:00
alex merged commit 9968ed4b9e into main 2026-10-04 11:52:11 +02:00
alex deleted branch claude/1540-fold-toolkit-self-jobs 2026-10-04 11:52:11 +02:00

Landing identity record

  • PR: #1546
  • landed commit (server merge_commit_sha): 9968ed4b9e97e4b6b7e361261763520fec82a5d0
  • effective official approval(s):
    • @alex, review #7223, stamped commit: 9968ed4b9e97e4b6b7e361261763520fec82a5d0
  • 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=9968ed4b9e97e4b6b7e361261763520fec82a5d0 --> ## Landing identity record - PR: #1546 - landed commit (server merge_commit_sha): `9968ed4b9e97e4b6b7e361261763520fec82a5d0` - effective official approval(s): - @alex, review #7223, stamped commit: `9968ed4b9e97e4b6b7e361261763520fec82a5d0` - 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!1546
No description provided.