Rust proxy phase 6: packaging (maturin wheel + lockstep release)¶
Part of the Rust SPICE proxy master plan. Phase 5
made the Python daemon supervise the Rust proxy, resolving the binary
via kerbside/proxy_supervisor.py::find_proxy_bin()
(KERBSIDE_PROXY_BIN env → shutil.which('kerbside-proxy') → the dev
build tree). Phase 6 makes pip install kerbside actually put
kerbside-proxy on PATH, so that shutil.which leg resolves in a
real deployment: the Rust crate is published as a separate
kerbside-proxy PyPI package built with maturin (bindings = "bin"),
and kerbside gains an exact-version pin on it, released in lockstep
from a single v* tag.
Prompt¶
Ground every decision in the code and the existing release machinery.
Read pyproject.toml (the kerbside package: setuptools +
setuptools_scm, the [project.scripts] entry points, the pinned
dependency list and the # END_OF_INDIRECT_DEPS sentinel used by the
pin-indirect-dependencies workflow), rust/kerbside-proxy/Cargo.toml
([package] version, the [[bin]] target, the ryll git dependency
pinned by rev), rust/kerbside-proxy/build.rs (it compiles
../../kerbside/rpc/kerbside.proto — a path outside the crate),
rust/kerbside-proxy/{Makefile,Dockerfile} (the Docker build wrapper and
why it mounts the whole repo), kerbside/proxy_supervisor.py
(find_proxy_bin() — the resolution order phase 6 must satisfy),
.github/workflows/{release.yml,rust.yml,pin-indirect-dependencies.yml},
and RELEASE-SETUP.md (PyPI trusted publishers + the release
environment). Verify how maturin bindings = "bin" lays the binary into
a wheel's scripts directory and how that lands on PATH after pip
install. Flag uncertainty explicitly rather than guessing.
Planning effort: medium — packaging is a well-established pattern,
but two wrinkles push individual steps to high effort: cross-compiling
the ring/rustls/ryll dependency chain to aarch64, and keeping two PyPI
packages version-locked from one setuptools_scm tag.
Repository and branch logistics¶
All work is in kerbside (the Rust crate, the new maturin project
files, the kerbside pyproject.toml, the CI workflows, and tools/
scripts); no other repo is involved. The ryll dependency is already a
merged, rev-pinned git dependency and does not change here. Phase 6
builds on phase 5, which is unmerged, so branch rust-proxy-phase-6
from the phase-5 tip (done); rebase onto develop once phases 2–5 land.
This plan file lives on the phase-6 branch.
Situation (verified 2026-07-08)¶
kerbsideis a setuptools +setuptools_scmpackage.pyproject.tomldeclaresdynamic = ["version"], derives the version fromgit describe --tags --match v*([tool.setuptools_scm],write_to = "kerbside/_version.py"), ships two console entry points (kerbside,kerbside-util), and carries an exhaustively pinned dependency list terminated by a# END_OF_INDIRECT_DEPSsentinel that thepin-indirect-dependenciesworkflowsed-appends to. There is no dependency onkerbside-proxytoday — that pin is net-new here.- The Rust crate is a plain binary crate, unversioned against git.
rust/kerbside-proxy/Cargo.tomlhard-codes[package] version = "0.1.0", defines a single[[bin]] name = "kerbside-proxy", and pinsshakenfist-spice-protocolto a git rev on the ryll repo (not a crates.io release — this matters for source builds). There is nopyproject.tomlin the crate and no maturin machinery anywhere. build.rsreaches outside the crate. It compiles../../kerbside/rpc/kerbside.proto(resolved relative to the crate dir), asserting the file exists and pointing at the Makefile, which mounts the repo root at/repoand sets the workdir to/repo/rust/kerbside-proxyprecisely so that../../kerbside/rpcresolves.protocis vendored (protoc-bin-vendored), so no system protobuf is needed;protocruns on the build host, not the target.find_proxy_bin()(phase 5) already expects an installed binary.kerbside/proxy_supervisor.pyresolves in order:KERBSIDE_PROXY_BINenv (authoritative — raises if set-but-invalid) →shutil.which( 'kerbside-proxy')→ the in-repo dev target dirs. Phase 6's entire job on the Python side is to make theshutil.whichleg succeed afterpip install kerbside, i.e. land the binary onPATH. No change tofind_proxy_bin()itself is required.- Release today publishes only
kerbside..github/workflows/ release.ymltriggers onv*tags, runs on[self-hosted, static]/[self-hosted, debian-12, static],python -m builds the sdist+wheel,twine checks it, signs the tag with Sigstore/gitsign, publishes to PyPI via a trusted publisher (OIDC, no tokens) gated on thereleaseenvironment (required reviewer), and cuts a GitHub Release.RELEASE-SETUP.mddocuments the singlekerbsidetrusted publisher. - Rust CI exists but builds no wheel.
.github/workflows/rust.ymlruns on[self-hosted, vm, debian-12](x86_64), installs the toolchain viadtolnay/rust-toolchain@stable, and runs fmt/clippy/test/cargo build --releasefromrust/kerbside-proxy. It never invokes maturin and never produces a distributable artifact. - Runners are x86_64. Both
rust.ymland the release jobs run on x86_64 self-hosted runners; there is no aarch64 runner in evidence. aarch64 wheels therefore require cross-compilation or emulation. - CLAUDE.md constraint: CI step bodies must stay ≤5 lines; anything
larger goes into a
tools/script the workflow calls. The pin-indirect-dependencies workflow's inlinesed/loop is the existing (grandfathered) exception; new logic here follows the rule.
Mission¶
kerbside-proxyis a maturinbindings = "bin"project whose wheel contains the compiledkerbside-proxybinary, laid into the wheel's*.data/scripts/directory sopip installputs it onPATHandfind_proxy_bin()'sshutil.whichleg resolves it.kerbsideexact-pinskerbside-proxyat the same version, sopip install kerbsidetransitively installs a matching proxy and the gRPC contract matches by construction.- Both packages release in lockstep from one
v*tag: the CI wheel build produces manylinux x86_64 and aarch64 wheels, and the release workflow publishes both PyPI projects under the existingrelease-environment approval gate. - Packaging breakage is caught on PRs, not at release:
rust.ymlgains a cheapmaturin buildsmoke step on the native runner.
Out of scope (later phases / future work): an sdist for kerbside-proxy
(operator chose wheels-only — see decision 1; the source-build path,
which would need a Rust toolchain and network access to the ryll git dep
plus making the crate self-contained for the proto, is future work);
musllinux wheels; making Rust the default proxy and removing the Python
proxy (phase 8); the direct-qemu CI lane on the Rust proxy (phase 7).
Design decisions¶
Decisions 1 (wheel matrix) and 2 (sdist policy) are settled with the operator. The rest are the planner's calls, open to revision at back brief.
1. Wheel matrix: manylinux x86_64 + aarch64 (settled). The operator
confirmed OpenStack Kolla / Kolla-Ansible CI runs on both x86_64 and
arm64, and that manylinux aarch64 wheels are sufficient for the arm64
CI workflows (aarch64 == arm64; a manylinux_*_aarch64 wheel installs on
Linux arm64). So the matrix is two targets. Since the runners are x86_64
only, the aarch64 wheel is cross-compiled:
- Primary approach: maturin + zig. maturin build --release
--target aarch64-unknown-linux-gnu --zig --compatibility
manylinux_2_28 (with rustup target add aarch64-unknown-linux-gnu
and pip install ziglang). zig supplies the cross linker and a
pinned-glibc sysroot so the manylinux_2_28 tag is honest, and it
builds on the x86_64 host with no emulation. The build-host protoc
is x86_64 (vendored) and runs on the host, so cross-compilation does
not touch it. The risk is the ring crypto provider (via
rustls feature ring) and the ryll git dependency cross-compiling
cleanly; ring supports aarch64 cross with a clang/zig toolchain, so
this is expected to work but must be proven (step 6c is high-effort
for exactly this reason).
- Documented fallback: QEMU-emulated manylinux container. If the
zig cross of the ring/ryll chain fights back, build the aarch64
wheel inside quay.io/pypa/manylinux_2_28_aarch64 under binfmt/QEMU
emulation (slower, but the canonical manylinux path). Recorded in
the tools script and the plan so the sub-agent can pivot without
re-planning.
2. sdist policy: wheels only, no sdist (settled). Publishing a
source distribution would let pip attempt a source build on an
unsupported platform — which needs a Rust toolchain and network
access to fetch the rev-pinned ryll git dependency at install time
(fragile, slow, confusing offline), and would require making the crate
self-contained for the proto (build.rs currently reaches out to
../../kerbside/rpc). Wheels-only means pip fails cleanly with "no
matching distribution" on an unsupported platform instead. This also
means the crate-self-containment / proto-relocation problem does not
block phase 6 — the CI wheel build runs from a full repo checkout
(like rust.yml today), so build.rs's ../../kerbside/rpc path
resolves and build.rs needs no change. (Making the crate
self-contained is recorded as the prerequisite for a future sdist.)
3. maturin bindings = "bin", version driven from Cargo.toml. Add
rust/kerbside-proxy/pyproject.toml with [build-system] requires =
["maturin>=1.7,<2"], build-backend = "maturin", [project] name =
"kerbside-proxy", requires-python = ">=3.9" (match kerbside),
dynamic = ["version"], and [tool.maturin] bindings = "bin". With
dynamic = ["version"] maturin reads the version from Cargo.toml
[package] version, giving a single mutable source of truth (the
Cargo version) that the lockstep step stamps. bindings = "bin" packages
the [[bin]] target into the wheel's scripts dir; the resulting wheel is
platform-tagged (it carries a compiled binary), which is why the matrix
exists.
4. Version lockstep: the v* tag is the single source of truth;
release stamps it into both packages. kerbside's version comes from
setuptools_scm (the tag). For the two packages to share a version and
for kerbside to exact-pin kerbside-proxy, a release-time
tools/stamp-proxy-version.sh derives the release version (from the tag
/ the setuptools_scm result) and writes it into (a) the crate's
Cargo.toml [package] version (which maturin then reports), and (b)
the kerbside-proxy==X.Y.Z pin in kerbside's pyproject.toml, before
either package is built. Both build jobs check out the same tag, so
setuptools_scm and the stamped Cargo version agree by construction.
- Committed-pin policy (settled during 6b): the source tree carries
no kerbside-proxy pin — only a # KERBSIDE_PROXY_PIN marker
comment in the dependency list — and the release inserts the exact
pin before the marker (mirroring the # END_OF_INDIRECT_DEPS
insertion idiom the pin-indirect-dependencies workflow uses). A
committed == pin was tried first and rejected: it makes tox -epy3
(and any pip install .) fail because it tries to install a
kerbside-proxy release that does not exist on PyPI yet. Omitting it
is also semantically right — a dev checkout resolves the proxy from
the build tree (or KERBSIDE_PROXY_BIN) via find_proxy_bin(), not
from this pin, so nothing in dev/CI needs the sibling package. The
stamp script is idempotent: if a pin is already present it replaces
the version, otherwise it inserts one. Considered and deferred: an
in-tree PEP 517 build-backend shim injecting
kerbside-proxy==<setuptools_scm version> dynamically (always-exact
in-tree, but more machinery than the repo currently carries).
- Publish order: publish kerbside-proxy before/with kerbside
so the exact pin resolves for anyone installing at release time.
(pip resolves at install, not publish, and both ship in one release,
but proxy-first is the safe ordering.)
5. CI shape: extend the existing workflows, don't fork the release.
The single v* trigger, the Sigstore tag signing, and the release
environment approval gate stay in release.yml; phase 6 adds to it a
matrix wheel-build job (target: [x86_64, aarch64]) that calls
tools/build-proxy-wheel.sh <target>, a stamp step calling
tools/stamp-proxy-version.sh, and a publish-proxy-pypi job that
publishes the proxy wheels to the separate kerbside-proxy PyPI project
via its own trusted publisher. rust.yml gains a native-only maturin
build smoke step so packaging regressions surface on every rust/** PR.
All non-trivial logic lives in tools/ scripts per the CLAUDE.md CI
rule.
Open questions (to settle during the phase)¶
- ~~Committed-pin policy~~ (decision 4): settled during 6b — no
committed pin (a
# KERBSIDE_PROXY_PINmarker only), inserted at release. A committed==pin was tried and rejected because it breakstox -epy3(the sibling is not on PyPI yet). - manylinux floor:
manylinux_2_28(glibc 2.28, EL8/Debian 10+; recommended, modern and matches the debian-12 build host) vs the oldermanylinux2014(glibc 2.17, widest reach). Kolla base images decide what is actually required — confirm against the Kolla-Ansible arm64 job image. - Whether the x86_64 wheel builds natively or also inside a manylinux
container for tag honesty. Native
--compatibility manylinux_2_28audits the glibc floor; a container guarantees it. Lean: native with--compatibilityfor x86_64, revisit ifauditwheel/maturin flags a symbol above the floor. - maturin invocation in Docker vs on the runner.
rust.ymlinstalls the toolchain directly on the self-hosted runner today; the operator's "Rust builds in Docker" preference is about the dev host, not CI runners. Lean: matchrust.yml(toolchain on the runner) for CI, keep a Docker path in thetools/script for local reproduction. - Second trusted publisher / PyPI project creation for
kerbside-proxy(aRELEASE-SETUP.mdaddition + a one-time operator action, like the pending-publisher note forkerbside).
Execution¶
One commit per logical change. Every Rust/maturin step is proven to
build a wheel (in Docker locally, matching the operator's preference, and
mirrored by the CI smoke step); every Python step passes
tox -eflake8/-epy3; pre-commit run --all-files before each commit.
| Step | Effort | Model | Isolation | Brief for sub-agent |
|---|---|---|---|---|
| 6a | high | opus | worktree | maturin bindings = "bin" project. Add rust/kerbside-proxy/pyproject.toml: [build-system] requires = ["maturin>=1.7,<2"], build-backend = "maturin"; [project] with name = "kerbside-proxy", description (reuse the Cargo.toml one), requires-python = ">=3.9", dynamic = ["version"], readme, license, [project.urls] (Homepage/Bug Tracker matching kerbside), and useful classifiers; [tool.maturin] bindings = "bin". Do NOT hand-set [project] version (maturin reads it from Cargo.toml [package] version under dynamic). Build a wheel in a venv/Docker (pip install maturin; maturin build --release from the crate dir, repo root available so build.rs's ../../kerbside/rpc resolves). Verify: the wheel is platform-tagged and contains kerbside-proxy under *.data/scripts/; pip installing it into a fresh venv puts kerbside-proxy on PATH such that shutil.which('kerbside-proxy') (the phase-5 find_proxy_bin leg) returns it. Add wheel/target artifacts to .gitignore. Do not change build.rs, Cargo.toml deps, or find_proxy_bin(). |
| 6b | high | opus | none | Version lockstep + the kerbside pin. Write tools/stamp-proxy-version.sh (>5 lines, so a real script): given a version (arg or derived via python -m setuptools_scm / git describe --tags --match 'v*', normalized to PEP 440), rewrite rust/kerbside-proxy/Cargo.toml [package] version = "X.Y.Z" and the kerbside-proxy==X.Y.Z pin line in kerbside/pyproject.toml, idempotently, with clear failure if either target is missing. Add the kerbside-proxy==<current dev version> pin to kerbside/pyproject.toml's dependencies (a clearly commented block, NOT inside the # END_OF_INDIRECT_DEPS indirect region the pin-workflow manages). Document the single-source-of-truth (the v* tag) and the committed-pin policy in a script header comment. Prove: running the script with a sample version updates both files and cargo metadata/maturin report it; tox -eflake8/-epy3 still pass (the pin must be a syntactically valid dependency even if kerbside-proxy is not yet on PyPI — installation resolution is a release-time concern). |
| 6c | high | opus | worktree | aarch64 cross-build (the tricky bit). Write tools/build-proxy-wheel.sh <x86_64\|aarch64> that builds a manylinux wheel for the given target. x86_64: native maturin build --release --compatibility manylinux_2_28. aarch64: rustup target add aarch64-unknown-linux-gnu, pip install ziglang, maturin build --release --target aarch64-unknown-linux-gnu --zig --compatibility manylinux_2_28. The script must run the repo-root-available build (like the Makefile) so build.rs resolves the proto, print the produced wheel path + its platform tag, and fail loudly if the tag is not manylinux. Prove the aarch64 wheel actually builds — this exercises cross-compiling ring/rustls/the ryll git dep, the one place this phase can genuinely break. If the zig cross fails on that chain after a real attempt, pivot to the documented fallback (build inside quay.io/pypa/manylinux_2_28_aarch64 under QEMU/binfmt) and record which path was used and why in a script comment + the Outcome. Do not weaken to a non-manylinux linux tag. |
| 6d | medium | opus | none | Release + PR CI wiring. Extend .github/workflows/release.yml: add a matrix job build-proxy-wheels (strategy.matrix.target: [x86_64, aarch64]) that checks out the tag with fetch-depth: 0, installs the Rust toolchain (mirror rust.yml's dtolnay/rust-toolchain@stable) + maturin, runs tools/stamp-proxy-version.sh then tools/build-proxy-wheel.sh ${{ matrix.target }}, and uploads each wheel as an artifact; and a publish-proxy-pypi job (environment: release, id-token: write) that downloads the proxy wheels and publishes them to the kerbside-proxy PyPI project via pypa/gh-action-pypi-publish (proxy publishes before/independently of the existing kerbside publish). Also add the stamp step to the existing build job so the kerbside wheel ships the exact pin. Keep every step body ≤5 lines (logic is in the tools/ scripts). Separately, extend .github/workflows/rust.yml with a native-only maturin build --release smoke step (after the existing Build (release) step) so packaging regressions surface on rust/** PRs. Update RELEASE-SETUP.md with the second trusted publisher (project kerbside-proxy, workflow release.yml, environment release) + a note on the two-package lockstep and pending-publisher setup. |
| 6e | medium | sonnet | none | Docs + Outcome. Update README.md, ARCHITECTURE.md, AGENTS.md, docs/proxy-architecture.md, and docs/configuration.md to describe the two-package split (kerbside pure-Python + kerbside-proxy maturin bin wheel), the exact-version lockstep pin, the wheels-only x86_64+aarch64 matrix, and how find_proxy_bin() resolves the wheel-installed binary on PATH. Note the sdist / musllinux / self-containment items as future work. Write the phase-6 Outcome section in this plan after the pre-push audit; flip the phase-6 row in PLAN-rust-proxy.md's Execution table and docs/plans/index.md to Complete. |
Sequencing: 6a is the foundation (the maturin project must exist before anything builds a wheel). 6b (version lockstep) and 6c (aarch64 build) both depend on 6a but are independent of each other and can proceed in parallel worktrees. 6d wires 6b+6c into CI. 6e is last. 6a and 6c use worktrees because they are build-experimental (wheel layout, cross toolchain); 6b and 6d are safe in the main tree.
Success criteria¶
maturin buildfromrust/kerbside-proxy/produces a platform-tagged wheel that carries thekerbside-proxybinary; installing it puts the binary onPATHandfind_proxy_bin()(phase 5) resolves it viashutil.whichwith noKERBSIDE_PROXY_BINoverride.- Both a manylinux x86_64 and a manylinux aarch64 wheel build successfully (aarch64 cross-compiled), each carrying a correct manylinux platform tag.
kerbside'spyproject.tomlexact-pinskerbside-proxy, andtools/stamp-proxy-version.shkeeps the crate version and the pin equal to a givenv*release version (single source of truth).release.ymlbuilds and publishes both PyPI packages from onev*tag under the existingrelease-environment approval;rust.ymlbuilds a wheel onrust/**PRs as a regression guard.- No sdist is published for
kerbside-proxy(wheels-only). - Rust
fmt/clippy -D warnings/teststill green; Pythonflake8/py3green;pre-commitclean; docs updated; pre-push audit clean.
Future work (recorded)¶
kerbside-proxysdist once the crate is self-contained (relocate or vendorkerbside.protointo the crate sobuild.rsneeds no out-of-crate path) and, ideally, onceshakenfist-spice-protocolis a crates.io release rather than a rev-pinned git dependency (so a source build needs no network git fetch).- musllinux wheels (Alpine-based deployments) and additional architectures beyond x86_64/aarch64.
- Dynamic in-tree pin via a PEP 517 build-backend shim (always-exact
kerbside-proxy==<setuptools_scm version>in the source tree) if the release-time stamp ever proves error-prone. - Phase 8 (cutover) flips
PROXY_IMPLEMENTATIONtorustby default and removes the Python proxy; the packaging here is what makes the Rust binary present for that flip.
Outcome¶
Completed 2026-07-08 on the kerbside rust-proxy-phase-6 branch,
unmerged and unpushed pending operator review. All planned steps landed;
each was proven by building real wheels, not just by inspection.
- 6a (
8915675): the maturinbindings = "bin"project (rust/kerbside-proxy/pyproject.toml+ a crateREADME.md), version read dynamically fromCargo.toml.build.rswas left untouched — wheels-only means the CI build always runs from a full repo checkout, so its../../kerbside/rpc/kerbside.protoreference resolves. Verified:maturin buildproduces a platform-tagged wheel carryingkerbside-proxyunder*.data/scripts/; installing it into a fresh venv puts the binary onPATHandshutil.which('kerbside-proxy')(phase 5'sfind_proxy_binleg) returns it. - 6b (
aba9bae):tools/stamp-proxy-version.sh— the single-source-of -truth lockstep. It stamps the release version into the crateCargo.tomland inserts the exactkerbside-proxy==X.Y.Zpin intokerbside'spyproject.toml(before a# KERBSIDE_PROXY_PINmarker; idempotently replacing an existing pin). Design decision 4 was revised during this step: a committed==pin was tried first and broketox -epy3(it resolves akerbside-proxyrelease that does not exist on PyPI yet), so the committed tree carries only the marker and the release inserts the pin. Verified: insert then replace paths, a dev version rejected, andtox -epy3green with the marker-only tree. - 6c (
1294d5e):tools/build-proxy-wheel.sh— a manylinux_2_28 wheel per architecture via maturin--zig(pinned-glibc sysroot; aarch64 cross-compiled from x86_64 with no emulation). Verified in therust:slim-bookwormcontainer: bothmanylinux_2_28_x86_64andmanylinux_2_28_aarch64wheels build; the aarch64 wheel carries a genuine ARM aarch64 ELF whose highest referenced symbol isGLIBC_2.28, so the tag is honest. The risky part — cross-compiling thering/rustls/aws-lc-rs/ryll dependency chain — worked with zig; the documented QEMU-container fallback was not needed. - 6d (
3e9769b): CI wiring.release.ymlnow stamps the pin in thekerbsidebuild (tag builds only), builds the proxy wheels in a[x86_64, aarch64]matrix job, and publishes them to the separatekerbside-proxyPyPI project in apublish-proxy-pypijob thatpublish-pypidepends on (so the proxy is on PyPI beforekerbside, which pins it).rust.ymlgained a cheapmaturin buildsmoke step as a per-PR packaging guard.RELEASE-SETUP.mddocuments the second trusted publisher and the lockstep model. - 6e (
6075931, this commit): docs (README.md,AGENTS.md,docs/proxy-architecture.md) describe the two-package split, and this Outcome + the status flips below.
Notable decisions, deliberate:
- Wheels-only was a genuine simplification, not just a smaller scope.
It removed the need to make the crate self-contained for an sdist (the
proto lives outside the crate); the CI wheel build sees the whole repo,
so
build.rsis unchanged. Making the crate self-contained (relocate the proto) and, ideally, a crates.io release ofshakenfist-spice-protocolare recorded as the prerequisites for a future sdist. - Both architectures build with
--zig, including x86_64. Using zig for the native build too pins the glibc floor at 2.28, so amanylinux_2_28tag is honest even though the CI/build host has a newer glibc — avoiding a manylinux container for x86_64. - The manylinux floor is
manylinux_2_28(glibc 2.28). The other open questions (native-vs-container x86_64 — resolved to native+zig; Docker-vs-runner maturin — CI uses the runner toolchain, local proof used Docker) landed on their planned leans.
Pre-push audit (2026-07-08)¶
Reviewed the phase-6 diff (git diff rust-proxy-phase-5..HEAD; 12 files,
all intended, no tracked build artifacts). Rust fmt/clippy/test are
untouched by this phase; pre-commit (flake8 + the 66-test py3 suite +
actionlint on the workflow YAML) is green. No blocking, high, or medium
findings.
- Injection:
stamp-proxy-version.shvalidates the version against^[0-9]+\.[0-9]+\.[0-9]+$before it reaches anysed, so no shell/sed metacharacters can be injected; both scripts areset -euo pipefailwith explicit argument validation and clear failure on missing files/markers. - Release integrity: publishing uses the existing OIDC trusted-
publisher model (no tokens); a second trusted publisher for the
kerbside-proxyproject is required (documented inRELEASE-SETUP.md). The job graph gates both publishes behindsign-tagand thereleaseenvironment, and orders the proxy publish beforekerbsideso the pin always resolves. - Correctness: the wheel-detection glob is architecture-specific
(
x86_64/aarch64are not substrings of each other), so a matrix leg cannot mistake the other arch's wheel; thebuildjob's incidental stamp ofCargo.toml(unused by the Python build) is harmless because each job checks out fresh and the proxy job stamps its own tree.
Observation (not a fix): each environment: release job triggers the
required-reviewer gate, so a release now has three gated jobs (sign-tag,
publish-proxy-pypi, publish-pypi) rather than two — the pre-existing
pattern, one more approval. Left as-is.
Back brief¶
Before executing any step, back brief the operator on the approach and its alignment with this plan and the master plan. The two operator decisions are settled: x86_64 + aarch64 manylinux wheels (arm64 wheels satisfy the Kolla-Ansible arm64 CI), and wheels only, no sdist (clean install failure on unsupported platforms over a fragile source build, and it sidesteps the proto-self-containment problem for now). The remaining choices — the committed-pin policy, the manylinux floor, native-vs-container x86_64 build, and Docker-vs-runner maturin invocation — are the planner's leans recorded under Open questions and are open to revision.