# Native Rust release candidate rail The native candidate rail creates local release-candidate evidence. It does not tag, upload, publish, or approve publication. Delivery is a separate operator step that accepts only the exact retained candidate. Two architectures ship, x86_64 and aarch64, each built and proven natively on a host of that architecture. The rail takes its architecture from the build host (`uname -m`); `RELEASE_ARCH` may name it explicitly and must equal the host. Nothing is cross-built, and candidate creation and proof resume refuse any other architecture. Each architecture's candidate is a separate run with its own payload, checksum file, manifest, and proofs. Four evidence activities remain deliberately separate: 1. The offline release-manifest validator checks a named manifest or an exact five-file payload directory. 2. The candidate transaction builds tar, Debian, and RPM artifacts from one committed immutable context and atomically promotes the five-file payload. 3. Three package-bound proofs install and verify the Debian, RPM, and tar artifacts in separate network-disabled environments. 4. The live FLAC checkpoint exercises the observer on a desktop after candidate proof. It is not part of `candidate-proven`. ## Operator preconditions Use a clean checkout at the exact release commit. The compiler authority is `rust-toolchain.toml`; Cargo operations use `Cargo.lock`. The stock bases are pinned per architecture in the `Makefile` (`UBUNTU_STOCK_BASE_`, `FEDORA_STOCK_BASE_`), with the manifest lists they were pulled from. Regenerate all four Ubuntu and Fedora release images from the locally pinned stock bases of the host's architecture, without pulling, then inspect their bare local image IDs: ```bash make release-images podman image inspect --format '{{.Id}}' localhost/solstone-linux-build-ubuntu podman image inspect --format '{{.Id}}' localhost/solstone-linux-build-fedora podman image inspect --format '{{.Id}}' localhost/solstone-linux-proof-ubuntu podman image inspect --format '{{.Id}}' localhost/solstone-linux-proof-fedora ``` Commit those IDs as `sha256:` in the matching build and proof roles of the host architecture's table (`[x86_64]` or `[aarch64]`) in `packaging/release-policy.toml`, and its proof observations in that table's proof tables. A value of `unpinned` marks an architecture not yet observed on a native host; the rail refuses it by name. Rebuild the build-tool images when `Cargo.lock`; dependency-affecting workspace or member manifests; `rust-toolchain.toml`; the Rust, cargo-deb, or cargo-generate-rpm pins; the stock base references; or `packaging/Containerfile.tools` changes. The Ubuntu image carries Cargo state warmed for the committed lockfile. The Fedora image deliberately carries no warmed Cargo registry because its offline metadata and RPM generation path does not require one. Rebuild the proof images when their stock base references, runtime dependency closure, or `packaging/Containerfile.tools` changes. Every `make release-images` run uses `--no-cache`, produces new local image IDs, and requires all four observed IDs to be re-pinned. Locally regenerated images are not guaranteed to be bit-for-bit reproducible: distro repositories, rustup distribution content, and tool build environments can change while declared versions remain the same. Prove each freshly regenerated image offline before committing its observed ID. Re-read and repin IDs only after `make release-images` exits successfully; after a partial failure, rerun the complete target before pinning any image. The `proof_debian`, `proof_rpm`, and `proof_tar` roles use clean OS images carrying only the runtime dependency closure, with no compiler, Rust toolchain, Cargo, or other build tooling. Stock bases cannot install or execute the dynamically linked observer because it requires GLib, GStreamer, and PulseAudio libraries. Debian and RPM proofs install into the disposable proof container's live root, so their declared dependencies are genuinely enforced; the tar proof retains its dedicated `/proof-root`. Keep all four images backing the five policy roles present locally before transaction entry. For each proof image, observe and commit the exact normalized OS release, package-manager output, install argv, version argv, executable path, and executable mode. Commit those values before selecting `EXPECTED_RELEASE_COMMIT`. The proof producer observes the same values inside the selected image and fails closed when committed policy is wrong. The transaction never pulls an image. Acquire the advisory database outside the transaction. It must be a clean local Git worktree, including no untracked or ignored changes. Write a strict descriptor with exactly these private operator inputs: ```json { "schema_version": 1, "source_id": "privacy-safe cohort identifier", "db_path": "/absolute/canonical/path/to/local/advisory-db", "acquired_at": "2026-07-21T00:00:00Z" } ``` The descriptor and database paths never enter public candidate evidence. Candidate creation requires acquisition within 24 hours. Proof resume validates the retained database identity without reapplying that freshness window. ## Candidate commands Create the complete candidate and all three proofs: ```bash make release-candidate \ EXPECTED_RELEASE_COMMIT= \ ADVISORY_DESCRIPTOR= ``` `make release` delegates to this same target and requires the same variables. There is no second native release state machine. Resume missing proofs without rebuilding or changing valid retained evidence: ```bash make release-candidate-prove \ VERSION= \ ADVISORY_DESCRIPTOR= ``` Read-only recovery validation requires no advisory descriptor: ```bash make release-candidate-recover VERSION= ``` All mutating candidate commands use `dist/.rust-release-candidate.lock`. Commands do not auto-clear a stale lock. After confirming no candidate process is running, the operator may remove that exact lock file and retry. Do not remove any broader directory as lock recovery. ## Outputs and meanings The unsigned promoted payload is exactly, for x86_64: - `dist/rust/solstone-linux--linux-x86_64.tar.gz` - `dist/rust/solstone-linux_-1_amd64.deb` - `dist/rust/solstone-linux--1.x86_64.rpm` - `dist/rust/SHA256SUMS` - `dist/rust/solstone-linux--linux-x86_64.rust-release-manifest.json` and for aarch64: - `dist/rust/solstone-linux--linux-aarch64.tar.gz` - `dist/rust/solstone-linux_-1_arm64.deb` - `dist/rust/solstone-linux--1.aarch64.rpm` - `dist/rust/SHA256SUMS-aarch64` - `dist/rust/solstone-linux--linux-aarch64.rust-release-manifest.json` Both sets publish into one version directory, so no filename is shared; only the x86_64 checksum file keeps the bare `SHA256SUMS` name. Before delivery, sign that manifest with the dedicated Linux identity. This writes `dist/rust/solstone-linux--linux-.rust-release-manifest.json.minisig`; the signed candidate therefore has six files. Verification checks the pinned public key, manifest, checksums, and all three artifacts together: ```bash make sign-release-manifest \ RELEASE_DIR=dist/rust \ RELEASE_MINISIGN_KEY=/absolute/path/to/solstone-linux-release-1.key \ RELEASE_MINISIGN_PASS=/absolute/path/to/solstone-linux-release-1.pass make verify-release-signature RELEASE_DIR=dist/rust ``` The detached signature is a step the verifier takes; `apt` and `dnf` do not verify it automatically. Retained evidence is versioned and disjoint from the payload: - `dist/rust-evidence//ledger.json` - `dist/rust-evidence//proof-runner` - `dist/rust-evidence//proofs/debian-amd64.json` - `dist/rust-evidence//proofs/rpm-x86_64.json` - `dist/rust-evidence//proofs/tar-x86_64.json` On aarch64 the proofs are `debian-arm64.json`, `rpm-aarch64.json`, and `tar-aarch64.json`. The ledger records the target triple, and every later check reads the architecture from it. `candidate-proven` means all local payload, ledger, policy, image, source, and three package-install/version proofs validated together. `retained-candidate-valid` means the retained candidate validates read-only in the matching clean checkout. Both are local evidence statuses. Neither is publication approval, and neither includes the live FLAC checkpoint. The Debian and RPM proofs install only their bound local package. The tar proof runs both installer dry-run and isolated-prefix installation. All three validate the installed executable path, mode, hash, and exact version output with networking disabled. Candidate creation builds one proof runner in the pinned Ubuntu build environment and retains it with the evidence. Initial proof and proof resume both use the runner at that reserved path after revalidating it as a no-follow regular executable, so resume does not depend on the invoking host binary or a toolchain inside a proof image. The runner bytes are not bound into the candidate ledger; treat the retained candidate directory as trusted between creation and resume. ## Validator Run the offline fixture and schema gate: ```bash make check-rust-release-manifest ``` Validate a named manifest or classify an exact payload: ```bash make check-rust-release-manifest MANIFEST=dist/rust/solstone-linux--linux-.rust-release-manifest.json make check-rust-release-manifest RELEASE_DIR=dist/rust ``` Named-manifest validation does not imply candidate readiness. Directory classification requires exactly five regular files of one architecture and rejects stale or extra entries. The manifest's target triple, its rustc host, its filename, and every artifact name must name the same architecture, and the package audit requires that architecture's ELF machine and dynamic loader. Release roots must be real confined directories. The portable, Debian, and RPM packages must contain the same executable bytes. ## Non-candidate drift helper The individual lane helper is deliberately outside candidate state: ```bash bash scripts/build-release.sh deb bash scripts/build-release.sh rpm ``` It builds for the host architecture and refuses any other. It writes only `dist/rust-drift/`, labels its output as drift evidence, and cannot create or replace the candidate payload or readiness evidence. Drift output is not retained candidate evidence. ## Blocking live FLAC checkpoint After `candidate-proven`, install the relevant candidate artifact on a test Linux desktop with the runtime dependencies in `packaging/INSTALL-NOTES`. Run the packaged observer long enough to produce a new audio segment, confirm the observer remains alive, and validate that exact segment with `flac -t` (including each split mono FLAC when applicable). An encoder crash or decode failure blocks release handling. `flac -t` proves the stream decodes. It does not prove the stream carries anything. A file whose entire system channel is digital silence passes `flac -t` with `ok` and exit 0 — which is exactly how a build that recorded no system audio at all reached owners and stayed there for nine days. **Decode integrity is not content evidence.** So drive both legs during the window and then measure them separately. Play audio through the default sink and speak into the microphone while the segment is open, then assert that neither channel is silent: ```bash ffmpeg -v quiet -i /audio.flac -f s16le -ac 2 -ar 16000 - \ | python3 -c 'import array,math,sys a=array.array("h"); a.frombytes(sys.stdin.buffer.read()) for name,ch in (("mic",a[0::2]),("system",a[1::2])): peak=max(abs(v) for v in ch)/32768.0 print(f"{name} peak={peak:.6f}") assert peak > 0.001, f"{name} channel is silent"' ``` A silent channel blocks release handling exactly as a decode failure does. For a muted segment the split mono files are checked the same way, one channel each. If a leg is legitimately expected to be silent on the test desktop, say which and why in the release evidence — do not skip the measurement. This live checkpoint is separate operator evidence. It does not modify the ledger, proofs, bundle digest, or candidate status. ## Delivery Delivery has two destinations and they are not equals. The release origin, `https://updates.solstone.app`, is where the release is published, and where the install documentation points once the first `release` publish lands. GitHub Releases is an optional mirror that carries the tag, the release notes, and a copy of the same bytes. ### The release origin After `candidate-proven`, retained-candidate recovery, the live FLAC checkpoint, and the product release gates pass, publish the exact signed candidate: ```bash make publish-origin LANE=release RELEASE_DIR=dist/rust ``` Objects land at `https://updates.solstone.app/solstone-linux///`, and `/latest` (one line, `version=`, the pointer the documentation tells people to read) is written only after every file in the set is published. Each architecture publishes its own six files into the same version directory. `latest` is shared by both, so it moves to a newer version only once every architecture published under the current version also has a complete set, verified by the publisher, under the new one. Publishing one architecture first holds `latest` until the other lands. The published key is at `https://updates.solstone.app/solstone-linux/minisign.pub` and is the same key this repository pins at `packaging/keys/solstone-linux-release.pub`. Nothing else places that object, so the publisher owns it: it writes the key when the origin has none, and refuses if the two ever disagree. Lanes are exactly `release`, `staging`, and `dev`. Versioned objects on `release` and `staging` are immutable: the publisher refuses to overwrite one, republishing identical bytes is a no-op, and adding a missing filename under an existing version is allowed. `dev` may be overwritten. `latest` never moves backwards. No R2 bucket lock rule covers these prefixes today (`wrangler r2 bucket lock list solstone-updates` reports none), so the store enforces none of that and `wrangler r2 object put` overwrites silently. The publisher enforces it instead, by reading what is already there before it writes. The origin publish never contacts GitHub and does not require `gh`. A GitHub outage cannot delay or fail it. Before publishing, the publisher runs the same verification the documentation asks a reader to run: the exact file set, the manifest signature under the pinned key, and every declared digest. The `release` lane adds a clean checkout at the manifest's exact source commit, agreement with the shipping package version, and the repository's own release-model validation. The `staging` and `dev` proof lanes deliberately do not require the publishing checkout to sit at the candidate's commit; that is what lets a retained candidate be republished for proof without re-cutting it. ### The GitHub mirror ```bash make publish-release RELEASE_DIR=dist/rust ``` Run this after the origin publish, never before it. It requires a clean checkout at the manifest's exact source commit, re-runs read-only candidate recovery, and binds the workspace version, changelog, annotated tag, release metadata, and all public files to that commit. It creates or resumes one draft, uploads only missing files, downloads every existing file to compare its SHA-256 with the retained candidate, and publishes only a complete exact set. An exact published release is an idempotent success. Any differing tag, metadata, or file is a hard stop. Never move, replace, delete, or retarget release state; byte changes require a new version. The second architecture's run attaches its own six files to the release the first published, adding only names it owns. GitHub does not build, validate, approve, or define the release. There is no GitHub workflow or repository-setting gate. Skipping the mirror leaves a complete, correct release. From the first `release` publish onward, the install documentation names the origin. ## Host and advisory gates `make ci` produces host evidence: formatting, lint, tests, shell checks, and the offline licenses/bans/sources policy. It does not run target package proofs or the live FLAC checkpoint. `make audit` verifies a signed, local advisory packet and never refreshes or contacts an advisory database. Supply exactly four operator inputs: ```bash make audit \ BUNDLE= \ RECEIPT= \ PUBKEY= \ LOCATOR= ``` The detached signature must be adjacent to the receipt as `.minisig`; it is derived, not supplied separately. The command pins the approved public-key digest and key ID, authenticates the receipt and its 24-hour freshness window, verifies and materializes only the local Git bundle, and runs cargo-deny 0.20.2 locked and offline. The locator is cargo-deny database identity only and is never contacted. Success is one JSON object naming product `solstone-linux`, source cohort `sol-controlled-rustsec-mirror-v1`, the signed commit and receipt time, Cargo.lock digest, cargo-deny version, and `pass` verdict. Audit staging is removed before that witness is printed. The advisory database is materialized and scanned only within isolated staging; no advisory data is fetched into or written to the ambient Cargo home. Packet inputs, tracked source, candidates, proofs, signatures, tags, uploads, and runtime state are not modified. A failed named gate prints one safe repair; replace the local packet component identified by that gate and retry. Candidate creation remains separate: it consumes its explicitly acquired descriptor cohort and leaves the repository `deny.toml` unchanged. ## Release transparency Each changed release gets a v2 transparency record after origin publication, followed by an independent audit. Records are published at [trust.solstone.app](https://trust.solstone.app/). The v2 record is produced outside this repository; a late or failed record never holds app delivery. ### Dormant v1 publisher The in-repo v1 publisher is retired. `make publish-transparency` and `make resign-transparency-pointer` exit 2 unless `TRANSPARENCY_ACTIVATED=1`, and that flag does not by itself authorize a publish. Do not override that guard during a release. The rest of this section describes the dormant v1 publisher. When active, publication runs after delivery with `make publish-transparency RELEASE_DIR=`. This env-driven, retryable step never gates delivery; see the retained candidate paths above rather than reconstructing its inventory. Minisign is a development prerequisite. Version keys are one-shot and permanent. The final `latest.json` body PUT is the commit boundary: before it the pointer body is old and afterward it is new. The signature-first write can briefly produce a pointer/signature mismatch; consumers retry that recognized transient. The archive retains the candidate artifact bytes alongside the release evidence; the public transparency surface carries evidence only, never artifact bytes. If an entry was uploaded before its pointer, retry the same command; if the version was permanently recorded against a superseded chain head, cut the next version. `publish-transparency` reads exactly these operator settings: - `TRANSPARENCY_BASE_URL` — public HTTPS base URL; defaults to `https://transparency.solstone.app`. - `TRANSPARENCY_S3_ENDPOINT` — HTTPS endpoint for the S3-compatible transparency store. - `TRANSPARENCY_BUCKET` — bucket containing the transparency objects. - `TRANSPARENCY_S3_ACCESS_KEY_ID` — access-key identifier used for S3 SigV4 authentication. - `TRANSPARENCY_S3_SECRET_ACCESS_KEY` — secret access key supplied to curl over stdin, never in its argument vector. - `TRANSPARENCY_MINISIGN_KEY` — local path to the encrypted transparency signing key. - `TRANSPARENCY_MINISIGN_PUB` — local path to the corresponding public trust anchor. - `TRANSPARENCY_ARCHIVE_CHANNEL` — archive command invoked with the staging directory as its sole argument; required for publication. The command must emit an `ARCHIVED ` receipt as the last line of stdout, which the publisher verifies against the manifest SHA-256. - `TRANSPARENCY_GENESIS` — first-publication approval; only the literal value `1` enables genesis. The retained evidence directory is derived from the validated release manifest, not supplied separately. For release directory `` and manifest version ``, publication reads `/../rust-evidence//ledger.json` and `/../rust-evidence//proofs/{debian-amd64,rpm-x86_64,tar-x86_64}.json` (on aarch64, `{debian-arm64,rpm-aarch64,tar-aarch64}.json`). It rejects evidence whose version or source commit does not bind to the release manifest. For the first publication into a confirmed empty transparency bucket, verify that `transparency-head-log.jsonl` has no recorded sequence, set `TRANSPARENCY_GENESIS=1`, and run `make publish-transparency RELEASE_DIR=`. Genesis additionally rejects any existing object under the product's version prefix. After genesis succeeds, unset `TRANSPARENCY_GENESIS`; later publications derive their predecessor exclusively from the verified signed chain. If the bucket appears empty while the local head log records a sequence, stop and restore or select the correct bucket rather than starting a new genesis. Verifiers fetch the public trust anchor at `releases/keys/solpbc-transparency-1.pub`; key rotation increments the numeric suffix and publishes cross-signed successor files. `TRANSPARENCY_MINISIGN_PUB` remains the operator-supplied local path to that trust anchor. `make resign-transparency-pointer` is the freeze defense. It first verifies the signed pointer, signed tip, product binding, and rollback protection against the [transparency head log](transparency-head-log.jsonl), then renews only the pointer signature and validity. It never re-attests a rolled-back or foreign pointer. The surface attests what was released, that it is immutable, and that history is publicly reconstructible — not that binaries provably match source. Publication is operator-approved.