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

Choosing versions together

A release report describes what changed. The skill assesses the change’s semantic impact on users, then produces a plan of the versions and workspace edits required to release it. This separation keeps a text diff from pretending to decide whether a consumer promise was broken.

Semantic impact and version bumps

The skill records each package’s semantic impact:

ImpactUse
breakingA change breaks a consumer promise.
nonbreakingA compatible addition extends the consumer’s promises.
patchA correction or other change needs no stronger semantic impact.

Consumer promises include documented behavior, CLI arguments, data formats, feature availability and build requirements, not just Rust function signatures.

The resulting numeric version bump is patch, minor or major. The decisions document calls the semantic field impact; the proposal calls the numeric field bump. An exact version can be supplied instead of a bump.

Anchor versionSemantic impactExample resulting versionVersion bump
1.4.0nonbreaking1.5.0minor
1.4.0breaking2.0.0major
0.4.2nonbreaking0.4.3patch
0.4.2breaking0.5.0minor

These examples start without an adequate pending increment or other group effects. Proposal generation retains an already sufficient version rather than incrementing it again.

The translation follows Cargo compatibility: the leftmost nonzero version component defines the incompatible boundary. A 0.0.z package has no compatible next version. Explicit target versions are also supported when numeric increments do not express the intended release.

External API evidence is a floor

cargo-semver-checks detects supported Rust API incompatibilities. Establish that the checker can run before using its result: a rustdoc-format failure is not a compatibility pass.

Compare public library contracts with all features, but review feature changes manually too. Moving an existing item behind a new feature can break callers using a subset even when both all-features APIs contain the item. Removing a feature, changing observable behavior or breaking a CLI can also require a stronger decision than the checker reports.

Raise the decision when the contract requires it. Do not lower it below a valid compatibility finding.

The checker has a cross-crate limitation: re-exported dependency definitions are not fully available in its input. A public facade is the package consumers use while another package supplies its implementation. Review the definitions and behavior it exposes, including public methods on re-exported types, even when the facade’s own source is unchanged. A passing facade comparison does not replace this assessment.

Procedural macros transform consumer input into Rust code during compilation. Their Cargo targets are unsupported for direct comparison, regardless of whether they offer a public API. Review accepted invocations, attribute arguments, generated APIs and trait implementations, consumer requirements, and promised behavior. Explicit unavailable evidence supplies no minimum impact, not a default patch. Consumer tests and source review support the decision; missing evidence is not proof of compatibility.

Version groups

An exact local dependency such as version = "=1.4.0" joins its Git-tracked workspace endpoints into a version group. Connected exact relationships, in either dependency direction, make one group. There is no separate group configuration.

Groups include normal, build and development dependencies, optional and target-specific declarations, and inherited workspace dependencies. A registry dependency, an outside-workspace path, a versionless path or an unused workspace entry does not create a group.

Use a single exact =major.minor.patch requirement. Partial exact versions, prerelease/build suffixes and compound exact requirements do not declare valid workspace groups.

Every member shares the resolved version. A non-publishable package has publish = false. Its version can move for group alignment, but its source changes receive no semantic impact and it is never uploaded. The highest declared member version participates in choosing the result, so alignment does not lower a member.

The running example is:

widget             public library             1.4.0
  exact dependency on widget_impl
widget_impl        private implementation     1.4.0
widget-fixtures    publish = false helper      1.4.0
  exact dependency on widget_impl

widget-cli         separate binary package    2.0.0
  compatible dependency on widget
  executable name: widget

The library, implementation and helper form one group. widget-cli does not: compatible dependency requirements do not make exact-version groups.

Private APIs and public dependencies

A published implementation package can declare:

[package.metadata.release-plan]
private-api = true

That means its library surface serves another package rather than consumers directly. It is not the same as publish = false. It still has released content, version-group obligations and dependency effects.

When widget_impl changes, compatibility selection reaches the public widget contract through their group. Re-exported items are compared where consumers use them only to the extent supported by the checker; the author assesses their full contract there rather than demanding compatibility for every private implementation item. Unsupported procedural-macro members are excluded from direct comparison after group selection, so a macro implementation change can still select the public facade. All publishable packages remain in dependency-first semantic assessment.

A public origin is a workspace package defining types exposed by another package’s public API, directly or through another exposed owner’s API. Its incompatible version movement requires an incompatible release of the exposing package too. This is based on the version relationship, not a guess that the particular exposed types were unaffected.

Exposure comes from each package’s externally verified allowed_external_types declarations. Defining crate names can differ from the direct dependency through which a type is re-exported. The declarations are followed transitively through the defining packages, reachable through normal workspace dependencies. Sharing a third package’s origin does not make a dependency’s own types part of the consumer’s API.

Private implementation packages do not interrupt breaking-change propagation. A re-exported type can expose another dependency through its methods without the outer library’s allow-list enumerating every such type. Following the defining implementation package’s own allow-list preserves that consumer contract. Using its whole allow-list can propagate changes from unrelated items, and exact-version groups can move an unaffected public member.

Inference or method-call ambiguity introduced by coexisting dependency versions’ trait implementations is intentionally considered nonbreaking. Implementation contributors are not tracked solely to prevent that ambiguity. Explicitly exposed trait and type identities, bounds and associated types still participate in propagation; other behavioral and API changes still require semantic assessment.

A compatible dependency-version movement imposes no type-identity break. It does not cap the facade’s own impact at patch: a compatible new method on a re-exported type is a nonbreaking facade capability. Conversely, changing an unexported implementation item is not a public API change merely because it is pub inside that crate. Assess its observable effects and let group alignment follow the semantic decisions.

This model requires a real external-type validation gate. Ordinary check does not verify those declarations against compiled APIs.

Requirement rewrites also matter

Workspace requirements must name the version their target declares, not merely admit it within a broad range. An ordinary requirement such as 1.4.0 and an exact requirement such as =1.4.0 have different grouping effects, but both need updating when their target moves.

A dependent manifest rewrite is released content. Preview accounts for that rewrite and any binary lockfile effects, retaining sufficient pending increments. The complete release set is therefore larger than the packages whose source the author initially edited.