Craton Shield
Release Process
This document describes how to cut a new release of Craton Shield.
Prerequisites
- Write access to the
mainbranch - Member of
@craton-co/founders - GPG key configured for tag signing
- All CI checks passing on
main
Pre-Release Checklist
Before starting the release process, verify the following:
- All CI workflows are green on
main -
CHANGELOG.mdhas been updated (items moved from[Unreleased]) - Version bumped in root
Cargo.toml - Documentation version references are up to date
-
cargo auditreports no actionable advisories -
cargo deny checkpasses - No unresolved security advisories (see
SECURITY.md)
Version Bump Process
Craton Shield uses a single workspace version defined in the root Cargo.toml. Updating the version there propagates it to all workspace crates (currently 49 crates across core/, auto/, embedded/, and industrial/).
[workspace.package]
version = "X.Y.Z"
No per-crate version bumps are needed. All crates share the workspace version via version.workspace = true in their individual Cargo.toml files.
Steps
1. Prepare the release
# Ensure you're on an up-to-date main
git checkout main && git pull
# Create a release branch
git checkout -b release/vX.Y.Z
2. Update version numbers
Update the version in the root Cargo.toml:
[workspace.package]
version = "X.Y.Z"
3. Update CHANGELOG.md
Follow the Keep a Changelog format:
- Move items from
[Unreleased]to a new[X.Y.Z] - YYYY-MM-DDsection - Organize entries under
Added,Changed,Deprecated,Removed,Fixed,Security - Add a migration guide under
core/docs/if there are breaking changes - Update the comparison links at the bottom of the file
4. Update SECURITY.md
- Update the supported versions table to reflect the new release
5. Verify CI passes
Run the full verification suite locally:
cargo fmt --all -- --check
cargo build --workspace
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo deny check
cargo audit
cargo doc --workspace --no-deps
6. Commit and create PR
git add -A
git commit -m "release: prepare vX.Y.Z"
git push -u origin release/vX.Y.Z
gh pr create --title "release: vX.Y.Z" --body "Release preparation for vX.Y.Z"
7. Merge and tag
After PR approval and CI pass:
# Merge the PR (squash)
gh pr merge --squash
# Tag the release (GPG-signed)
git checkout main && git pull
git tag -s vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.Z
All release tags must be GPG-signed (git tag -s). Unsigned tags will be rejected by branch protection rules.
8. Create GitHub Release
gh release create vX.Y.Z --title "vX.Y.Z" --notes-file - <<EOF
See [CHANGELOG.md](CHANGELOG.md#xyz--yyyy-mm-dd) for full details.
EOF
Post-Release
- Announce the release on the project's communication channels.
- Monitor for regressions -- watch CI on
mainand issue tracker for the first 48 hours. - Update downstream -- notify the sibling repos (
craton-shield-avia,craton-shield-med,craton-shield-enterprise) if they need to bump their dependency.
Versioning Policy
This project follows Semantic Versioning:
- Major (X): Breaking changes to public API or behavior
- Minor (Y): New features, non-breaking additions
- Patch (Z): Bug fixes, security patches, documentation
Hotfix Process
For critical security fixes that cannot wait for a regular release:
- Branch from the latest release tag:
git checkout -b hotfix/vX.Y.Z+1 vX.Y.Z - Apply the fix with a minimal, focused changeset
- Update
CHANGELOG.mdwith aSecurityentry - Follow steps 5-8 above (verify, commit, merge, tag)
- Cherry-pick the fix to
mainif applicable
For vulnerability-driven releases, follow the coordinated disclosure process described in SECURITY.md.
crates.io Publication
All 49 workspace crates are published to crates.io. Because the workspace is a single connected dependency graph, publication must proceed in dependency order: a crate cannot reference a version of an internal dependency that crates.io has not yet indexed. The tiers below reflect that order; within a tier, ordering does not matter.
Prerequisites
-
CARGO_REGISTRY_TOKENconfigured locally (cargo login) and scoped to a crates.io account with publish rights on everyvs-*crate. - Working tree is clean (
git statusreports nothing to commit) and the checkout is on the signed release tag (git describe --exact-match). - Tag exists and is GPG-signed (e.g.
v0.7.1), per the steps above. -
CHANGELOG.mdentry for the release is finalized and merged. - Full verification suite is green:
cargo build --workspace,cargo test --workspace,cargo clippy --workspace -- -D warnings. -
cargo deny checkis clean. -
cargo doc --workspace --no-depsbuilds without warnings.
Step 1 -- Dry-run validation
For each crate, run:
cargo publish --dry-run -p <crate>
--dry-run validates packaging, manifest metadata, license file inclusion, and
README rendering, but it cannot resolve a version = "X.Y.Z" dependency on
an internal crate until that crate is real-published to crates.io. For the
first publish of a tier, expect dry-runs of dependents to fail with "no
matching package found"; dry-run each tier after the previous tier has been
real-published in Step 2.
Step 2 -- Publish in dependency order
Run cargo publish -p <crate> for each crate, tier by tier. Wait for index
propagation (Step 3) between tiers before publishing the next one.
-
Tier 0 -- Types / no internal deps
vs-typesvs-types-autovs-types-embeddedvs-types-ind
-
Tier 1 -- Depends on types only
vs-healthvs-anomalyvs-evidence-envelopevs-hal
-
Tier 2 -- Crypto and storage stack
vs-crypto(publish first within the tier)vs-key-managervs-secure-bootvs-storagevs-event-loggervs-integrityvs-ota-validator
-
Tier 3 -- Policy and core monitors
vs-policy-enginevs-netfwvs-can-monitorvs-eth-monitorvs-ids-engine
-
Tier 4 -- Monitor crates (order within tier does not matter)
- Embedded:
vs-mqtt-monitor,vs-coap-monitor,vs-ble-monitor,vs-zigbee-monitor,vs-lora-monitor,vs-modbus-monitor-emb - Industrial:
vs-modbus-monitor-ind,vs-opcua-monitor,vs-profinet-monitor,vs-ethernetip-monitor,vs-dnp3-monitor,vs-bacnet-monitor,vs-s7comm-monitor,vs-iec60870-monitor,vs-iec61850-monitor - Auto:
vs-signal-ids
- Embedded:
-
Tier 5 -- HAL implementation and report generators
vs-hal-linuxvs-report-iec62443vs-report-iso21434vs-report-iec62304
-
Tier 6 -- Auto domain feature crates
vs-autosarvs-v2xvs-diag-gateway
-
Tier 7 -- Runtimes
vs-runtimevs-runtime-autovs-runtime-embeddedvs-runtime-ind
-
Tier 8 -- FFI shims (publish last)
vs-ffivs-ffi-auto
Step 3 -- Wait for index propagation
After publishing each tier, wait 30-60 seconds before publishing the next
tier so the crates.io sparse index can serve the new versions. The next tier's
cargo publish will fail with "no matching package found" if the index has
not yet caught up; if that happens, sleep and retry rather than republishing.
Step 4 -- Verification
For every published crate:
- Visit
https://crates.io/crates/<crate>and confirm the new version is listed, the README renders, and the listed features match the manifest. - Visit
https://docs.rs/<crate>/<version>and confirm the build succeeded. The[package.metadata.docs.rs]section in each crate'sCargo.tomlcontrols the rendered docs configuration; if a build fails on docs.rs, inspect the build log there before publishing a follow-up patch. - Confirm
cargo add <crate>@<version>resolves in a scratch project.
Step 5 -- Rollback note
crates.io publishes are immutable. A published X.Y.Z cannot be edited,
overwritten, or deleted. The only available recourse is cargo yank, which
marks the version as un-selectable by new Cargo.toml resolutions; existing
Cargo.lock files continue to resolve against the yanked version.
# Yank a specific crate-version pair
cargo yank --vers X.Y.Z <crate>
# Un-yank (only if the yank itself was a mistake -- see Yank Policy below)
cargo yank --vers X.Y.Z --undo <crate>
A workspace-wide release rollback is one cargo yank invocation per affected
crate. See the Yank Policy section below for when yanking is the right
call versus publishing a patch release.
Until you have published a release, you can also depend on Craton Shield via git as a fallback:
[dependencies]
vs-types = { git = "https://github.com/craton-co/craton-shield", tag = "v0.7.0" }
Yank Policy
Effective from v1.0.0. The policy applies to every Craton Shield crate published to crates.io.
cargo yank is a downgrade-only signal: yanking a version does not delete it
from the registry, but new Cargo.toml resolutions skip it. Existing
Cargo.lock files still resolve. This makes yank the right tool when a
published version must not be picked up by new consumers, but not a way
to "delete" a release.
When to yank
Yank a published version if any of the following is true:
- Confirmed Critical vulnerability (CVSS >= 9.0) with a plausible exploitation path against the default configuration. See the "CVE Response SLA" section of SECURITY.md for the procedure -- yank is immediate, before the patched release is cut.
- The release is fundamentally broken: it fails to build, fails its own
tests, or panics on the documented entry-point inputs. Yank, then publish
X.Y.Z+1with the fix. - An incorrect security output: a verifier returns "valid" for invalid input, a constant-time check has a data-dependent branch, the firewall's default-deny path is reachable as default-allow. These are correctness regressions in the security promise itself; yank regardless of severity classification.
- A licensing or attribution mistake that needs to be removed from the
chain of downstream copies (e.g. a vendored dependency under an
incompatible license slipped in). Yank, then publish
X.Y.Z+1with the offending content removed.
When to deprecate (not yank)
Deprecate -- by publishing a new minor with #[deprecated] on the offending
items and a migration note in core/docs/ -- if all of the following are
true:
- The existing version is safe to use as documented.
- A better API exists or is on the way.
- Continuing to consume the deprecated version is a maintenance choice, not a security or correctness exposure.
Deprecation gives integrators time to migrate without breaking their pinned builds. Use deprecation for: replaced APIs, configuration flags that no longer make sense, modules that have moved between crates.
When to publish a patch (not yank, not deprecate)
Publish X.Y.Z+1 and leave X.Y.Z un-yanked if the bug:
- Has a low CVSS score (< 7.0) and no immediate exploitability concern.
- Affects an edge case that integrators are unlikely to hit by default.
- Is fixed in the new release such that the upgrade path is a no-op (no config changes, no API changes).
This is the common case for routine bug-fix releases. Yanking would impose churn on existing users for no proportionate benefit.
Yank execution checklist
When yanking is the right call:
-
Identify the full version set. A single vulnerability can affect a range; yank every crate-version pair in the range, not just the latest. For workspace-wide releases, that is one yank command per crate per version:
for crate in vs-types vs-crypto vs-runtime ...; do cargo yank --vers X.Y.Z "$crate" done -
Record the yank in the GHSA, in
CHANGELOG.mdunder aSecuritysubsection of the next release, and incore/docs/known-limitations.mdif the issue has a published workaround. The CHANGELOG entry must list:- the crate-version pairs yanked,
- the date and time (UTC),
- the GHSA identifier and CVE,
- the version integrators should move to.
-
Notify downstream. Open issues on the sibling repos (
craton-shield-avia,craton-shield-med,craton-shield-enterprise) and email known commercial integrators per the disclosure list. -
Do not un-yank. A patched release supersedes the yanked one; we do not reverse a yank after the fact. If a yank turns out to be unjustified (false-positive report, mis-classified severity), publish a corrective GHSA explaining the situation and let the regular
>= X.Y.Z+1constraint move integrators forward. -
Audit afterwards. Within 30 days of the yank, file the post-mortem referenced in SECURITY.md under
core/docs/security-postmortems/. Include whether yanking was the right tool and what would change the decision next time.