Skip to content

The Rust Engineering Handbook

Appendix Q — Edition, MSRV, SemVer, and Release Policy Templates

Adapt edition, compiler-support, compatibility, and release evidence to a Rust library, application, internal platform, or embedded product.

A release policy should let a maintainer answer a compatibility question without inventing policy during the release. It should say which language edition interprets each crate, which compilers the product supports, what downstream behavior counts as a contract, which evidence admits an artifact, and who can stop or recover a release.

Those answers differ by product shape. A library cannot control its users’ lockfiles. An application controls a deployed artifact but may have database and protocol migrations. An internal platform supports a population of consumer repositories. Embedded firmware couples compiler, target, linker, hardware, bootloader, and field-recovery assumptions. Copying one policy across all four creates impressive-looking text that fails at the first consequential decision.

Use this appendix as a policy-selection and adaptation artifact. The complete machine-readable example is in examples/rust-engineering-handbook/appendices/release-review-policy-pack/policy-pack.json; its verifier checks that all four policy families retain their required evidence and fail-closed disposition.

Select the policy by the thing that must remain compatible

Start with the released subject, not the Cargo package:

Product shape Primary compatibility subject Default evidence center Condition that reverses the default
public or partner library downstream source, types, features, behavior, and supported compiler downstream witness builds plus package audit a bundled application or generated protocol makes runtime artifacts the real subject
deployed application or service exact artifact, configuration, state, protocols, and rollback path locked build, migration rehearsal, staged rollout a separately consumed SDK or plugin interface also needs library-style compatibility
internal Rust platform toolchain train, templates, CI actions, approved components, and consumer support pilot consumers plus support-window ledger a regulated or safety-critical product requires its own qualification policy
embedded product firmware image, target, hardware, memory map, update protocol, and rescue path qualified toolchain/target bundle plus hardware evidence a reusable crate published independently also needs a library policy

A mixed workspace may need several policies. One repository-wide rust-version does not establish the compatibility of a public crate, a deployment tool, and firmware built with a specialized target. Name the subjects separately, even if one team owns them.

Keep four compatibility rails separate

Every policy in this appendix carries four rails. A release proceeds only when they converge on the same subject:

edition rail ───────── language rules, lints, migration configurations ───┐
MSRV rail ──────────── compiler floor, dependencies, targets, tools ──────┤
public-contract rail ─ API, behavior, features, protocols, operations ────┼─> classified change
resolved-artifact rail source, lock, build, package/image, digest ─────────┘           │
                                                                  admit, hold, or reject

An edition is a per-crate language choice. Crates from different editions are designed to interoperate, so an edition migration does not inherently require a package major version. It still needs configuration coverage because cargo fix --edition operates through compiler checks, cannot repair every concern, and sees only the selected targets and features.

An MSRV is a support promise, not the compiler on one developer’s machine. package.rust-version makes the floor machine-readable and lets Cargo report an unsupported invocation. It does not prove that every supported feature, target, dependency selection, build dependency, procedural macro, example, or documentation test works there.

SemVer is a label applied after compatibility analysis. Public items matter, but so do trait implementation space, generic bounds, auto traits, feature names and defaults, documented panic or cancellation behavior, macro syntax, and public dependency types. Runtime products add stored data, configuration, protocols, rollout, and operator contracts that package SemVer cannot fully describe.

The resolved artifact rail asks which source and dependency graph produced which bytes. A library package dry run, an application image, a platform template, and a firmware binary need different custody evidence. None can borrow a green result from another rail.

Library policy: preserve downstream choice

Copy this policy beside a public crate’s manifest and replace every bracketed field.

These clauses are drafting prompts, not release authority. A retained bracket, teaching value, unnamed role, or untested support claim means the policy is incomplete.

Compatibility subject

