Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Release and evolution policy

This policy applies after the protocol 1.0 freeze audit. It keeps Cargo package evolution, wire compatibility, feature selection, unsafe code, dependency selection, and target support aligned.

Version dimensions

There are two separate version axes:

  • Cargo crate versions describe the Rust API and package graph.
  • Protocol versions describe driver/device wire compatibility and conformance artifacts.

The two axes advance independently. A Cargo patch, minor, or major change does not by itself change the wire protocol, and a wire protocol change must follow the protocol classification below even when the Rust crate version is still pre-1.0. A public protocol 1.0 release needs a matching release note and a frozen conformance/v1.0 directory; it does not require a 1.0.0 Cargo version.

Cargo version posture

The workspace publishes at 0.1.x while carrying the frozen protocol 1.0 baseline. This is deliberate rather than an unreconciled gap:

  • The Cargo version tracks the Rust API and package graph, which is young and expected to change as backend, guest, device, and transport adapter authors build against it.
  • The protocol version tracks wire compatibility, which is frozen by the v1.0 freeze audit and governed by the classification table below.

A pre-1.0 Cargo version is therefore the accurate signal on both axes, and is not a statement about protocol stability. Consumers who need the stable artifact should depend on protocol 1.0 and its conformance directory, not on a Cargo version number.

Moving the workspace to 1.0.0 is a separate, later decision. It requires the public Rust API to have real downstream users and an explicit semver promise recorded in a release note. Until then, breaking Rust API changes ship as 0.x minor bumps under the classification table below.

Change classification examples

ChangeCargo classificationProtocol classificationRequired evidence
Fix rustdoc, examples, comments, non-normative rationale, or tests without changing accepted/emitted bytesPatchErratum or no protocol changeCI plus updated docs when relevant
Add a new helper type or trait method with a default implementation that preserves existing behaviorMinor while pre-1.0/public policy permits it; otherwise semver-compatible minorNo protocol changeAPI review and downstream compile coverage
Remove, rename, or change the meaning of a public Rust item used by backend, guest, device, or transport authorsCargo majorNo protocol change unless wire behavior also changesMigration note and affected-crate review
Raise MSRV or remove a supported portable targetCargo minor only if release notes document it and no public semver promise forbids it; otherwise Cargo majorNo protocol changeTarget/MSRV rationale and CI matrix update
Add a platform adapter crate that depends on portable crates but is not a default dependencyAdditive Cargo minorNo protocol changeDependency-policy and portability review
Add a default feature that selects Linux, macOS, Windows, VMM, kernel, vendor SDK, filesystem, socket, thread, or runtime behavior in a portable crateForbiddenForbidden unless it is a negotiated protocol feature and isolated from portable defaultsMust be redesigned
Assign a reserved feature bit, opcode, status, flag, field, or capability with negotiated behavior and unchanged 1.0 framesCargo minor or major depending on Rust API impactProtocol minor with a new conformance directoryNormative docs, feature negotiation tests, vectors, scenarios, and clean-room coverage
Append fields to an existing response a 1.0 driver can receive without a negotiated featureForbidden in protocol 1.xProtocol major if requiredNew major-version directory
Change an assigned opcode value, structure size, field meaning, status success/failure interpretation, ownership rule, reset rule, or existing payload lengthCargo major if Rust API also changesProtocol majorNew normative documents and conformance directory

Wire evolution

Protocol 1.0 freezes the assigned values, exact payload lengths, ownership rules, and golden bytes in conformance/v1.0. Unknown fields are not a baseline extension mechanism: protocol 1.0 receivers validate exact payload lengths and reject trailing bytes unless a negotiated feature explicitly selects a different layout. Unknown opcodes remain unsupported without side effects, unknown request or object flags are rejected before backend invocation, unknown response statuses are opaque failures, and unknown event states require recovery rather than being guessed terminal.

New behavior should prefer one of these forms, in order:

  1. A new opcode with exact request and response payloads.
  2. A previously reserved feature bit that gates all changed behavior.
  3. A previously reserved value whose semantics are specified in full before it is advertised.
  4. A new protocol major version when compatibility cannot be preserved.

Reserved values are invalid until assigned by a later policy. A constant that records a reserved number is not permission for a device to advertise it or a driver to accept it.

Cargo feature policy

Cargo features are additive. Disabling a default feature may remove convenience code but must not select a different protocol interpretation. Enabling a feature must not make a portable crate depend on an operating system, VMM, kernel, guest-memory library, vendor SDK, filesystem, socket, thread, global runtime, or platform synchronization primitive.

Platform integrations must live in adapter crates that depend inward on the portable layers. They must not become default dependencies of virtio-accel-core, virtio-accel-proto, virtio-accel-transport, virtio-accel-device, virtio-accel-guest, virtio-accel-split-queue, or the facade crate.

MSRV and supported targets

