ci: add sealed Rust 1.99 beside 1.93 behind a toolchain selector #20

Merged
noel merged 3 commits from ci/runner-rust199 into main 2026-10-13 14:22:50 +00:00
Owner

Ready: the owner approved the Rust 1.99.0 move in full on 2026-10-13 ("just go"). The coordinator relayed it. Merged with a merge commit, so the image's source revision 34a2916ec7b0 stays reachable from main. Codewire's own move to Rust 1.99.0 still lands after its v0.3.388 release.

Outcome

An additive runner-image leaf: Rust 1.99.0 (with aarch64-unknown-linux-musl as a sixth target) beside the Rust 1.93.0 the image already has, behind one fixed toolchain selector. Each repository runs the toolchain it pins, so adopting the image changes nothing for a repository until it changes its own rust-toolchain.toml. This PR adds source only. It builds, publishes and adopts nothing.

File Purpose
Dockerfile.system-hz-rust199 One networkless RUN over the Rust 1.93 image: installs six checksum-pinned archives into /opt/infra-rust/1.99.0 and the selector into /opt/infra-rust/bin, first on PATH
system-hz-rust-select.sh The selector (52 lines of POSIX sh); the recipe pins its SHA-256
rust199-inputs.json The six archives; every URL and SHA-256 is from the official channel-rust-1.99.0.toml (2026-10-01, SHA-256 ce6dddc8…16b6a2)
test-system-hz-rust199.py Static recipe contract against the official manifest, and the selector against synthetic toolchains
verify-installed-system-hz-rust199.py Installed-image verifier, 49 commands
test-installed-system-hz-rust199.py Refusal tests for that verifier
README.system-hz-rust199.md Notes for this leaf

How selection works

The selector follows rustup's order: RUSTUP_TOOLCHAIN; else the channel of the nearest rust-toolchain.toml or rust-toolchain from the working directory upward; else the image default, 1.93.0. It runs /opt/infra-rust/<version>/bin/<tool> with that prefix first on PATH and RUSTUP_TOOLCHAIN set, so children stay on the same toolchain. An uninstalled toolchain, or a toolchain file that names no channel, exits 66. Nothing is installed at job time. rustup show active-toolchain and rustup target list --installed report the selected toolchain's own values (five targets for 1.93.0, six for 1.99.0); every other rustup operation still exits 64.