[crate-name] supports downstream source code that uses its documented public API under [supported features and targets]. The contract includes public item names and types, trait implementations and implementor permissions, documented behavior and errors, panic and safety contracts, feature names and defaults, intentionally public dependency types, and the MSRV rule below. Undocumented representation and implementation details are excluded unless another explicit contract exposes them.

Edition and migration

  • Each package declares edition = "[edition]"; a virtual workspace declares its resolver explicitly.
  • Edition migrations are reviewed as configuration-wide source changes, not bundled automatically with API redesign.
  • Run migration lints for all supported targets and feature sets. Inspect doctests, examples, generated code, macros, build scripts, and formatting separately.
  • The migration record retains the old and new manifests, compiler version, commands, applied diff, manual repairs, behavior and performance checks, and reviewer.
  • A migration may use a patch or minor release only when the existing package policy permits it and downstream witnesses show no contract change. Any intentional contract change is classified independently.

MSRV

  • The manifest declares rust-version = "[exact floor]".
  • [complete/verified/other precisely named] support means [targets, features, examples, docs, and tests actually covered].
  • The MSRV lane runs the exact compiler against both [committed/development] resolution evidence and the fresh dependency policy users can select.
  • Current stable runs formatting, lints, documentation, all supported targets, and the broad feature matrix. Beta or nightly lanes are advisory unless this policy names them as blocking.
  • An MSRV increase follows [major/minor/other] classification, [notice or support window], release-note and manifest updates, and identification of the last release line supporting the old floor.
  • A transitive dependency raising its compiler requirement is a compatibility event to investigate, not permission to revise the promise silently.

Cargo’s Rust-version-aware resolver can prefer compatible dependencies under the applicable resolver configuration. Treat that as selection assistance. Retain fresh-resolution evidence because package metadata can be absent, wrong, or irrelevant to an untested feature or target.

SemVer and release

  • Patch releases correct behavior within the documented contract and require no downstream migration.
  • Minor releases add backward-compatible capability or deprecation. New trait implementations, default features, bounds, variants, fields, associated items, macros, and public dependency types receive explicit compatibility review even when they look additive.
  • Major releases may require downstream source or behavior migration. The release notes provide witness programs, mechanical steps, deprecation history, and an overlap plan when maintained branches coexist.
  • For a pre-1.0 crate, state the exact Cargo-compatible versioning convention; “0.x has no compatibility” is not a policy.
  • Before publication, run the supported feature/target matrix, exact MSRV, current-stable checks, doctests, downstream witnesses, package-content inspection, license/security gates, and cargo publish --dry-run or the registry-equivalent dry run.
  • Only [role] may upload. Credentials are unavailable to ordinary test jobs. The uploaded package identity, checksum, source revision, changelog, and evidence record are retained.

Yanking prevents new dependency selection in ordinary registry resolution; it does not delete downloaded packages or rewrite existing lockfiles. Use it when continued new selection is harmful, then publish a corrective version and communicate the affected range. Never describe a yank as field rollback.

Application policy: preserve an operable artifact

An application policy centers the exact deployed artifact and its transitions.

Supported build and runtime

  • [application] builds from [source identity], a committed lockfile, [toolchain pin], and [target/base image/native toolchain].
  • The manifest’s rust-version states the compiler floor for supported local builds. Deployment qualification uses the complete pinned bundle, not rustc alone.
  • The supported runtime contract names configuration schema, secret sources, storage schema, protocols, feature selection, operating-system or container assumptions, resource bounds, startup/readiness behavior, shutdown, and observability.
  • Dependency refreshes and edition migrations are separate reviewable changes where practical. Both rebuild the full artifact and run migration, load, security, and rollback evidence.

Change and release classification

Package SemVer may label the binary, but operational compatibility controls rollout:

