# Crate release process
This workspace publishes **three crates to crates.io**, each **versioned and
tagged independently**:
| `revolutx` | `Cargo.toml` | `CHANGELOG.md` | `vX.Y.Z` | the library; the dependency the binaries build on |
| `revolutx-cli` | `cli/Cargo.toml` | `cli/CHANGELOG.md` | `revolutx-cli-vX.Y.Z` | binary (`cargo install` builds it) |
| `revolutx-mcp` | `mcp/Cargo.toml` | `mcp/CHANGELOG.md` | `revolutx-mcp-vX.Y.Z` | binary (`cargo install` builds it) |
Each crate's version lives in **its own `Cargo.toml`** and moves on its own
cadence — bumping the library does not force a binary bump, and vice versa. The
library keeps the historical bare `vX.Y.Z` tag (`v0.1.0`, `v0.2.0`, …); the
binaries are namespaced so their tags never collide with it.
crates.io versions are **immutable**: once `cargo publish` succeeds you cannot
overwrite or re-upload that version (you can only *yank* it). Everything before
the publish step exists to make the artifact correct *before* it goes out. None
of these crates ship prebuilt binary assets — the GitHub release is just notes.
## Automated path: `scripts/release.sh`
The mechanical half — sync, dry-run, tag, push, publish, GitHub release — is
automated:
```bash
It reads the version from that crate's `Cargo.toml`, derives the tag, and runs
the steps below in order. It **assumes the human steps are already done and
committed** (review, validation gate, changelog, version bump) — it does not make
those judgments for you. Before the irreversible push/publish it prints a summary
and asks for confirmation (`--yes` skips the prompt).
**Release order matters.** Publish `revolutx` **first**; the binaries depend on it
by version, so a binary's verify build resolves `revolutx = "X.Y"` from crates.io
and fails fast if the library isn't live yet. So:
```bash
scripts/release.sh revolutx # 1. the library
scripts/release.sh revolutx-cli # 2. then the binaries (any order)
scripts/release.sh revolutx-mcp
```
What the script does:
1. **Pre-flight** — on `master`, clean working tree, `gh` authenticated, a
crates.io token present (`cargo login` or `CARGO_REGISTRY_TOKEN`), and the tag
not already taken.
2. **`git pull --rebase origin master`** — *before* tagging. CI's coverage job
pushes a `[skip ci]` badge commit after every master push, so the release
commit must be rebased onto the current remote tip first; tagging only after
the rebase means the tag lands on the final commit and never has to be deleted
and re-created.
3. **`cargo publish --dry-run -p <crate>`** — builds the package in isolation (the
OpenAPI submodule is excluded), catching a broken artifact before anything is
pushed.
4. **tag + push** — annotated tag on the rebased release commit, then push the
branch and the tag.
5. **`cargo publish -p <crate>`** — the irreversible upload.
6. **`gh release create`** — notes from this version's section of the crate's own
changelog (`CHANGELOG.md`, `cli/CHANGELOG.md`, or `mcp/CHANGELOG.md`).
The sections below are the human judgment the script relies on; do them, commit,
then run the script.
## Pre-flight
0. Be on `master`, up to date, with a clean working tree and CI green on the last
commit. Authenticate to crates.io (`cargo login`, or `CARGO_REGISTRY_TOKEN`
set) and GitHub (`gh auth login`) — the script checks both, but fix them now.
The OpenAPI submodule must be initialized (`git submodule update --init`) so
the spec-backed tests can run.
## Review what's shipping
1. Check what changed since the crate's last release: `git log <last-tag>..HEAD`
(`git describe --tags --abbrev=0 --match 'v*'` for the library; for a binary,
`git describe --tags --abbrev=0 --match 'revolutx-cli-v*'`). For a crate's
first publish, review its whole tree.
2. Check the files that changed: `git diff <last-tag>..HEAD --name-status`.
3. Read the changed Rust files in full and make sure:
- a) all doc comments and code comments are correct and precise;
- b) every public item is documented and the docs match the real API;
- c) `docs/openapi-inventory.md` and `tests/spec_coverage.rs` still match the
spec (the coverage test fails if they drift).
4. **Groom the user-facing docs and examples** so they reflect this release:
- a) the root `README.md` (and `cli/README.md`, `mcp/README.md`) cover **all**
current functionality — every Cargo feature, every endpoint group, every CLI
command / MCP tool, and any capability shipped since the last release — with
nothing stale;
- b) bump the version references to the version you're releasing: the
`revolutx = "X.Y"` install snippets and the MSRV note in `README.md`. The
`cargo build` gate in [Validate](#validate) catches code that stops
compiling, but **not** a doc that still advertises an old version;
- c) the runnable examples in `examples/` exercise the current API, and a new
public capability worth showing has an example (they must also compile —
`cargo build --examples`, run below).
5. **Look for opportunities to improve the code** — abstractions that can be
simplified, code that can be made more readable, duplication that can be
removed. THIS IS REALLY IMPORTANT. Ask a human if you have ideas you are not
certain about. Commit and re-test any changes you make here before continuing.
## Validate
6. Make the full gate pass cleanly (and commit any fixes). These mirror CI; the
`--workspace` flags matter because the binary crates live here too and must
build/lint/test as well:
```bash
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo build --examples ```
Also keep the optional-feature tree clean (mirrors CI's feature matrix — this
is what catches a feature accidentally pulling a default-on dependency, or
non-default code that fails to compile in isolation):
```bash
cargo clippy -p revolutx --no-default-features -- -D warnings cargo clippy -p revolutx --no-default-features --features fix -- -D warnings
cargo clippy -p revolutx --no-default-features --features agent --all-targets -- -D warnings
cargo clippy -p revolutx --no-default-features --features commands --all-targets -- -D warnings
cargo clippy -p revolutx --all-features -- -D warnings
```
The default test suite is offline and deterministic. Do **not** run the
ignored live smoke tests as part of a release (they need real credentials and
hit the network); they are not a release gate.
## Update the changelog
7. Update the changelog **for the crate you're releasing** ([Keep a
Changelog](https://keepachangelog.com/) format) — each crate keeps its own and
versions independently:
- `CHANGELOG.md` (repo root) for the `revolutx` library;
- `cli/CHANGELOG.md` for `revolutx-cli`;
- `mcp/CHANGELOG.md` for `revolutx-mcp`.
From the `git log` review above (scoped to that crate's directory — e.g. `git
log <last-tag>..HEAD -- cli/`), turn the `[Unreleased]` section into a dated
version section, grouping notable changes under `Added` / `Changed` / `Fixed` /
`Removed`. Summarize what *that crate's* users care about, not raw commit
subjects, and keep library-vs-binary changes in their own files (a lib API
change goes in the root changelog; the CLI flag or MCP tool that surfaces it
goes in the binary's).
```markdown
## [0.3.0] - 2026-07-01
### Changed
- Credential vault moved to rcypher 0.3's `SecretStore` format …
```
This section is the single source of truth for that crate's GitHub release
notes (the script extracts it). Each `CHANGELOG.md` ships in its own published
crate — `exclude` only drops the submodule, dev-only files, `scripts/`, and the
two submodule-dependent tests.
## Bump the versions
8. Decide each crate's new version from the review above (pre-1.0 semver: breaking
changes bump the **minor**, features/fixes bump the **patch**). Set `version`
in the relevant `Cargo.toml`(s) — only the crates that actually changed — then
run `cargo build` so `Cargo.lock` picks the new versions up. If you bumped
`revolutx`, also update its dependents — see
[Workspace dependencies](#workspace-dependencies).
9. Commit the bumps and changelog as the release commit(s), so the tree is clean
before the script tags it:
```bash
git add -A && git commit -m "Release v0.3.0"
```
The script then verifies the package without `--allow-dirty` (which would
validate a tarball containing uncommitted changes you'll never tag). You can
eyeball the contents first:
```bash
cargo package --list -p <crate> ```
## Workspace dependencies
The crates in this workspace depend on `revolutx` by **both** `path` and
`version` — e.g. `revolutx-mcp` has:
```toml
revolutx = { path = "..", version = "0.3", features = ["rest", "agent", "commands"] }
```
The `path` is only used for local/workspace builds; the **`version` requirement is
what gets enforced once the crate is published** (cargo strips the `path` on
publish). So every time you release `revolutx`, check each dependent and update it
deliberately — these requirements do **not** update themselves:
1. Decide whether the dependent relies on anything introduced in *this* `revolutx`
release.
2. If it does, bump that crate's `revolutx` `version` requirement to the version
you're releasing, and publish the dependent **after** `revolutx` is live
(dependency before dependents). A requirement that's too loose (e.g. `"0.2"`
when the dependent now needs `0.3` APIs) would let the *published* dependent
resolve against an older `revolutx` it cannot compile against — a broken
`cargo install`.
3. If it doesn't, leave the requirement, but confirm it still resolves.
## Run the release script
10. With everything above committed, publish each crate — `revolutx` first:
```bash
scripts/release.sh revolutx
scripts/release.sh revolutx-cli
scripts/release.sh revolutx-mcp
```
Then confirm the library on <https://crates.io/crates/revolutx>, that docs
build at <https://docs.rs/revolutx>, and the binaries at
<https://crates.io/crates/revolutx-cli> /
<https://crates.io/crates/revolutx-mcp>.
## Manual fallback
If the script can't be used, do its steps by hand for one crate (`revolutx`
shown; for a binary, `cargo publish -p revolutx-cli` and tag
`revolutx-cli-vX.Y.Z`). Note the **rebase before the tag**:
```bash
git fetch origin master
git pull --rebase origin master # absorb the CI badge commit FIRST
cargo publish --dry-run # verify the artifact
git tag -a v0.3.0 -m "revolutx 0.3.0" # tag the rebased commit
git push origin master
git push origin v0.3.0
cargo publish # irreversible
# GitHub release notes = this version's CHANGELOG.md section:
awk '/^## \[0.3.0\]/{f=1;next} /^## \[/{f=0} f' CHANGELOG.md > /tmp/notes-v0.3.0.md
gh release create v0.3.0 --title "v0.3.0" --notes-file /tmp/notes-v0.3.0.md
rm /tmp/notes-v0.3.0.md
```
There are no binary assets to attach: every crate lives on crates.io as source.