0001. Monotonic 0.0.x versioning and suite lockstep#

  • Status: Accepted

  • Date: 2026-08-29 (practised since 0.0.51; written down 2026-09-10)

  • Deciders: maintainer

Context#

pain001 is one of five packages that ship together: the core, an MCP server, a language server and two loader plugins. Users install the members separately and combine them, and the failure they hit when the numbers diverge is a version they cannot reason about: is pain001-loader-xlsx==0.0.54 meant to work with pain001==0.0.60? By 0.0.62 the five packages had drifted to four different numbers with no breakage, which is exactly why it went unnoticed.

The project is pre-1.0 and its public surface (CLI flags, CSV column contract, generated XML, plugin protocols) still moves.

Options considered#

  1. Independent SemVer per package with compatibility ranges. Precise, but every member needs its own release judgement and the ranges are what users cannot reason about.

  2. One number across the suite, advancing one 0.0.x step at a time. Coarse, but a version pin on one package is a pin on all, and a release is one coordinated event.

  3. Jump to 1.0 and use minor/patch semantics. Premature while the output contract and plugin protocol are still being shaped; a 1.0 promise that has to be broken is worse than none.

Decision#

Option 2. Every member ships the same 0.0.x number; releases advance by exactly one step; 0.1.0 follows 0.0.999, never 0.0.60. The maintainer decides when the next number opens for work; contributors never bump it. Inside the line, breaking changes are still called out in the CHANGELOG and announced one release ahead (see the stability section of the README).

Consequences#

  • pain001/suite.py lists the members; scripts/check_suite_consistency.py queries PyPI daily and fails when any member lags the core or declares a floor the core has not published.

  • tests/test_suite_conformance.py, a byte-identical file vendored in every repository, checks that the version is restated consistently and that the CHANGELOG’s newest heading is the declared version.

  • A change that adds a message type or alters the required-field set is a five-repository release. That cost is accepted because the alternative was users guessing.

  • scripts/preflight_release.py additionally checks CITATION.cff and the SECURITY.md support table.