Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

on:
push:
branches:
- master
pull_request:
branches:
Comment thread
vpetersson marked this conversation as resolved.
- master

permissions:
contents: read

jobs:
lint:
name: Markdown Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Comment thread
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'
7 changes: 7 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"default": true,
"MD013": false,
"MD033": false,
"MD040": true,
"MD041": false
}
16 changes: 16 additions & 0 deletions .pre-commit-config.yaml
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
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pre-commit hook relies on bun being installed on the developer machine (language: system + bun x ...). Without bun, pre-commit run will fail. If bun is not a guaranteed prerequisite for contributors, consider switching to a non-system hook (e.g., a Node-based markdownlint hook with managed dependencies) or documenting bun as a required tool for this repo.

Suggested change
- 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]

Copilot uses AI. Check for mistakes.
files: \.md$
pass_filenames: true
85 changes: 85 additions & 0 deletions 0002-adopt-calver-versioning.md
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.
27 changes: 27 additions & 0 deletions CLAUDE.md
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.
Loading