How to Release mif-rs
How to Release mif-rs
Section titled “How to Release mif-rs”Create, monitor, and — if needed — roll back a release of the mif-rs
workspace: 12 crates (mif-core, mif-problem, mif-schema,
mif-frontmatter, mif-ontology, mif-embed, mif-store, mif-cli,
mif-mcp, mif-rh, mif-rh-cli, mif-rh-mcp) published independently to
crates.io, plus attested binaries for the four binary crates (mif-cli,
mif-mcp, mif-rh-cli, mif-rh-mcp) and one container image per binary
crate.
Prefer the
/releaseskill. Releases are orchestrated end-to-end by the/releaseskill (.github/skills/release/SKILL.md): release-prep PR, tag, monitoring of every workflow chain, and independent workstation verification. The manual procedure below is the same process the skill drives.
Version Numbering (SemVer)
Section titled “Version Numbering (SemVer)”All 12 crates share one workspace version (version.workspace = true in
every crate’s Cargo.toml, set once in [workspace.package]). This project
follows Semantic Versioning 2.0.0:
| Change type | Version bump | Example | When to use |
|---|---|---|---|
| Breaking API change in any published crate | MAJOR | 1.0.0 -> 2.0.0 |
Removed public types, changed function signatures |
| New feature (backward-compatible) | MINOR | 0.1.0 -> 0.2.0 |
New public functions, new optional fields |
| Bug fix (backward-compatible) | PATCH | 0.1.0 -> 0.1.1 |
Fix incorrect behavior, performance improvement |
Pre-1.0 policy: while on 0.x.y, MINOR bumps may include breaking
changes. Document these clearly in commit messages with BREAKING CHANGE:
in the body.
Prerequisites
Section titled “Prerequisites”Publication gate
Section titled “Publication gate”All 12 crates, including mif-rh, mif-rh-cli, and mif-rh-mcp (added later,
alongside the rest of the workspace), are now published on crates.io. None of
the 12 carries a publish = false line, so publish.yml’s guard job (which
reads .packages[] | select(.publish != []) from cargo metadata, not a
hardcoded crate list) treats every workspace member as publishable — no
publish-gate flag to flip, and no per-crate onboarding step remains
outstanding.
Republishing the next release requires a version bump in two places,
both in the root Cargo.toml: bump [workspace.package].version, and
bump the version field in each of the 8
[workspace.dependencies.mif-*] entries (mif-core, mif-schema,
mif-ontology, mif-problem, mif-frontmatter, mif-embed, mif-store,
mif-rh) to match. Both matter: cargo publish rewrites each path = "..." version = "..." dependency down to just the version requirement when it
packages a crate, so a crate whose internal mif-* deps still point at the
old version will fail to build against what’s actually live on crates.io the
moment any of those internal APIs changed (confirmed directly: cargo publish --dry-run on a crate depending on a stale-versioned mif-ontology
fails to compile once the local trait surface has moved past what’s
published). See Pre-Release Checklist below for
both edits. Confirm the current published state before releasing:
cargo metadata --no-deps --locked --format-version 1 \ | jq -r '.packages[] | select(.publish != []) | "\(.name) \(.version)"'Required Secrets and Setup
Section titled “Required Secrets and Setup”Configure on crates.io and in GitHub repository settings (Settings > Secrets and variables > Actions):
| Item | Purpose | How to set up |
|---|---|---|
| crates.io Trusted Publishing (one-time, per crate) | Publish via OIDC — no long-lived token | On crates.io, for each of the 12 published crates: crate Settings > Trusted Publishing > add repo modeled-information-format/mif-rs, workflow publish.yml, environment release. This should be configured for all 12, including mif-rh, mif-rh-cli, and mif-rh-mcp — verify per-crate configuration on crates.io rather than inferring it from publication status alone. A brand-new crate not yet on crates.io still needs its first publish done manually (cargo login + cargo publish -p <crate>, in dependency order) before Trusted Publishing can be added for it |
HOMEBREW_TAP_TOKEN (secret, optional) |
Push formula updates to the Homebrew tap | Fine-grained PAT with write access to {owner}/homebrew-tap |
HOMEBREW_TAP_REPO (variable, optional) |
Override the tap repo name (default homebrew-tap) |
Settings > Secrets and variables > Actions > Variables |
GITHUB_TOKEN |
Provided automatically | No setup needed |
Environment protection
Section titled “Environment protection”publish.yml, release.yml, and package-homebrew.yml all gate on the
release GitHub Environment. Configure real protection rules (at minimum a
required reviewer) on it in Settings > Environments before arming
external publish channels.
Permissions
Section titled “Permissions”- GitHub Packages (Docker): Settings > Actions > General > Workflow permissions > “Read and write permissions”
Pre-Release Checklist
Section titled “Pre-Release Checklist”Run through this checklist before every release.
- All CI checks pass on
main(check Actions) - Update the workspace version in the root
Cargo.toml— both places:[workspace.package]version = "X.Y.Z" # New version — every crate inherits it via version.workspace = true# And each of these 8 (mif-core, mif-schema, mif-ontology, mif-problem,# mif-frontmatter, mif-embed, mif-store, mif-rh) — cargo publish rewrites# path+version deps down to version-only, so a stale pin here ships a# crate that depends on the OLD, already-published version of another# workspace crate:[workspace.dependencies.mif-core]path = "crates/mif-core"version = "X.Y.Z" - Run the full local check suite:
Terminal window just check # fmt-check + lint + test + doc-build + denyjust msrv # cargo +1.95 check --all-features - Build all four release binaries locally to verify:
Terminal window cargo build --release -p mif-cli -p mif-mcp -p mif-rh-cli -p mif-rh-mcp - Review
CHANGELOG.mdand recent commits since the last tag:Terminal window git log $(git describe --tags --abbrev=0)..HEAD --oneline - Verify conventional commit messages are correct (they drive changelog generation)
- If breaking changes exist, confirm a MAJOR version bump and
BREAKING CHANGE:in commit bodies - Commit the version bump separately:
Terminal window cargo check # regenerates Cargo.lock for the new version — never hand-edit itgit add Cargo.toml Cargo.lockgit commit -m "chore: bump version to X.Y.Z"git push
Step-by-Step: Create and Push a Release Tag
Section titled “Step-by-Step: Create and Push a Release Tag”1. Create an Annotated Tag
Section titled “1. Create an Annotated Tag”git tag -a vX.Y.Z -m "Release vX.Y.Z"2. Push the Tag
Section titled “2. Push the Tag”git push origin vX.Y.ZThis single push triggers all release automation.
3. Triggered Workflows
Section titled “3. Triggered Workflows”Pushing a v*.*.* tag triggers these workflows in parallel:
| Workflow | File | What it does |
|---|---|---|
| Release | release.yml |
Builds every binary crate (mif-cli, mif-mcp, mif-rh-cli, mif-rh-mcp) across 5 platforms each ({bin}-{version}-{platform}), resolved dynamically from cargo metadata — with SLSA build provenance, generates + attests a CycloneDX SBOM, verifies every attestation fail-closed, then creates the GitHub Release with auto-generated notes |
| Publish | publish.yml |
Publishes every publishable workspace member (all 12 crates: mif-core, mif-problem, mif-schema, mif-frontmatter, mif-ontology, mif-embed, mif-store, mif-cli, mif-mcp, mif-rh, mif-rh-cli, mif-rh-mcp) to crates.io independently, in dependency order, each via its own crates.io Trusted Publishing (OIDC) config — then downloads each registry-served .crate, byte-compares it, and attests it. All 12 crates, including mif-rh/mif-rh-cli/mif-rh-mcp, are expected to have Trusted Publishing configured and publish cleanly — verify per-crate configuration (see Prerequisites) rather than assuming it from publication status alone |
| Pipeline (container) | pipeline.yml |
Builds and pushes one multi-platform container image (linux/amd64, linux/arm64) per binary crate to ghcr.io/modeled-information-format/mif-rs/{bin} (mif-cli, mif-mcp, mif-rh-cli, mif-rh-mcp) — the bin matrix is resolved dynamically from cargo metadata — each signed/attested by the central signer workflow and verified fail-closed |
After the Release workflow completes, package-homebrew.yml fires via
workflow_run and regenerates the tap formula(e) in {owner}/homebrew-tap
— but only when that workflow is enabled and the tap repo exists; as of
this writing it is disabled_manually and no tap repo exists, so no
Homebrew run is expected (see Post-Release Verification).
Never re-run release.yml against an existing tag. Builds are not
reproducible; a re-run would overwrite published assets with different
bytes, violating the immutability the attestations protect.
Monitoring Workflow Progress
Section titled “Monitoring Workflow Progress”GitHub Actions Dashboard
Section titled “GitHub Actions Dashboard”- All workflows: https://github.com/modeled-information-format/mif-rs/actions
- Filter by tag: click the specific workflow run triggered by the tag push
CLI Monitoring
Section titled “CLI Monitoring”gh run list --limit 10gh run watch <run-id>gh run view <run-id> --log-failedWhat to Watch For
Section titled “What to Watch For”| Stage | Expected duration | Common failure point |
|---|---|---|
| Build Binaries (4 bins x 5 platforms) | ~15-25 min | The macos-amd64 leg (cross-target on macos-latest), for any binary |
| Test + Cargo Audit gates | ~3 min | New advisory in Cargo.lock (audit scans the raw lockfile; deny may not have flagged it) |
| SBOM (generate + attest) | ~1 min | Attestation permissions (id-token, attestations) |
| Verify Attestations | <1 min | Fail-closed gate: any missing/unverifiable attestation blocks the release |
| Create Release | ~1 min | Only runs on tags, after verify passes |
| Publish (crates.io, 12 crates) | ~5 min | Trusted Publishing misconfigured or revoked for one of the crates (see Prerequisites); dependency-order failure blocking a downstream crate |
| Docker chain (pipeline) | ~5-10 min | Buildx multi-platform, central signer/verify |
| Homebrew (after Release) | ~2 min | workflow_run trigger, tap token |
Post-Release Verification
Section titled “Post-Release Verification”Run through this after all workflows complete.
- GitHub Release exists with correct version:
Terminal window gh release view vX.Y.Z - Binary assets are attached for all four binary crates, on every
platform (
{bin}-{version}-{platform}per binary —mif-cli,mif-mcp,mif-rh-cli,mif-rh-mcpeach across linux-amd64, linux-arm64, macos-amd64, macos-arm64, windows-amd64.exe), plus SBOM and checksum assets:Terminal window gh release view vX.Y.Z --json assets --jq '.assets[].name' - Attestations verify from an independent machine (full reference:
SECURITY.md):
Terminal window gh release download vX.Y.Z --repo modeled-information-format/mif-rsfor BIN in mif-cli mif-mcp mif-rh-cli mif-rh-mcp; dogh attestation verify "${BIN}-X.Y.Z-linux-amd64" \--repo modeled-information-format/mif-rsgh attestation verify "${BIN}-X.Y.Z-linux-amd64" \--repo modeled-information-format/mif-rs \--predicate-type https://cyclonedx.org/bomdoneshasum -a 256 -c *-X.Y.Z-checksums.txt - Release notes are generated correctly
- Container images are pushed — one per binary crate:
Terminal window for BIN in mif-cli mif-mcp mif-rh-cli mif-rh-mcp; dodocker pull ghcr.io/modeled-information-format/mif-rs/${BIN}:vX.Y.Zdocker pull ghcr.io/modeled-information-format/mif-rs/${BIN}:latestdone - crates.io — each of the 12 crates is updated, and each served
.crateattestation verifies:Terminal window for NAME in mif-core mif-problem mif-schema mif-frontmatter mif-ontology mif-embed mif-store mif-cli mif-mcp mif-rh mif-rh-cli mif-rh-mcp; docurl -fsSL -A 'release-check' \-O "https://static.crates.io/crates/${NAME}/${NAME}-X.Y.Z.crate"gh attestation verify "${NAME}-X.Y.Z.crate" \--repo modeled-information-format/mif-rsdone - Homebrew formula — only if the Homebrew chain is armed, i.e.
the
package-homebrew.ymlworkflow is enabled (gh api repos/modeled-information-format/mif-rs/actions/workflows/package-homebrew.yml --jq .statereportsactive) and the{owner}/homebrew-taprepo exists: apackage-homebrew.ymlrun appeared after Release completed and the tap formula shows the new version. If the workflow is disabled or there is no tap repo (the current state — the workflow isdisabled_manuallyand no tap exists), no run is expected; mark this item skipped and move on - Install and test each binary crate on at least one platform:
Terminal window cargo install mif-cli --lockedcargo install mif-mcp --lockedcargo install mif-rh-cli --lockedcargo install mif-rh-mcp --lockedmif-cli --versionmif-mcp --versionmif-rh-cli --versionmif-rh-mcp --version
Rollback Procedures
Section titled “Rollback Procedures”Roll Back a GitHub Release
Section titled “Roll Back a GitHub Release”gh release delete vX.Y.Z --yesgit push --delete origin vX.Y.Zgit tag -d vX.Y.ZRoll Back a crates.io Publish
Section titled “Roll Back a crates.io Publish”You cannot unpublish from crates.io. Your options, per affected crate:
- Yank the version (prevents new projects from depending on it — this
is a workspace with multiple published crates, so
-pis required):Terminal window cargo yank --version X.Y.Z -p <crate-name> - Publish a fix as a patch release, bumping the shared workspace version (see Hotfix Release Process below).
Roll Back the Container Image
Section titled “Roll Back the Container Image”GHCR images are immutable by tag. To mitigate:
- Point users to a previous version (per binary crate):
Terminal window for BIN in mif-cli mif-mcp mif-rh-cli mif-rh-mcp; dodocker pull ghcr.io/modeled-information-format/mif-rs/${BIN}:vPREVIOUSdone - Delete the package version via GitHub UI, for each affected package
(
mif-rs/mif-cli,mif-rs/mif-mcp,mif-rs/mif-rh-cli,mif-rs/mif-rh-mcp): Packages ><package>> Package versions > Delete. - Re-tag
latestto the previous good version by re-pushing a known-good tag.
Hotfix Release Process
Section titled “Hotfix Release Process”When a critical bug or security issue is found in the latest release:
1. Create a Hotfix Branch
Section titled “1. Create a Hotfix Branch”git checkout -b hotfix/vX.Y.(Z+1) vX.Y.Z2. Apply the Fix
Section titled “2. Apply the Fix”just checkjust msrv3. Bump Version and Tag
Section titled “3. Bump Version and Tag”The workspace version lives in one place, so a hotfix touches
[workspace.package].version and the 8 [workspace.dependencies.mif-*]
version pins, all in the root Cargo.toml (see
Pre-Release Checklist above for why both matter):
# Edit Cargo.toml: version = "X.Y.(Z+1)"cargo check # regenerates Cargo.lockgit add -Agit commit -m "fix: <description of the critical fix>"4. Merge and Release
Section titled “4. Merge and Release”git checkout maingit merge hotfix/vX.Y.(Z+1)git push origin main
git tag -a vX.Y.(Z+1) -m "Release vX.Y.(Z+1)"git push origin vX.Y.(Z+1)5. If the Bad Version Was on crates.io
Section titled “5. If the Bad Version Was on crates.io”# Yank the bad version from each affected cratecargo yank --version X.Y.Z -p <crate-name># The hotfix tag push triggers an automatic re-publish of X.Y.(Z+1) for# every crate, not just the one that changed — all 12 share one version.Changelog and Release Notes
Section titled “Changelog and Release Notes”CHANGELOG.md (workspace root, covering all 12 crates under the shared
version) is maintained by hand (Keep a Changelog format) and updated
before tagging: the release-prep step moves the ## [Unreleased]
entries under a new ## [X.Y.Z] - <date> heading and updates the compare
links (the /release skill does this in the prep PR). GitHub Release notes
are auto-generated by the Release workflow (generate_release_notes: true).
Conventional commit prefixes map onto changelog sections:
| Commit prefix | Changelog section |
|---|---|
feat: |
Added |
fix: |
Fixed |
docs: |
Documentation |
perf: |
Performance |
refactor: |
Refactored |
test: |
Testing |
chore: |
Miscellaneous |
Best practices:
- Use scoped prefixes for clarity:
feat(mif-schema): add citation validator - Include
BREAKING CHANGE:in the commit body for breaking changes - A release with an empty
[Unreleased]section is a red flag — stop and confirm what the release contains
Deployment Targets Quick Reference
Section titled “Deployment Targets Quick Reference”GitHub Releases
Section titled “GitHub Releases”- URL: https://github.com/modeled-information-format/mif-rs/releases
- Binaries:
mif-cli,mif-mcp,mif-rh-cli,mif-rh-mcp - Platforms: Linux (amd64, arm64), macOS (amd64, arm64), Windows (amd64)
- Attestations: SLSA build provenance + CycloneDX SBOM attestation per binary; verify per SECURITY.md
Container Image (GHCR)
Section titled “Container Image (GHCR)”- Registry:
ghcr.io/modeled-information-format/mif-rs/{bin}— one image per binary crate (mif-cli,mif-mcp,mif-rh-cli,mif-rh-mcp) - Platforms: linux/amd64, linux/arm64
- Base image:
chainguard/glibc-dynamic(minimal attack surface, rebuilt from source continuously) - User:
nonroot:nonroot(unprivileged) - Tags:
vX.Y.Z,X.Y,X,latest,sha-<commit>
crates.io
Section titled “crates.io”- Packages:
mif-core,mif-problem,mif-schema,mif-frontmatter,mif-ontology,mif-embed,mif-store,mif-cli,mif-mcp,mif-rh,mif-rh-cli,mif-rh-mcp— each published independently, each via its own Trusted Publishing (OIDC) config on crates.io (see Prerequisites above). A failure on one crate does not block the others’ channels, but does block anything published after it in the dependency order.
Homebrew Tap
Section titled “Homebrew Tap”- Tap:
{owner}/homebrew-tap(override with theHOMEBREW_TAP_REPOvariable) - Formula: generated from Cargo.toml metadata after each release
Install Methods
Section titled “Install Methods”# From GitHub release (Linux)wget https://github.com/modeled-information-format/mif-rs/releases/download/vX.Y.Z/mif-cli-X.Y.Z-linux-amd64chmod +x mif-cli-X.Y.Z-linux-amd64
# From crates.iocargo install mif-clicargo install mif-mcp
# From sourcecargo install --git https://github.com/modeled-information-format/mif-rs mif-cliTroubleshooting
Section titled “Troubleshooting”See CI-TROUBLESHOOTING.md § Release workflow failures and § Docker build failures for the full table of failure modes and fixes. The release process is complete once every checkbox in Post-Release Verification passes.