-
Notifications
You must be signed in to change notification settings - Fork 0
ADR: Adopt CalVer (YY.MINOR.PATCH) versioning format #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
926ba7f
5dd27b9
1c44cfe
902b2c8
7a3d404
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - master | ||
| pull_request: | ||
| branches: | ||
| - master | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| lint: | ||
| name: Markdown Lint | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
|
vpetersson marked this conversation as resolved.
|
||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: oven-sh/setup-bun@ecf28ddc73e819eb6fa29df6b34ef8921c743461 # v2.1.3 | ||
|
|
||
| - name: Run markdownlint | ||
| run: bun x markdownlint-cli '**/*.md' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "default": true, | ||
| "MD013": false, | ||
| "MD033": false, | ||
| "MD040": true, | ||
| "MD041": false | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||||||||||||||||||
| # See https://pre-commit.com for more information | ||||||||||||||||||||||||
| # See https://pre-commit.com/hooks.html for more hooks | ||||||||||||||||||||||||
| repos: | ||||||||||||||||||||||||
| - repo: https://github.com/pre-commit/pre-commit-hooks | ||||||||||||||||||||||||
| rev: v5.0.0 | ||||||||||||||||||||||||
| hooks: | ||||||||||||||||||||||||
| - id: check-merge-conflict # checks for files that contain merge conflict strings. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - repo: local | ||||||||||||||||||||||||
| hooks: | ||||||||||||||||||||||||
| - id: markdownlint | ||||||||||||||||||||||||
| name: markdownlint | ||||||||||||||||||||||||
| entry: bun x markdownlint-cli --fix | ||||||||||||||||||||||||
| language: system | ||||||||||||||||||||||||
|
Comment on lines
+9
to
+14
|
||||||||||||||||||||||||
| - repo: local | |
| hooks: | |
| - id: markdownlint | |
| name: markdownlint | |
| entry: bun x markdownlint-cli --fix | |
| language: system | |
| - repo: https://github.com/igorshubovych/markdownlint-cli | |
| rev: v0.41.0 | |
| hooks: | |
| - id: markdownlint | |
| args: [--fix] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # 2. Adopt CalVer (YY.MINOR.PATCH) versioning format | ||
|
|
||
| Date: 2026-03-23 | ||
|
|
||
| ## Status | ||
|
|
||
| Proposed | ||
|
|
||
| ## Context | ||
|
|
||
| We currently use [Semantic Versioning](https://semver.org/) (SemVer, MAJOR.MINOR.PATCH) across all sbomify projects. While SemVer has served us well, we want to revisit whether it remains the best fit as the organization and its release practices evolve. | ||
|
|
||
| Below we evaluate the major versioning schemes, presenting the strongest case for each before identifying trade-offs. | ||
|
|
||
| ### SemVer (MAJOR.MINOR.PATCH) | ||
|
|
||
| **Best case:** SemVer is the most widely adopted versioning standard in open source. Its explicit compatibility contract — increment MAJOR for breaking changes, MINOR for features, PATCH for fixes — gives consumers a machine-readable signal about upgrade safety. Ecosystem tooling (npm, Cargo, pip version specifiers, Dependabot) is built around SemVer semantics, making automated dependency management straightforward. | ||
|
|
||
| **Challenges:** In practice, the MAJOR version bump is often political rather than technical — teams delay it for years or inflate it unnecessarily, leading to what Mahmoud Hashemi calls the ["fatuous 2.0 problem"](https://sedimental.org/designing_a_version.html). Version numbers carry no temporal information: `3.12.1` reveals nothing about when the software was released or how current it is. For supply chain security tooling, where freshness signals matter, this is a meaningful gap. | ||
|
|
||
| ### CalVer — YY.MM.MICRO | ||
|
|
||
| **Best case:** Encodes both year and month of release at a glance. Proven at scale by [Black](https://black.readthedocs.io/) (the Python formatter), [Twisted](https://twisted.org/), and in a related form by [Ubuntu](https://ubuntu.com/) (YY.0M). Ideal for projects with monthly or near-monthly release cadences. Makes maintenance status immediately visible — users can instantly see whether a release is recent. | ||
|
|
||
| **Challenges:** When months are skipped, version sequences have gaps (26.1.0, 26.3.0, 26.7.0), which can look inconsistent. Multiple releases within one month are forced into micro-version increments that may obscure whether a release contains features or fixes. The zero-padding question (26.3 vs 26.03) introduces formatting inconsistencies. This scheme works best for projects that release on a predictable monthly cadence, which does not describe sbomify's release pattern. | ||
|
|
||
| ### CalVer — YYYY.MINOR.PATCH | ||
|
|
||
| **Best case:** The four-digit year makes it unambiguous that this is calendar versioning — no one mistakes `2026` for a SemVer major version. Recognized in enterprise contexts through [JetBrains IDEs](https://www.jetbrains.com/) (e.g., PyCharm 2025.3.2). | ||
|
|
||
| **Challenges:** Versions are visually longer, and a four-digit major version can trigger warnings or look odd in tools that expect small integers. Docker tag sorting, Helm chart version constraints, and some CI tools handle four-digit major versions less gracefully than two-digit ones. | ||
|
|
||
| ### CalVer — YY.MINOR.PATCH | ||
|
|
||
| **Best case:** Combines the temporal context of CalVer with the familiar three-segment shape of SemVer. The two-digit year signals when the software was released; sequential MINOR and PATCH segments retain intuitive incremental semantics. Accommodates any release cadence equally well — two releases per year or twelve. Migration from SemVer is operationally smooth because the version shape is structurally identical: existing CI/CD pipelines and most dependency tooling that expect three numeric segments continue to parse these versions correctly. However, consumers may need to revisit their version constraints: for example, npm/Cargo/Helm-style `^26.1.0` ranges typically cap at `<27.0.0`, which in a YY.MINOR.PATCH scheme can prevent upgrades in the next calendar year, and `^` is not a valid operator under [PEP 440](https://peps.python.org/pep-0440/). In Python ecosystems, constraints like `>=26.1.0` or `~=26.1.0` remain valid PEP 440 specifiers, but their year-based semantics should be documented for users who rely on cross-year upgrade behavior. This is the scheme adopted by [pip](https://pip.pypa.io/) — the most widely used Python package manager — since 2024, and it is fully PEP 440 compliant. | ||
|
|
||
| **Challenges:** Without explicit documentation, users might mistake versions for SemVer and expect MAJOR-version compatibility semantics. Requires clear communication in release notes and documentation. | ||
|
|
||
| ### 0ver (0.x.y) | ||
|
|
||
| **Best case:** By never incrementing past 0, projects avoid prematurely signaling stability. This can be appropriate for experimental or rapidly changing software where any release might contain breaking changes. | ||
|
|
||
| **Challenges:** Communicates permanent instability, which is unsuitable for enterprise supply chain tooling where customers need confidence in the software's maturity. Offers no temporal or compatibility information. | ||
|
|
||
| ### Build/hash-based versioning | ||
|
|
||
| **Best case:** Fully automated — every build gets a unique identifier derived from the commit hash or build number, with no human judgment required. Eliminates all versioning debates. | ||
|
|
||
| **Challenges:** Opaque to users, provides no semantic or temporal signal, and is poorly suited for dependency management. Not viable for published packages or tools consumed by external users. | ||
|
|
||
| ### References | ||
|
|
||
| - [semver.org](https://semver.org/) — Semantic Versioning specification | ||
| - [calver.org](https://calver.org/) — Calendar Versioning convention and format notation | ||
| - [Mahmoud Hashemi, "Designing a Version"](https://sedimental.org/designing_a_version.html) — argues CalVer provides absolute rather than relative version semantics | ||
| - [pip CalVer adoption](https://pip.pypa.io/en/stable/news/) — precedent for YY.MINOR.PATCH at scale (adopted 2024) | ||
| - [PEP 440](https://peps.python.org/pep-0440/) — Python version identification and dependency specification | ||
|
|
||
| ## Decision | ||
|
|
||
| We will adopt **CalVer with the `YY.MINOR.PATCH` format** across all sbomify projects. | ||
|
|
||
| The convention is: | ||
|
|
||
| - **YY** — two-digit calendar year (26, 27, 28…) | ||
| - **MINOR** — sequential feature release within the year, starting at 1 (26.1.0, 26.2.0, 26.3.0…) | ||
| - **PATCH** — bugfix and security releases (26.1.1, 26.1.2…) | ||
| - **Pre-releases** — ecosystem-specific suffixes, e.g. SemVer-style `26.1.0-rc.1`, `26.1.0-alpha.1` and PEP 440–style `26.1.0rc1`, `26.1.0a1` for Python packages | ||
|
|
||
| This format best fits sbomify because: | ||
|
|
||
| 1. **Freshness signal matters in supply chain security.** Version numbers appear in SBOMs, VEX documents, compliance reports, and procurement records. A year prefix immediately communicates how current the tooling is — and running outdated supply chain tools is itself a security risk. | ||
| 2. **Our release cadence is irregular.** Releases are driven by specification updates (SPDX 3.x, CycloneDX), vulnerability disclosures, and customer needs — not a fixed monthly calendar. `YY.MINOR.PATCH` accommodates two releases or twelve releases in a year equally well. | ||
| 3. **Seamless migration for internal tooling.** The three-segment structure is identical to SemVer, so existing CI/CD pipelines, Docker tags, and changelog tooling that only assume a `X.Y.Z` numeric pattern typically require no syntax changes. Downstream consumers that use SemVer-style version constraints (for example `<2.0.0`, `^1.4.0`, or Helm constraints) will need to revisit those constraints when adopting versions like `26.1.0`. | ||
| 4. **Proven at scale.** pip uses this exact scheme, validating it across millions of installations. | ||
| 5. **PATCH segment for hotfixes.** We retain the ability to ship targeted security patches without a full minor release. | ||
|
|
||
| ## Consequences | ||
|
|
||
| - Version numbers across all sbomify projects will immediately communicate the release year, making it easy for users and auditors to assess software currency. | ||
| - The "major version anxiety" problem disappears — there is no agonizing over when to bump from 2.x to 3.x. | ||
| - Users accustomed to SemVer compatibility semantics (where MAJOR changes signal breaking changes) will need clear documentation that our versioning does not carry that contract. Release notes must explicitly flag breaking changes. | ||
| - Downstream consumers who parse versions for compatibility signals will need to adjust expectations; we should document the versioning scheme in each repository's README and in our public documentation. | ||
| - The first release under this scheme will be versioned `26.1.0` (or the appropriate year at the time of adoption). All subsequent releases follow the convention above. | ||
| - Existing version references in package managers, Docker registries, and CI configurations will continue to work — the structural change is semantic, not syntactic. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Purpose | ||
|
|
||
| This is the **organization-wide** ADR (Architecture Decision Record) repository for [sbomify](https://github.com/sbomify). It holds cross-cutting architectural decisions that span multiple repositories or affect the organization as a whole. Repo-specific ADRs live in their respective repos. | ||
|
|
||
| ## ADR Format | ||
|
|
||
| All ADRs follow the Nygard template with sections: Title, Status (Proposed/Accepted/Deprecated/Superseded), Context, Decision, Consequences. See `0001-record-architecture-decisions.md` for the canonical example. | ||
|
|
||
| ## Managing ADRs | ||
|
|
||
| ADRs are managed with [adr-tools](https://github.com/npryce/adr-tools/tree/master): | ||
|
|
||
| ```bash | ||
| adr new "Title of the decision" # Create a new ADR | ||
| adr list # List existing ADRs | ||
| adr new -s <number> "New title" # Supersede a previous ADR | ||
| ``` | ||
|
|
||
| The `.adr-dir` file is set to `.` — ADR files live in the repository root (not a subdirectory). | ||
|
|
||
| ## Naming Convention | ||
|
|
||
| ADR files are numbered sequentially: `NNNN-slug.md` (e.g., `0001-record-architecture-decisions.md`). The `adr-tools` CLI handles numbering automatically. |
Uh oh!
There was an error while loading. Please reload this page.