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-coreandmeridian-kernelscrates → crates.io. - The
meridianPython 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
mainis always-releasable. Nothing merges that breaks CI.- No release branches.
release-plzdrives the changelog and tag frommaindirectly. - Hotfixes are commits on
mainfollowed 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
meridianPython wheel — built and uploaded as GitHub release assets byrelease.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-generatorand uploaded as a release asset. - A signed Git tag (when GPG is configured) and an annotated tag otherwise.
- mdBook deploy → GitHub Pages (via
docs.ymlon push tomain).
Note on crates.io and PyPI publishing:
release.ymlcarries a manual (workflow_dispatch)publishjob. It defaults to dry-run —cargo publish --dry-runfor the crates plustwine checkon the wheel — so packaging is validated on every invocation without pushing anything. Selectingpublish_mode = liveperforms the real publish, but each live step is token-gated: it runs only whenCARGO_REGISTRY_TOKEN(crates.io) andPYPI_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-plzconfig plus the CI release workflow. There is no human gate between a green CI onmainand 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
- SemVer 2.0.0.
- Keep a Changelog.
- SLSA Specification.
release-plz— drives the changelog and tag.- CycloneDX — SBOM format.
- ADR-0006 (Disagg KV transfer) — wire format
versionfield references this policy. SECURITY.md— disclosure SLAs.