ADR-007: Product versioning and releases
- Status: ACCEPTED
- Date: 2026-07-10
- Deciders: cproudlock
- Supersedes: none
Context
ADR-002 established semantic versioning for the plugin contract via
__contract_version__ in shopdb/__init__.py. That number answers one
question only: is a given plugin compatible with this platform build. It
says nothing about the state of the product as a whole. A sister site
standing up its own instance needs a different answer: which release am I
running, and what changed since the last one.
Until now the product had no version, no tags, no changelog, and no CI. ADR-002's pinning model implicitly assumes that a downstream site can pin a known-good build, but there were no tags to pin to. Adopters had no release record to read before upgrading, and no automated gate confirming that a given commit builds and passes tests.
Decision
The product carries its own release version, separate from the plugin contract version.
-
Product version (
shopdb/__init__.py): a single__version__constant. This is the version of the shopdb-flask product as a whole. It follows semantic versioning of the product's user-visible and operator-visible behavior. It is deliberately NOT part of theshopdb.apicontract surface, so it is never re-exported throughshopdb.api; exporting it would itself be a contract change under ADR-002. -
Plugin contract version (
__contract_version__): unchanged from ADR-002. It moves only when the plugin contract surface changes, per the major/minor/patch rules in ADR-002.
The two are distinct series with independent bump rules. They happen to
coincide at 0.5.0 for this release; that is a coincidence of timing,
not a coupling. A product release that changes no contract surface bumps
__version__ while leaving __contract_version__ fixed, and vice versa.
-
Release record (
CHANGELOG.md, repo root): the canonical, human-readable record of what changed in each release, in Keep-a-Changelog format. Every release has an entry; work in flight accumulates under## [Unreleased]. -
Git tags: each product release is tagged
vX.Y.Z, whereX.Y.Zmatches__version__at the tagged commit. Tags are what ADR-002's downstream-pinning model pins to. -
Frontend version (
frontend/package.json): kept in lock-step with__version__so the shipped single-page app reports the same product version as the backend that serves it.
Release procedure
To cut release X.Y.Z:
- Bump
__version__inshopdb/__init__.pytoX.Y.Z. - Bump
versioninfrontend/package.jsonto the sameX.Y.Z. - Move the accumulated
## [Unreleased]notes inCHANGELOG.mdinto a new## [X.Y.Z] - YYYY-MM-DDsection, leaving a fresh empty## [Unreleased]above it. - If the plugin contract surface changed this release, bump
__contract_version__per ADR-002 (independently of__version__). - Commit, then tag:
git tag -a vX.Y.Z -m "shopdb-flask X.Y.Z". - Push the commit and the tag.
Consequences
Positive
- Adopters have a real release to name in bug reports and a changelog to read before upgrading.
- ADR-002's pinning story finally has tags to pin to.
- Product and contract can evolve at their own pace without one dragging the other into a misleading bump.
Negative / cost
- Two version numbers to keep straight. The comment in
shopdb/__init__.pyand this ADR exist to keep the distinction clear. - Release discipline: the changelog and both version constants must be updated together, or the tagged build misreports itself.
Neutral
- CI (
.gitea/workflows/ci.yml) runs the backend tests, the naming/style gate, and the frontend build on push and PR. It is best-effort: Gitea Actions availability on the host is unverified, so the workflow is config-only until a runner is confirmed.
Alternatives considered
- Reuse
__contract_version__as the product version. Conflates two independent concerns; a docs-only or UI-only release would either falsely bump the contract or leave the product looking unchanged. Rejected. - Calendar versioning for the product (e.g.
2026.07.0). Easy to bump but poor at signaling breaking operator-facing changes. Rejected for the same reasons ADR-002 rejected it for the contract. - No product version, rely on git SHAs. Opaque to adopters and unpinnable in any human-meaningful way. Rejected.
References
- ADR-001 (defines the contract surface)
- ADR-002 (plugin contract versioning;
__contract_version__bump rules) shopdb/__init__.py(__version__,__contract_version__)CHANGELOG.md(release record)frontend/package.json(frontend version, kept in lock-step).gitea/workflows/ci.yml(CI gate)
Docs
Install and operate
Data import
Plugins
Integrations
Project
ADRs
- ADR-001-asset-as-platform-contract
- ADR-002-plugin-versioning
- ADR-003-plugin-distribution
- ADR-004-deployment-topology
- ADR-005-equipment-vs-measuringtools
- ADR-006-collector-contract
- ADR-007-product-versioning-and-releases
- ADR-008-plugin-migration-ownership
- ADR-009-frontend-plugin-gating
- ADR-010-frontend-plugin-hooks
- ADR-011-machines-rename
- ADR-012-geenforce-manifest-ownership
- README
Proposals