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

ADR-0007: Release and versioning policy

  • Status: Accepted
  • Date: 2026-05-20
  • Authors: angelnicolasc
  • Reviewers: sole-maintainer decision record

Context

Meridian ships three artefacts on three different cadences:

  • The meridian-core and meridian-kernels crates → crates.io.
  • The meridian Python package (the vLLM plugin) → PyPI.
  • The mdBook → GitHub Pages.

A release that bumps any of them must keep the others coherent — breaking the Python plugin while leaving meridian-core stable would strand operators on a half-upgraded stack. We need an explicit policy for what triggers a release, which artefacts move together, and how breaking changes are signalled — both for the pre-1.0 phase (today) and for the post-1.0 phase (after a year of production use).

We also need to commit to a provenance and SBOM story so the project is auditable end-to-end. SLSA Level 2 is the bar reasonable consumers expect in 2026.

Decision

SemVer interpretation

  • Post-1.0 — strict SemVer. Breaking changes require a major bump; additive changes require a minor bump; bugfixes are patches.
  • Pre-1.0 — breaking changes ship as minor bumps. The CHANGELOG entry for every minor bump must list every breaking change under a BREAKING CHANGE: footer. Operators reading the CHANGELOG before upgrading get a complete diff with no surprises.

Release cadence

  • Minor: every six weeks. The window is fixed; the content is whatever passes CI plus the ADRs accepted in that window.
  • Patch: on demand for security fixes and high-severity bugs. No fixed cadence — patches ship within 48 h of a confirmed report for SEV-1 (CVSS ≥ 7.0), per the security policy.

Branch policy

  • main is always-releasable. Nothing merges that breaks CI.
  • No release branches. release-plz drives the changelog and tag from main directly.
  • Hotfixes are commits on main followed by a patch tag. We do not back-port to N-1 minors during pre-1.0 — operators on an old pre-1.0 minor are expected to upgrade forward.

Artefact set per release

Every tagged release ships:

  • Compiled Rust static libs and the meridian Python wheel — built and uploaded as GitHub release assets by release.yml.
  • CycloneDX SBOM for the Rust workspace and the Python wheel, attached to the GitHub release.
  • SLSA Level 2 provenance attestation, generated by slsa-github-generator and uploaded as a release asset.
  • A signed Git tag (when GPG is configured) and an annotated tag otherwise.
  • mdBook deploy → GitHub Pages (via docs.yml on push to main).

Note on crates.io and PyPI publishing: release.yml carries a manual (workflow_dispatch) publish job. It defaults to dry-runcargo publish --dry-run for the crates plus twine check on the wheel — so packaging is validated on every invocation without pushing anything. Selecting publish_mode = live performs the real publish, but each live step is token-gated: it runs only when CARGO_REGISTRY_TOKEN (crates.io) and PYPI_API_TOKEN (PyPI) are present in repo secrets. Until those are configured the job is safe to run and simply validates. This keeps publish off the automatic tag path while the project stabilises pre-1.0.

The three artefacts (crates, wheel, mdBook) move together. A release with a partial set is a CI failure, not a partial release.

Version coupling

The policy is that meridian-core, meridian-kernels, and the meridian Python package carry the same version string. release-plz enforces this for the Rust side via Cargo workspace inheritance.

Current state of automation as of v0.1.0: python/pyproject.toml carries a hardcoded version = "0.1.0" that must be bumped manually in sync with the Cargo workspace. A build hook to read the workspace version automatically is planned but not yet wired. Operators upgrading the Rust workspace must also update python/pyproject.toml until that hook is in place.

Yanking

We will yank a crates.io publish only for a security incident or a correctness regression with no workaround. Style fixes, doc errors and "meh, the version should have been a minor" do not justify yanking — those get a forward fix in the next release.

Consequences

Positive

  • Operators reading the CHANGELOG ahead of an upgrade get a complete picture of what's breaking — no surprises during pre-1.0.
  • One number versions the entire stack. Cross-artefact compatibility questions ("does the wheel work with crate X.Y.Z?") become trivially answerable.
  • SLSA L2 provenance + SBOM at every release make Meridian a credible citizen of supply-chain-conscious deployments.
  • A six-week minor cadence is short enough to feel responsive but long enough that operators don't get release fatigue.

Negative / risks

  • A monolithic version bump means even a docs-only release moves every artefact. Cargo wastes a publish; PyPI wastes a wheel. The cost is real but small — well below the cost of decoupled versions diverging during an incident.
  • The fixed six-week cadence will sometimes ship "nothing material". We accept that; consistency beats hoarding.

Neutral

  • The policy is enforced by release-plz config plus the CI release workflow. There is no human gate between a green CI on main and a tagged release.

Alternatives considered

Independent versions per artefact. Considered for the parity it gives with how Cargo and PyPI usually work in larger projects. Rejected because the cross-artefact compatibility matrix becomes a second README, and we are not large enough yet to justify the overhead.

Release on every merge to main. Considered as a continuous-release model. Rejected because it would publish to crates.io and PyPI dozens of times a week, polluting the index and triggering downstream Dependabot noise for every contributor.

Long-lived release branches per minor. Considered for the back-port story it enables. Rejected pre-1.0 because we explicitly do not support N-1; operators are expected to upgrade forward.

References