Change Required evidence Default disposition
source-only edition migration all configurations, generated code, behavior and benchmark comparison staged rollout if evidence is unchanged
compiler or dependency floor increase build-worker, operator, plugin, extension, and recovery-tool inventory hold until every required producer is migrated
configuration or protocol addition old/new reader and writer matrix; default behavior compatible only when mixed-version operation is proved
destructive schema or state change backup, forward migration, rollback or fix-forward decision, rehearsal separate migration gate
performance-affecting default representative baseline, variance, capacity and overload guardrails canary with automatic stop

The release record binds source, lockfile, toolchain, target, tests, SBOM/provenance as required, artifact digest, configuration and schema versions, rollout cohort, health criteria, operator, and recovery command. “CI passed” is not an artifact identity.

Roll back only when old code can safely read current state and protocol traffic. Otherwise define a tested fix-forward or compatibility bridge. A configuration kill switch must be exercised before it is listed as recovery. Retain the previous deployable artifact and its dependencies for the stated recovery window.

Internal-platform policy: preserve a supported consumer population

An internal platform might distribute toolchain files, base images, CI actions, templates, lints, approved dependency sets, build caches, and consultation. Its release is incomplete until consumer teams can adopt or remain on a supported train.

Train contract

  • Train [name] contains exact rustc/Cargo/components, host tools, target packages, base images, template revisions, and CI action identities.
  • Supported trains are [named trains]; each has an admission date, last security-maintenance date, retirement date, and owner.
  • Consumer repositories declare their selected train. Invisible “latest” resolution is prohibited for blocking build inputs.
  • Edition choice remains per package. The platform provides migration tooling and examples but does not rewrite consumer code without owner review.
  • The platform’s MSRV statement distinguishes what generated packages promise from what platform tooling itself requires.

Promotion and retirement

Promote a train through platform self-tests, representative pilot repositories, target and feature builds, cache isolation, template generation, rollback, support-runbook rehearsal, and consumer sign-off. The pilot set must include a public library, service, build-script or proc-macro user, and specialized target when the platform claims them.

Retirement requires a dated inventory of remaining consumers, upgrade instructions, known incompatibilities, exception owners, and escalation route. Exceptions have exact scope, compensating controls, expiry, and exit. An expired exception blocks retirement or blocks the unsupported consumer’s releases according to authority declared in advance; it must not disappear from a dashboard.

Rolling back a template or platform train cannot change artifacts already built or deployed. Query them by builder and material identity, then route product owners through their own recovery policies.

Embedded-product policy: preserve hardware and recovery contracts

For firmware, “works on Rust 1.85” omits most of the build and field contract.

Qualified bundle

  • Product [name] supports hardware revisions [list], target [triple/specification revision], rustc/Cargo [version], edition [edition], linker and binary tools [versions], bootloader [version], and memory map [revision].
  • rust-version records the crate floor. Qualification applies to the entire bundle; changing a target specification, LLVM behavior, linker, device crate, or generated register description can require requalification without changing Rust source.
  • Core, alloc, standard-library, panic, atomic, and floating-point assumptions are named per target. Nightly components or build-std workflows, when unavoidable, are pinned and labeled experimental product inputs.
  • The compatibility surface includes wire and storage formats, bootloader/image headers, interrupt and DMA assumptions, memory and timing budgets, hardware errata, panic/watchdog behavior, secure-boot/signing policy, update ordering, and physical or remote rescue.

Firmware admission

Every candidate retains source and generated-code identity, lockfile, compiler and component bundle, linker map and size budget, static analysis, host tests, target compile/link, hardware-in-loop behavior, power-loss and interrupted-update tests, protocol compatibility, image digest/signature evidence, and recovery rehearsal.

SemVer can label crates and firmware families, but hardware compatibility may need a separate tuple such as (board revision, bootloader generation, protocol generation, firmware version). State whether older bootloaders accept the image, whether new firmware can downgrade, and which state migrations are one-way.

A registry yank cannot recover flashed devices. The policy must choose A/B image rollback, rescue partition, wired recovery, device replacement, or an explicitly accepted absence of field recovery. The owner approving that risk must be named before shipment.