The minimum supported Rust version is the workspace rust-version. A change to MSRV requires a release-note entry, a CI matrix update, and an explanation of why the old compiler cannot preserve the current API or implementation invariants.

The supported portable target set is the one documented in docs/portability.md and enforced by CI. Removing a target or moving a crate to a less-portable runtime tier requires a release-note entry and an explicit portability review. Adding a platform adapter cannot reduce the portability tier of an existing crate.

Unsafe-code policy

Project-authored code in portable crates, reference crates, and fuzz harness support code forbids or denies unsafe code at the crate root. This is an intentional v1 invariant, not incidental linting. There are six reviewed, confined exceptions. The host-native virtio-accel-coreml adapter’s Rust FFI and aligned-allocation code is documented in crates/virtio-accel-coreml/SAFETY.md; non-macOS builds still forbid unsafe code. The host-native virtio-accel-openvino adapter’s OpenVINO C API FFI and aligned-allocation code is documented in crates/virtio-accel-openvino/SAFETY.md; builds without a detected OpenVINO runtime still forbid unsafe code. virtio-accel-tosa denies unsafe code globally but locally permits its private, checked-in official FlatBuffers bindings after bounded verification; the boundary and regeneration procedure are documented in crates/virtio-accel-tosa/SAFETY.md. The host-native virtio-accel-hexagon adapter’s QNN C API boundary is documented in crates/virtio-accel-hexagon/SAFETY.md; builds without a detected QNN runtime still forbid unsafe code. The host-native virtio-accel-xdna adapter’s HRX C ABI, persistent mappings, and dispatch ownership are documented in crates/virtio-accel-xdna/SAFETY.md; builds without a detected HRX runtime still forbid unsafe code. The host-native virtio-accel-vulkan adapter’s ash entry-point inventory, persistent mappings, submission ring, and device-loss handling are documented in crates/virtio-accel-vulkan/SAFETY.md; builds forced to the placeholder (or on a host outside the Vulkan loader target set) still forbid unsafe code.

A future unsafe exception requires all of the following in one reviewed change:

  • the crate-level forbid(unsafe_code) removal or replacement is explicit;
  • every unsafe block has a local safety comment naming the invariant it relies on;
  • the release review records why a safe abstraction, zerocopy validation, ownership token, or adapter boundary could not preserve the invariant;
  • tests or conformance evidence exercise the unsafe boundary; and
  • the public API does not transfer unsafe obligations to downstream users unless those obligations are documented on the item that requires them.

Dependency and license policy

Workspace dependencies must be centralized in [workspace.dependencies] unless there is a narrow crate-local reason to diverge. Normal dependencies for portable crates should use minimal features and default-features = false when the dependency supports it.

Dependency review must check:

  • cargo-deny advisories, yanked crates, duplicate versions, wildcard requirements, unknown sources, and licenses;
  • whether a build dependency leaks std or alloc into a target graph;
  • whether a proc macro or helper crate is build-host-only or runtime-visible;
  • whether a dependency introduces platform defaults; and
  • whether its license remains inside the workspace allowlist.

The workspace license is MIT OR Apache-2.0. Every published manifest inherits license, rust-version, repository, homepage, keywords, and categories from [workspace.package], declares its own description and readme, and carries its own byte-identical copies of LICENSE-MIT and LICENSE-APACHE. Cargo only packages files inside a package directory, so the root license files do not reach the sub-crate tarballs; the copies exist for that reason and are copies rather than symlinks because CI runs windows-latest.

ci/check-release-policy.py enforces all of this against an explicit eighteen-crate allowlist. A new package fails that check until it is added to the allowlist, which forces a decision about whether it is public rather than letting it default either way. The check also validates the crates.io keyword and category limits, which neither cargo package nor cargo publish --dry-run catches before an upload is attempted.

The fuzz/ harness is a separate workspace at version 0.0.0 and stays publish = false.

Publication, yank, and rollback

Eighteen packages are published to crates.io. Publication is ordered: a crate cannot be published before every crate it depends on, and that includes development dependencies, because a published crate’s versioned dev-dependencies must resolve from the registry for cargo test to run on the packaged source.

#CrateNormal dependenciesDevelopment dependencies
1virtio-accel-transport——
2virtio-accel-cleanroom——
3virtio-accel-proto—cleanroom
4virtio-accel-coretransport—
5virtio-accel-tosacore, FlatBuffers—
6virtio-accel-tosa-buildtosa, FlatBuffers—
7virtio-accel-split-queuetransport—
8virtio-accel-guestproto, transportsplit-queue
9virtio-accel-mockcore—
10virtio-accel-devicecore, proto, transportmock
11virtio-accel-conformancecoremock, tosa
12virtio-accel-vaccelcoreconformance
13virtio-accel-xdnacore, tosaconformance, tosa-build
14virtio-accel-coremlcore, tosaconformance
15virtio-accel-openvinocore, tosaconformance
16virtio-accel-hexagoncore, tosaconformance
17virtio-accel-vulkancore, tosaconformance
18virtio-accelthe six runtime cratesconformance, mock, cleanroom

