chore(ci): fold four tests jobs into tests / checks and four PR checks into toolkit-self-gates #1546
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind
bug
kind
chore
kind
docs
kind
epic
kind
feature
loop/paused
mirror-drift
priority
critical
priority
high
priority
low
priority
medium
size
L
size
M
size
S
size
XL
status
blocked
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
frankenbit/release-toolkit!1546
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/1540-fold-toolkit-self-jobs"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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).
testsran 5 jobs (workflow-schema,bats,dated-examples,contract-paths,shellcheck).check-self-bootstrap / check,gitea-twin-check / check,prep-order-check / checkandreadme-pin-check / digestwere 4 more jobs on every PR.testsruns 2 jobs,batsandchecks. The four short jobs are 12 steps ofchecks.toolkit-self-gatesgrows from 9 gate steps to 14, picking up the PR halves of the other four.$RUNNER_TEMP/verdicts/<id>as its last command, and attest requires the exactGATE_IDSset.check-self-bootstrapandgitea-twin-checkdo not change. Neither doreadme-pin-check / mirrorandbase-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:
Add this 1:
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-pushreads the live required set, and this PR updates its tables to the set after the swap. Between the push and the swap, a localrt pre-pushreportstests / checksas a dead declaration and the 8 old names as undeclared. Once the swap is done,mainand the table agree.Decision tree
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), soreadme-pin-check / mirrorstays. Its own job also fits a trust boundary or a different event;base-divergence-checkand the push halves stay where they are.pull_request:trigger when the workflow keeps a push half:check-self-bootstrap.ymlandgitea-twin-check.yml. Their deadpull_request-only steps went too: the replay step, andcheck-self-bootstrap's job-levelif:, which was always true on push. Delete the file when the PR half was the whole workflow, as withprep-order-check.yml. Delete the job when the workflow keeps another job, as with thedigestjob inreadme-pin-check.yml.batsbe 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.$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.release-prep/exemption (#304) work? A step-levelif: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 recordsexempt. Attest acceptsexemptonly for ids inEXEMPTABLE_GATE_IDS(justg10).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 / checkscheckoutfetch-depth: 0is the deepest of the four. It is load-bearing fordated-examples(#926: a shallow clone gives an empty retired-script population and the check refuses, rc 2) and forcontract-paths(#903: provenance needs history). Both "DO NOT lower" comments are kept.workflow-schemaandshellcheckused depth 1 and do not care.replay onto the landing base (#1195)verify standard go runner toolspython3 git shellcheck. The #863 bats arm now executes thecheckspreflight with an empty PATH.fetch the pinned Forgejo Actions validatorready=true, which every gate's guard reads.run shellcheck on scripts/exit "$rc", so it writes its verdict only when rc is 0.EXITtrap. Before, each control had a private checkout and restored only on success.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.pyreads all ofdocs/, including both mutated files.FORGEJO_TOKEN: secrets.GITHUB_TOKENtoolkit-self-gates(#1253, #1323), with the genericGATE_IDSform.Into
toolkit-self-gatesif:skippingrelease-prep/heads (#304)release-prep/head it recordsexemptand does not call rt. Attest accepts that only forEXEMPTABLE_GATE_IDS: g10. The #304 rationale moved into g10's comment. The #304 bats arm now executes g10 against a stub rt.fetch-depth: 0,fetch-tags: truegatescheckout. The replay already unshallowed every PR checkout, so it adds no cost. Under@mainthe check exits before reading a tag. Under a tag pin it needs them (AGENTS.md section 2.3).build rt from THIS PR, replayrun check-self-bootstrap--check--checkwrites nothing to the tree.permissions: contents: read, pull-requests: readcontents: read.FORGEJO_BASE_URL,FORGEJO_TOKEN: secrets.GITHUB_TOKEN$RUNNER_TEMP/control.logtoprep-order-control.log, its own name in the shared temp dir. g12's step name is now quoted: its unquoted#1157was 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.types: [opened, synchronize, reopened]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 onedited. It adds no job.fetch release tags(git fetch --force --tags)env:, as the digest job had none.rt tag-action-digest-checkreads local tags throughgit show(cmd/rt/tag_action_digest_check.go). The no-token comment atreadme-pin-check.yml~105 belongs to themirrorstep, which stays where it is. The PASS label changed fromreadme-pin-check / digest:totag-action-digest-check:, because that context no longer exists.workflow_dispatchfor digestmirror. 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 bymirror-release.yml'sset-adopter-pinbefore it pushes the pin. A one-off check runs locally withrt tag-action-digest-check --doc README.md --doc docs/integration.md --doc examples.pull_requesttrigger, PR-only replay stepAlso in this PR
3ea3e9f, is a mechanical transliteration only: em-dash to-, the emoji markers to(!)/KEY:/NOTE:, and§tosection. The content changes read on their own after it.cmd/rt/pre_push.go. Updated to the post-swap required set.check-self-bootstrapno 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.cli-surface.md,integration.md, AGENTS.md section 2.3 and thecontract-paths-check.shheader now name where each verb runs.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 runinforgejo-ci-go:latest:0 issues.bats --print-output-on-failure tests/in dockerrt-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-runneris available to run a job. Soemulate_job.py(scratch, not committed) executes each job's real YAML steps:bash --noprofile --norc -eo pipefail, with per-step$GITHUB_OUTPUT, a shared$RUNNER_TEMP, andsteps.*.outcomesubstituted into env;if:forms these two jobs use, and refuses any other;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=1531standing in for this PR. Each mutant is a copy of the workflow file.make_mutants.pyasserts each one applied and prints its diff.tests / checksbaseline (pull_request)attest: all 12 gate steps AND the shared-tree guard ran (13 of 13 ...). JOB successNO VERDICT: steps.c4 succeeded but never wrote verdicts/c4. attest failure, JOB failureCOULD-NOT-GRADE: steps.c3.outcome did not resolve. attest failure, JOB failurefalse(a gate fails)13 of 13) and attest passed, so there is no double red. JOB failureleaked-by-c5.txtREFUSED: all 12 gates passed, so the tree was left dirty. tree guard failure, JOB failuretoolkit-self-gatesbaselineattest: all 14 gate steps AND the shared-tree guard ran (15 of 15 ...). JOB successrelease-prep/rollingEXEMPT: g10 recorded an exemption, which EXEMPTABLE_GATE_IDS allows. JOB successNO VERDICT: steps.g9 .... JOB failureCOULD-NOT-GRADE: steps.g11.outcome did not resolve. JOB failurefalseverdicts/g15UNEXPECTED VERDICT: verdicts/g15 names no gate in GATE_IDS. JOB failureThe 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.batsgives 11 of 11 ok. Each mutant was copied over a disposable copy of the clone, andgit diff --statshowed it landed.casegutted)runs [...] after writing its verdict)scripts/workflow-parse-check.shneedsforgejo-runner. Here it ran only inside the emulator, through the downloaded validator (c1 green). CI'sworkflow-parse-checkandtests / checksgrade 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 / checksand the growntoolkit-self-gateson 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 theeditedgates.Self-bootstrap (AGENTS.md section 2)
No re-pin: toolkit-self floats
@main(AGENTS.md section 2.1). Nothing here touches a reusable orgates.DefaultComposeScripts. Adopters see nothing: these are toolkit-self workflows only.rt check-self-bootstrapat this head: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:
toolkit-self-gates.ymland the 12 intests.ymlagainst their sources at6b5d554. The only intended differences are those in the carry-forward tables.status_check_contexts.tests / checksis the context name this PR's run posted (see the CI comment).🤖 Generated with Claude Code
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 exactlytests / 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 thatpull_requestworkflows 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;bats36 s), from its log:this head already contains origin/main at 6b5d554...validator: forgejo-runner version v12.8.2, sha256 verifiedPARSED=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 OKcontrol OKlinetree 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:[check-self-bootstrap] floating-pin (release-toolkit#456): main matches main by constructiongitea-twin: PASS - 11 twin(s) match their source.positive control OK: #1157 still refused (exit 1). The step name now shows in full, ending in#1157.[prep-order-check] PASS #1546 - no prepare commit on this PR/prep-order-check: PASS[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-quotedecho, 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.
Verdict: APPROVE at
9968ed4b9eSummary: 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)
mainhas 25 required contexts. All 8 to remove are present, andtests / 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
3ea3e9fis 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.3ea3e9fto head. I diffed every step of the oldworkflow-schema,dated-examples,contract-paths,shellcheck,check-self-bootstrap,gitea-twin-check,prep-order-checkanddigestjobs against the newchecksandgatessteps (run, env, if, with). The only differences are the ones the tables name:rc==0wrappers;id:and the guardif:;EXIT-trap restores on c8-c12;ready=trueandbuilt=true;fetch-depth: 0on both checkouts;caseexemption in place of the job-levelif:.The nine #1253 gate steps are byte-identical.
check-self-bootstrap.ymlandgitea-twin-check.yml, the only non-comment lines that change are deletions: thepull_requesttrigger, the PR-only replay step and the always-true jobif:. The checkout, build and gate steps are untouched.release-prep/exemption (g10 writesexempt, accepted only forEXEMPTABLE_GATE_IDS: g10);pull-requests: readplus a step-levelFORGEJO_TOKENon g12 and g13 only;env:;editedis accepted;workflow_dispatchis retired with a reason.git grepat head finds none of the 8 contexts orprep-order-check.ymloutside comments, the retirement note in cli-surface.md and the bats arm that asserts the file is gone.pre_push.godrops the fourtests /rows and thegitea-twin-checkrow, addstests / checks, and makescheck-self-bootstrapclaim nothing.tests / checksonpush: 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, commit9968ed4b9e): replay PASS against6b5d554; preflight foundpython3 git shellcheck; validator v12.8.2, sha256 verified. Every gate passed:PASSED=36 TOTAL=36;0 unresolved;Then
tree unchanged across every gate step (c1 ... c12), and attest reads13 of 13. c3 shellcheck prints nothing, the same as main's owntests / shellcheckjob on run 19638.toolkit-self-gates(run 19651, job 0, commit9968ed4b9e):floating-pin (release-toolkit#456): main matches main by construction;11 twin(s) match;#1157 still refused (exit 1);prep-order-check: PASSover this PR's 4 commits;tag-action-digest-check: PASS (rc=0);15 of 15.check: command not foundis pre-existing. It appears in the same two steps of #1544'stoolkit-self-gatesrun (19639), whose workflows are main's.Mutants (Axis B), in an LF clone at head, bats in
rt-bats:localThe baseline
-f '304|1540|1253'gives 8 of 8 ok.release-prep/*)changed torelease-prepX/*): the #304 arm goes red.G12outcome binding is removed from attest'senv:only: the attest arm goes red.EXEMPTABLE_GATE_IDS: g10widened tog10 g13: survives, 8 of 8 ok. This is inert on its own, since only g10 ever writesexempt. But it is the one fail-open lever no arm pins. Optional: assert the exact valueg10in the #1540 arm, and''fortests / checks.On the unmeasured M2 (how the real runner renders
steps.<deleted>.outcome): whatever it renders, attest fails closed. Onlysuccessandfailurecount 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 assuccess. 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 incanonicalFiles. A comment fix plusrt gitea-twinregeneration is small. Fine as a follow-up, but the body's "baked" reason is inaccurate.After the merge
Read the first
push: mainrun oftests / 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.Landing identity record
9968ed4b9e97e4b6b7e361261763520fec82a5d09968ed4b9e97e4b6b7e361261763520fec82a5d0This 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.