Publishing runbook

This chapter is the maintainer-only reference. It covers the publish-to-crates.io procedure: the pre-flight checklist, the dependency topological order, the dry-run, the actual publish, and the rollback procedure if something goes wrong.

The audience is the maintainer cutting a release. An adopter does not need this chapter; the chapter is here so the maintainer has a written reference and so the procedure can be followed by a different maintainer if needed.

Pre-flight

The pre-flight checklist runs before the first dry-run. Each item is a binary pass-or-fail; one failure blocks the release.

The CI is green on the release branch. The full test matrix (default features, all-features, per-backend isolation, FIPS backend) all pass. A red CI does not publish.

The version bumps are consistent across the workspace. Every member of the workspace gets the same version bump (this is the versioning policy: the workspace ships as one unit). The Cargo.toml in each crate carries the new version; the version.workspace = true shape inherits from the root.

The Cargo.lock is up to date. Run cargo update --workspace, review the changes, commit if needed.

The migration guide is complete. The Migration guide chapter in the book carries an entry for every breaking change in the release. The entry covers the symptom, the rationale, and the fix.

The CHANGELOG.md has a current entry for the release. The entry covers the new features, the breaking changes (referencing the migration guide), and the bug fixes. The entry is the short-form version of the migration guide; both exist because they serve different audiences (the CHANGELOG is the per-release overview, the migration guide is the per-change reference).

The docs.rs configuration is present and correct on every crate that publishes to crates.io. The shape:

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

Verify by running cargo doc --all-features locally; the build must succeed without warnings. A docs build that fails on docs.rs after publish is a maintenance problem that surfaces once the release is out.

The description, license, repository, keywords, and categories fields are populated on every published crate. The crates.io page renders these; a missing field is a missing detail in the listing.

The publish = false flag is removed from every crate that should publish. This is the deliberate gate that keeps accidental publishes from happening; flipping the flag is what makes the release possible.

The version branch (a fresh release/0.2.0 branch from main) exists. The branch is the source of truth for the release; any fixes during the publish window land on the branch and merge back to main after.

Topological dependency order

The workspace's library crates publish in dependency order: a crate must be published before any crate that depends on it. The order:

  1. axess-strings (no axess deps)
  2. axess-clock (no axess deps)
  3. axess-rng (no axess deps)
  4. axess-identity (no axess deps)
  5. axess-cache (depends on axess-clock)
  6. axess-events (depends on axess-identity)
  7. axess-factors (depends on axess-identity, axess-clock, axess-rng)
  8. axess-macros (no axess deps; procedural macros stand alone)
  9. axess-core (depends on everything in the previous tier)
  10. axess (the facade, depends on axess-core and axess-factors and axess-macros)

The order is generated by cargo publish --dry-run against each crate in turn, but the maintainer should know it manually so a publish that fails partway through can be resumed at the right position.

The dry-run

Before the actual publish, run a dry-run for each crate in topological order. The shape:

cargo publish --dry-run -p axess-strings
cargo publish --dry-run -p axess-clock
cargo publish --dry-run -p axess-rng
cargo publish --dry-run -p axess-identity
cargo publish --dry-run -p axess-cache
cargo publish --dry-run -p axess-events
cargo publish --dry-run -p axess-factors
cargo publish --dry-run -p axess-macros
cargo publish --dry-run -p axess-core
cargo publish --dry-run -p axess

The dry-run does everything the publish does except the upload. It builds the package, verifies the manifest, runs the publish-time checks, and prints the path of the .crate file it would have uploaded. A failure here is an opportunity to fix without having to yank a half-published release.

A failure on a downstream crate (say axess-core) typically means an upstream crate (say axess-factors) needs an update that the dry-run does not yet reflect. The fix is to update the upstream first; the downstream dry-run picks up the change.

The publish

After the dry-runs pass, run the actual publish in the same topological order. The shape:

cargo publish -p axess-strings
# wait ~30s for crates.io to index, then:
cargo publish -p axess-clock
# wait, then:
cargo publish -p axess-rng
# ... and so on

The wait between publishes is necessary because each subsequent publish needs the previous one to be available on crates.io's index. Without the wait, the downstream publish fails with "crate not found"; with the wait, the index has propagated by the time the next publish queries it.

The wait time is short (30 seconds is generous; sometimes 15 seconds works). For automation, the wait can be scripted with a retry loop that polls the crates.io API until the expected version is listed.

After the final publish (cargo publish -p axess), wait a few minutes and verify on crates.io that all the crates are listed at the new version.

The smoke test

After the publish, run a smoke test against a fresh dependency on the published version. The shape:

mkdir /tmp/axess-smoke
cd /tmp/axess-smoke
cargo new --name axess-smoke .
echo 'axess = "0.2"' >> Cargo.toml
cargo build

The build pulls the published crates from crates.io (not from the workspace) and verifies they assemble. A failure here indicates an issue that the dry-run did not catch (typically a crates.io-specific issue like a missing file in the package manifest); the rollback procedure is the response.

For a more thorough smoke test, copy examples/sqlite/ to a fresh directory, point its Cargo.toml at the published versions (replacing path = "../../axess" with version = "0.2"), and verify it builds and runs.

The smoke test is the last gate before announcing the release. A successful smoke test means the publish is real.

The announcement

After the smoke test passes:

Tag the release in git (git tag v0.2.0 && git push --tags). The tag is the canonical reference point.

Update the status banner near the top of README.md from "pre-release" to "0.2.0 released."

Open a 0.3.0-pending section in CHANGELOG.md. Future PRs land entries under that section until the next release cuts.

Post to the project's announcement channels (the GitHub releases page is the canonical one; the project's Discord, Slack, mailing list, or other channels mirror as appropriate).

Update the docs.rs links anywhere they hardcode a version. The canonical version is now the released one, not the development branch.

Rollback

If the publish goes wrong (a critical bug surfaces, a crate is broken on crates.io, the release was premature), the rollback procedure:

cargo yank --version 0.2.0 axess (and every other crate) withdraws the version from crates.io. Yanked versions remain available to existing consumers (so Cargo.lock references continue to work), but new resolves do not pick them up.

A yanked version cannot be unyanked, and a new publish with the same version number is not possible. The next release picks a new version (0.2.1); the fix lands there.

In practice the rollback is needed less often than the dry-run suggests; the topological-publish discipline catches most issues before the upload. A yank is the genuine emergency response, typically for a security issue that warrants withdrawing a specific version.

Post-release maintenance

After the release lands, the maintenance window is the period where the maintainer watches for issues. The shape:

The first 24 hours: monitor crates.io for download numbers (a quick check that the publish reached an audience); monitor the GitHub issue tracker for new bug reports; monitor the dashboards of any deployments that follow main closely.

The first week: triage the issues that come in; assess whether any warrant a patch release (0.2.1). The criteria for a patch release: a critical bug, a security issue, a regression from 0.1.x that the migration guide did not catch.

The first month: roll up the lessons learned. The patches shipped, the issues that surfaced, the documentation gaps the release exposed. The roll-up feeds the next release's planning.

Further reading

Migration guide covers the cross-version compatibility surface that the release-management decisions depend on. Contributing covers the development workflow that produces the changes a release ships. The CHANGELOG.md covers the exhaustive list of changes per release.