Work a change across all four rails

The fixture’s ledger-core council considers three changes in one proposed release:

  1. migrate Rust 2021 source to Rust 2024;
  2. raise MSRV from 1.85 to 1.88;
  3. add impl<T: Display> From<T> for RecordId.

The edition migration can remain source-compatible, but only after migration lints, generated code, doctests, targets, features, behavior, and formatting are inspected. It does not authorize the compiler-floor change.

The MSRV increase is blocked until the published library policy supplies its classification, user inventory or support window, notice, and last old-floor release. The fact that the current maintainer toolchain is newer is irrelevant.

The blanket conversion is rejected. It occupies implementation and inference space, can conflict with downstream implementations, and promises that arbitrary Display output is a valid identifier. A named fallible constructor or narrow implementation carries a more honest contract.

The candidate therefore remains on hold before version selection or upload. The machine verifier confirms that blocking change IDs resolve to retained records; it cannot judge the API argument or grant release authority.

Exercise: govern a mixed workspace migration

Type: migration and design. Level: review board.

Your workspace contains a public parsing library, a deployed ingestion service, an internal code generator, and firmware for two board revisions. The library promises Rust 1.85; the service builders use 1.97; the generator uses a newly stabilized API; one firmware target depends on a pinned nightly component. Leadership wants “Rust 2024 everywhere” and one version number within six weeks.

Produce four linked policy records. For each, state the compatibility subject, edition, MSRV meaning, dependency-resolution strategy, targets/features, SemVer or operational change classes, evidence gates, release authority, recovery mechanism, support window, exceptions, and archival record. Classify the generator and firmware constraints instead of hiding them behind the workspace toolchain.

Your deliverable must include:

  1. a dependency and toolchain matrix showing which subjects can migrate independently;
  2. at least two credible sequences, including the cost and failure mode of each;
  3. downstream witnesses for the library and mixed-version evidence for the service;
  4. a qualified-bundle and power-loss recovery plan for firmware;
  5. an explicit decision on whether one version number communicates useful compatibility;
  6. hold conditions, owners, dates, and exit evidence for every exception.

You may assume the four components have independent release owners and that no external legal or regulatory rule selects the answer. Use the release-review-policy-pack as the repository stage, but replace its teaching values rather than treating them as approved thresholds. Evaluation favors complete rail separation, reproducible evidence, credible alternatives, and recovery that matches the actual product shape.

Acceptable answers may choose different support windows. A strong answer refuses unsupported unification, preserves per-product authority, and defines evidence that another reviewer can reproduce.

Policy-diff card

When changing a policy, review the policy itself as a public contract:

  • Which released subject and users gain or lose support?
  • Did edition, MSRV, public contract, or artifact custody change—and were they classified separately?
  • Does the manifest agree with the prose, CI matrix, dependency resolution, and supported targets?
  • Can a supposedly additive trait, feature, default, bound, macro, or dependency-type change break a permitted user?
  • Are application state, protocols, configuration, rollout, and recovery versioned outside package SemVer where needed?
  • Does an internal platform name trains, consumers, retirement dates, exceptions, and support ownership?
  • Does firmware bind compiler, target, linker, hardware, budgets, update order, and rescue evidence?
  • Who can admit, hold, publish, deploy, yank, roll back, fix forward, retire support, and communicate?
  • Are credentials and signing authority separated from ordinary test jobs?
  • Can a future maintainer reconstruct source, dependencies, tools, artifact identity, decision, and known exclusions?

A declared policy becomes operational only when a reviewer applies it to a concrete change, traces the governing invariant, and refuses to average away a blocker. The next review artifact performs that job.

Sources and version notes

The policy fixture records Rust 2024, a teaching snapshot of Rust 1.97.0, and a teaching MSRV of 1.85. Revalidate the current stable toolchain, Cargo behavior, supported targets, registry policy, organization authority, and every product-specific value before adopting any policy.