This order is executable, not just documentary: ci/publish-dry-run.py walks it against an isolated local registry, adding each crate only after it has been built, tested, and documented from its own extracted tarball. A crate can therefore only ever resolve its predecessors, so a wrong order fails with an unresolvable dependency instead of passing quietly. The same script is a required CI job.

All eighteen packages share the workspace version and are published together. A GitHub release tag must be exactly v<workspace-version> and must point at the commit being released. Publishing a GitHub release triggers .github/workflows/publish.yml, which checks out that tag, runs the release policy checks and publication-driver tests, repeats the full ordered local-registry dry run on the tagged source, and only then gives ci/publish.py the crates.io token from the CRATES_IO_KEY repository secret. The job has read-only GitHub permissions and serializes releases so two publication sequences cannot overlap. The tagged commit must be reachable from the repository’s default branch; a release cannot use an unmerged tag to run token-bearing repository code.

The production publisher uses the same order from ci/publication.py. Before each upload, it builds the actual .crate archive and queries crates.io. An existing version is skipped only when the registry checksum exactly matches the local archive. This makes a rerun safe after a partial publish or an upload whose result was ambiguous, while refusing to bless a tag whose immutable crates.io version contains different bytes. The upload itself names the crates-io registry explicitly and uses Cargo’s --no-verify mode: all compilation and tests have already completed in the prior step, so package build scripts never execute in the step that holds the crates.io token.

cargo package’s own verify step is not sufficient and must not be treated as sufficient: it builds only the library target. That is how four cross-package include_str! sites reached outside their package directories unnoticed, leaving assertions that could never have compiled from a published tarball. Any check on packaged output must run the tests inside the packaged source.

When a mid-order publish fails

crates.io publication is not transactional across crates. If crate N fails after 1..N-1 succeeded, those earlier versions are live and permanent.

  1. Stop. Do not publish the remaining crates by hand.
  2. Inspect crates.io before rerunning. The automated job may be rerun unchanged only when every version that appeared has the exact checksum of the tagged source; it will enforce this check.
  3. Diagnose against the local registry, not against crates.io. Reproduce with ci/publish-dry-run.py.
  4. If any source or packaging metadata must change, fix forward: bump the lockstep workspace patch version, create the matching tag, and publish a new GitHub release. Versions already accepted by crates.io are immutable and cannot be replaced.
  5. Yank only if a published version is actively harmful — see below. A version that is merely stranded, because its dependents were never published, is not harmful; it is unreachable.

Yank versus patch

A crates.io version is immutable. It cannot be edited, replaced, or deleted, and its contents remain downloadable even after a yank. Publishing is therefore a one-way action, and a mistaken publish is corrected by publishing again, never by trying to undo.

Yanking only stops new resolution: existing Cargo.lock files continue to resolve a yanked version, so a yank is not a security control and never a substitute for an advisory.

Publish a patch, and do not yank, when:

  • the defect is a bug, a missing file, or wrong metadata that a newer version supersedes;
  • the version is stranded but harmless; or
  • downstream users are better served by upgrading than by a broken resolution.

Yank, in addition to publishing a patch, when:

  • the version is a security risk to anyone who resolves it — coordinate with SECURITY.md and publish an advisory, since the yank alone protects nobody;
  • it claims a protocol conformance it does not have, so a driver or device could interoperate incorrectly on the wire; or
  • it was published in error and has no valid use, such as a wrong version number or a crate published out of order with an unsatisfiable dependency.

Never un-yank to “restore” a version that was yanked for a wire-compatibility or security reason. Publish a new version instead.

Rollback

There is no rollback. The recovery path for every publication mistake is a new version, in the same documented order, with a release-note entry recording what happened and why. If a protocol-affecting defect ships, the classification table above governs whether the fix is an erratum, a protocol minor extension, or a new protocol major version with its own conformance directory — a security fix is not exempt from that classification.

Release review checklist

Every release or compatibility-affecting PR should answer:

  • Does this change alter accepted or emitted protocol bytes, exact payload lengths, ownership, reset, error, timeout, or feature-negotiation behavior?
  • If yes, is it a protocol erratum, minor extension, or major-version change under this policy?
  • Are layout.json, vectors.json, scenarios.json, requirements.json, and performance budgets still authoritative inputs rather than regenerated by accident?
  • Did any public Rust API change affect backend implementers, guest/device authors, or transport adapters?
  • Did any default dependency, Cargo feature, or target move platform behavior into a portable crate?
  • Did any crate add or permit unsafe code, and is the audit trail complete?
  • Did dependency, license, advisory, and MSRV checks pass?
  • Are deferred optional features still unadvertised and documented as out of scope?