Skip to content

sparkles:versions — Scheme Catalogue and Provenance

Audience: contributors using or extending the shipped version schemes. This page is the per-scheme catalogue: it records each scheme's declared capabilities, real-world example strings, ordering rules and edge cases, native-range grammar, and the authoritative source every example was checked against. For the desired-state specification of the traits and generic algorithms, see SPEC.md.

1. Overview

This catalogue documents each shipped scheme: its purl type, real-world example strings, ordering rules and edge cases, native-range grammar, the capabilities it declares, and the authoritative source every example was checked against. For the concepts these schemes conform to (isVersion!T, isVersionScheme!S) and the capability traits, see SPEC.md.

d
import sparkles.versions.schemes.semver : SemVer;
import sparkles.versions.schemes.pypi   : PypiVersion;
import sparkles.versions.schemes.deb    : DebianVersion;

auto rust   = SemVer      .parse("1.78.0").value;
auto python = PypiVersion .parse("3.13.0a1").value;
auto apt    = DebianVersion.parse("2:4.13.1-0ubuntu0.16.04.1.1~").value;

Eleven schemes ship: semver is the SemVer 2.0.0 reference (it also drives most package managers at the value level); six are D-internal compact encodings; three (pypi, maven, deb) are structural — their ordering cannot be reduced to an integer key; and generic is the opaque baseline.

2. Capability matrix

This is the canonical capability matrix for the shipped schemes; adding a scheme adds a row here. Every scheme provides the required isVersion!T surface (opCmp + toString) and differs only in the optional capabilities below. Each yes/no is read against the traits in SPEC §3.2 (version capabilities) and SPEC §6.2 (scheme capabilities); the rationale behind each cell is in the per-scheme sections (§3).

SchemeorderKeyprereleasecomponentsbuildnativeRangeloose
semveryesyesyesyesyesyes
dmdyesyesyesnoyesyes
dmd_compactyesyesyesnoyesno
tinyyesnoyesnoyesyes
calver_yymmyesnoyesnoyesyes
calver_yyyymmddyesnoyesnoyesyes
vimyesnoyesnoyesyes
pypinoyesyesnoyesyes
mavennoyesnonoyesno
debnonononoyesno
genericnononononono

Two cross-cutting rules explain most of the table:

  • The structural schemes (pypi, maven, deb) omit orderKey — their ordering does not pack into an unsigned integer of any width (see §3.8–§3.10), so their opCmp does the full structural walk.
  • components is arity-free; only schemes whose list begins ["major","minor","patch"] also satisfy hasSemVerComponents and get caret ^ / tilde ~. The calendar schemes declare ["year","month","day"]components yes, caret/tilde no.

3. Per-scheme catalogue

Each subsection records: the purl type string, real-world example strings, ordering rules and edge cases, the declared capabilities (and, for structural schemes, why orderKey is omitted), parse modes, the native-range grammar where applicable, and provenance.

