Skip to main content

ADR-0007: Versioning and Release Model

Accepted

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:

  1. Freeze: a global feature freeze is declared across all components. Only integration fixes, breaking change resolution, and regression fixes are accepted on main.
  2. Stabilize: components are built and tested together through the workspace until the integrated system passes all acceptance criteria.
  3. Branch: a stable/X branch is created in every component from the current tip of main.
  4. Tag: the initial release vX.0.0 is tagged on the branch point.
  5. Advance: main automatically 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.