Decisions to review

  • Additive, on the Rust 1.93 image (sha256:37f659e6…), at the coordinator's direction, so there is no red window. The first commit on this branch (eeb7d98) built a replacing leaf on that image's parent; the second (34a2916) converts it; the third (29979d6) only corrects a size figure in the notes. A replacing image breaks Codewire main (it asserts 1.93.0) from adoption until Codewire's own change merges.
  • Cost: size. The image carries both toolchains, about 2.0 GB unpacked (9.83 GB against 7.82 GB; its archive is 0.49 GB larger) more than the Rust 1.93 image. Drop 1.93.0 with a later leaf on sha256:3208f9a7… once nothing pins it.
  • Selector in the image, not PATH edits per repository. The alternative (default PATH stays 1.93.0; each repository's own step adds /opt/infra-rust/<version>/bin) needs a change in every consumer, covers only the steps after that one, and would not let Codewire's assertions pass unchanged.
  • Default stays 1.93.0. A tool started outside any checkout, with no RUSTUP_TOOLCHAIN, gets 1.93.0, as today. A job that runs cargo from a scratch directory should export RUSTUP_TOOLCHAIN.
  • rust-lld. The recipe asserts the 1.99.0 toolchain's own rust-lld, and the verifier links a static arm64 musl probe with it, the way Codewire's guest build links.
  • No host controller. Infra's scripts/forgejo-runner-image (noel/infra#1372) owns the host side.
  • README.md is unchanged. The Rust 1.93 host controller freezes its SHA-256 and its test checks it, so the notes are in README.system-hz-rust199.md.

Both Codewire assertions pass unchanged

The verifier runs Codewire's assert-ci-runner-image.sh twice, each from a directory holding that revision's own rust-toolchain.toml, so the selector picks the toolchain exactly as a job in a Codewire checkout does:

Codewire revision Pin Assertion SHA-256 Expects
main at 049161a6b 1.93.0 61326164…565c 1.93.0 identities, five targets
chore/rust-upgrade at c9349f217 1.99.0 00ea0f8d…8023 1.99.0 identities, six targets

If either file changes before publication, its pin in the verifier must be updated and the image rebuilt.

Evidence

Workstation, 2026-10-12, at commit 34a2916ec7b0bc8ec4455205642d9e43579355b7 (the image's source revision; the branch head 29979d6 changes only README.system-hz-rust199.md).

Command Exit Result
python3 -I -B test-system-hz-rust199.py Dockerfile.system-hz-rust199 rust199-inputs.json channel-rust-1.99.0.toml 0 26 tests; 42 source mutations and 34 input mutations refused
RUST199_TEST_CUSTODY_SOURCE=… python3 -I -B test-installed-system-hz-rust199.py 0 15 tests
shellcheck -s sh system-hz-rust-select.sh 0 clean
Infra scripts/forgejo-runner-image dry-run (noel/infra#1372) 0 networkless, uncached build in 121.6 s; installed verification passed 49 of 49 commands in 13.2 s

Dry-run artifact: manifest sha256:e54a2c5df3a30adc900715efabd336991b214d7338988f27be53f65a8926e31a, config sha256:5c95d5489f5849b56acdf62ddbaf447e994cc89202fe2317a45af305cba156fc, archive SHA-256 0f00a3f20977e80663b05562de750277cdd38f42647692ff5208423d380b87a3 (2,466,947,072 bytes). It is local only. The build is not reproducible, so that archive is the artifact to publish; a rebuild has a new digest.

Not run: test-system-hz-rust193.py and the Rust 1.93 host tests. This PR changes none of their inputs.

Dependencies and order

  1. This PR merges with a merge commit, so 34a2916e stays reachable as the image's source revision.
  2. noel/infra#1372 (the runner image command), then noel/infra#1373 (adoption).
  3. Nothing is red after adoption: Codewire main, its open branches and Infra keep selecting 1.93.0. Codewire's Rust 1.99.0 PR can then merge whenever it is ready.

Owner gates

  • Publishing the image to registry.noel.sh/oci-approved/act-node-docker with robot$oci-promoter.
  • Merging Infra's adoption PR, which changes the image under every repository's jobs.
Ready: the owner approved the Rust 1.99.0 move in full on 2026-10-13 ("just go"). The coordinator relayed it. Merged with a merge commit, so the image's source revision `34a2916ec7b0` stays reachable from main. Codewire's own move to Rust 1.99.0 still lands after its v0.3.388 release. ## Outcome An additive runner-image leaf: Rust 1.99.0 (with `aarch64-unknown-linux-musl` as a sixth target) beside the Rust 1.93.0 the image already has, behind one fixed toolchain selector. Each repository runs the toolchain it pins, so adopting the image changes nothing for a repository until it changes its own `rust-toolchain.toml`. This PR adds source only. It builds, publishes and adopts nothing. | File | Purpose | |---|---| | `Dockerfile.system-hz-rust199` | One networkless `RUN` over the Rust 1.93 image: installs six checksum-pinned archives into `/opt/infra-rust/1.99.0` and the selector into `/opt/infra-rust/bin`, first on `PATH` | | `system-hz-rust-select.sh` | The selector (52 lines of POSIX sh); the recipe pins its SHA-256 | | `rust199-inputs.json` | The six archives; every URL and SHA-256 is from the official `channel-rust-1.99.0.toml` (2026-10-01, SHA-256 `ce6dddc8…16b6a2`) | | `test-system-hz-rust199.py` | Static recipe contract against the official manifest, and the selector against synthetic toolchains | | `verify-installed-system-hz-rust199.py` | Installed-image verifier, 49 commands | | `test-installed-system-hz-rust199.py` | Refusal tests for that verifier | | `README.system-hz-rust199.md` | Notes for this leaf | ## How selection works The selector follows rustup's order: `RUSTUP_TOOLCHAIN`; else the `channel` of the nearest `rust-toolchain.toml` or `rust-toolchain` from the working directory upward; else the image default, 1.93.0. It runs `/opt/infra-rust/<version>/bin/<tool>` with that prefix first on `PATH` and `RUSTUP_TOOLCHAIN` set, so children stay on the same toolchain. An uninstalled toolchain, or a toolchain file that names no channel, exits 66. Nothing is installed at job time. `rustup show active-toolchain` and `rustup target list --installed` report the selected toolchain's own values (five targets for 1.93.0, six for 1.99.0); every other `rustup` operation still exits 64. ## Decisions to review - **Additive, on the Rust 1.93 image** (`sha256:37f659e6…`), at the coordinator's direction, so there is no red window. The first commit on this branch (`eeb7d98`) built a replacing leaf on that image's parent; the second (`34a2916`) converts it; the third (`29979d6`) only corrects a size figure in the notes. A replacing image breaks Codewire `main` (it asserts 1.93.0) from adoption until Codewire's own change merges. - **Cost: size.** The image carries both toolchains, about 2.0 GB unpacked (9.83 GB against 7.82 GB; its archive is 0.49 GB larger) more than the Rust 1.93 image. Drop 1.93.0 with a later leaf on `sha256:3208f9a7…` once nothing pins it. - **Selector in the image, not `PATH` edits per repository.** The alternative (default `PATH` stays 1.93.0; each repository's own step adds `/opt/infra-rust/<version>/bin`) needs a change in every consumer, covers only the steps after that one, and would not let Codewire's assertions pass unchanged. - **Default stays 1.93.0.** A tool started outside any checkout, with no `RUSTUP_TOOLCHAIN`, gets 1.93.0, as today. A job that runs `cargo` from a scratch directory should export `RUSTUP_TOOLCHAIN`. - **`rust-lld`.** The recipe asserts the 1.99.0 toolchain's own `rust-lld`, and the verifier links a static arm64 musl probe with it, the way Codewire's guest build links. - **No host controller.** Infra's `scripts/forgejo-runner-image` (noel/infra#1372) owns the host side. - **`README.md` is unchanged.** The Rust 1.93 host controller freezes its SHA-256 and its test checks it, so the notes are in `README.system-hz-rust199.md`. ## Both Codewire assertions pass unchanged The verifier runs Codewire's `assert-ci-runner-image.sh` twice, each from a directory holding that revision's own `rust-toolchain.toml`, so the selector picks the toolchain exactly as a job in a Codewire checkout does: | Codewire revision | Pin | Assertion SHA-256 | Expects | |---|---|---|---| | `main` at `049161a6b` | 1.93.0 | `61326164…565c` | 1.93.0 identities, five targets | | `chore/rust-upgrade` at `c9349f217` | 1.99.0 | `00ea0f8d…8023` | 1.99.0 identities, six targets | If either file changes before publication, its pin in the verifier must be updated and the image rebuilt. ## Evidence Workstation, 2026-10-12, at commit `34a2916ec7b0bc8ec4455205642d9e43579355b7` (the image's source revision; the branch head `29979d6` changes only `README.system-hz-rust199.md`). | Command | Exit | Result | |---|---|---| | `python3 -I -B test-system-hz-rust199.py Dockerfile.system-hz-rust199 rust199-inputs.json channel-rust-1.99.0.toml` | 0 | 26 tests; 42 source mutations and 34 input mutations refused | | `RUST199_TEST_CUSTODY_SOURCE=… python3 -I -B test-installed-system-hz-rust199.py` | 0 | 15 tests | | `shellcheck -s sh system-hz-rust-select.sh` | 0 | clean | | Infra `scripts/forgejo-runner-image dry-run` (noel/infra#1372) | 0 | networkless, uncached build in 121.6 s; installed verification passed 49 of 49 commands in 13.2 s | Dry-run artifact: manifest `sha256:e54a2c5df3a30adc900715efabd336991b214d7338988f27be53f65a8926e31a`, config `sha256:5c95d5489f5849b56acdf62ddbaf447e994cc89202fe2317a45af305cba156fc`, archive SHA-256 `0f00a3f20977e80663b05562de750277cdd38f42647692ff5208423d380b87a3` (2,466,947,072 bytes). It is local only. The build is not reproducible, so that archive is the artifact to publish; a rebuild has a new digest. Not run: `test-system-hz-rust193.py` and the Rust 1.93 host tests. This PR changes none of their inputs. ## Dependencies and order 1. This PR merges with a merge commit, so `34a2916e` stays reachable as the image's source revision. 2. noel/infra#1372 (the runner image command), then noel/infra#1373 (adoption). 3. Nothing is red after adoption: Codewire `main`, its open branches and Infra keep selecting 1.93.0. Codewire's Rust 1.99.0 PR can then merge whenever it is ready. ## Owner gates - Publishing the image to `registry.noel.sh/oci-approved/act-node-docker` with `robot$oci-promoter`. - Merging Infra's adoption PR, which changes the image under every repository's jobs.
Codewire moves to Rust 1.99.0 (b940084d7 2026-09-28, released
2026-10-01) and needs aarch64-unknown-linux-musl in the runner's sealed
target set, so its static arm64 guest build can later leave a pinned
container.

Dockerfile.system-hz-rust199 installs the six checksum-pinned archives
of rust199-inputs.json into /opt/infra-rust/1.99.0. Every URL and
SHA-256 comes from the official channel-rust-1.99.0.toml. The recipe
also asserts the toolchain's own rust-lld, which links the arm64 musl
target.

The leaf builds on the parent of the Rust 1.93 leaf
(sha256:3208f9a7), not on that leaf. A toolchain leaf replaces the
previous one; layering would keep 1.7 GB of unused 1.93 toolchain in
this image and every later one.

The installed verifier runs 34 commands: the 33 of the Rust 1.93
verifier plus a static arm64 musl probe linked with rust-lld. Its
consumer pins are Codewire's sealed assertion with six targets and
Infra's current R2 lifecycle test with 17 mock plans.

Infra's runner image command owns the host side of installed
verification, so this leaf adds no host controller. README.md stays
byte-identical because the Rust 1.93 host controller freezes its
checksum; the leaf's notes are in README.system-hz-rust199.md.
The first Rust 1.99 leaf replaced the toolchain: it built on the parent
of the Rust 1.93 image. Adopting it would break every job that pins
1.93.0 (Codewire main asserts it) until that repository's own change
merged.

The leaf now builds on the Rust 1.93 image and keeps its
/opt/infra-rust/1.93.0 unchanged. It adds /opt/infra-rust/1.99.0 and
puts /opt/infra-rust/bin first on PATH, where every tool name of either
toolchain links to system-hz-rust-select.sh.

The selector follows rustup's order: RUSTUP_TOOLCHAIN, else the nearest
rust-toolchain.toml or rust-toolchain from the working directory
upward, else the image default, 1.93.0. It runs the selected prefix's
tool with that prefix first on PATH and RUSTUP_TOOLCHAIN set, so
children stay on the same toolchain. A toolchain that is not installed,
or a toolchain file that names no channel, exits 66; nothing is
installed at job time. Each prefix keeps its own fixed rustup
interface, so the target list is five for 1.93.0 and six for 1.99.0.

The recipe pins the selector's SHA-256 and checks the inherited 1.93.0
identities, target list and read-only mode before and after. The
installed verifier now runs 49 commands. It runs Codewire's unchanged
assertion from main (1.93.0, five targets) and from its Rust 1.99
branch (1.99.0, six targets), each from a directory holding that
revision's own rust-toolchain.toml, and requires each compiled probe to
carry its compiler's identity.

The cost is size: two toolchains instead of one. A later leaf on the
image without /opt/infra-rust drops 1.93.0 once nothing pins it.
The built image is 9.83 GB unpacked against 7.82 GB for the Rust 1.93
image, and its archive is 0.49 GB larger. The earlier figure was the
size of the 1.93 toolchain alone. No recipe input changes.
noel changed title from WIP: DO NOT MERGE — ci: add sealed Rust 1.99 runner leaf to WIP: DO NOT MERGE — ci: add sealed Rust 1.99 beside 1.93 behind a toolchain selector 2026-10-12 13:26:50 +00:00
noel changed title from WIP: DO NOT MERGE — ci: add sealed Rust 1.99 beside 1.93 behind a toolchain selector to ci: add sealed Rust 1.99 beside 1.93 behind a toolchain selector 2026-10-13 14:22:43 +00:00
noel merged commit 5b25d93d4b into main 2026-10-13 14:22:49 +00:00
Sign in to join this conversation.
No reviewers
No labels
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
noel/act-node-docker!20
No description provided.