3.1 semverSemVer (Semantic Versioning 2.0.0)

  • purl type: semver. The same value grammar drives npm, cargo, gem, composer, golang, hex, conan, nginx, mozilla, and github — those purl types map to this scheme via the purl→scheme table while keeping their own native-range dialects.
  • Examples: 20.13.1 (Node.js), 1.78.0 (Rust), 1.30.0 (Kubernetes), 17.3.0 (Angular), 18.3.1 (React), 6.8.9 (Linux), 2.45.1 (Git), 8.3.7 (PHP), 3.3.1 (Ruby), 1.26.0 (Nginx), 2.4.59 (Apache httpd), 7.2.4 (Redis), 7.0.8 (MongoDB), 3.45.3 (SQLite), 8.7.1 (cURL), 7.0.1 (FFmpeg), 14.5.1 (macOS), 26.1.1 (Docker). With prerelease/build: 1.0.0-rc.1, 1.0.0-alpha.1+build.5.
  • Ordering (SemVer §11): compare major, then minor, then patch numerically; a version with a prerelease has lower precedence than the same major.minor.patch without one (1.0.0-alpha < 1.0.0); prerelease identifiers compare per SemVer §11.4 (numeric identifiers numerically, alphanumeric lexically, numeric < alphanumeric, more fields wins on a shared prefix); build metadata (+…) is ignored in ordering (SemVer §10).
  • Edge cases: major dominates everything — 2.0.0-alpha > 1.999.999. Leading zeroes in numeric identifiers are rejected in strict mode.
  • Capabilities: orderKey yes — the major:minor:patch:has-no-prerelease shape packs into a ulong with the stable-flag at the LSB, so unsigned compare reproduces SemVer precedence up to the prerelease-identifier tiebreak (equal keys fall through to the full opCmp). prerelease yes, components yes, build yes. nativeRange yes (node-semver grammar). loose yes.
  • Parse modes: strict follows SemVer 2.0.0 exactly. loose additionally accepts a leading v (v1.2.3) and partial versions (1, 1.2), infilling missing components with 0 — this is how PostgreSQL 16.3 round-trips.
  • Native range grammar (node-semver subset): caret ^1.2.0, tilde ~1.2.0, wildcard 1.2.x / 1.*, hyphen 1.2.0 - 1.5.0, union ||, AND-by-space >=1.2.0 <2.0.0, plain comparators > >= < <= =. Prerelease-in-range follows the node-semver rule (a prerelease satisfies a comparator only when some comparator in the same set names a prerelease of the same major.minor.patch).
  • Provenance: semver.org 2.0.0; node-semver grammar at github.com/npm/node-semver. Example strings checked against upstream releases (nodejs/node, rust-lang, kubernetes, git, php, etc.).

3.2 dmdDmd (D compiler internal)

  • purl type: none (D-internal; not published to a package registry).
  • Examples: 2.111.0, 2.079.0. The minor field is printed zero-padded to 3 digits (079), while values ≥ 100 print at their natural width (111).
  • Ordering: standard 3-component numeric compare with SemVer-style prerelease tiebreak; the zero-padding is a formatting property only and does not affect ordering (2.079.0 and a hypothetical 2.79.0 would compare equal numerically — but the parser only admits the 3-digit form).
  • Capabilities: orderKey yes, prerelease yes, components yes. build no (DMD releases carry no build metadata). nativeRange yes — SemVer-shaped, so it inherits the SemVer/npm range grammar. loose yes.
  • Parse modes: strict requires the 3-digit minor; loose relaxes the leading-v and partial-version rules as for semver.
  • Provenance: dlang.org/changelog (2.079.0 and 2.111.0 release notes).

3.3 dmd_compactDmdCompact (4-byte bitfield encoding)

  • purl type: none (D-internal compact storage).
  • Examples: 2.111.0, 2.111.0-beta.2, 2.111.0-rc.3.
  • Encoding: a 4-byte packed integer. The prerelease is encoded as a 2-bit phase (beta < rc < stable) plus a small number, exploiting the fact that DMD prereleases follow the constrained grammar beta.N / rc.N. Because the (phase, num) pair sits just below the stable marker in the packed integer, a single unsigned compare yields 2.111.0-beta.N < 2.111.0-rc.M < 2.111.0.
  • Ordering: entirely via the packed integer; the phase encoding is monotone by construction.
  • Capabilities: orderKey yes — the 4-byte packed integer is the order key, so OrderKeyType!DmdCompact is uint. prerelease yes, components yes. build no. nativeRange yes (SemVer-shaped). loose no — the compact encoding admits only the exact canonical forms it can represent, so there is no lenient parse.
  • Edge cases: the reserved 4th phase code is rejected by the parser; prerelease numbers beyond the encodable range are a parse error rather than a silent truncation.
  • Provenance: DMD prerelease tags on github.com/dlang/dmd/releases (v2.111.0-beta.1, v2.111.0-rc.1, …).

3.4 tinyTiny (4-byte, no prerelease)

  • purl type: none (internal storage-sensitive use).
  • Examples: 7.8.9.
  • Ordering: three numeric components packed into 4 bytes; plain unsigned compare.
  • Capabilities: orderKey yes — the three components pack into 4 bytes, so OrderKeyType!Tiny is uint. components yes. prereleaseno, build no. nativeRange yes (SemVer-shaped grammar over the three components). loose yes (partial versions infill with 0).
  • Provenance: internal; no external ecosystem. The example exercises the no-prerelease, no-build packed path.

