ADR-0007: Versioning and Release Model
Context
The project consists of multiple independent components, each in its own repository with its own development
pace (ADR-0004). Components depend on each other through standard
mechanisms — pkg-config with version requirements and CMake's find_package() with minimum versions
(ADR-0005).
A versioning scheme must answer three questions:
- Compatibility: how does a consumer know whether a given version of a dependency is safe to use?
- Coordination: how do independently-versioned components stay coherent as a system?
- Maintenance: how are stable releases maintained while development continues?
Decision
Project-wide major version
The project defines a single major version that applies to all components. Any component at major version N must be compatible with any other component at major version N.
The compatibility guarantee is forward compatibility: a consumer built against an older minor version of a dependency will continue to work with any newer minor version within the same major. The guarantee does not extend in the other direction — a consumer that uses APIs introduced in a newer minor version will require at least that version. Components enforce minimum dependency versions through their pkg-config files and CMake configurations.
Semantic versioning
Each component follows Semantic Versioning:
- Major (
X.0.0): the project-wide major version, shared across all components. - Minor (
X.Y.0): incremented independently by each component when new functionality is added in a backward-compatible manner. - Patch (
X.Y.Z): incremented independently by each component for backward-compatible bug fixes.
Branch model
Each component repository uses two types of branches:
main— the development branch, always targeting the next major version. Breaking changes are accepted.stable/X— the stable branch for major version X. Only backward-compatible changes (minor and patch releases) are accepted. Created when major version X is released.
Releases are identified by tags on the corresponding branch: v1.0.0, v1.1.0, v1.1.1, etc. The
build system derives component version numbers from these tags.
Release process
When the project lead decides to release a new major version:
- Freeze: a global feature freeze is declared across all components. Only integration fixes, breaking
change resolution, and regression fixes are accepted on
main. - Stabilize: components are built and tested together through the workspace until the integrated system passes all acceptance criteria.
- Branch: a
stable/Xbranch is created in every component from the current tip ofmain. - Tag: the initial release
vX.0.0is tagged on the branch point. - Advance:
mainautomatically moves to the next major version (X+1).
There is no fixed release calendar. Major releases are made at the discretion of the project lead when the accumulated changes justify a new stable version.
Maintenance
Each component maintains at least the two most recent stable versions in addition to the development
branch. When stable/X+2 is created, stable/X reaches end of life and no longer receives updates. This
gives users one full major cycle to migrate before losing support.
Bug fixes are applied on whichever branch is most appropriate — the stable branch or main — and then
backported or forward-ported to the other. The direction depends on the nature of the fix and where it was
discovered.
Minor releases on a stable branch (new backward-compatible features) are permitted and follow the same backport/forward-port discipline as bug fixes.
Alternatives Considered
Independent major versions per component. Each component defines its own major version, incrementing it whenever it needs a breaking change. This maximizes autonomy but destroys the system-wide compatibility guarantee — there is no simple way to know which version of component A works with which version of component B without an external compatibility matrix. A shared major version eliminates this problem at the cost of coordinating major bumps across components.
Calendar-based versioning (CalVer). Versions like 2026.03 communicate when a release was made but say
nothing about compatibility or breaking changes. CalVer works well for end-user products where the version
is a marketing tool, but poorly for libraries where consumers need machine-readable compatibility guarantees.
Rolling release with no stable branches. Always ship from main, no maintenance branches. Simple but
unsuitable for a project that provides libraries — downstream consumers need stable APIs they can depend on
for more than one development cycle.
Consequences
- Clear compatibility rule. "Same major version = compatible" is simple to understand and communicate. Downstream consumers and distribution packagers can rely on this guarantee.
- Independent pacing. Components release minor and patch versions at their own pace. A bug fix in the base library does not require a coordinated release of every consumer.
- Freeze cost. Major releases require a global freeze, blocking feature development across all components simultaneously.
- Forward compatibility only. Old consumers work with new providers, but not the reverse. Components declare minimum dependency versions, and the build system enforces them. The workspace ensures coherence during development; distribution packaging ensures it at install time.
- Two supported stable versions. Users have one full major cycle to migrate before losing support.