3.5 calver_yymmCalVerYYMM (Ubuntu-style CalVer)

  • purl type: none (calendar scheme; Ubuntu does not publish a purl type for the distro version itself).
  • Examples: 24.04.1 (Ubuntu 24.04.1 LTS). The month is printed zero-padded to 2 digits (04).
  • Ordering: numeric compare on (year, month, patch) — chronological because the fields are most-significant-first.
  • Capabilities: orderKey yes. components yes — declared honestly as ["year","month","patch"], so hasComponents holds (it gets generic compare and truncateTo!"month") but hasSemVerComponents does not (a calendar version has no caret/tilde). prerelease no, build no. nativeRange yes. loose yes.
  • Edge cases: the 2-digit month is a formatting width, not a range constraint — 24.4.1 is not accepted in strict mode because the canonical form pads the month.
  • Provenance: ubuntu.com point-release announcements (24.04.1 LTS, August 2024).

3.6 calver_yyyymmddCalVerYYYYMMDD (Arch-style CalVer)

  • purl type: none (calendar scheme).
  • Examples: 2024.05.01 (Arch Linux monthly ISO). Both month and day print zero-padded to 2 digits.
  • Ordering: numeric compare on (year, month, day).
  • Capabilities: orderKey yes. components yes — declared as ["year","month","day"]; hasComponents holds, hasSemVerComponents does not (no caret/tilde for a date version). prerelease no, build no. nativeRange yes. loose yes.
  • Provenance:archlinux.org/releng/releases (monthly ISO snapshots).

3.7 vimVimVer (4-digit zero-padded patch)

  • purl type: none (Vim's patch scheme is project-specific).
  • Examples: 9.1.0400 (Vim patch 9.1.0400). The patch field prints zero-padded to 4 digits.
  • Ordering: numeric compare on (major, minor, patch).
  • Capabilities: orderKey yes, components yes. prereleaseno, build no. nativeRange yes. loose yes.
  • Edge cases: the 4-digit patch is a formatting width; Vim's running patch counter has millennia of headroom in the packed field.
  • Provenance: github.com/vim/vim release tags (patch 9.1.0400).

3.8 pypiPypiVersion (PEP 440) — structural, no orderKey

  • purl type: pypi.

  • Examples: 3.13.0a1 (alpha), 1.0.0.post1 (post-release), 2.0.0.dev1 (dev-release), 1.0.0+local (local version label), 1!2.0.0 (explicit epoch 1).

  • Ordering (PEP 440): compare lexicographically by structured segments in this order: epoch, the release tuple (numeric, zero-padded to equal length), then the pre/post/dev ranking, then the local version. Within a release, the segment classes sort:

    1.dev0 < 1.0.dev456 < 1.0a1 < 1.0a2.dev456 < 1.0a12 < 1.0b1
          < 1.0b2 < 1.0rc1 < 1.0 < 1.0.post456 < 1.0.15 < 1.1.dev1

    Key facts: a dev release sorts before a pre-release of the same version; a post release sorts after the final release; a local version (+local) sorts after the same public version, and a numeric local segment outranks a lexicographic one. An explicit epoch dominates everything (1!1.0 > 1.0).

  • Why orderKey is omitted: the comparison is genuinely structural. The release tuple is unbounded in length, the pre/post/dev ranking is a small enum crossed with an unbounded number, and the local version is an arbitrary dot-separated mix of numeric and lexicographic segments. There is no fixed-width integer whose unsigned compare reproduces this order, so pypi declares no hasOrderKey primitive and its opCmp walks the structure. Generic sort and Ranges!PypiVersion therefore use the comparison-based fallback path — exactly the contract the DbI design guarantees.

  • Capabilities: prerelease yes (isPrerelease true for aN/bN/rcN/.devN), components yes (the release tuple exposes major/minor/patch). build no — the local version participates in ordering, so it is not SemVer-style build metadata. orderKey no. nativeRange yes. loose yes (PEP 440 normalisation: case-folding, v-prefix stripping, separator canonicalisation 1.0-a11.0a1).

  • Native range grammar (PEP 440 specifier set): comma-separated AND of clauses using ==, !=, <, <=, >, >=, ~= (compatible release), and === (arbitrary equality). ~=1.4.5 means >=1.4.5, ==1.4.*. Trailing .* wildcards are supported on ==/!=.

  • Provenance:PEP 440 / packaging.python.org version specifiers (canonical ordering example verified verbatim). Example strings checked against python.org/downloads (CPython 3.13.0a1).

3.9 mavenMavenVersion (ComparableVersion) — structural, no orderKey

  • purl type: maven.

  • Examples: 1.0, 1.0-SNAPSHOT, 1.0-alpha-1, 1.0-rc1, 1.0-1.

  • Ordering (Maven ComparableVersion): the string is tokenised at ., -, _, and digit↔letter transitions; trailing "null" tokens (0, empty, final, ga, release) are trimmed so that 1.0.0 == 1. Numeric tokens compare numerically; qualifier tokens compare by the fixed rank:

    alpha < beta < milestone < rc (= cr) < snapshot < ""(= ga = final = release) < sp

    Hence 1.0-alpha-1 < 1.0-beta-1 < 1.0-milestone-1 < 1.0-rc1 < 1.0-SNAPSHOT < 1.0 < 1.0-sp1. Comparison is case-insensitive (1.0-RC1 == 1.0-rc1), and alpha/beta/milestone may be abbreviated a/b/m when directly followed by a number.

  • Why orderKey is omitted: like PyPI, the order is structural — an unbounded alternation of numeric and qualifier tokens with the "ga beats snapshot beats pre-release" rule and trailing-null trimming. No fixed-width integer key reproduces it, so maven declares no hasOrderKey and opCmp compares token lists.

  • Capabilities: prerelease yes (the qualifier rank below the empty/ga release). components noMavenVersion declares no components list because its token model has no fixed ordered set of numeric fields (the token count and kinds vary per value), and the trait is all-or-nothing so a sometimes-available prefix does not qualify. build no, orderKey no. nativeRange yes (interval notation, below). loose noComparableVersion already accepts essentially anything, so there is no separate lenient mode.

  • Native range grammar (Maven version requirements): bracket interval notation — [1.0] (exactly), (,1.0] (≤), [1.2,1.3] (closed), [1.0,2.0) (half-open), [1.5,) (≥), and unions via comma-separated intervals (,1.0],[1.2,). Caveat preserved from the spec: because 2.0-rc1 < 2.0, [1.0,2.0) includes 2.0-rc1.

  • Provenance:maven.apache.org POM version order specification and the maven-artifact ComparableVersion test corpus.

3.10 debDebianVersion (dpkg) — structural, no orderKey

  • purl type: deb.
  • Examples: 1.2.3-4 (upstream 1.2.3, Debian revision 4), 2:4.13.1-0ubuntu0.16.04.1.1~ (epoch 2, upstream 4.13.1, an Ubuntu security revision ending in ~).
  • Structure: [epoch:]upstream_version[-debian_revision]. The epoch is an optional unsigned integer (default 0) compared first; then the upstream version; then the Debian revision.
  • Ordering (dpkg algorithm): epoch numerically first; then upstream and revision are each compared left-to-right by alternating non-digit and digit runs. In a non-digit run, all letters sort before all non-letters, and the tilde ~ sorts before everything — even the end of the string, so 1.0~beta1 < 1.0 and ~~ < ~~a < ~ < "" < a. In a digit run, leading zeroes are ignored and an empty run counts as 0.
  • Why orderKey is omitted: the dpkg algorithm is the canonical example of a non-packable order — the ~-before-empty rule alone defeats any fixed-width integer key (a shorter string can sort after a longer one that ends in ~). deb declares no hasOrderKey; its opCmp implements the dpkg two-phase walk directly.
  • Capabilities: nativeRange yes (dpkg relations). prereleaseno (the ~ mechanism subsumes it but is not exposed as an isPrerelease boolean), components no (the upstream version is free-form, not a guaranteed triple), build no, orderKeyno, loose no (dpkg parsing is already permissive; there is no stricter mode to relax from).
  • Native range grammar (dpkg relations): the comparison relations >=, <=, << (strictly less), >> (strictly greater), =, each followed by a version, as used in dpkg --compare-versions and Depends: fields (e.g. >= 2.0, << 3.0).
  • Provenance:Debian Policy §5.6.12 (version) and the dpkg --compare-versions algorithm (~-ordering example verified verbatim).

3.11 genericGeneric (void-hook baseline)

  • purl type: generic.
  • Examples: any opaque string — "build-2024-05-30", "r1234", "snapshot-xyz".
  • Ordering: plain lexicographic (code-point) comparison of the raw string. No structure is parsed.
  • Capabilities: none. orderKey no, prerelease no, components no, build no, nativeRange no, loose no. This is the mandated void-hook baseline: it provides only the required isVersion!T surface (opCmp + toString) and therefore exercises every generic algorithm's fallback path — comparison-based sort, comparison-based Ranges!Generic, and the "no native range, exact versions only" branch of the VERS/purl layers.
  • Parse: parse always succeeds (any string is a valid Generic); there is no parseLoose or parseNativeRange.
  • Provenance: none required — it is a structural baseline, not an ecosystem mapping.

4. Adding a new scheme

A scheme is just a struct conforming to isVersionScheme!S. For the step-by-step recipe — required surface, capability declaration, conformance assertions, registration, and tests — see Add a new scheme.

5. Deferred schemes

These are catalogued for the test corpus but not shipped. The corrected real-world example strings are recorded here so future implementers do not copy the wrong forms (several appeared mangled in the original analyst catalogue).

5.1 Bucket F — hyphenless pseudo-SemVer

Versions whose alphanumeric prerelease segment lacks the SemVer - separator. Supporting them needs a per-scheme custom tokeniser at the numeric→alphanumeric boundary, which is its own design problem.

EcosystemWrong form (do not use)Correct real-world formNote
Go1.22.3rc1go1.22rc1 or go1.22.3go prefix; prerelease glued without a hyphen
Python (alt)3.12.3a13.13.0a1covered by pypi; listed here as the glued form
OpenSSL legacy1.1.1wsingle-letter patch suffix
OpenSSH9.7p1pN portable suffix, no hyphen
Unity2023.2.1f1fN "final" glue suffix

5.2 Part-2 heavyweight schemes

Schemes needing wider cores, 4+ components, or pure-alphanumeric fallback. Tracked in the design notes — open questions.

Note — 4-component arity is no longer a design blocker. With the list-based components capability, the 4-part schemes below (.NET, Windows, Chrome) are now directly expressible — each declares ["major","minor","build","revision"] and packs into a ulongorderKey. They remain unshipped only on implementation bandwidth (parser + native-range grammar per ecosystem), not on a missing abstraction. Java's 21.0.1+12 and Android's pure-alphanumeric UP1A.231005.007 still need, respectively, build-metadata-aware parsing and a no-numeric-core fallback.

EcosystemCorrect real-world formNote
Java (modern)21.0.1+123-part + build; not the 4-part 21.0.1.2
.NET assemblies8.0.0.04-part Major.Minor.Build.Revision
Windows10.0.19045.33244-part heavyweight
Google Chrome125.0.6422.604-part heavyweight
Android buildsUP1A.231005.007pure-alphanumeric; bypasses any numeric core

6. Provenance appendix

Every example string baked into this catalogue was checked against an authoritative source before inclusion. The structural-ordering rules for the three non-packable schemes were verified verbatim against their canonical specifications.

SchemeWhat was verifiedSource
semver2.0.0 grammar + §11 ordering; npm range grammarsemver.org/spec/v2.0.0; node-semver
dmd / dmd_compact2.079.0, 2.111.0, beta/rc prerelease tagsdlang.org/changelog; dlang/dmd releases
tinypacked no-prerelease pathinternal
calver_yymmUbuntu 24.04.1 LTS point releaseubuntu.com
calver_yyyymmddArch 2024.05.01 monthly ISOarchlinux.org/releng/releases
vimpatch 9.1.0400github.com/vim/vim
pypiPEP 440 ordering example (verbatim); 3.13.0a1packaging.python.org version specifiers; python.org
mavenqualifier order + interval notation (verbatim)maven.apache.org POM version order
debdpkg ~-ordering rule (verbatim); epoch/revisionDebian Policy §5.6.12
genericstructural baseline; no ecosystemn/a

SPEC.md — desired-state specification (traits, Ranges!V, VERS, purl) → PLAN.md — delivery milestones